Search code examples
bashreplacequotes

Bash parameter expansion ${parameter/pattern/string} with quotes


It's my first post on SO (long time lurker though) so I apologies in advance for the numerous "faux pas" and other mistakes I am certainly about to make.

I've been looking on google for a while now trying to find an answer to how quotes are parsed when inside parameter expansion that are inside double quotes and it seems that I either have the wrong keywords or a very twisted mind for attempting this.

For example, if I have a string like It's a complicated string, I would like to transform that string into the sequence It'\''s a complicated string using bash's parameter expansion ${parameter/pattern/string}. I know I can achieve that result using one of many other builtin or external tool (I'm quite fond of sed myself), but this question really is about understanding what is going on in bash's mind so that I can put my own mind to ease.

Bash's reference don't seem to specify what happen in that special case when describing its "pattern" and the closest question on SO doesn't seem to work in my case :

$ echo "$str"
It's a complicated string
$ echo "${str//'/'\''}"
> ^C
$ echo "${str//'/'\''}"
> ^C
$ echo "${str//\'/'\''}"
> ^C
$ echo "${str//\'/\'\''}"
> ^C
$ echo "${str//\'/\'\'\'}"
It\'\'\'s a complicated string
$ echo "${str//\\'/\'\'\'}"
It's a complicated string
$ echo "${str//\\'/\\'\'\'}"
It's a complicated string
$ echo "${str//\\'/\\'\\'\'}"
It's a complicated string
$ echo "${str//\\'/\\'\\'\\'}"
It's a complicated string
$ echo "${str//\\\'/\\'\\'\\'}"
> ^C
$ echo "${str//\\\'/\\\'\\'\\'}"
It's a complicated string
$ echo "${str//\\\'/\\\'\\\'\\'}"
> ^C
$ echo "${str//\\\'/\\\'\\\'\\\'}"
It's a complicated string

(The > ^C lines means that the quotes were not parsed correctly and I was prompted for more input, which I ruthlessly deny each time using Ctrl-C.)

Would any of you be kind enough to explain how exactly bash sees this? I really hope it's just a communication problem between us, I kinda like him. :)

EDIT:

For those wondering, Etan Reisner's answer works :

$ q=\'
$ echo "${str//\'/$q\'$q}"
It'\''s a complicated string

As for sputnick's answer, I'm even more puzzled :

$ echo "${str//\047/\047\\\047\047}"
It's a complicated string
$ echo "${str//\047/\047\047}"
It's a complicated string
$ echo "${str//\'/\047\'\047}"
It\047\'\047s a complicated string
$ echo "${str//\'/\047\047\047}"
It\047\047\047s a complicated string
$ echo "${str//'/\047\047\047}"
> ^C
$ echo "${str//\047/\047\047\047}"
It's a complicated string
$ echo "${str//\047/\047\\\047\047}"
It's a complicated string

EDIT2 :

Apparently this is a bug affecting at least bash 4.1 and 4.2 and fixed in bash 4.3. Therefore, there is nothing to understand from the above test.


Solution

  • The following works (with or without double quotes):

    echo "${str//\'/\'\\\'\'}"
    

    Each single quote is escaped with a backslash to prevent it from beginning a single-quoted string. The literal backslash is also escaped in the replacement pattern.


    Direct copy-and-paste from a bash 4.3 session:

    $ str="It's a complicated string"
    $ echo "${str//\'/\'\\\'\'}"
    It'\''s a complicated string
    $ echo ${str//\'/\'\\\'\'}
    It'\''s a complicated string
    

    The output in bash 3.2 is identical.