how to edit a file in bash script gracefully - bash

i have a text file which contains some specified strings, firstly locate them , delete few lines between them , then add something new. .e.g
$cat foo.txt
aaaaaa
bbbbbb
####start
123456
987655
121212
####end
cccccc
dddddd
$ cat bar.txt
AA
BB
CC
now, i want a bash script to edit foo.txt. delete content between "####start" and "####end" , insert content of bar.txt
new foo.txt will be
$cat foo.txt
aaaaaa
bbbbbb
####start
AA
BB
CC
####end
cccccc
dddddd

We can execute the following command,
sed -i '/####start/,/####end/{//!d};/####start/r bar.txt' foo.txt
The first part "/####start/,/####end/{//!d}" is to remove the lines in between the pattern and second part "/####start/r bar.txt" is to add the contents of bar.txt to foo.txt.
**Test:**
✓ krishna-VB# cat foo.txt
aaaaa
bbbbbb
####start
123465
454687
146546
####end
cccccc
dddddd
✓ krishna-VB# cat bar.txt
AA
BB
CC
✓ krishna-VB# sed -i '/####start/,/####end/{//!d};/####start/r bar.txt' foo.txt
✓ krishna-VB# cat foo.txt
aaaaa
bbbbbb
####start
AA
BB
CC
####end
cccccc
dddddd
If you want to verify the just output before modification of file, you can remove '-i' option before sed.

This awk one-liner should help:
awk 'NR==FNR{a[NR]=$0;n=NR;next}
!p;/##start/{p=1;for(i=1;i<=n;i++)print a[i];}/#end/{p=0;print}' bar foo

Related

gawk use to replace a line containing a pattern with multiple lines using variable

I am trying to replace a line containing the Pattern using gawk, with a set of lines. Let's say, file aa contains
aaaa
ccxyzcc
aaaa
ddxyzdd
I'm using gawk to replace all lines containing xyz with a set of lines 111\n222, my changed contents would contain:
aaaa
111
222
aaaa
111
222
But, if I use:
gawk -v nm2="111\n222" -v nm1="xyz" '{ if (/nm1/) print nm2;else print $0}' "aa"
The changed content shows:
aaaa
ccxyzcc
aaaa
ddxyzdd
I need the entire lines those contain xyz i.e. lines ccxyzcc and ddxyzdd having to be replaced with 111 followed by 222. Please help.
The problem with your code was that /nm1/ tries to match nm1 as pattern not the value in nm1 variable
$ gawk -v nm2="111\n222" -v nm1="xyz" '$0 ~ nm1{print nm2; next} 1' aa
aaaa
111
222
aaaa
111
222
Thanks #fedorqui for suggestion, next can be avoided by simply overwriting content of input line matching the pattern with required text
gawk -v nm2="111\n222" -v nm1="xyz" '$0 ~ nm1{$0=nm2} 1' aa
Solution with GNU sed
$ nm1='xyz'
$ nm2='111\n222'
$ sed "/$nm1/c $nm2" aa
aaaa
111
222
aaaa
111
222
The c command would delete the line matching pattern and add the text given
When using awk's ~ operator, and you don't need to provide a literal regex on the right-hand side.
Your command as-such with the correction of improper syntax would be something like,
gawk -v nm2="111\n222" -v nm1="xyz" '{ if ( $0 ~ nm1 ) print nm2;else print $0}' input-file
which produces the output.
aaaa
111
222
aaaa
111
222
This is how I'd do it:
$ cat aa
aaaa
ccxyzcc
aaaa
ddxyzdd
$ awk '{gsub(/.*xyz.*/, "111\n222")}1' aa
aaaa
111
222
aaaa
111
222
$
Passing variables as patterns to awk is always a bit tricky.
awk -v nm2='111\n222' '{if ($1 ~ /xyz/){ print nm2 } else {print}}'
will give you the output, but the 'xyz' pattern is now fixed.
Passing nm1 as shell variable will also work:
nm1=xyz
awk -v nm2='111\n222' '{if ($1 ~ /'$nm1'/){ print nm2 } else {print}}' aa

How to insert a text?

