sed: how to replace line if found or append to end of file if not found?

48,431

Solution 1

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

Solution 2

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

Solution 3

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

Solution 4

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.

Solution 5

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 "FOOBAR=newvalue" unless $c==1}' file > tmpfile && 
mv tmpfile file
Share:
48,431

Related videos on Youtube

BlakBat
Author by

BlakBat

Updated on September 18, 2022

Comments

  • BlakBat
    BlakBat over 1 year

    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.

  • vladr
    vladr over 8 years
    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)?
  • BlakBat
    BlakBat about 8 years
    You lose line ordering while adding an extra commands and grep and echo
  • MUY Belgium
    MUY Belgium about 8 years
    Indeed if you insert the new items at the end of file, but keep ordering if you replace a old value.
  • DavidPostill
    DavidPostill about 8 years
    This is really a comment and not an answer to the original question. To critique or request clarification from an author, leave a comment below their post - you can always comment on your own posts, and once you have sufficient reputation you will be able to comment on any post. Please read Why do I need 50 reputation to comment? What can I do instead?
  • user1810087
    user1810087 over 6 years
    How would this look like if newvalue is stored in a variable?
  • guillem
    guillem over 5 years
    This version is very good if you plan to update a file that may have an existing but outdated value.
  • DarkMukke
    DarkMukke about 5 years
    sed "/^${varName}=/{h;s/=.*/=${varValue}/};\${x;/^$/{s//${varNam‌​e}=${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//${varNam‌​e}=${!varName}/;H};x‌​}" ${VARFILE}
  • user2066480
    user2066480 almost 5 years
    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 :)
  • Alek
    Alek about 4 years
    Great answer! If you don't mind that the existing line is moved to the end. Thanks! I prefer to put it in one parameter: sed -nr '/^FOOBAR=/!p;$aFOOBAR=newvalue' infile
  • Alek
    Alek about 4 years
    Great solution! If you prefer to keep existing line in place!
  • Daniel Böhmer
    Daniel Böhmer almost 4 years
    You can use Perl’s -i switch to edit the file in place and save the hassle of managing a temp file
  • Daniel Böhmer
    Daniel Böhmer almost 4 years
    This didn’t work for me as long as the file was completely empty (just created with touch) using sed (GNU sed) 4.7 Packaged by Debian. The file just stayed empty with no error message.
  • haridsv
    haridsv almost 4 years
    @DanielBöhmer I just happened to hit this a couple of days back and understand that this is normal behavior for sed, considering how it is line oriented (when empty, there are no lines). One workaround is to start with a dummy line that will always be removed.
  • DKebler
    DKebler over 3 years
    the append will fail if the file has no lines like when a file was just created by touch.
  • DKebler
    DKebler over 3 years
    one could put this before the sed command to make sure file has at least one line. tail -c1 infile | read -r _ || echo >> infile stackoverflow.com/a/34865616/4695378
  • BlakBat
    BlakBat about 3 years
    Question asks for a one liner. Even @snapshoe didn't do it as a one-liner.
  • Waqleh
    Waqleh about 3 years
    @BlakBat you do realize that if you add && after each var instead of new line or if you replace the vars with string it is then one line! learn command line before down voting answers
  • BlakBat
    BlakBat about 3 years
    One line = One command. && means multiple commands. Learn to be agreable and not to post an anecdotal answer ("didn't work for me yada yada"). BTW, pretty much sure it works with sed 4.4 (used on debian:stretch)
  • phsource
    phsource almost 3 years
    This solution is much more understandable to someone who doesn't have detailed knowledge of sed or grep
  • ShahQermez
    ShahQermez almost 3 years
    I tried this option and I couldn't get it to work with in-place editing (-i). It seemed to duplicate previous lines.
  • haridsv
    haridsv over 2 years
    @Danny When I tried using gsed with -i option on mac, it worked just fine. Make sure you are using a GNU version of sed.
  • ShahQermez
    ShahQermez over 2 years
    @haridsv Odd, I was testing on a CentOS 7 box, so I would have been using GNU sed. I'll have to play with it again and see if I was making on error. I ended up having to use don_crissti's method to get it working yesterday.
  • Scott - Слава Україні
    Scott - Слава Україні over 2 years
    equation=E=mc².  This chokes if the old value contains an =.
  • Scott - Слава Україні
    Scott - Слава Україні over 2 years
    This is an interesting contribution. However, beware of sub(). This will choke if the old value ($2) is a regular expression that doesn’t match itself (e.g., a[b] or 1+2), or if it is a substring of the variable name (e.g., cat=c or dog=o).
  • glenn jackman
    glenn jackman over 2 years
    True. Instead of $2 = newval, could use $0 = $1 OFS newval