tac file |
awk -v string='apple1' -v replace='444444444' '
!flag && $1 == string { $3 = replace; flag = 1 }
{ print }' |
tac
This pipeline first reverses the ordering of the lines in the data using tac
from GNU coreutils. The last line is where the 1st column is a particular string is easier to find that way.
The awk
command simply compares the first column to the given string, and if we haven't yet made a replacement (!flag
is non-zero), we modify the third column as soon as we find the string in the 1st column. When doing so, we also set flag
to one so that no further replacements are made.
The rest of the awk
program simply prints the current line (including the modified one).
At the end of the pipeline, we reverse the order of the lines again with tac
.
The output of this, given the data in the question, is
apple1 10109283 20012983
apple1 10983102 10293809
apple1 10293893 444444444
apple10 109283019 109238901
apple10 192879234 234082034
apple10 234908443 3450983490
The columns on the modified line are a bit different from those of the other lines due to the modification of the 3rd column. To make it look nicer, you may pass the result through an additional column -t
stage at the end of the pipeline. If you do, the output would look like
apple1 10109283 20012983
apple1 10983102 10293809
apple1 10293893 444444444
apple10 109283019 109238901
apple10 192879234 234082034
apple10 234908443 3450983490
with multiple spaces between the columns.
With sed
, it's not so easy as just replacing the 3rd column on the first line where the string occurs in the 1st column (assuming we reverse the lines of the data as in the above pipeline). We must also not replace the 3rd column in on any subsequent lines, even if the 1st column matches our string.
This is a sed
editing script that does it correctly (there may be any number of variations of this that may work):
/^apple1\>/ ! {
p
d
}
s/[[:digit:]]*$/444444444/
:loop
n
$ ! b loop
The first part takes care of printing lines at the start of the input that does not match apple1
in the first column. The \>
in the expression matches the end of the word apple1
so that we don't accidentally match apple10
or apple12
or whatever other similar string may occur.
The p
(print) and d
(delete + continue with next line from the top of the script) within { ... }
are executed for each line at the start of the input that does not match the expression.
The s
command (substitute) is executed for the first line of input that does match apple1
at the start of the line. It simply substitutes the string of digits at the end of the line with our 4
s.
Then comes a section labelled loop
that takes care of passing through the the rest of the data unmodified by printing the current line and reading the next line with n
(n
does both printing and reading). The "current line" will have the modification done by the s
command on the first trip through this loop.
The very last line branches back to the loop
label if we're not yet at the last line of input.
Example run:
$ tac file | sed -f script.sed | tac
apple1 10109283 20012983
apple1 10983102 10293809
apple1 10293893 444444444
apple10 109283019 109238901
apple10 192879234 234082034
apple10 234908443 3450983490
sed -zE "s/(.*\n$k\s+[0-9]+\s+)[0-9]+/\1$v/"
? I can't test it by myself right now. – Philippos Aug 02 '19 at 07:43