foo.txt consists of
printf("%f \n\n",row1.req_pnttime);
printf("%f \n\n",avinash);
printf("%f \n\n",foo);
printf("%f \n\n",bar);
bar.txt consists of,
foo
bar
foo1
bar1
I want to combine first line of bar.txt with the first line of foo.txt in a specified place.Same for all the lines.Like below,
Expected output:
printf("foo%f \n\n",row1.req_pnttime);
printf("bar%f \n\n",avinash);
printf("foo1%f \n\n",foo);
printf("bar1%f \n\n",bar);
I tried the below , but it won't work.
awk -v FS="\"" -v OFS="\"" 'FNR==NR{a=$0;}{$2=a[FNR]$2}1' bar.txt foo.txt
You can try this,
sed 'R bar.txt' foo.txt | sed 'N;s/^\(.*\)\(%.*\)\n\(.*\)/\1\3\2/'
Test:
sat:~# sed 'R bar.txt' foo.txt | sed 'N;s/^\(.*\)\(%.*\)\n\(.*\)/\1\3\2/'
printf("foo%f \n\n",row1.req_pnttime);
printf("bar%f \n\n",avinash);
printf("foo1%f \n\n",foo);
printf("bar1%f \n\n",bar);
Ok, i figured out the problem. One through awk,
$ awk -v FS="\"" -v OFS="\"" 'FNR==NR{a[FNR]=$0;next}{$2=a[FNR]$2}1' bar.txt foo.txt
printf("foo%f \n\n",row1.req_pnttime);
printf("bar%f \n\n",avinash);
printf("foo1%f \n\n",foo);
printf("bar1%f \n\n",bar);
Assuming that the text is always aligned to the same column you could do this:
cut -b1-8 foo.txt > a
cut -b9- foo.txt > b
paste -d '' a bar.txt b

Replace line after match

Given this file
$ cat foo.txt
AAA
111
BBB
222
CCC
333
I would like to replace the first line after BBB with 999. I came up with this command
awk '/BBB/ {f=1; print; next} f {$1=999; f=0} 1' foo.txt
but I am curious to any shorter commands with either awk or sed.
This might work for you (GNU sed)
sed '/BBB/!b;n;c999' file
If a line contains BBB, print that line and then change the following line to 999.
!b negates the previous address (regexp) and breaks out of any processing, ending the sed commands, n prints the current line and then reads the next into the pattern space, c changes the current line to the string following the command.
This is some shorter:
awk 'f{$0="999";f=0}/BBB/{f=1}1' file
f {$0="999";f=0} if f is true, set line to 999 and f to 0
/BBB/ {f=1} if pattern match set f to 1
1 print all lines, since 1 is always true.
can use sed also, it's shorter
sed '/BBB/{n;s/.*/999/}'
$ awk '{print (f?999:$0); f=0} /BBB/{f=1}' file
AAA
111
BBB
999
CCC
333
awk '/BBB/{print;getline;$0="999"}1' your_file
sed 's/\(BBB\)/\1\
999/'
works on mac

BASH print 2 files

I want to print the output of file1 to first column in new file and file 2 to the second column in the new file.
Something like this.
file1
AAA
BBB
CCC
file2
XXX
YYY
ZZZ
file3
AAA XXX
BBB YYY
CCC ZZZ
paste command will do this job out-of-the-box:
paste file1 file2 > file3
AAA XXX
BBB YYY
CCC ZZZ
Try this click here
You can use paste and format using cut to remove leading and trailing spaces

Transpose Columns in a single comma separated row conditionally

I have an input file that looks like this:
aaa 111
aaa 222
aaa 333
bbb 444
bbb 555
I want to create a transposed output file that looks like this:
aaa 111,222,333
bbb 444,555
How can I do this using awk, sed, etc?
One way using awk:
$ awk '{a[$1]=a[$1]?a[$1]","$2:$2}END{for(k in a)print k,a[k]}' file
aaa 111,222,333
bbb 444,555
And if your implementation of awk doesn't support the ternary operator then:
$ awk 'a[$1]{a[$1]=a[$1]","$2;next}{a[$1]=$2}END{for(k in a)print k,a[k]}' file
aaa 111,222,333
bbb 444,555
Your new file does not cause any problems for the script, what output are you getting? I suspect it's probably a line ending issue. Run dos2unix file to fix the line ending.
$ cat file
APM00065101435 189
APM00065101435 190
APM00065101435 191
APM00065101435 390
190104555 00C7
190104555 00D1
190104555 00E1
190104555 0454
190104555 0462
$ awk '{a[$1]=a[$1]?a[$1]","$2:$2}END{for(k in a)print k,a[k]}' file
APM00065101435 189,190,191,390
190104555 00C7,00D1,00E1,0454,0462
Code for GNU sed:
I made a question for this and got a very good & useful answer from potong:
sed -r ':a;$!N;s/^(([^ ]+ ).*)\n\2/\1,/;ta;P;D' file
sed -r ':a;$!N;s/^((\S+\s).*)\n\2/\1,/;ta;P;D' file

Resources