How to escape single quotes within single quoted strings

593,565

Solution 1

If you really want to use single quotes in the outermost layer, remember that you can glue both kinds of quotation. Example:

 alias rxvt='urxvt -fg '"'"'#111111'"'"' -bg '"'"'#111111'"'"
 #                     ^^^^^       ^^^^^     ^^^^^       ^^^^
 #                     12345       12345     12345       1234

Explanation of how '"'"' is interpreted as just ':

  1. ' End first quotation which uses single quotes.
  2. " Start second quotation, using double-quotes.
  3. ' Quoted character.
  4. " End second quotation, using double-quotes.
  5. ' Start third quotation, using single quotes.

If you do not place any whitespaces between (1) and (2), or between (4) and (5), the shell will interpret that string as a one long word.

Solution 2

I always just replace each embedded single quote with the sequence: '\'' (that is: quote backslash quote quote) which closes the string, appends an escaped single quote and reopens the string.


I often whip up a "quotify" function in my Perl scripts to do this for me. The steps would be:

s/'/'\\''/g    # Handle each embedded quote
$_ = qq['$_']; # Surround result with single quotes.

This pretty much takes care of all cases.

Life gets more fun when you introduce eval into your shell-scripts. You essentially have to re-quotify everything again!

For example, create a Perl script called quotify containing the above statements:

#!/usr/bin/perl -pl
s/'/'\\''/g;
$_ = qq['$_'];

then use it to generate a correctly-quoted string:

$ quotify
urxvt -fg '#111111' -bg '#111111'

result:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

which can then be copy/pasted into the alias command:

alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

