Replace or append a line with string within a file

sedtext processing

I am looking for advice on how to search a part of a string within a file and replace the complete line OR append that string to that file if not found. I "played" with sed for a while now, but couldn't get it to work as expected.

I need to add:

/swapfile   none    swap    sw    0   0

to /etc/fstab (on Ubuntu 14.04 – Trusty Tahr).

Conditions:

  • If any line starting with /swapfile is present in /etc/fstab, remove that line and replace with the string provided above
  • If more than one line starting with /swapfile is found, remove them all and append the string above to the end of the file
  • If no /swapfile is present in /etc/fstab, append the string to /etc/fstab
  • The command must not show console output and must be a "one-liner" (due to automation purposes with puppet)

I am confident that's possible, but I simply didn't find a related tutorial about using sed in the way I need it.

I used sudo sed -i '$a/swapfile none swap sw 0 0' /etc/fstab
but this only appends the string 🙁

Best Answer

You can do this with sed — it's Turing-complete. But it isn't the best tool for the job. Sed doesn't have a convenient way of remembering that it's already made a replacement.

What you can relatively easily do with sed is to blank all the lines starting /swapfile, and add a new one at the end:

sed -i '$! s/^\/swapfile[\t ]//; $s/\(^\/swapfile.*\)\?$/\n\/swapfile none swap sw/' /etc/fstab

but beyond that we're quickly getting into territory where I wouldn't leave such sed code for another sysadmin to maintain, especially when a simple, readable combination of shell commands would do a better job:

{ </etc/fstab grep -v '/swapfile[\t ]'; echo '/swapfile none swap sw'; } >/etc/fstab.new && mv /etc/fstab.new /etc/fstab

If you want to preserve the existing position of the /swapfile line if it's there and only modify the file if it needs modifying, a combination of shell logic and awk is a better tool. I've used multiple lines here for clarity but you can put all the code on the same line if you like. As a bonus, if the file already contained the intended line (with exact spacing), it won't be modified.

awk '
  /\/swapfile[\t ]/ {if (replaced) {next}
                     else {replaced=1; print "/swapfile none swap sw"}}
  1 {print}
  END {if (!replaced) print "/swapfile none swap sw"}
' /etc/fstab >/etc/fstab.new &&
if cmp -s /etc/fstab.new /etc/fstab.new; then
  rm /etc/fstab.new;
else
  mv /etc/fstab.new /etc/fstab;
fi
Related Question