How to edit the entire file after match a grep pattern?












4














To simplify, I want to edit the whole file after matched pattern, example of a file:



$ cat file
ip=x.x.x.a
mask=255.0.0.0
host=a
ip=x.x.x.b
mask=255.0.0.0
host=b
ip=x.x.x.c
mask=255.0.0.0
host=c
ip=x.x.x.x
blahblah
mask=255.0.0.0
host=d


Let's suppose that I want to edit the IP from host c, but note that this may be variable, so I don't know this value. If I grep host c and use the -B2 to print the lines I can't edit it in the original file! Another point the line may not have the same structure, that's the case of host d, between ip and mask info there's some text, so I can't assume that the IP pattern always will be 2 lines before my search pattern.



Resuming, I can't grep the IP directly because I don't know it, instead I need to search for host, and edit the line before this match to change the value of IP. How can I do this?










share|improve this question









New contributor




s.hayha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




















  • So what can you know? Can you be sure that each hosts's entry will always start with the ip= line? And what operating system are you using? Can we assume GNU tools (default on Linux)?
    – terdon
    2 days ago












  • yes, each "block" contains at least these 3 information ( ip, mask and host ), but it may contains another information between these 3. And the order is always the same ( ip, mask, host ) they don't change, but I don't want to edit by it's line number. Yes it's a Linux OS Debian. My pattern search needs to be by the host. For example, if I search for host=c I need to change the IP that is equals x.x.x.c
    – s.hayha
    2 days ago
















4














To simplify, I want to edit the whole file after matched pattern, example of a file:



$ cat file
ip=x.x.x.a
mask=255.0.0.0
host=a
ip=x.x.x.b
mask=255.0.0.0
host=b
ip=x.x.x.c
mask=255.0.0.0
host=c
ip=x.x.x.x
blahblah
mask=255.0.0.0
host=d


Let's suppose that I want to edit the IP from host c, but note that this may be variable, so I don't know this value. If I grep host c and use the -B2 to print the lines I can't edit it in the original file! Another point the line may not have the same structure, that's the case of host d, between ip and mask info there's some text, so I can't assume that the IP pattern always will be 2 lines before my search pattern.



Resuming, I can't grep the IP directly because I don't know it, instead I need to search for host, and edit the line before this match to change the value of IP. How can I do this?










share|improve this question









New contributor




s.hayha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.




















  • So what can you know? Can you be sure that each hosts's entry will always start with the ip= line? And what operating system are you using? Can we assume GNU tools (default on Linux)?
    – terdon
    2 days ago












  • yes, each "block" contains at least these 3 information ( ip, mask and host ), but it may contains another information between these 3. And the order is always the same ( ip, mask, host ) they don't change, but I don't want to edit by it's line number. Yes it's a Linux OS Debian. My pattern search needs to be by the host. For example, if I search for host=c I need to change the IP that is equals x.x.x.c
    – s.hayha
    2 days ago














4












4








4







To simplify, I want to edit the whole file after matched pattern, example of a file:



$ cat file
ip=x.x.x.a
mask=255.0.0.0
host=a
ip=x.x.x.b
mask=255.0.0.0
host=b
ip=x.x.x.c
mask=255.0.0.0
host=c
ip=x.x.x.x
blahblah
mask=255.0.0.0
host=d


Let's suppose that I want to edit the IP from host c, but note that this may be variable, so I don't know this value. If I grep host c and use the -B2 to print the lines I can't edit it in the original file! Another point the line may not have the same structure, that's the case of host d, between ip and mask info there's some text, so I can't assume that the IP pattern always will be 2 lines before my search pattern.



Resuming, I can't grep the IP directly because I don't know it, instead I need to search for host, and edit the line before this match to change the value of IP. How can I do this?










share|improve this question









New contributor




s.hayha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











To simplify, I want to edit the whole file after matched pattern, example of a file:



$ cat file
ip=x.x.x.a
mask=255.0.0.0
host=a
ip=x.x.x.b
mask=255.0.0.0
host=b
ip=x.x.x.c
mask=255.0.0.0
host=c
ip=x.x.x.x
blahblah
mask=255.0.0.0
host=d


Let's suppose that I want to edit the IP from host c, but note that this may be variable, so I don't know this value. If I grep host c and use the -B2 to print the lines I can't edit it in the original file! Another point the line may not have the same structure, that's the case of host d, between ip and mask info there's some text, so I can't assume that the IP pattern always will be 2 lines before my search pattern.



Resuming, I can't grep the IP directly because I don't know it, instead I need to search for host, and edit the line before this match to change the value of IP. How can I do this?







text-processing awk sed grep






share|improve this question









New contributor




s.hayha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.











share|improve this question









New contributor




s.hayha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









share|improve this question




share|improve this question








edited 2 days ago









terdon

128k31250425




128k31250425






New contributor




s.hayha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.









asked 2 days ago









s.hayha

232




232




New contributor




s.hayha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.





New contributor





s.hayha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.






s.hayha is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.












  • So what can you know? Can you be sure that each hosts's entry will always start with the ip= line? And what operating system are you using? Can we assume GNU tools (default on Linux)?
    – terdon
    2 days ago












  • yes, each "block" contains at least these 3 information ( ip, mask and host ), but it may contains another information between these 3. And the order is always the same ( ip, mask, host ) they don't change, but I don't want to edit by it's line number. Yes it's a Linux OS Debian. My pattern search needs to be by the host. For example, if I search for host=c I need to change the IP that is equals x.x.x.c
    – s.hayha
    2 days ago


















  • So what can you know? Can you be sure that each hosts's entry will always start with the ip= line? And what operating system are you using? Can we assume GNU tools (default on Linux)?
    – terdon
    2 days ago












  • yes, each "block" contains at least these 3 information ( ip, mask and host ), but it may contains another information between these 3. And the order is always the same ( ip, mask, host ) they don't change, but I don't want to edit by it's line number. Yes it's a Linux OS Debian. My pattern search needs to be by the host. For example, if I search for host=c I need to change the IP that is equals x.x.x.c
    – s.hayha
    2 days ago
















So what can you know? Can you be sure that each hosts's entry will always start with the ip= line? And what operating system are you using? Can we assume GNU tools (default on Linux)?
– terdon
2 days ago






So what can you know? Can you be sure that each hosts's entry will always start with the ip= line? And what operating system are you using? Can we assume GNU tools (default on Linux)?
– terdon
2 days ago














yes, each "block" contains at least these 3 information ( ip, mask and host ), but it may contains another information between these 3. And the order is always the same ( ip, mask, host ) they don't change, but I don't want to edit by it's line number. Yes it's a Linux OS Debian. My pattern search needs to be by the host. For example, if I search for host=c I need to change the IP that is equals x.x.x.c
– s.hayha
2 days ago




yes, each "block" contains at least these 3 information ( ip, mask and host ), but it may contains another information between these 3. And the order is always the same ( ip, mask, host ) they don't change, but I don't want to edit by it's line number. Yes it's a Linux OS Debian. My pattern search needs to be by the host. For example, if I search for host=c I need to change the IP that is equals x.x.x.c
– s.hayha
2 days ago










6 Answers
6






active

oldest

votes


















3














This will change the IP associated with host c to 1.2.3.4:



