How to echo an escaped string

13,268

Solution 1

In the script:

echo "${1//$/\\$}"

This is a simple string replacement build into the shell. The syntax is ${[VARIABLE_NAME]//[PATTERN]/[REPLACEMENT]} for a global literal string replacement.

Not sure why you would want to do this. The script is actually just reporting the string you sent to it; \$sad\$test evaluates to exactly the same string as '$sad$test', except the latter one uses single quotes to escape the $.

Solution 2

I assume you want the string to be shown “shell quoted”.

The printf built-in command in ksh, bash, and zsh all understand the %q format specifier. The actual output is not standardized and can vary depending on the input string (see below). Because of the variation in the form of the output, you may not be able to use the output from one shell with another (especially if your target shell is a less feature-rich shell like ash, dash, etc.).

zsh also has support for generating quotes directly in parameter expansions:

echo "${(q)1}"    # backslashes, with $'' for unprintable/invalid characters
echo "${(qq)1}"   # single quotes
echo "${(qqq)1}"  # double quotes
echo "${(qqqq)1}" # $'' quoting

If you want to “roll your own”, the easiest and most reliable thing to do is to thing generate your own single quoting. A simple algorithm follows:

  1. Change any single quotes to the sequence '\''.
  2. Wrap the result in single quotes.

This works for Bourne-like shells because single quoting is completely literal1. To represent a single quote, you end the current single quoted span, supply a backslash-escaped single quote and resume single quoting the rest of the string. The above algorithm is inefficient when dealing with runs of consecutive single quotes and single quotes at the beginning and end of the string, but it is very reliable and quite simple to implement.

1 The single quote in other languages often is not completely literal (at the very least, they often allow some limited backslash escaping). This is why it is critically important that you know all the quoting rules for the form of quoting you will be generating (i.e. in what language/context the quoted text will be used).


Here are some examples of printf %q in the shells I have installed locally:

for s in /bin/ksh {,/opt/local}/bin/bash {,/opt/local}/bin/zsh
do
  "$s" --version | head -1
  "$s" -c 'printf "%q\n" "$@"' - \$sad\$test \'mixed\ quotes\" back\`tick
  echo
done

Output:

  version         sh (AT&T Research) 1993-12-28 s+
'$sad$test'
$'\'mixed quotes"'
'back`tick'

GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin10.0)
\$sad\$test
\'mixed\ quotes\"
back\`tick

GNU bash, version 4.2.10(2)-release (i386-apple-darwin10.7.0)
\$sad\$test
\'mixed\ quotes\"
back\`tick

zsh 4.3.9 (i386-apple-darwin10.0)
\$sad\$test
\'mixed\ quotes\"
back\`tick

zsh 4.3.12 (i386-apple-darwin10.7.0)
\$sad\$test
\'mixed\ quotes\"
back\`tick

We see that ksh seems to “like” quoting with single quotes, and uses the $'' form in some cases. The other shells seem to mostly use backslash escapes (though they will also use $'' if some characters are present).

Solution 3

Put single quotes around \$sad\$test:

$ echo $SHELL
/bin/bash
$ ./test.sh '\$sad\$test'
\$sad\$test

Solution 4

By doing test.sh \$sad\$test, the test.sh script receives $sad$test as its parameter, not \$sad\$test. The reason for that is the shell that replaces \$ by $ before callingtest.sh. If you want your script to receive \$sad\$test, you should either do test.sh '\$sad\$test' or test.sh "\\\$sad\\\$test".

Try this to convince yourself:

function cat1stpar() {
cat << EOF
$1
EOF
}
cat1stpar \$sad\$test
cat1stpar '\$sad\$test'
cat1stpar "\\\$sad\\\$test"

Also, in the general case, using echo $1 is dangerous. If you do:

./test.sh "aaa bbb"
./test.sh "aaa    bbb"

The output will be:

aaa bbb
aaa bbb

and not:

aaa bbb
aaa    bbb

If you want the 2nd output, you shoud at least do echo "$1" which will make sure echo receives a single parameter, not as much as there is words in $1. For a complete word discussion, see the IFS variable in the shell man.

It is also said that doing printf "%s\n" "$1"is safer than echo "$1" because echo changes some chars before printing, but I have not fully understand it yet.

Edit:

About echo changing chars, I just understood it. Try:

echo "-n"
printf "%s\n" "-n"
./test.sh "-n"
cat1stpar "-n"
Share:
13,268

Related videos on Youtube

Admin
Author by

Admin

Updated on September 18, 2022

Comments

  • Admin
    Admin over 1 year

    How can I echo an escaped string that contains $ in Bourne Shell?

    user@server:~$ cat test.sh 
    #!/bin/sh
    echo $1
    
    user@server:~$ ./test.sh \$sad\$test
    $sad$test
    

    I want it to return an escaped version like this:

    user@server:~$ ./test.sh \$sad\$test
    \$sad\$test
    

    I have tried doing tricks with sed and awk but no luck. Any guidance would be greatly appreciated.

  • Chris Johnsen
    Chris Johnsen over 12 years
    That still only handles $. If you only use backslash escaping, many other characters also need escaping: backslash, single and double quotes, back quote, semicolon, ampersand, less- and greater-than, asterisk, question mark, etc. The list is pretty much unbounded since newer shells/features often give special meaning to (unquoted) characters that were not previously special. The only safe thing to do would be to escape every character (though you could probably get away with not escaping the alphanumerics).
  • amphetamachine
    amphetamachine over 12 years
    No. The safe thing to do would be to learn proper quoting techniques and string safety.
  • amphetamachine
    amphetamachine over 12 years
    Chances are, if you're shell-escaping something you're about to do something extremely dangerous with it like pass it through eval.
  • jfg956
    jfg956 over 12 years
    This is not the solution. This only echo a value doing substitution. It does not change the fact that the test.sh script receives $sad$test as the value of $1.
  • dlamblin
    dlamblin over 6 years
    This is a fantastic thorough answer that deserves to be the correct answer.
  • mcandre
    mcandre over 6 years
    Unfortunately %q is missing from the printf in MINIX's default sh, sh.