Shell – sed repeating the last line in the stream

quotingsedshell-scripttext processing

I have code in a script similar to this…

smbconffile="/etc/samba/smb.conf"
sed -i 's/.*[\[CMI\]]/\[CMI\$\]/' $smbconffile && echo "Success" || "Failed"
sed -i 's/.*[\[LOCAL\]]/\[LOCAL\$\]/' $smbconffile && echo "Success" || "Failed"
sed -i 's/.*[\[NATIONAL\]]/\[NATIONAL\$\]/' $smbconffile && echo "Success" || "Failed"

this changes the names of the shares in the samba conf (smb.conf) to the CMI$, LOCAL$, NATIONAL$.

the problem i am having is that after the second sed command, sed finds "NATIONAL" but it renames it as "LOCAL$" instead of "NATIONAL$". Does anyone see an issue with the commands?
Any help would be greatly appreciated.

Best Answer

You're mixing character classes (a list of characters inside square brackets) with the smb.conf share names which are surrounded by square bracket literals. Also, the echo command is not well-formed: in the case where sed exits with a non-zero status, the shell will attempt to invoke the command Failed.

A few suggestions:

  1. Remove the character class (outer brackets)
  2. Remove the leading .* which will remove leading comments, etc.
  3. Run a single sed command with multiple statements
  4. Fix the error case message


sed -i 's/\[CMI\]/\[CMI\$\]/;
        s/\[LOCAL\]/\[LOCAL\$\]/;
        s/\[NATIONAL\]/\[NATIONAL\$\]/' $smbconffile && echo "Success" || echo "Failed"`

You could use backreferences to avoid retyping the original prefix in the replacement expression (e.g., `[CMI'), but that can make the expressions even harder to read.

Another suggest is to use Perl which has readable grouping and backreferences IMO:

perl -pi -e 's/(\[(?:CMI|LOCAL|NATIONAL))\]/$1\$]/g' $smbconffile \
    && echo "Success" || echo "Failed"


Edit: To clarify, the problem with the original expression with the outer brackets is that you are creating a character class which matches any string containing (in the first case) [, C, M, I, ] in any order followed by a ]. For example:

$ echo '[IMC]' | sed 's/[\[CMI\]]/[something_else]/'
[IM[something_else]

$ echo '[FOOM]' | sed 's/[\[CMI\]]/[something_else]/'
[FOO[something_else]

Removing the outer brackets removes the character class so the expression will match the exact substring provided.

Related Question