4

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/c\SMEAR=-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.

6

It appears that the sequence of the lines doesn't matter for your use case. Given that, I would use ex and simply:

  1. Remove all instances of SMEAR;
  2. Insert the line you want.

You can do this like so:

printf '%s\n' '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 '%s\n' 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 '%s\n' '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 '%s\n' '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 '%s\n' '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.

  • Is there a way to backup the original file. Something equivalent to -i.bkp in sed – WanderingMind May 11 '16 at 20:10
  • 2
    @WanderingMind sed '/SMEAR/d;$a\SMEAR=-5 would be something like this ex method to which the (unportable) -i option could be included. – thrig May 11 '16 at 20:19
  • 1
    @thrig - that would fail if the last line matched though... – don_crissti May 11 '16 at 20:22
  • @WanderingMind, that's a good question; I've updated my answer to describe the ex method for doing this. – Wildcard May 11 '16 at 20:37
  • I'm unable to pass a variable as a value in this command. Can you tell me how is this done – WanderingMind May 11 '16 at 23:12
5

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
  • Hi can you tell me is there a way to pass the value of SMEAR as a variable. If I use $var and " " in above command, sed complains saying bad substitution – WanderingMind May 11 '16 at 23:25
1

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
  • This will probably be much faster when used with -F and dealing with huge input files... – don_crissti Nov 1 '18 at 21:29
  • Yeah, but, while the OP talks about a line containing the string SMEAR, I suspect that they would not want to lose a line that said FOO=BAR_SMEAR_BAZ or even BESMEARED=42. I believe that any solution that doesn’t anchor SMEAR to the beginning of the line and require that it be immediately followed by an = is flawed, and has the risk of behaving in ways that the OP doesn’t want. – G-Man Nov 2 '18 at 6:43

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.