4

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 :(

  • 1
    Please stop reverting to your broken edit. Work from the correct version. – jasonwryan Dec 20 '14 at 21:04
5

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
  • Hi Gilles, the sed would be my favorite thanks for that. However, it behaves strange: When there is no /swapfile it places the string, but when it finds "/swapfile" it removes it / them without appending the string. any hints? – frank Dec 20 '14 at 21:56
  • @frank Ah, oops, the line would be deleted if it was already as the last line. Fixing that gets us in the sed territory that your fellow administrators would rue you for using, see my edit. – Gilles Dec 20 '14 at 22:18
  • lol, your are right, that should do the job but will drive some people mad - unless there is an option to keep the timestamp of the original file ;) - just kidding, I think I will need to re-consider my "concept", inject that stuff with cloudinit and have fstab monitored. thanks! – frank Dec 20 '14 at 22:26
  • @frank As soon as you use sed -i, that will break the timestamp of the original file. This is why I do the test with cmp in my last snippet: the original file is preserved if it was already suitable. – Gilles Dec 20 '14 at 22:32
4

Since you are automating this with Puppet, it's better to let Puppet handle the fstab for you. Use the mount resource type.

Something like:

mount{'swapfile':
    name    => 'none',
    fstype  => 'swap',
    ensure  => mounted,
    atboot  => true,
    device  => '/swapfile',
    options => 'sw',
}

should work.

If this absolutely has to be done with a one-liner (which, for the record, I'm still against), the following perl should help:

perl -pi -e 'BEGIN{$string = "/swapfile   none    swap    sw    0   0"} s!^/swapfile.*!$string! && $x++;END{print "$string\n" unless $x}' /etc/fstab

Broken down into several lines for clarity:

perl -pi -e '
             BEGIN{$string = "/swapfile   none    swap    sw    0   0"}
             s!^/swapfile.*!$string! && $x++; # Modify line if it exists
             END{print "$string\n" unless $x} # Append it if it wasn't found
            ' /etc/fstab
  • joseph, the focus is to get the sed command right, ideally in one line - puppet is just the vehicle here and has nothing to do with the problem I am focusing. thx. – frank Dec 20 '14 at 21:16
  • @frank My point is that if you're using Puppet, then using sed for this kind of task is not the best approach. – Joseph R. Dec 20 '14 at 21:19
  • thx, but I am running this as command from within a puppet module - next to a bunch of other related commands to modify several files to get the swapfile right. As this is just about one line in that file that is managed in this example I will not use a .erb template or other methods for this. leaving this a commandline "issue" (unless someone knows how to solve this). Can we pls. stop focusing on puppet and get back to the question? ;) – frank Dec 20 '14 at 21:22

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.