(If you need to insert the command into an eval, run the quotify again:

 $ quotify
 alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

result:

'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''

which can be copy/pasted into an eval:

eval 'alias rxvt='\''urxvt -fg '\''\'\'''\''#111111'\''\'\'''\'' -bg '\''\'\'''\''#111111'\''\'\'''\'''\'''

Solution 3

Since Bash 2.04 syntax $'string' allows a limit set of escapes.

Since Bash 4.4, $'string' also allows the full set of C-style escapes, making the behavior differ slightly in $'string' in previous versions. (Previously the $('string') form could be used.)

Simple example in Bash 2.04 and newer:

  $> echo $'aa\'bb'
  aa'bb

  $> alias myvar=$'aa\'bb'
  $> alias myvar
  alias myvar='aa'\''bb'

In your case:

$> alias rxvt=$'urxvt -fg \'#111111\' -bg \'#111111\''
$> alias rxvt
alias rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

Common escaping sequences works as expected:

\'     single quote
\"     double quote
\\     backslash
\n     new line
\t     horizontal tab
\r     carriage return

Below is copy+pasted related documentation from man bash (version 4.4):

Words of the form $'string' are treated specially. The word expands to string, with backslash-escaped characters replaced as specified by the ANSI C standard. Backslash escape sequences, if present, are decoded as follows:

    \a     alert (bell)
    \b     backspace
    \e
    \E     an escape character
    \f     form feed
    \n     new line
    \r     carriage return
    \t     horizontal tab
    \v     vertical tab
    \\     backslash
    \'     single quote
    \"     double quote
    \?     question mark
    \nnn   the eight-bit character whose value is the octal 
           value nnn (one to three digits)
    \xHH   the eight-bit character whose value is the hexadecimal
           value HH (one or two hex digits)
    \uHHHH the Unicode (ISO/IEC 10646) character whose value is 
           the hexadecimal value HHHH (one to four hex digits)
    \UHHHHHHHH the Unicode (ISO/IEC 10646) character whose value 
               is the hexadecimal value HHHHHHHH (one to eight 
               hex digits)
    \cx    a control-x character

The expanded result is single-quoted, as if the dollar sign had not been present.


See Quotes and escaping: ANSI C like strings on bash-hackers.org wiki for more details. Also note that "Bash Changes" file (overview here) mentions a lot for changes and bug fixes related to the $'string' quoting mechanism.

According to unix.stackexchange.com How to use a special character as a normal one? it should work (with some variations) in bash, zsh, mksh, ksh93 and FreeBSD and busybox sh.

Solution 4

I don't see the entry on his blog (link pls?) but according to the gnu reference manual:

Enclosing characters in single quotes (‘'’) preserves the literal value of each character within the quotes. A single quote may not occur between single quotes, even when preceded by a backslash.

so bash won't understand:

alias x='y \'z '

however, you can do this if you surround with double quotes:

alias x="echo \'y "
> x
> 'y

Solution 5

I can confirm that using '\'' for a single quote inside a single-quoted string does work in Bash, and it can be explained in the same way as the "gluing" argument from earlier in the thread. Suppose we have a quoted string: 'A '\''B'\'' C' (all quotes here are single quotes). If it is passed to echo, it prints the following: A 'B' C. In each '\'' the first quote closes the current single-quoted string, the following \' glues a single quote to the previous string (\' is a way to specify a single quote without starting a quoted string), and the last quote opens another single-quoted string.

Share:
593,565
cons
Author by

cons

Updated on July 11, 2022

Comments

  • cons
    cons almost 2 years

    Let's say, you have a Bash alias like:

    alias rxvt='urxvt'
    

    which works fine.

    However:

    alias rxvt='urxvt -fg '#111111' -bg '#111111''
    

    won't work, and neither will:

    alias rxvt='urxvt -fg \'#111111\' -bg \'#111111\''
    

    So how do you end up matching up opening and closing quotes inside a string once you have escaped quotes?

    alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''
    

    seems ungainly although it would represent the same string if you're allowed to concatenate them like that.

  • cons
    cons over 14 years
  • Jacob
    Jacob over 14 years
    But this isn't perl. And as Steve B pointed out above, with his reference to the "gnu reference manual", you can't escape quotes in bash within the same type of quote. And in fact, don't need to escape them within alternate quotes, e.g. "'" is a valid single-quote string and '"' is a valid double-quote string without requiring any escaping.
  • Clinton Blackmore
    Clinton Blackmore about 14 years
    I was trying to put something within single quotes that was in double quotes which were, in turn, in single quotes. Yikes. Thank you for your answer of "try a different approach". That made the difference.
  • Uphill_ What '1
    Uphill_ What '1 almost 13 years
    alias splitpath='echo $PATH | awk -F : '"'"'{print "PATH is set to"} {for (i=1;i<=NF;i++) {print "["i"]",$i}}'"'" It works when there are both single quotes and double quotes in the alias string!
  • Piotr Dobrogost
    Piotr Dobrogost over 11 years
    Contents enclosed with double quotes are being evaluated so enclosing only single quotes in double quotes as suggested by liori seems to be proper solution.
  • Adrian Pronk
    Adrian Pronk almost 11 years
    @nicerobot: I've added an example showing that: 1) I don't attempt to escape quotes within the same type of quote, 2) nor in alternative quotes, and 3) Perl is used to automate the process of generating a valid bash string containg imbedded quotes
  • Gorm Casper
    Gorm Casper over 10 years
    My interpretation: bash implicitly concatenates differently quoted string expressions.
  • JAMESSTONEco
    JAMESSTONEco over 10 years
    worked for me, example of double escaped single quotes: alias serve_this_dir='ruby -rrack -e "include Rack;Handler::Thin.run Builder.new{run Directory.new'"'"''"'"'}"'
  • oberlies
    oberlies over 10 years
    Certainly not the most readable solution. It over-uses single quotes where they are not really needed.
  • Pyrolistical
    Pyrolistical about 10 years
    This is a nice solution, since it avoids the hard problem of reworking the quotes for the command, which requires thinking. This solution just requires a simple translation. If only bash had macros, the you could implement this.
  • 2rs2ts
    2rs2ts about 10 years
    Does this work when you swap the roles of the double and single quotes? i.e. "urxvt -fg "'"'"#111111"'"'" etc? Doesn't seem to be working for me but maybe I'm doing something wrong.
  • liori
    liori about 10 years
    @2rs2ts: it should work. If not—please make a new question here on the site.
  • regilero
    regilero almost 10 years
    could be used but the single quoted string here is not a real single quoted one, content on this string may be interprested by the shell: echo $'foo\'b!ar'=> !ar': event not found
  • user14922101
    user14922101 almost 10 years
    On my machine > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!ar
  • regilero
    regilero almost 10 years
    Yes, that's the reason for "may", I had it on a Red hat 6.4, certainly an older bash version.
  • user14922101
    user14922101 almost 10 years
    Bash ChangeLog contains a lot of bug fixes related to $' so probably easiest way is to try it yourself on older systems.
  • juanmf
    juanmf almost 10 years
    Nice! I was having trouble with an alias in .bash_aliases I did as you suggest and then issuing alias in CLI, outputs ... awk '\''{print $3}'\'''` i.e. it's also valid \' instead of "'" outside single quote string.
  • Chad Skeeters
    Chad Skeeters almost 10 years
    @rs2ts Leave off the last double quote. echo "it "'"'"works"'"'
  • karmakaze
    karmakaze over 9 years
    TL;DR replace each internal ' with '\'' command = "echo '" + arg.replace("'", "'\\''") + "'"; exec(command)
  • Steve Midgley
    Steve Midgley over 9 years
    Great answer content-wise. But it would be easier to understand if it were possible to concatenate with a symbol like + -- that's not possible (afaik) but an example showing what that looks like might help others grok the One True Way (this example doesn't work it's to help understand what the hey is going on): alias rxvt='urxvt -fg '+"'"+'#111111'+"'"+' -bg '+"'"+'#111111'+"'"
  • cmroanirgo
    cmroanirgo over 9 years
    For those who just want the solution (where double is ok): alias rxvt="urxvt -fg '#111111' -bg '#111111'"
  • Matthew G
    Matthew G almost 9 years
    This is the actual answer to the question. While the accepted answer may provide a solution, it's technically answering a question that wasn't asked.
  • Fernando Cordeiro
    Fernando Cordeiro almost 9 years
    Matthew, the question was about escaping single quotes inside single quotes. This answer asks the user to change their behavior, and if you have an impediment to using double quotes (as the question title suggests), this answer wouldn't help. It's pretty useful though (Albeit obvious), and as such deserves an upvote, but the accepted answer addresses the precise problem Op asked about.
  • Alex Gray
    Alex Gray over 8 years
    for those of you that REALLY want the answer, lol... just use '"'"'. and if you want to escape THOSE single quotes, use '\"'\"' LOL
  • teknopaul
    teknopaul over 8 years
    This is misleading, this syntax '\'' does not go "inside" a single quoted string. In this statement 'A '\''B'\'' C' you are concatenating 5 \ escape and single quotes strings
  • teknopaul
    teknopaul over 8 years
    This is wrong "single quotes in the outermost layer" is not what's being achieved, you are concatenating strings using different escape types. There is no outermost layer.
  • Andreas Maier
    Andreas Maier over 8 years
    Hi Kyle. Your solution worked great for a case I had, when I needed to pass a group of arguments as a single argument: vagrant ssh -c {single-arg} guest. The {single-arg} needs to be treated as a single arg because vagrant takes the next arg after it as the guest name. The order cannot be changed. But I needed to pass a command and its arguments within {single-arg}. So I used your quote_args() to quote the command and its args, and put double quotes around the result, and it worked like a charm: vagrant ssh -c "'command' 'arg 1 with blanks' 'arg 2'" guest. Thanks!!!
  • Dave Causey
    Dave Causey about 8 years
    The first paragraph by itself is the answer I was looking for.
  • arekolek
    arekolek almost 8 years
    This is what bash does as well, type set -x and echo "here's a string" and you'll see that bash executes echo 'here'\''s a string'. (set +x to return normal behavior)
  • mtraceur
    mtraceur over 7 years
    I contend that '\'' is vastly more readable in most contexts than '"'"'. In fact, the former is almost always clearly distinct within a single-quoted string, and thus is just a matter of mapping it semantically to the "it's an escaped quote" meaning, as one does with \" in double-quoted strings. Whereas the latter blends into one line of quote ticks and needs careful inspection in many cases to properly distinguish.
  • Teemu Leisti
    Teemu Leisti over 7 years
    @teknopaul The assignment alias something='A '\''B'\'' C' does result in something being a single string, so even while the right-hand side of the assignment is not technically a single string, I don't think it much matters.
  • krb686
    krb686 over 7 years
    While this works in your example, it isn't technically providing a solution for how to insert a single quote inside a single quoted string. You've already explained it, but yes it's doing 'A ' + ' + 'B' + ' + ' C'. In other words, a solution for inserting single quote characters inside a single-quoted string should allow me to create such a string by itself, and print it. However this solution will not work in this case. STR='\''; echo $STR. As designed, BASH does not truly allow this.
  • Jingguo Yao
    Jingguo Yao over 7 years
    @mikhail_b, yes, '\'' works for bash. Could you point out which sections of gnu.org/software/bash/manual/bashref.html specify such a behavior?
  • Ligemer
    Ligemer over 7 years
    Another example of usage: awk '{print "'"'"'"$2"'"'"' => '"'"'"$3"'"'"'"}' outputs a string such as: 'value$2' => 'value$3'
  • Julien
    Julien over 7 years
    I'm 5 years late, but aren't you missing a single quote in your last alias?
  • Jacob
    Jacob over 7 years
    @Julien I don't see a problem ;-)
  • Korny
    Korny over 6 years
    I wish I'd scrolled down this far before - I reinvented this approach and came here to post it! This is far cleaner and more readable than all the other escaping approaches. Not it will not work on some non-bash shells, such as dash which is the default shell in Ubuntu upstart scripts and elsewhere.
  • Amil Waduwawara
    Amil Waduwawara over 6 years
    Double quotations can also be escaped in the same way; simply interchange double and single quotations in '"'"'
  • wisbucky
    wisbucky about 6 years
    "real answer is that you can't escape single-quotes within single-quoted strings." That's technically true. But you can have a solution that starts with a single quote, ends with a single quote, and only contains single quotes in the middle. stackoverflow.com/a/49063038
  • wisbucky
    wisbucky about 6 years
    LOL, using double quotes is cheating. :) Then you might as well just use double quotes on the outside for the simplest common sense solution. A real solution using only single quotes is stackoverflow.com/a/49063038
  • teknopaul
    teknopaul about 6 years
    Not by escaping, only by concatenation.
  • Will Sheppard
    Will Sheppard about 6 years
    Could you show an example of this in operation? For me it just prints a literal x27 (on Centos 6.6)
  • stason
    stason almost 6 years
    @liori, that's an awesome trick. Thank you! Now I'm able to mess with single quotes from within perl on-liners! a demo: perl -le '$q = chr(39); $_=shift; s/$q/$q"$q"$q/g; print qq[echo $q$_$q]' "It's amazing" gives: echo 'It'"'"'s amazing' I added it to my recipes cookbook!
  • stiller_leser
    stiller_leser almost 6 years
    be aware: e. Bash no longer inhibits C-style escape processing ($'...') while performing pattern substitution word expansions. Taken from tiswww.case.edu/php/chet/bash/CHANGES. Still works in 4.3.42 but not in 4.3.48.
  • Jonathan Cross
    Jonathan Cross almost 6 years
    Of the options below, this is the only one that works in my situation: alias kernel_ext='kextstat -kl | awk '"'"'!/com\.apple/{printf "%s %s\n", $6, $7}'"'"
  • Matthew D. Scholefield
    Matthew D. Scholefield almost 6 years
    No need to quote a single quote in a double quote string.
  • Igor Tverdovskiy
    Igor Tverdovskiy almost 4 years
    Thank you! that what I looked for, the way to define a command as is via heredoc and pass the auto escaped command to ssh. BTW cat <<COMMAND without quotes allows to interpolate vatiables inside command and works as well for this approach.
  • João Ciocca
    João Ciocca almost 4 years
    this is THE ONLY way I got to make a grep alias to work correctly!
  • mtraceur
    mtraceur almost 4 years
    +1 for using the '\'' method I recommend over the '"'"' method which is often harder for humans to read.
  • alecxs
    alecxs almost 4 years
    use '$'\047'' or '$'\\047'' as replacement for '\'' depending on shell
  • Marcos
    Marcos almost 4 years
    @WillSheppard echo -e "\x27 \\x22" prints ' "
  • ojdo
    ojdo over 3 years
    Thanks, this just worked perfectly for creating an alias containing single quotes, backslashes and a dollar sign without any manual fiddling on my side: print(shlex_quote(r"..<nasty string>..."))
  • Gabriel Staples
    Gabriel Staples over 3 years
    @WillSheppard and others, here are a bunch of examples of this I just wrote: stackoverflow.com/a/65878993/4561887.
  • cokedude
    cokedude almost 3 years
    Can you help with this? Not sure how to deal with the ! point here. ssh server "awk 'del=(a&&a--) {print; da=\!a} $0~pattern{if (da) {print "--"; da=0} a=A; if (B) {for(i=NR; i<B+NR; i++) if((i%B) in b) print b[i%B]} {print; da=1}} (B) {if (del) delete b[NR%B]; else b[NR%B]=$0}' B=5 A=2 pattern=Successful file"
  • Gabriel Staples
    Gabriel Staples almost 3 years
    @cokedude, try asking a new question. Paste a link to your new question here so I can help you on your question.
  • cokedude
    cokedude almost 3 years
    Is it better to post in the regular stackoverlow or the Unix stackoverflow?
  • Gabriel Staples
    Gabriel Staples almost 3 years
    @cokedude, either is fine, I think. I'd probably just do regular StackOverflow. But, be sure to describe your problem in detail, ensure that what you post is runnable by anyone. Explain what you did, what output you saw, and what you expected to see or wanted to happen instead. Even if you do all that and get it perfect, expect some downvotes. Be sure to search for existing questions which already answer it before posting. If your question lasts longer than 10 minutes before getting closed, consider it a success. That's just the nature of this site, unfortunately.
  • Jose V
    Jose V over 2 years
    This does bring in all the C-style sequences into your bash line, so certain character sequences that work fine on bash might stop working as expected because they become C-style sequences. Typically easy to solve by adding extra \ to escape the C-style sequences. Example: alias foo=$'echo \1' is different than alias boo='echo \1'
  • Kellen Stuart
    Kellen Stuart over 2 years
    This is horrible. How does a language based on manipulating strings not have a clean string interpolation function built in?
  • Hola Soy Edu Feliz Navidad
    Hola Soy Edu Feliz Navidad about 2 years
    The worst thing in regards to your answer is that you are 100% right.
  • pyrocrasty
    pyrocrasty about 2 years
    @MatthewD.Scholefield: it needs to be quoted because it's in an alias. There won't be any double quotes when the alias is expanded. (What is unnecessay is the space at the end, though).