seriousgeek
seriousgeek

Reputation: 1034

Reverse file using tac and sed

I have a usecase where I need to search and replace the last occurrence of a string in a file and write the changes back to the file. The case below is a simplified version of that usecase:

I'm attempting to reverse the file, make some changes reverse it back again and write to the file. I've tried the following snippet for this:

tac test | sed s/a/b/ | sed -i '1!G;h;$!d' test

test is a text file with contents:

a
1 
2 
3
4
5

I was expecting this command to make no changes to the order of the file, but it has actually reversed the contents to:

5
4
3
2
1
b

How can i make the substitution as well as retain the order of the file?

Upvotes: 3

Views: 2398

Answers (5)

James Brown
James Brown

Reputation: 37404

Another in awk. First a test file:

$ cat file
a
1
a
2
a

and solution:

$ awk '
$0=="a" && NR>1 {             # when we meet "a"
    print b; b=""             # output and clear buffer b
}
{
    b=b (b==""?"":ORS) $0     # gether the buffer
}
END {                         # in the end
    sub(/^a/,"b",b)           # replace the leading "a" in buffer b with "b"
    print b                   # output buffer 
}' file
a
1
a
2
b

Writing back the happens by redirecting the output to a temp file which replaces the original file (awk ... file > tmp && mv tmp file) or if you are using GNU awk v. 4.1.0+ you can use inplace edit (awk -i inplace ...).

Upvotes: 1

anubhava
anubhava

Reputation: 785156

Here is a way to do this in a single command using awk.

First input file:

cat file
a
1
2
3
4
a
5

Now this awk command:

awk '{a[i++]=$0} END{p=i; while(i--) if (sub(/a/, "b", a[i])) break;
      for(i=0; i<p; i++) print a[i]}' file
a
1
2
3
4
b
5

To save output back into original file use:

awk '{a[i++]=$0} END{p=i; while(i--) if (sub(/a/, "b", a[i])) break;
for(i=0; i<p; i++) print a[i]}' file >> $$.tmp && mv $$.tmp f

Upvotes: 1

Jack
Jack

Reputation: 6158

Another way is to user grep to get the number of the last line that contains the text you want to change, then use sed to change that line:

$ linno=$( grep -n 'abc' <file> | tail -1 | cut -d: -f1 )

$ sed -i "${linno}s/abc/def/" <file>

Upvotes: 2

SLePort
SLePort

Reputation: 15461

You can tac your file, apply substitution on first occurrence of desired pattern, tac again and tee result to a temporary file before you rename it with the original name:

tac file | sed '0,/a/{s//b/}' | tac > tmp && mv tmp file

Upvotes: 4

Vadim Beskrovnov
Vadim Beskrovnov

Reputation: 961

Try to cat test | rev | sed -i '1!G;h;$!d' | rev

Or you can use only sed coomand: For example you want to replace ABC on DEF:

You need to add 'g' to the end of your sed:

sed -e 's/\(.*\)ABC/\1DEF/g'

This tells sed to replace every occurrence of your regex ("globally") instead of only the first occurrence.

You should also add a $, if you want to ensure that it is replacing the last occurrence of ABC on the line:

sed -e 's/\(.*\)ABC$/\1DEF/g'

EDIT

Or simply add another | tac to your command: tac test | sed s/a/b/ | sed -i '1!G;h;$!d' | tac

Upvotes: 2

Related Questions