$ sed 's/^ip/nip/' file | perl -00pe 'if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} s/nn/n/' 
ip=x.x.x.a
mask=255.0.0.0
host=a
ip=x.x.x.b
mask=255.0.0.0
host=b
ip=1.2.3.4
mask=255.0.0.0
host=c
ip=x.x.x.x
blahblah
mask=255.0.0.0
host=d


Explanation:




  • sed 's/^ip/nip/' file : add an extra newline (n) to each line beginning with ip. I think this might not work with all implementations of sed, so if yours doesn't support this, replace the sed command with perl -pe 's/^ip/nip/'. We need this in order to use Perl's "paragraph mode" (seen below).


  • perl -00pe : the -00 makes perl run in "paragraph mode" where a "line" is defined by two consecutive newlines. This enables us to treat each host's block as a single "line". The -pe means "print each line after applying the script given by -e to it".


  • if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} : if this "line" (section) matches a newline followed by the string host=c and then another newline, then replace ip= and 1 or more non-whitespace characters (S+) following it with ip=1.2.3.4.



  • s/nn/n/ replace each pair of newlines with a single newline to get the original file's format back.


If you want this to change the file in place, you can use:



tmp=$(mktemp); sed 's/^ip/nip/' file > $tmp; 
perl -00pe 'if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} s/nn/n/' $tmp > file





share|improve this answer























  • great! worked fine, and I tested with time and got better time.
    – s.hayha
    2 days ago



















4














This gets a lot easier if you go through the file backwards. Fortunately, you can do so easily with tac (which happens to be cat backwards). We can then use a relatively simple awk script to look for your host, and change only its ip:



$ tac input | awk -v OFS="=" -v myip="changed_address" -v myhost="d" -F"=" '$1 == "host" { if( $2 == myhost ) { sw = "on" } else { sw="off" } } sw == "on" && $1 == "ip" { $2=myip } { print $0 }' | tac
ip=x.x.x.a
mask=255.0.0.0
host=a
ip=x.x.x.b
mask=255.0.0.0
host=b
ip=x.x.x.c
mask=255.0.0.0
host=c
ip=changed_address
blahblah
mask=255.0.0.0
host=d


I shall explain in detail how the awk works:



First, we declare a few variables: one each for the host and new value for the ip:



-v myip="changed_address" -v myhost="d"


Further, we declare the field separator for the input and output:



-v OFS="=" -F"="


Now, the actual awk script itself:



$1 == "host" {             // If we see the "host" line..
if( $2 == myhost ) { // And it matches the one we're looking for..
sw = "on" // Set a flag to swap the next IP
} else {
sw="off" // Otherwise, unset the flag
}
}

sw == "on" && $1 == "ip" { // If the flag is set and this is an IP line..
$2=myip // Swap in the new IP
}

{
print $0 // Finally, print out the processed line
}


Once that's all done, we just use tac again to re-reverse it, making it forwards again.






share|improve this answer























  • Worked great too, but I got better performance with perl command.
    – s.hayha
    2 days ago



















3














Another option is to send some commands to ed:



h=c
ed -s file <<< $'/^host='"${h}"$'$n?^ip=ncnip=new.ip.heren.nwnq' > /dev/null


The basic idea is to edit the file and pipe in a newline-separated list of commands (in two ANSI C quote strings $' ... ') that search for the given host (in variable $h), then search backwards for a line that begins with ip=, then change that line to be something new, then save and quit ed. Broken out by newlines (n), the commands are:




  1. /^host= ... $h $ -- start a search (/) for the string host= at the beginning of the line (^), followed by the contents of $h, and ending the line ($). Relax the end-of-line requirement if your host isn't a complete match.


  2. ?^ip= -- Now that we're on the matching line, search backwards for the text ip= at the beginning of a line.


  3. c -- change this line


  4. insert the text ip=new.ip.here


  5. . -- end the insertion text


  6. w -- write the file to disk


  7. q -- quit ed



Invoking ed with -s silences the byte-count it emits when opening and saving the file. Redirecting the entire command to /dev/null silences the default output when ed finds the host= and ip= lines we're searching for.






share|improve this answer





















  • This is genious! A stepwise approach, using an automated editor more intuitive for complex cases than regular expressions. And people are still arguing whether Vim or Emacs is king, right :D? A bit less convenient, but inspired by the answer, here is a solution using vim: vim -c 'execute "normal /host=d<Enter>?^ip=<Enter>f=lCnew_ip_for_c<Esc>:wq<Enter>"' file.txt
    – Larry
    yesterday



















3














I know you are expecting something very fast, and simple like a one-line sed command or a smart awk code, but if you don't care...



#!/bin/bash
#Note: Adjusted to run with a posix shell (tested in dash)
filename='file'
newip='127.0.0.1'
hostchar='d'

tac "$filename" | while IFS= read -r line ; do
case $line in
host=${hostchar})
flag=on
;;
host=*)
flag=off
;;
esac
if [ "$flag" = "on" ]; then
case $line in
ip=*)
echo ip=$newip
continue
;;
#you can replace more variables at once by adding it here
#in the same standard.
#for ex: mask=*) echo mask=$newmask; continue ;; etc...
esac
fi
echo $line
done | tac


Results:



ip=x.x.x.a
mask=255.0.0.0
host=a
ip=x.x.x.b
mask=255.0.0.0
host=b
ip=x.x.x.c
mask=255.0.0.0
host=c
ip=127.0.0.1
blahblah
mask=255.0.0.0
host=d





