34

With a single input file that only contains comments (starting with #) and VARIABLE=value lines, is it possible to replace a value for a single variable if found and, otherwise, append the pair to the end of file if not found?

My current method works by deleting it in a first pass, then appending it to the end of the file in a second pass, but this method messes up the line ordering (and is also two different commands):

sed -r "/^FOOBAR=.*$/d"      -i samefile &&
sed -r "$ a\FOOBAR=newvalue" -i samefile

Is there anyway to do this, ie. keeping line order, in a single sed line? If some other utility (awk, ...) does this, I'ld take it over sed.

29

It's actually quite simple with sed: 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 the desired variable=value then append to the current line in the hold buffer. Finally, exchange again:

sed '/^FOOBAR=/{h;s/=.*/=newvalue/};${x;/^$/{s//FOOBAR=newvalue/;H};x}' infile

The above is gnu sed syntax. Portable:

sed '/^FOOBAR=/{
h
s/=.*/=newvalue/
}
${
x
/^$/{
s//FOOBAR=newvalue/
H
}
x
}' infile
  • 1
    How would this look like if newvalue is stored in a variable? – user1810087 Dec 29 '17 at 0:31
  • sed "/^${varName}=/{h;s/=.*/=${varValue}/};\${x;/^$/{s//${varName}=${varValue}/;H};x}" ${VARFILE} with variables, double quotes to allow for variable substitution and and escaping a $ that is not a substitution at this point, additionally if you value is stored in $$varName : sed "/^${varName}=/{h;s/=.*/=${!varName}/};\${x;/^$/{s//${varName}=${!varName}/;H};x}" ${VARFILE} – DarkMukke Apr 23 at 17:18
  • 1
    This solution is not 'simple' lol. But it works. sed man pages are not the easiest things to sift through, it would be really helpful if you could detail the effect of every section of the expression :) – user2066480 Jul 31 at 11:04
18

This can probably be shortened. It's not a single sed command and it also uses grep, but this seems to be basically what you're wanting. It's a single line, and it edits the file in-place (no temp files).

grep -q "^FOOBAR=" file && sed "s/^FOOBAR=.*/FOOBAR=newvalue/" -i file || 
    sed "$ a\FOOBAR=newvalue" -i file
13

Here is a simpler sed approach, as I don't find sed hold space easy to work with. If you are comfortable with hold space, using don_crissti approach gives additional opportunity to preserve anything from the existing line, but this is usually very rare.

In this approach, you just print all but the line that you want to drop and then at the end, append the replacement.

sed -n -e '/^FOOBAR=/!p' -e '$aFOOBAR=newvalue' infile
  • 1
    This version is very good if you plan to update a file that may have an existing but outdated value. – guillem Sep 30 '18 at 14:37
3

Based on the other answers, if what you want to do is replace a variable's value if that variable is present in the file and append it to the end of the file if it is not (which is not what your posted sed commands do), you could try this:

perl -ne '$c=1 if s/^FOOBAR=.*$/FOOBAR=newvalue/;  
             print; 
             END{print "FOBAR=newvalue" unless $c==1}' file > tmpfile && 
mv tmpfile file
1

It's a bit easier in awk, although the "in place editing" is not automatic:

awk -v varname="FOOBAR" -v newval="newvalue" '
    BEGIN {FS = OFS = "="}
    $1 == varname {$2 = newval; found = 1}
    {print}
    END {if (! found) {print varname, newval}}
' file > tempfile &&
mv tempfile file
1

Simply use grep and echo to create an empty record :

grep -q '^FOOBAR=' somefile || echo 'FOOBAR=VALUE' >> somefile
sed -i 's/FOOBAR=.*$/FOOBAR=VALUE/' somefile 

Each line escapes with error code zero.

  • You lose line ordering while adding an extra commands and grep and echo – BlakBat Mar 8 '16 at 15:46
  • Indeed if you insert the new items at the end of file, but keep ordering if you replace a old value. – MUY Belgium Mar 8 '16 at 15:54
0

Actually works with the following: sed -i '/^FOOBAR=/{h;s/=.*/=newvalue/};${x;/^$/{s//FOOBAR=newvalue/;H};x}' infile In the answer choosen -i is missing.

-1

to do this with perl and in-place this works fine for me:

grep ^FOOBAR= my.file && perl -i -ple "s/^FOOBAR=.+/FOOBAR=newvalue/g" my.file || echo FOOBAR=newvalue >> my.file
  • 2
    This answer is bad in so many ways! grep and echo and perl instead of doing everything in perl? Also, writing to a file (>> my.file) as you're reading from it (grep my.file)? – vladr Nov 6 '15 at 18:53

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.