10

I know sed can replace a piece of string within a text file like this :

sed -i 's/oldstring/newstring/g' test.txt

But, how do I replace the line if it exists and automatically add it on a new line if it doesn't exist?

Thanks.

Edit : Thank you for your responses. As per the requests on your comments and answer, here's more details :

  • I want to check the existence of the old string and replace it with the new string if it exists.
  • I want to add the new string in a new line at the end of the txt file if the old string does not exist
  • If there are multiple occurances of the same string, it would be an error since its a config file.The other occurances can be removed after replacing the first occurance.
2
  • 2
    Add it in a new line where? At the end of the doc?
    – muru
    Commented Nov 21, 2015 at 10:11
  • 1
    Check existence of old or new string or both? Add old or new string?
    – Sadi
    Commented Nov 21, 2015 at 10:51

4 Answers 4

15

One way to do this it to bring grep into the equation. First check whether the file contains the string with a quick grep and then append or substitute accordingly:

grep -q string file && 
    sed -i 's/string/newstring/' file || echo "newstring" >> file

The above is a shorthand way of writing:

if grep -q string file; then 
    sed -i 's/string/newstring/' file
else
    echo "newstring" >> file
fi

Personally, however, I would use perl for this instead. Just read the file once and set a variable to 1 if the substitution was made. Then, at the end, add the string if the variable is not 1:

perl -lpe '$i=1 if s/oldstring/newstring/; 
           END{print "newstring" if $i!=1;}' file > tmpfile && mv tmpfile file
4
  • Thanks for the nice shorthand, but wouldn't it be better to use printf "\nnewstring" as echo "newstring" seems to append it to the last (non-empty) line? On the other hand, using printf "\n..." leaves an empty line above when there's already an empty line at the end. Any better solution for this?
    – Sadi
    Commented Nov 21, 2015 at 15:41
  • @Sadi no, echo foo >> file will add foo in a new line. So will printf 'foo\n' >> file.
    – terdon
    Commented Nov 22, 2015 at 0:06
  • I also thought so but when I tested on this occasion in a new text file ending with a non-empty line (i.e. no empty line at the end of the file) the command echo foo >> file always appended "foo" to that last non-empty line and added an empty line after that, so it's apparently inserting "foo\n" instead of "\nfoo" then (Ubuntu 15.10). I'll further check this issue...
    – Sadi
    Commented Nov 22, 2015 at 8:33
  • @Sadi that's different. The >> operator appends to the end of the file. Normally, the last character of a text file is \n so it will append the text after the \n and make a new line. If you know your file doesn't end with a \n, you would have to use printf '\nfoo\n' >> file, yes. If you need to deal with such cases, please edit your question and make it clear.
    – terdon
    Commented Nov 22, 2015 at 13:08
3

This should achieve what is required:

grep -q "oldstring" test.txt
if [ $? -eq 1 ]; then
    echo "newstring" >> test.txt
else
    sed -i 's/oldstring/newstring/g' test.txt
fi
2
  • @kos : Thank you for the UUOC award ( : - D ) and for taking the time to teach something to this beginner in shell scripting. I'll now try to improve the code above.
    – Sadi
    Commented Nov 21, 2015 at 11:33
  • 1
    I didn't really mean to "award" it to you, just to point that out. :D. Looks better, you could also shorten the if / else check to this single line: [ $? -eq 1 ] && echo "newstring" >> test.txt || sed -i 's/oldstring/newstring/g' test.txt.
    – kos
    Commented Nov 21, 2015 at 11:47
2

Using AWK:

<<<"$(<in)" awk '{if(/foo/){x=sub(/foo/, "bar", $0)};print}END{if(x!=1){print "bar"}}' >in
% cat in1
string oldstring string
% cat in2
string foo string
% <<<"$(<in1)" awk '{if(/oldstring/){x=sub(/oldstring/, "newstring", $0)};print}END{if(x!=1){print "newstring"}}' >in1
user@user-X550CL ~/tmp % cat in1
string newstring string
% <<<"$(<in2)" awk '{if(/oldstring/){x=sub(/oldstring/, "newstring", $0)};print}END{if(x!=1){print "newstring"}}' >in2
% cat in2
string foo string
newstring
0

Here the right answer, because above proposal is illegat for multimple calls of script. For example you have file text1:

echo "1aaab" > text1

and script:

grep -q 1aaab text1&& 
    sed -i 's/1aaab/aaab/' text1|| echo "aaab" >> text1

on first call you'll got:

aaab

on second:

aaab
aaab


The correct script is below:

_conf=/etc/sysctl.conf                # our file
_commented=#net.ipv4.ip_forward=1     # searched insufficient text
_uncommented=net.ipv4.ip_forward=1    # text to reaplace insufficient or absent text

grep -q $_commented $_conf && (sed -i 's/'$_commented'/'$_uncommented/ $_conf || echo $_uncommented >> $_conf)

  • if net.ipv4.ip_forward=1 is absent - will be added new record net.ipv4.ip_forward=1
  • if exists (commented by #) - will be uncommented from #net.ipv4.ip_forward=1 to net.ipv4.ip_forward=1
  • and will be not spawn multiple copies of expected text (net.ipv4.ip_forward=1) on each script call
1
  • 1) I think you're missing a few $ in your code (_commented instead of $_commented?) 2) Even if we did add the $, variable expansion doesn't take place in single quotes 3) Even if we did switch to double quotes, your code will fail to add net.ipv4.ip_forward=1 if the file didn't contain #net.ipv4.ip_forward=1 in the first place.
    – muru
    Commented Jan 11 at 6:35

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .