A relatively short solution using negative lookahead assertions in a perl regular expression:
perl -p0e 's/(ip=)[^\n]*((?:(?!ip=).)*?host=c)/\1new_ip_for_host_c\2/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 lookahead 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 lookahead 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 lookahead - 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.
ip=
line? And what operating system are you using? Can we assume GNU tools (default on Linux)?host=c
I need to change the IP that is equals x.x.x.c