3

I'm having a problem with a script. It is meant to change a value in a file called %DIR% so that it becomes a path name. The problem is the slashes in the directory name upset sed, so I get weird errors. I need to convert the slashes in the path name into escaped slashes.

So /var/www would become \/var\/www

But I don't know how to do this.

Currently the script runs sed with this:

sed -i "s/%DIR%/$directory/g" "$config"
  • 2
    Why not just change the sed delimiter? – Bratchley Feb 23 '16 at 17:12
  • 1
    @Bratchley, probably because any delimiter you choose could be present in a file name (you can't use the NUL character: sed complains with "unterminated 's' command"). – Toby Speight Feb 23 '16 at 17:20
  • @TobySpeight there are many different delimiters you can pick from. I highly doubt an asterisk would be in the filename, though. – Bratchley Feb 24 '16 at 15:56
  • I doubt it too. But being able to handle those cases is helpful when you might be dealing with hostile inputs (I'll leave it as an exercise to decide whether Bash is the right tool in such circumstances...). – Toby Speight Feb 27 '18 at 9:04
  • Using ctrl-A as a delimiter is quite safely uniq : delim=$(printf '\001') ; sed -e "s${delim}regexp${delim}replacement${delim}" . It doesn't have to be different from everything sed SEES but from everything is at "regexp" and "replacement" ... which is usually printable characters and regexps – Olivier Dulac Oct 1 '18 at 13:34
11

Since you say you are using Bash, you can use Parameter Expansion to insert the slashes:

$ directory=/var/www
$ echo "${directory//\//\\/}"
\/var\/www

This breaks up as

  • substitute directory
  • replacing every (//)
  • slash (\/)
  • with (/)
  • backslash+slash (\\/).

Putting this into your sed command gives:

sed -i "s/%DIR%/${directory//\//\\/}/g" "$config"
3

Use parameter expansion / substitution:

${directory////\\/}

Explanation

${directory // / / \\/}
            ^  ^ ^  ^
            |  | |  |
            |  | |  replacement, backslash needs to be backslashed
            |  | delimiter
            |  string
        global
      substitution
2

You can use bash's string manipulation facilities provided by parameter expansion, specifically the ${VARIABLE//PATTERN/REPLACEMENT} form. Keep in mind that / isn't the only character that needs replacing: in an s replacement, the characters \, &, newline, and the s separator all need to be prefixed with a backslash.

replacement=$directory
replacement=${replacement//\\/\\\\}
replacement=${replacement//\//\\/}
replacement=${replacement//&/\\&}
replacement=${replacement//$'\n'/\\$'\n'}
sed -i "s/%DIR%/$replacement/g" -- "$config"

Alternatively, use a tool that doesn't require such quoting, such as awk or Perl.

directory=$directory awk '{gsub(/%DIR%/, ENVIRON["directory"])} 1' <"$config" >"$config.new" &&
mv -- "$config.new" "$config"

or

directory=$directory perl -i -pe 's/%DIR%/$ENV{directory}/g' "$config"
1

The other answers here are already great, but I just wanted to add that you can use a different delimiter besides / with sed. I do this whenever I need to operate on input that contains /:

sed -i "s@%DIR%@${directory}@g" "$config"

Or even:

sed -i "s#%DIR%#${directory}#g" "$config"

It's all the same to sed; it will use whatever character you specify after the s as the delimiter.

Not the answer you're looking for? Browse other questions tagged or ask your own question.