Appending a line to a file only if it does not already exist

Viewed 140123

I need to add the following line to the end of a config file:

include "/configs/projectname.conf"

to a file called lighttpd.conf

I am looking into using sed to do this, but I can't work out how.

How would I only insert it if the line doesn't already exist?

23 Answers

Try this:

grep -q '^option' file && sed -i 's/^option.*/option=value/' file || echo 'option=value' >> file

If, one day, someone else have to deal with this code as "legacy code", then that person will be grateful if you write a less exoteric code, such as

grep -q -F 'include "/configs/projectname.conf"' lighttpd.conf
if [ $? -ne 0 ]; then
  echo 'include "/configs/projectname.conf"' >> lighttpd.conf
fi

Here is a one-liner sed which does the job inline. Note that it preserves the location of the variable and its indentation in the file when it exists. This is often important for the context, like when there are comments around or when the variable is in an indented block. Any solution based on "delete-then-append" paradigm fails badly at this.

   sed -i '/^[ \t]*option=/{h;s/=.*/=value/};${x;/^$/{s//option=value/;H};x}' test.conf

With a generic pair of variable/value you can write it this way:

   var=c
   val='12 34' # it handles spaces nicely btw
   sed -i '/^[ \t]*'"$var"'=/{h;s/=.*/='"$val"'/};${x;/^$/{s//c='"$val"'/;H};x}' test.conf

Finally, if you want also to keep inline comments, you can do it with a catch group. E.g. if test.conf contains the following:

a=123
# Here is "c":
  c=999 # with its own comment and indent
b=234
d=567

Then running this

var='c'
val='"yay"'
sed -i '/^[ \t]*'"$var"'=/{h;s/=[^#]*\(.*\)/='"$val"'\1/;s/'"$val"'#/'"$val"' #/};${x;/^$/{s//'"$var"'='"$val"'/;H};x}' test.conf

Produces that:

a=123
# Here is "c":
  c="yay" # with its own comment and indent
b=234
d=567

Here's an awk implementation

/^option *=/ { 
  print "option=value"; # print this instead of the original line
  done=1;               # set a flag, that the line was found
  next                  # all done for this line
}
{print}                 # all other lines -> print them
END {                   # end of file
  if(done != 1)         # haven't found /option=/ -> add it at the end of output
    print "option=value"
}

Run it using

awk -f update.awk < /etc/fdm_monitor.conf > /etc/fdm_monitor.conf.tmp && \
   mv /etc/fdm_monitor.conf.tmp /etc/fdm_monitor.conf

or

awk -f update.awk < /etc/fdm_monitor.conf | sponge /etc/fdm_monitor.conf

EDIT: As a one-liner:

awk '/^option *=/ {print "option=value";d=1;next}{print}END{if(d!=1)print "option=value"}' /etc/fdm_monitor.conf | sponge /etc/fdm_monitor.conf
 sed -i 's/^option.*/option=value/g' /etc/fdm_monitor.conf
 grep -q "option=value" /etc/fdm_monitor.conf || echo "option=value" >> /etc/fdm_monitor.conf

here is an awk one-liner:

 awk -v s="option=value" '/^option/{f=1;$0=s}7;END{if(!f)print s}' file

this doesn't do in-place change on the file, you can however :

awk '...' file > tmpfile && mv tmpfile file

Using sed, you could say:

sed -e '/option=/{s/.*/option=value/;:a;n;:ba;q}' -e 'aoption=value' filename

This would replace the parameter if it exists, else would add it to the bottom of the file.


Use the -i option if you want to edit the file in-place:

sed -i -e '/option=/{s/.*/option=value/;:a;n;:ba;q}' -e 'aoption=value' filename
sed -i '1 h
1 !H
$ {
   x
   s/^option.*/option=value/g
   t
   s/$/\
option=value/
   }' /etc/fdm_monitor.conf

Load all the file in buffer, at the end, change all occurence and if no change occur, add to the end

The answers using grep are wrong. You need to add an -x option to match the entire line otherwise lines like #text to add will still match when looking to add exactly text to add.

So the correct solution is something like:

grep -qxF 'include "/configs/projectname.conf"' foo.bar || echo 'include "/configs/projectname.conf"' >> foo.bar

Using sed: It will insert at the end of line. You can also pass in variables as usual of course.

grep -qxF "port=9033" $light.conf
if [ $? -ne 0 ]; then
  sed -i "$ a port=9033" $light.conf
else
    echo "port=9033 already added"
fi

Using oneliner sed

grep -qxF "port=9033" $lightconf || sed -i "$ a port=9033" $lightconf

Using echo may not work under root, but will work like this. But it will not let you automate things if you are looking to do it since it might ask for password.

I had a problem when I was trying to edit from the root for a particular user. Just adding the $username before was a fix for me.

grep -qxF "port=9033" light.conf
if [ $? -ne 0 ]; then
  sudo -u $user_name echo "port=9033" >> light.conf
else
    echo "already there"    
fi

You can use this function to find and search config changes:

#!/bin/bash

#Find and Replace config values
find_and_replace_config () {
   file=$1
   var=$2
   new_value=$3
   awk -v var="$var" -v new_val="$new_value" 'BEGIN{FS=OFS="="}match($1, "^\\s*" var "\\s*") {$2=" " new_val}1' "$file" > output.tmp && sudo mv output.tmp $file
}

find_and_replace_config /etc/php5/apache2/php.ini max_execution_time 60

If you want to run this command using a python script within a Linux terminal...

import os,sys
LINE = 'include '+ <insert_line_STRING>
FILE = <insert_file_path_STRING>                                
os.system('grep -qxF $"'+LINE+'" '+FILE+' || echo $"'+LINE+'" >> '+FILE)

The $ and double quotations had me in a jungle, but this worked. Thanks everyone

Try:

LINE='include "/configs/projectname.conf"'
sed -n "\|$LINE|q;\$a $LINE" lighttpd.conf >> lighttpd.conf

Use the pipe as separator and quit if $LINE has been found. Otherwise, append $LINE at the end.

Since we only read the file in sed command, I suppose we have no clobber issue in general (it depends on your shell settings).

Related