share|improve this answer































    1














    $ awk -v host=c -v newip=zzz.zzz.zzz.zzz '$0 ~ "^host=" host "$" { print; getline; $0 = sprintf("ip=%ss", newip) }; 1' file
    ip=x.x.x.a
    mask=255.0.0.0
    host=a
    ip=x.x.x.b
    mask=255.0.0.0
    host=b
    ip=x.x.x.c
    mask=255.0.0.0
    host=c
    ip=zzz.zzz.zzz.zzzs
    blahblah
    mask=255.0.0.0
    host=d


    This assumes that you'd like to change the IP address of some named host and that the IP address line is always occurring after the host= line for that host.



    The awk program takes the host name and the new IP address on the command line by setting the two awk variables host and newip. The code then locates the host= line corresponding to the given hostname, reads and discards the following line (the ip= line) and creates a new ip= line with the new IP address. The data (modified or not) is outputted by the trailing 1 in the program.






    share|improve this answer





























      1














      A relatively short solution using negative lookbehind assertions in a perl regular expression:



      perl -p0e 's/(ip=)[^n]*((?:(?!ip=).)*?host=c)/1new_ip_for_host_c2/gs' hosts.txt



      Explanation:



      First, a print-loop is used using the -p option. This iterates through lines of a file provided as argument to perl and prints them. If the line is changed, the changed variant is printed, as with sed.



      The file contents are not actually treated as separate lines, the -p option merely serves to make perl read in the file at once and use the contents as the default string to match in, without using a variable. This is achieved via the -0 option, which makes perl treat multiline input as a single line, substituting newline characters by null bytes.



      In the pattern, parts excluding the value to be changed are saved.



      The value is recognized as anything until the next newline, which I assume is understood as the next null-byte in this situation. Someone who knows this could perhaps explain.



      Then, using a negative lookbehind assertion, all characters are matched that are not preceded by an ip= part, until the host=d part is encountered. This is necessary, otherwise perl would prefer the left-most match over the shortest, and would match from the very first ip= to the required host=c part.



      The characters not preceded by ip= are matched in a non-greedy manner, otherwise again, the match would encompass everything from the first ip= until host=c, because by default, the longest match is preferred.
      In other terms, this ensures that text is matched in sections terminated by host=c, and started by the ip= segment that is closest above host=c, not furthest.



      The character restricted by the negative lookbehind expression inside the (?:) construct. It denotes a non-capturing group, meaning that it won't count toward the total number of backreferences in the substitution string.
      Grouping is used so the * sign can properly quantify the whole negative lookbehind - dot character combo as a single atom.
      In the example, backreferences range from 1 - 2, whilst without the non-capturing group, they would be 1 - 3, with 3 denoting the nested group.



      The backreferences 1 and 2 in the substitution string are the matched parts of the section that enclose exactly the ip address for the host found in the matched section.



      The ip address will thus be substituted by new_ip_for_host_c.



      General blablablah:



      I am not sure if the data format was the OP's choice, just putting this out in general: it is better to store such simple data in such a manner that records are separated by newlines, i.e. there are no multiline records.
      Refer to the format of /etc/passwd .
      Most standard Unix filters are line-oriented, and data manipulation is much easier if you follow "da Unix wae".
      If your data is more complex (nested data items, etc.), you are better off using a format such as JSON and manipulating it with a proper parser.






      share|improve this answer























        Your Answer








        StackExchange.ready(function() {
        var channelOptions = {
        tags: "".split(" "),
        id: "106"
        };
        initTagRenderer("".split(" "), "".split(" "), channelOptions);

        StackExchange.using("externalEditor", function() {
        // Have to fire editor after snippets, if snippets enabled
        if (StackExchange.settings.snippets.snippetsEnabled) {
        StackExchange.using("snippets", function() {
        createEditor();
        });
        }
        else {
        createEditor();
        }
        });

        function createEditor() {
        StackExchange.prepareEditor({
        heartbeatType: 'answer',
        autoActivateHeartbeat: false,
        convertImagesToLinks: false,
        noModals: true,
        showLowRepImageUploadWarning: true,
        reputationToPostImages: null,
        bindNavPrevention: true,
        postfix: "",
        imageUploader: {
        brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
        contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
        allowUrls: true
        },
        onDemand: true,
        discardSelector: ".discard-answer"
        ,immediatelyShowMarkdownHelp:true
        });


        }
        });






        s.hayha is a new contributor. Be nice, and check out our Code of Conduct.










        draft saved

        draft discarded


















        StackExchange.ready(
        function () {
        StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f492309%2fhow-to-edit-the-entire-file-after-match-a-grep-pattern%23new-answer', 'question_page');
        }
        );

        Post as a guest















        Required, but never shown

























        6 Answers
        6






        active

        oldest

        votes








        6 Answers
        6






        active

        oldest

        votes









        active

        oldest

        votes






        active

        oldest

        votes









        3














        This will change the IP associated with host c to 1.2.3.4:



        $ sed 's/^ip/nip/' file | perl -00pe 'if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} s/nn/n/' 
        ip=x.x.x.a
        mask=255.0.0.0
        host=a
        ip=x.x.x.b
        mask=255.0.0.0
        host=b
        ip=1.2.3.4
        mask=255.0.0.0
        host=c
        ip=x.x.x.x
        blahblah
        mask=255.0.0.0
        host=d


        Explanation:




        • sed 's/^ip/nip/' file : add an extra newline (n) to each line beginning with ip. I think this might not work with all implementations of sed, so if yours doesn't support this, replace the sed command with perl -pe 's/^ip/nip/'. We need this in order to use Perl's "paragraph mode" (seen below).


        • perl -00pe : the -00 makes perl run in "paragraph mode" where a "line" is defined by two consecutive newlines. This enables us to treat each host's block as a single "line". The -pe means "print each line after applying the script given by -e to it".


        • if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} : if this "line" (section) matches a newline followed by the string host=c and then another newline, then replace ip= and 1 or more non-whitespace characters (S+) following it with ip=1.2.3.4.



        • s/nn/n/ replace each pair of newlines with a single newline to get the original file's format back.


        If you want this to change the file in place, you can use:



        tmp=$(mktemp); sed 's/^ip/nip/' file > $tmp; 
        perl -00pe 'if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} s/nn/n/' $tmp > file





        share|improve this answer























        • great! worked fine, and I tested with time and got better time.
          – s.hayha
          2 days ago
















        3














        This will change the IP associated with host c to 1.2.3.4:



        $ sed 's/^ip/nip/' file | perl -00pe 'if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} s/nn/n/' 
        ip=x.x.x.a
        mask=255.0.0.0
        host=a
        ip=x.x.x.b
        mask=255.0.0.0
        host=b
        ip=1.2.3.4
        mask=255.0.0.0
        host=c
        ip=x.x.x.x
        blahblah
        mask=255.0.0.0
        host=d


        Explanation:




        • sed 's/^ip/nip/' file : add an extra newline (n) to each line beginning with ip. I think this might not work with all implementations of sed, so if yours doesn't support this, replace the sed command with perl -pe 's/^ip/nip/'. We need this in order to use Perl's "paragraph mode" (seen below).


        • perl -00pe : the -00 makes perl run in "paragraph mode" where a "line" is defined by two consecutive newlines. This enables us to treat each host's block as a single "line". The -pe means "print each line after applying the script given by -e to it".


        • if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} : if this "line" (section) matches a newline followed by the string host=c and then another newline, then replace ip= and 1 or more non-whitespace characters (S+) following it with ip=1.2.3.4.



        • s/nn/n/ replace each pair of newlines with a single newline to get the original file's format back.


        If you want this to change the file in place, you can use:



        tmp=$(mktemp); sed 's/^ip/nip/' file > $tmp; 
        perl -00pe 'if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} s/nn/n/' $tmp > file





        share|improve this answer























        • great! worked fine, and I tested with time and got better time.
          – s.hayha
          2 days ago














        3












        3








        3






        This will change the IP associated with host c to 1.2.3.4:



        $ sed 's/^ip/nip/' file | perl -00pe 'if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} s/nn/n/' 
        ip=x.x.x.a
        mask=255.0.0.0
        host=a
        ip=x.x.x.b
        mask=255.0.0.0
        host=b
        ip=1.2.3.4
        mask=255.0.0.0
        host=c
        ip=x.x.x.x
        blahblah
        mask=255.0.0.0
        host=d


        Explanation:




        • sed 's/^ip/nip/' file : add an extra newline (n) to each line beginning with ip. I think this might not work with all implementations of sed, so if yours doesn't support this, replace the sed command with perl -pe 's/^ip/nip/'. We need this in order to use Perl's "paragraph mode" (seen below).


        • perl -00pe : the -00 makes perl run in "paragraph mode" where a "line" is defined by two consecutive newlines. This enables us to treat each host's block as a single "line". The -pe means "print each line after applying the script given by -e to it".


        • if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} : if this "line" (section) matches a newline followed by the string host=c and then another newline, then replace ip= and 1 or more non-whitespace characters (S+) following it with ip=1.2.3.4.



        • s/nn/n/ replace each pair of newlines with a single newline to get the original file's format back.


        If you want this to change the file in place, you can use:



        tmp=$(mktemp); sed 's/^ip/nip/' file > $tmp; 
        perl -00pe 'if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} s/nn/n/' $tmp > file





        share|improve this answer














        This will change the IP associated with host c to 1.2.3.4:



        $ sed 's/^ip/nip/' file | perl -00pe 'if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} s/nn/n/' 
        ip=x.x.x.a
        mask=255.0.0.0
        host=a
        ip=x.x.x.b
        mask=255.0.0.0
        host=b
        ip=1.2.3.4
        mask=255.0.0.0
        host=c
        ip=x.x.x.x
        blahblah
        mask=255.0.0.0
        host=d


        Explanation:




        • sed 's/^ip/nip/' file : add an extra newline (n) to each line beginning with ip. I think this might not work with all implementations of sed, so if yours doesn't support this, replace the sed command with perl -pe 's/^ip/nip/'. We need this in order to use Perl's "paragraph mode" (seen below).


        • perl -00pe : the -00 makes perl run in "paragraph mode" where a "line" is defined by two consecutive newlines. This enables us to treat each host's block as a single "line". The -pe means "print each line after applying the script given by -e to it".


        • if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} : if this "line" (section) matches a newline followed by the string host=c and then another newline, then replace ip= and 1 or more non-whitespace characters (S+) following it with ip=1.2.3.4.



        • s/nn/n/ replace each pair of newlines with a single newline to get the original file's format back.


        If you want this to change the file in place, you can use:



        tmp=$(mktemp); sed 's/^ip/nip/' file > $tmp; 
        perl -00pe 'if(/nhost=cn/){s/ip=S+/ip=1.2.3.4/} s/nn/n/' $tmp > file






        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited 2 days ago

























        answered 2 days ago









        terdon

        128k31250425




        128k31250425












        • great! worked fine, and I tested with time and got better time.
          – s.hayha
          2 days ago


















        • great! worked fine, and I tested with time and got better time.
          – s.hayha
          2 days ago
















        great! worked fine, and I tested with time and got better time.
        – s.hayha
        2 days ago




        great! worked fine, and I tested with time and got better time.
        – s.hayha
        2 days ago













        4














        This gets a lot easier if you go through the file backwards. Fortunately, you can do so easily with tac (which happens to be cat backwards). We can then use a relatively simple awk script to look for your host, and change only its ip:



        $ tac input | awk -v OFS="=" -v myip="changed_address" -v myhost="d" -F"=" '$1 == "host" { if( $2 == myhost ) { sw = "on" } else { sw="off" } } sw == "on" && $1 == "ip" { $2=myip } { print $0 }' | tac
        ip=x.x.x.a
        mask=255.0.0.0
        host=a
        ip=x.x.x.b
        mask=255.0.0.0
        host=b
        ip=x.x.x.c
        mask=255.0.0.0
        host=c
        ip=changed_address
        blahblah
        mask=255.0.0.0
        host=d


        I shall explain in detail how the awk works:



        First, we declare a few variables: one each for the host and new value for the ip:



        -v myip="changed_address" -v myhost="d"


        Further, we declare the field separator for the input and output:



        -v OFS="=" -F"="


        Now, the actual awk script itself:



        $1 == "host" {             // If we see the "host" line..
        if( $2 == myhost ) { // And it matches the one we're looking for..
        sw = "on" // Set a flag to swap the next IP
        } else {
        sw="off" // Otherwise, unset the flag
        }
        }

        sw == "on" && $1 == "ip" { // If the flag is set and this is an IP line..
        $2=myip // Swap in the new IP
        }

        {
        print $0 // Finally, print out the processed line
        }


        Once that's all done, we just use tac again to re-reverse it, making it forwards again.






        share|improve this answer























        • Worked great too, but I got better performance with perl command.
          – s.hayha
          2 days ago
















        4














        This gets a lot easier if you go through the file backwards. Fortunately, you can do so easily with tac (which happens to be cat backwards). We can then use a relatively simple awk script to look for your host, and change only its ip:



        $ tac input | awk -v OFS="=" -v myip="changed_address" -v myhost="d" -F"=" '$1 == "host" { if( $2 == myhost ) { sw = "on" } else { sw="off" } } sw == "on" && $1 == "ip" { $2=myip } { print $0 }' | tac
        ip=x.x.x.a
        mask=255.0.0.0
        host=a
        ip=x.x.x.b
        mask=255.0.0.0
        host=b
        ip=x.x.x.c
        mask=255.0.0.0
        host=c
        ip=changed_address
        blahblah
        mask=255.0.0.0
        host=d


        I shall explain in detail how the awk works:



        First, we declare a few variables: one each for the host and new value for the ip:



        -v myip="changed_address" -v myhost="d"


        Further, we declare the field separator for the input and output:



        -v OFS="=" -F"="


        Now, the actual awk script itself:



        $1 == "host" {             // If we see the "host" line..
        if( $2 == myhost ) { // And it matches the one we're looking for..
        sw = "on" // Set a flag to swap the next IP
        } else {
        sw="off" // Otherwise, unset the flag
        }
        }

        sw == "on" && $1 == "ip" { // If the flag is set and this is an IP line..
        $2=myip // Swap in the new IP
        }

        {
        print $0 // Finally, print out the processed line
        }


        Once that's all done, we just use tac again to re-reverse it, making it forwards again.






        share|improve this answer























        • Worked great too, but I got better performance with perl command.
          – s.hayha
          2 days ago














        4












        4








        4






        This gets a lot easier if you go through the file backwards. Fortunately, you can do so easily with tac (which happens to be cat backwards). We can then use a relatively simple awk script to look for your host, and change only its ip:



        $ tac input | awk -v OFS="=" -v myip="changed_address" -v myhost="d" -F"=" '$1 == "host" { if( $2 == myhost ) { sw = "on" } else { sw="off" } } sw == "on" && $1 == "ip" { $2=myip } { print $0 }' | tac
        ip=x.x.x.a
        mask=255.0.0.0
        host=a
        ip=x.x.x.b
        mask=255.0.0.0
        host=b
        ip=x.x.x.c
        mask=255.0.0.0
        host=c
        ip=changed_address
        blahblah
        mask=255.0.0.0
        host=d


        I shall explain in detail how the awk works:



        First, we declare a few variables: one each for the host and new value for the ip:



        -v myip="changed_address" -v myhost="d"


        Further, we declare the field separator for the input and output:



        -v OFS="=" -F"="


        Now, the actual awk script itself:



        $1 == "host" {             // If we see the "host" line..
        if( $2 == myhost ) { // And it matches the one we're looking for..
        sw = "on" // Set a flag to swap the next IP
        } else {
        sw="off" // Otherwise, unset the flag
        }
        }

        sw == "on" && $1 == "ip" { // If the flag is set and this is an IP line..
        $2=myip // Swap in the new IP
        }

        {
        print $0 // Finally, print out the processed line
        }


        Once that's all done, we just use tac again to re-reverse it, making it forwards again.






        share|improve this answer














        This gets a lot easier if you go through the file backwards. Fortunately, you can do so easily with tac (which happens to be cat backwards). We can then use a relatively simple awk script to look for your host, and change only its ip:



        $ tac input | awk -v OFS="=" -v myip="changed_address" -v myhost="d" -F"=" '$1 == "host" { if( $2 == myhost ) { sw = "on" } else { sw="off" } } sw == "on" && $1 == "ip" { $2=myip } { print $0 }' | tac
        ip=x.x.x.a
        mask=255.0.0.0
        host=a
        ip=x.x.x.b
        mask=255.0.0.0
        host=b
        ip=x.x.x.c
        mask=255.0.0.0
        host=c
        ip=changed_address
        blahblah
        mask=255.0.0.0
        host=d


        I shall explain in detail how the awk works:



        First, we declare a few variables: one each for the host and new value for the ip:



        -v myip="changed_address" -v myhost="d"


        Further, we declare the field separator for the input and output:



        -v OFS="=" -F"="


        Now, the actual awk script itself:



        $1 == "host" {             // If we see the "host" line..
        if( $2 == myhost ) { // And it matches the one we're looking for..
        sw = "on" // Set a flag to swap the next IP
        } else {
        sw="off" // Otherwise, unset the flag
        }
        }

        sw == "on" && $1 == "ip" { // If the flag is set and this is an IP line..
        $2=myip // Swap in the new IP
        }

        {
        print $0 // Finally, print out the processed line
        }


        Once that's all done, we just use tac again to re-reverse it, making it forwards again.







        share|improve this answer














        share|improve this answer



        share|improve this answer








        edited 2 days ago

























        answered 2 days ago









        DopeGhoti

        43.5k55382




        43.5k55382












        • Worked great too, but I got better performance with perl command.
          – s.hayha
          2 days ago


















        • Worked great too, but I got better performance with perl command.
          – s.hayha
          2 days ago
















        Worked great too, but I got better performance with perl command.
        – s.hayha
        2 days ago




        Worked great too, but I got better performance with perl command.
        – s.hayha
        2 days ago











        3














        Another option is to send some commands to ed:



        h=c
        ed -s file <<< $'/^host='"${h}"$'$n?^ip=ncnip=new.ip.heren.nwnq' > /dev/null


        The basic idea is to edit the file and pipe in a newline-separated list of commands (in two ANSI C quote strings $' ... ') that search for the given host (in variable $h), then search backwards for a line that begins with ip=, then change that line to be something new, then save and quit ed. Broken out by newlines (n), the commands are:




        1. /^host= ... $h $ -- start a search (/) for the string host= at the beginning of the line (^), followed by the contents of $h, and ending the line ($). Relax the end-of-line requirement if your host isn't a complete match.


        2. ?^ip= -- Now that we're on the matching line, search backwards for the text ip= at the beginning of a line.


        3. c -- change this line


        4. insert the text ip=new.ip.here


        5. . -- end the insertion text


        6. w -- write the file to disk


        7. q -- quit ed



        Invoking ed with -s silences the byte-count it emits when opening and saving the file. Redirecting the entire command to /dev/null silences the default output when ed finds the host= and ip= lines we're searching for.






        share|improve this answer





















        • This is genious! A stepwise approach, using an automated editor more intuitive for complex cases than regular expressions. And people are still arguing whether Vim or Emacs is king, right :D? A bit less convenient, but inspired by the answer, here is a solution using vim: vim -c 'execute "normal /host=d<Enter>?^ip=<Enter>f=lCnew_ip_for_c<Esc>:wq<Enter>"' file.txt
          – Larry
          yesterday
















        3














        Another option is to send some commands to ed:



        h=c
        ed -s file <<< $'/^host='"${h}"$'$n?^ip=ncnip=new.ip.heren.nwnq' > /dev/null


        The basic idea is to edit the file and pipe in a newline-separated list of commands (in two ANSI C quote strings $' ... ') that search for the given host (in variable $h), then search backwards for a line that begins with ip=, then change that line to be something new, then save and quit ed. Broken out by newlines (n), the commands are:




        1. /^host= ... $h $ -- start a search (/) for the string host= at the beginning of the line (^), followed by the contents of $h, and ending the line ($). Relax the end-of-line requirement if your host isn't a complete match.


        2. ?^ip= -- Now that we're on the matching line, search backwards for the text ip= at the beginning of a line.


        3. c -- change this line


        4. insert the text ip=new.ip.here


        5. . -- end the insertion text


        6. w -- write the file to disk


        7. q -- quit ed



        Invoking ed with -s silences the byte-count it emits when opening and saving the file. Redirecting the entire command to /dev/null silences the default output when ed finds the host= and ip= lines we're searching for.






        share|improve this answer





















        • This is genious! A stepwise approach, using an automated editor more intuitive for complex cases than regular expressions. And people are still arguing whether Vim or Emacs is king, right :D? A bit less convenient, but inspired by the answer, here is a solution using vim: vim -c 'execute "normal /host=d<Enter>?^ip=<Enter>f=lCnew_ip_for_c<Esc>:wq<Enter>"' file.txt
          – Larry
          yesterday














        3












        3








        3






        Another option is to send some commands to ed:



        h=c
        ed -s file <<< $'/^host='"${h}"$'$n?^ip=ncnip=new.ip.heren.nwnq' > /dev/null


        The basic idea is to edit the file and pipe in a newline-separated list of commands (in two ANSI C quote strings $' ... ') that search for the given host (in variable $h), then search backwards for a line that begins with ip=, then change that line to be something new, then save and quit ed. Broken out by newlines (n), the commands are:




        1. /^host= ... $h $ -- start a search (/) for the string host= at the beginning of the line (^), followed by the contents of $h, and ending the line ($). Relax the end-of-line requirement if your host isn't a complete match.


        2. ?^ip= -- Now that we're on the matching line, search backwards for the text ip= at the beginning of a line.


        3. c -- change this line


        4. insert the text ip=new.ip.here


        5. . -- end the insertion text


        6. w -- write the file to disk


        7. q -- quit ed



        Invoking ed with -s silences the byte-count it emits when opening and saving the file. Redirecting the entire command to /dev/null silences the default output when ed finds the host= and ip= lines we're searching for.






        share|improve this answer












        Another option is to send some commands to ed:



        h=c
        ed -s file <<< $'/^host='"${h}"$'$n?^ip=ncnip=new.ip.heren.nwnq' > /dev/null


        The basic idea is to edit the file and pipe in a newline-separated list of commands (in two ANSI C quote strings $' ... ') that search for the given host (in variable $h), then search backwards for a line that begins with ip=, then change that line to be something new, then save and quit ed. Broken out by newlines (n), the commands are:




        1. /^host= ... $h $ -- start a search (/) for the string host= at the beginning of the line (^), followed by the contents of $h, and ending the line ($). Relax the end-of-line requirement if your host isn't a complete match.


        2. ?^ip= -- Now that we're on the matching line, search backwards for the text ip= at the beginning of a line.


        3. c -- change this line


        4. insert the text ip=new.ip.here


        5. . -- end the insertion text


        6. w -- write the file to disk


        7. q -- quit ed



        Invoking ed with -s silences the byte-count it emits when opening and saving the file. Redirecting the entire command to /dev/null silences the default output when ed finds the host= and ip= lines we're searching for.







        share|improve this answer












        share|improve this answer



        share|improve this answer










        answered yesterday









        Jeff Schaller

        39k1053125




        39k1053125












        • This is genious! A stepwise approach, using an automated editor more intuitive for complex cases than regular expressions. And people are still arguing whether Vim or Emacs is king, right :D? A bit less convenient, but inspired by the answer, here is a solution using vim: vim -c 'execute "normal /host=d<Enter>?^ip=<Enter>f=lCnew_ip_for_c<Esc>:wq<Enter>"' file.txt
          – Larry
          yesterday


















        • This is genious! A stepwise approach, using an automated editor more intuitive for complex cases than regular expressions. And people are still arguing whether Vim or Emacs is king, right :D? A bit less convenient, but inspired by the answer, here is a solution using vim: vim -c 'execute "normal /host=d<Enter>?^ip=<Enter>f=lCnew_ip_for_c<Esc>:wq<Enter>"' file.txt
          – Larry
          yesterday
















        This is genious! A stepwise approach, using an automated editor more intuitive for complex cases than regular expressions. And people are still arguing whether Vim or Emacs is king, right :D? A bit less convenient, but inspired by the answer, here is a solution using vim: vim -c 'execute "normal /host=d<Enter>?^ip=<Enter>f=lCnew_ip_for_c<Esc>:wq<Enter>"' file.txt
        – Larry
        yesterday




        This is genious! A stepwise approach, using an automated editor more intuitive for complex cases than regular expressions. And people are still arguing whether Vim or Emacs is king, right :D? A bit less convenient, but inspired by the answer, here is a solution using vim: vim -c 'execute "normal /host=d<Enter>?^ip=<Enter>f=lCnew_ip_for_c<Esc>:wq<Enter>"' file.txt
        – Larry
        yesterday











        3














        I know you are expecting something very fast, and simple like a one-line sed command or a smart awk code, but if you don't care...



        #!/bin/bash
        #Note: Adjusted to run with a posix shell (tested in dash)
        filename='file'
        newip='127.0.0.1'
        hostchar='d'

        tac "$filename" | while IFS= read -r line ; do
        case $line in
        host=${hostchar})
        flag=on
        ;;
        host=*)
        flag=off
        ;;
        esac
        if [ "$flag" = "on" ]; then
        case $line in
        ip=*)
        echo ip=$newip
        continue
        ;;
        #you can replace more variables at once by adding it here
        #in the same standard.
        #for ex: mask=*) echo mask=$newmask; continue ;; etc...
        esac
        fi
        echo $line
        done | tac


        Results:



        ip=x.x.x.a
        mask=255.0.0.0
        host=a
        ip=x.x.x.b
        mask=255.0.0.0
        host=b
        ip=x.x.x.c
        mask=255.0.0.0
        host=c
        ip=127.0.0.1
        blahblah
        mask=255.0.0.0
        host=d





        share|improve this answer




























          3














          I know you are expecting something very fast, and simple like a one-line sed command or a smart awk code, but if you don't care...



          #!/bin/bash
          #Note: Adjusted to run with a posix shell (tested in dash)
          filename='file'
          newip='127.0.0.1'
          hostchar='d'

          tac "$filename" | while IFS= read -r line ; do
          case $line in
          host=${hostchar})
          flag=on
          ;;
          host=*)
          flag=off
          ;;
          esac
          if [ "$flag" = "on" ]; then
          case $line in
          ip=*)
          echo ip=$newip
          continue
          ;;
          #you can replace more variables at once by adding it here
          #in the same standard.
          #for ex: mask=*) echo mask=$newmask; continue ;; etc...
          esac
          fi
          echo $line
          done | tac


          Results:



          ip=x.x.x.a
          mask=255.0.0.0
          host=a
          ip=x.x.x.b
          mask=255.0.0.0
          host=b
          ip=x.x.x.c
          mask=255.0.0.0
          host=c
          ip=127.0.0.1
          blahblah
          mask=255.0.0.0
          host=d





          share|improve this answer


























            3












            3








            3






            I know you are expecting something very fast, and simple like a one-line sed command or a smart awk code, but if you don't care...



            #!/bin/bash
            #Note: Adjusted to run with a posix shell (tested in dash)
            filename='file'
            newip='127.0.0.1'
            hostchar='d'

            tac "$filename" | while IFS= read -r line ; do
            case $line in
            host=${hostchar})
            flag=on
            ;;
            host=*)
            flag=off
            ;;
            esac
            if [ "$flag" = "on" ]; then
            case $line in
            ip=*)
            echo ip=$newip
            continue
            ;;
            #you can replace more variables at once by adding it here
            #in the same standard.
            #for ex: mask=*) echo mask=$newmask; continue ;; etc...
            esac
            fi
            echo $line
            done | tac


            Results:



            ip=x.x.x.a
            mask=255.0.0.0
            host=a
            ip=x.x.x.b
            mask=255.0.0.0
            host=b
            ip=x.x.x.c
            mask=255.0.0.0
            host=c
            ip=127.0.0.1
            blahblah
            mask=255.0.0.0
            host=d





            share|improve this answer














            I know you are expecting something very fast, and simple like a one-line sed command or a smart awk code, but if you don't care...



            #!/bin/bash
            #Note: Adjusted to run with a posix shell (tested in dash)
            filename='file'
            newip='127.0.0.1'
            hostchar='d'

            tac "$filename" | while IFS= read -r line ; do
            case $line in
            host=${hostchar})
            flag=on
            ;;
            host=*)
            flag=off
            ;;
            esac
            if [ "$flag" = "on" ]; then
            case $line in
            ip=*)
            echo ip=$newip
            continue
            ;;
            #you can replace more variables at once by adding it here
            #in the same standard.
            #for ex: mask=*) echo mask=$newmask; continue ;; etc...
            esac
            fi
            echo $line
            done | tac


            Results:



            ip=x.x.x.a
            mask=255.0.0.0
            host=a
            ip=x.x.x.b
            mask=255.0.0.0
            host=b
            ip=x.x.x.c
            mask=255.0.0.0
            host=c
            ip=127.0.0.1
            blahblah
            mask=255.0.0.0
            host=d






            share|improve this answer














            share|improve this answer



            share|improve this answer








            edited yesterday

























            answered 2 days ago









            Luciano Andress Martini

            3,553931




            3,553931























                1














                $ awk -v host=c -v newip=zzz.zzz.zzz.zzz '$0 ~ "^host=" host "$" { print; getline; $0 = sprintf("ip=%ss", newip) }; 1' file
                ip=x.x.x.a
                mask=255.0.0.0
                host=a
                ip=x.x.x.b
                mask=255.0.0.0
                host=b
                ip=x.x.x.c
                mask=255.0.0.0
                host=c
                ip=zzz.zzz.zzz.zzzs
                blahblah
                mask=255.0.0.0
                host=d


                This assumes that you'd like to change the IP address of some named host and that the IP address line is always occurring after the host= line for that host.



                The awk program takes the host name and the new IP address on the command line by setting the two awk variables host and newip. The code then locates the host= line corresponding to the given hostname, reads and discards the following line (the ip= line) and creates a new ip= line with the new IP address. The data (modified or not) is outputted by the trailing 1 in the program.






                share|improve this answer


























                  1














                  $ awk -v host=c -v newip=zzz.zzz.zzz.zzz '$0 ~ "^host=" host "$" { print; getline; $0 = sprintf("ip=%ss", newip) }; 1' file
                  ip=x.x.x.a
                  mask=255.0.0.0
                  host=a
                  ip=x.x.x.b
                  mask=255.0.0.0
                  host=b
                  ip=x.x.x.c
                  mask=255.0.0.0
                  host=c
                  ip=zzz.zzz.zzz.zzzs
                  blahblah
                  mask=255.0.0.0
                  host=d


                  This assumes that you'd like to change the IP address of some named host and that the IP address line is always occurring after the host= line for that host.



                  The awk program takes the host name and the new IP address on the command line by setting the two awk variables host and newip. The code then locates the host= line corresponding to the given hostname, reads and discards the following line (the ip= line) and creates a new ip= line with the new IP address. The data (modified or not) is outputted by the trailing 1 in the program.






                  share|improve this answer
























                    1












                    1








                    1






                    $ awk -v host=c -v newip=zzz.zzz.zzz.zzz '$0 ~ "^host=" host "$" { print; getline; $0 = sprintf("ip=%ss", newip) }; 1' file
                    ip=x.x.x.a
                    mask=255.0.0.0
                    host=a
                    ip=x.x.x.b
                    mask=255.0.0.0
                    host=b
                    ip=x.x.x.c
                    mask=255.0.0.0
                    host=c
                    ip=zzz.zzz.zzz.zzzs
                    blahblah
                    mask=255.0.0.0
                    host=d


                    This assumes that you'd like to change the IP address of some named host and that the IP address line is always occurring after the host= line for that host.



                    The awk program takes the host name and the new IP address on the command line by setting the two awk variables host and newip. The code then locates the host= line corresponding to the given hostname, reads and discards the following line (the ip= line) and creates a new ip= line with the new IP address. The data (modified or not) is outputted by the trailing 1 in the program.






                    share|improve this answer












                    $ awk -v host=c -v newip=zzz.zzz.zzz.zzz '$0 ~ "^host=" host "$" { print; getline; $0 = sprintf("ip=%ss", newip) }; 1' file
                    ip=x.x.x.a
                    mask=255.0.0.0
                    host=a
                    ip=x.x.x.b
                    mask=255.0.0.0
                    host=b
                    ip=x.x.x.c
                    mask=255.0.0.0
                    host=c
                    ip=zzz.zzz.zzz.zzzs
                    blahblah
                    mask=255.0.0.0
                    host=d


                    This assumes that you'd like to change the IP address of some named host and that the IP address line is always occurring after the host= line for that host.



                    The awk program takes the host name and the new IP address on the command line by setting the two awk variables host and newip. The code then locates the host= line corresponding to the given hostname, reads and discards the following line (the ip= line) and creates a new ip= line with the new IP address. The data (modified or not) is outputted by the trailing 1 in the program.







                    share|improve this answer












                    share|improve this answer



                    share|improve this answer










                    answered yesterday









                    Kusalananda

                    122k16230375




                    122k16230375























                        1














                        A relatively short solution using negative lookbehind assertions in a perl regular expression:



                        perl -p0e 's/(ip=)[^n]*((?:(?!ip=).)*?host=c)/1new_ip_for_host_c2/gs' hosts.txt



                        Explanation:



                        First, a print-loop is used using the -p option. This iterates through lines of a file provided as argument to perl and prints them. If the line is changed, the changed variant is printed, as with sed.



                        The file contents are not actually treated as separate lines, the -p option merely serves to make perl read in the file at once and use the contents as the default string to match in, without using a variable. This is achieved via the -0 option, which makes perl treat multiline input as a single line, substituting newline characters by null bytes.



                        In the pattern, parts excluding the value to be changed are saved.



                        The value is recognized as anything until the next newline, which I assume is understood as the next null-byte in this situation. Someone who knows this could perhaps explain.



                        Then, using a negative lookbehind assertion, all characters are matched that are not preceded by an ip= part, until the host=d part is encountered. This is necessary, otherwise perl would prefer the left-most match over the shortest, and would match from the very first ip= to the required host=c part.



                        The characters not preceded by ip= are matched in a non-greedy manner, otherwise again, the match would encompass everything from the first ip= until host=c, because by default, the longest match is preferred.
                        In other terms, this ensures that text is matched in sections terminated by host=c, and started by the ip= segment that is closest above host=c, not furthest.



                        The character restricted by the negative lookbehind expression inside the (?:) construct. It denotes a non-capturing group, meaning that it won't count toward the total number of backreferences in the substitution string.
                        Grouping is used so the * sign can properly quantify the whole negative lookbehind - dot character combo as a single atom.
                        In the example, backreferences range from 1 - 2, whilst without the non-capturing group, they would be 1 - 3, with 3 denoting the nested group.



                        The backreferences 1 and 2 in the substitution string are the matched parts of the section that enclose exactly the ip address for the host found in the matched section.



                        The ip address will thus be substituted by new_ip_for_host_c.



                        General blablablah:



                        I am not sure if the data format was the OP's choice, just putting this out in general: it is better to store such simple data in such a manner that records are separated by newlines, i.e. there are no multiline records.
                        Refer to the format of /etc/passwd .
                        Most standard Unix filters are line-oriented, and data manipulation is much easier if you follow "da Unix wae".
                        If your data is more complex (nested data items, etc.), you are better off using a format such as JSON and manipulating it with a proper parser.






                        share|improve this answer




























                          1














                          A relatively short solution using negative lookbehind assertions in a perl regular expression:



                          perl -p0e 's/(ip=)[^n]*((?:(?!ip=).)*?host=c)/1new_ip_for_host_c2/gs' hosts.txt



                          Explanation:



                          First, a print-loop is used using the -p option. This iterates through lines of a file provided as argument to perl and prints them. If the line is changed, the changed variant is printed, as with sed.



                          The file contents are not actually treated as separate lines, the -p option merely serves to make perl read in the file at once and use the contents as the default string to match in, without using a variable. This is achieved via the -0 option, which makes perl treat multiline input as a single line, substituting newline characters by null bytes.



                          In the pattern, parts excluding the value to be changed are saved.



                          The value is recognized as anything until the next newline, which I assume is understood as the next null-byte in this situation. Someone who knows this could perhaps explain.



                          Then, using a negative lookbehind assertion, all characters are matched that are not preceded by an ip= part, until the host=d part is encountered. This is necessary, otherwise perl would prefer the left-most match over the shortest, and would match from the very first ip= to the required host=c part.



                          The characters not preceded by ip= are matched in a non-greedy manner, otherwise again, the match would encompass everything from the first ip= until host=c, because by default, the longest match is preferred.
                          In other terms, this ensures that text is matched in sections terminated by host=c, and started by the ip= segment that is closest above host=c, not furthest.



                          The character restricted by the negative lookbehind expression inside the (?:) construct. It denotes a non-capturing group, meaning that it won't count toward the total number of backreferences in the substitution string.
                          Grouping is used so the * sign can properly quantify the whole negative lookbehind - dot character combo as a single atom.
                          In the example, backreferences range from 1 - 2, whilst without the non-capturing group, they would be 1 - 3, with 3 denoting the nested group.



                          The backreferences 1 and 2 in the substitution string are the matched parts of the section that enclose exactly the ip address for the host found in the matched section.



                          The ip address will thus be substituted by new_ip_for_host_c.



                          General blablablah:



                          I am not sure if the data format was the OP's choice, just putting this out in general: it is better to store such simple data in such a manner that records are separated by newlines, i.e. there are no multiline records.
                          Refer to the format of /etc/passwd .
                          Most standard Unix filters are line-oriented, and data manipulation is much easier if you follow "da Unix wae".
                          If your data is more complex (nested data items, etc.), you are better off using a format such as JSON and manipulating it with a proper parser.






                          share|improve this answer


























                            1












                            1








                            1






                            A relatively short solution using negative lookbehind assertions in a perl regular expression:



                            perl -p0e 's/(ip=)[^n]*((?:(?!ip=).)*?host=c)/1new_ip_for_host_c2/gs' hosts.txt



                            Explanation:



                            First, a print-loop is used using the -p option. This iterates through lines of a file provided as argument to perl and prints them. If the line is changed, the changed variant is printed, as with sed.



                            The file contents are not actually treated as separate lines, the -p option merely serves to make perl read in the file at once and use the contents as the default string to match in, without using a variable. This is achieved via the -0 option, which makes perl treat multiline input as a single line, substituting newline characters by null bytes.



                            In the pattern, parts excluding the value to be changed are saved.



                            The value is recognized as anything until the next newline, which I assume is understood as the next null-byte in this situation. Someone who knows this could perhaps explain.



                            Then, using a negative lookbehind assertion, all characters are matched that are not preceded by an ip= part, until the host=d part is encountered. This is necessary, otherwise perl would prefer the left-most match over the shortest, and would match from the very first ip= to the required host=c part.



                            The characters not preceded by ip= are matched in a non-greedy manner, otherwise again, the match would encompass everything from the first ip= until host=c, because by default, the longest match is preferred.
                            In other terms, this ensures that text is matched in sections terminated by host=c, and started by the ip= segment that is closest above host=c, not furthest.



                            The character restricted by the negative lookbehind expression inside the (?:) construct. It denotes a non-capturing group, meaning that it won't count toward the total number of backreferences in the substitution string.
                            Grouping is used so the * sign can properly quantify the whole negative lookbehind - dot character combo as a single atom.
                            In the example, backreferences range from 1 - 2, whilst without the non-capturing group, they would be 1 - 3, with 3 denoting the nested group.



                            The backreferences 1 and 2 in the substitution string are the matched parts of the section that enclose exactly the ip address for the host found in the matched section.



                            The ip address will thus be substituted by new_ip_for_host_c.



                            General blablablah:



                            I am not sure if the data format was the OP's choice, just putting this out in general: it is better to store such simple data in such a manner that records are separated by newlines, i.e. there are no multiline records.
                            Refer to the format of /etc/passwd .
                            Most standard Unix filters are line-oriented, and data manipulation is much easier if you follow "da Unix wae".
                            If your data is more complex (nested data items, etc.), you are better off using a format such as JSON and manipulating it with a proper parser.






                            share|improve this answer














                            A relatively short solution using negative lookbehind assertions in a perl regular expression:



                            perl -p0e 's/(ip=)[^n]*((?:(?!ip=).)*?host=c)/1new_ip_for_host_c2/gs' hosts.txt



                            Explanation:



                            First, a print-loop is used using the -p option. This iterates through lines of a file provided as argument to perl and prints them. If the line is changed, the changed variant is printed, as with sed.



                            The file contents are not actually treated as separate lines, the -p option merely serves to make perl read in the file at once and use the contents as the default string to match in, without using a variable. This is achieved via the -0 option, which makes perl treat multiline input as a single line, substituting newline characters by null bytes.



                            In the pattern, parts excluding the value to be changed are saved.



                            The value is recognized as anything until the next newline, which I assume is understood as the next null-byte in this situation. Someone who knows this could perhaps explain.



                            Then, using a negative lookbehind assertion, all characters are matched that are not preceded by an ip= part, until the host=d part is encountered. This is necessary, otherwise perl would prefer the left-most match over the shortest, and would match from the very first ip= to the required host=c part.



                            The characters not preceded by ip= are matched in a non-greedy manner, otherwise again, the match would encompass everything from the first ip= until host=c, because by default, the longest match is preferred.
                            In other terms, this ensures that text is matched in sections terminated by host=c, and started by the ip= segment that is closest above host=c, not furthest.



                            The character restricted by the negative lookbehind expression inside the (?:) construct. It denotes a non-capturing group, meaning that it won't count toward the total number of backreferences in the substitution string.
                            Grouping is used so the * sign can properly quantify the whole negative lookbehind - dot character combo as a single atom.
                            In the example, backreferences range from 1 - 2, whilst without the non-capturing group, they would be 1 - 3, with 3 denoting the nested group.



                            The backreferences 1 and 2 in the substitution string are the matched parts of the section that enclose exactly the ip address for the host found in the matched section.



                            The ip address will thus be substituted by new_ip_for_host_c.



                            General blablablah:



                            I am not sure if the data format was the OP's choice, just putting this out in general: it is better to store such simple data in such a manner that records are separated by newlines, i.e. there are no multiline records.
                            Refer to the format of /etc/passwd .
                            Most standard Unix filters are line-oriented, and data manipulation is much easier if you follow "da Unix wae".
                            If your data is more complex (nested data items, etc.), you are better off using a format such as JSON and manipulating it with a proper parser.







                            share|improve this answer














                            share|improve this answer



                            share|improve this answer








                            edited yesterday

























                            answered yesterday









                            Larry

                            1065




                            1065






















                                s.hayha is a new contributor. Be nice, and check out our Code of Conduct.










                                draft saved

                                draft discarded


















                                s.hayha is a new contributor. Be nice, and check out our Code of Conduct.













                                s.hayha is a new contributor. Be nice, and check out our Code of Conduct.












                                s.hayha is a new contributor. Be nice, and check out our Code of Conduct.
















                                Thanks for contributing an answer to Unix & Linux Stack Exchange!


                                • Please be sure to answer the question. Provide details and share your research!

                                But avoid



                                • Asking for help, clarification, or responding to other answers.

                                • Making statements based on opinion; back them up with references or personal experience.


                                To learn more, see our tips on writing great answers.





                                Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


                                Please pay close attention to the following guidance:


                                • Please be sure to answer the question. Provide details and share your research!

                                But avoid



                                • Asking for help, clarification, or responding to other answers.

                                • Making statements based on opinion; back them up with references or personal experience.


                                To learn more, see our tips on writing great answers.




                                draft saved


                                draft discarded














                                StackExchange.ready(
                                function () {
                                StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2funix.stackexchange.com%2fquestions%2f492309%2fhow-to-edit-the-entire-file-after-match-a-grep-pattern%23new-answer', 'question_page');
                                }
                                );

                                Post as a guest















                                Required, but never shown





















































                                Required, but never shown














                                Required, but never shown












                                Required, but never shown







                                Required, but never shown

































                                Required, but never shown














                                Required, but never shown












                                Required, but never shown







                                Required, but never shown







                                Popular posts from this blog

                                How to reconfigure Docker Trusted Registry 2.x.x to use CEPH FS mount instead of NFS and other traditional...

                                is 'sed' thread safe

                                How to make a Squid Proxy server?