I have an input file, say, file1, which appears as follows:
WRITE=2 START=1 SMEAR=0 MIN=6 MAX=100
My problem is to search for the string SMEAR and if the string exists replace the entire line with SMEAR=-5. However, if the SMEAR does not exist, then I need to insert a new line SMEAR=-5. Ultimately the line SMEAR=-5 should appear only once.
I can replace the string using sed:
sed -i '/SMEAR/cSMEAR=-5' file1
Or I can insert the string as a new line
sed -i '10i SMEAR=-5' file1
But how can I combine them based on the existence of the string in the file?
EDIT:
Also it would be helpful if I can pass the value of SMEAR as a variable.
Answers:
Thank you for visiting the Q&A section on Magenaut. Please note that all the answers may not help you solve the issue immediately. So please treat them as advisements. If you found the post helpful (or not), leave a comment & I’ll get back to you as soon as possible.
Method 1
It appears that the sequence of the lines doesn’t matter for your use case. Given that, I would use ex and simply:
- Remove all instances of
SMEAR; - Insert the line you want.
You can do this like so:
printf '%sn' 'g/SMEAR/d' '$a' 'SMEAR=-5' . x | ex file.txt
The first command is to globally delete all lines which match the regex /SMEAR/.
The next command is to append after the last line ($) the line SMEAR=-5. The . ends the text to be appended.
The x command saves changes and exits.
Each command is terminated by a newline by using printf '%sn' to send them to ex.
Also see this very similar solution which I wrote a while back on the vi/Vim stack exchange.
To test the changes by printing the changed file to the command line without saving the changes, replace the x with the two commands %p 'q!' like so:
printf '%sn' 'g/SMEAR/d' '$a' 'SMEAR=-5' . %p 'q!' | ex file.txt
% means “entire buffer,” which is what gets printed.
q! means “quit, discarding changes.”
To save the changes into a new file, replace the %p with w newfile.txt like so:
printf '%sn' 'g/SMEAR/d' '$a' 'SMEAR=-5' . 'w newfile.txt' 'q!' | ex file.txt
This writes the modified buffer into newfile.txt.
Alternatively you could do this at the start, to make a backup, and then save the changed file contents to the original location, file.txt, like so:
printf '%sn' 'w file.txt.bak' 'g/SMEAR/d' '$a' 'SMEAR=-5' . x | ex file.txt
Edit: Actually you don’t need to use q!; simply omitting the x is enough to avoid saving changes. When ex gets an EOF on trying to read further input, it will exit, and will not save changes.
Method 2
You could try like this: if a line matches just copy it to the hold space then substitute the value.
On the la$t line exchange hold space and pattern space then check if the latter is empty. If it’s not empty, it means the substitution was already made so nothing to do. If it’s empty, that means no match was found so replace the pattern space with SMEAR=-5 then append to the current line in the hold buffer. Finally, exchange again:
sed '/SMEAR/{h;s/.*/SMEAR=-5/};${x;/^$/{s//SMEAR=-5/;H};x}' infile
The above is gnu sed syntax. Portable:
sed '/SMEAR/{
h
s/.*/SMEAR=-5/
}
${
x
/^$/{
s//SMEAR=-5/
H
}
x
}' infile
Alternate way with ed:
ed -s infile<<IN || printf %s\n SMEAR=-5 >> infile /SMEAR.*/s//SMEAR=-5/ w q IN
If no line matches pattern, ed will error out so the second command (printf...) will be executed to append the line to the file. Otherwise ed will edit the file in-place.
If you have the value of SMEAR saved in variable e.g.
var=-7
you would run:
sed '/SMEAR/{h;s/.*/SMEAR='"$var"'/};${x;/^$/{s//SMEAR='"$var"'/;H};x}' infile
or
ed -s infile<<IN || printf %s\n "SMEAR=${var}" >> infile
/SMEAR.*/s//SMEAR=${var}/
w
q
IN
Method 3
Or, implementing Wildcard’s idea
(remove any/all existing line(s) that contain SMEAR,
and then insert the line you want) with stone-age tools:
(grep -v '^SMEAR=' file1; echo 'SMEAR=-5') > file1.tmp && cp file1.tmp file1 && rm file1.tmp
or, if you aren’t concerned
about preserving the attributes (e.g., permissions) of
and hard links to file1, you could do
(grep -v '^SMEAR=' file1; echo 'SMEAR=-5') > file1.tmp && mv file1.tmp file1
or, if you have sponge, you could do
(grep -v '^SMEAR=' file1; echo 'SMEAR=-5') | sponge file1
All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0