How to escape single quotes within single quoted strings
Asked Answered
T

25

1431

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.

Toweling answered 8/8, 2009 at 22:50 Comment(7)
Do you realise that you don't need to use single quotes for alias? Double quotes is much easier.Cherian
See also: Difference between single and double quotes in Bash.Piddle
Nested double quotes are escapable, "\"", so those should be used in preference to @liori's answer whenever possible.Jacquiline
Double quotes behave quite differently from single quotes in *nix (including Bash, and related tools like Perl), so substituting double quotes whenever there's a problem with single quotes is NOT a good solution. Double quotes specify $... variables are to be substituted before execution, while single quotes specify $... are to be treated literally.Joinder
If you're thinking, I used double quotes but it's still not working, source your script again.Attainment
The last try was close, just remove that last quote.Callahan
The canonical for double quotes: How can I escape a double quote inside double quotes?Corvin
Q
1926

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:

$ echo 'abc''123'  
abc123
$ echo 'abc'\''123'
abc'123
$ echo 'abc'"'"'123'
abc'123

It will also keep the internal representation with 'to be joined' strings, and will also prefer the shorter escape syntax when possible:

$ alias test='echo '"'"'hi'"'"
$ alias test
alias test='echo '\''hi'\'''
$ test
hi
Quarterphase answered 9/8, 2009 at 0:52 Comment(24)
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!Jacquelinejacquelyn
My interpretation: bash implicitly concatenates differently quoted string expressions.Peppie
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'"'"''"'"'}"'Palaeozoology
Certainly not the most readable solution. It over-uses single quotes where they are not really needed.Mccollough
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.Uuge
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.Thermograph
@2rs2ts: it should work. If not—please make a new question here on the site.Quarterphase
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.Fields
@rs2ts Leave off the last double quote. echo "it "'"'"works"'"'Carmina
TL;DR replace each internal ' with '\'' command = "echo '" + arg.replace("'", "'\\''") + "'"; exec(command)Maureen
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'+"'"Beckford
For those who just want the solution (where double is ok): alias rxvt="urxvt -fg '#111111' -bg '#111111'"Anallese
for those of you that REALLY want the answer, lol... just use '"'"'. and if you want to escape THOSE single quotes, use '\"'\"' LOLSatterlee
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.Cherian
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.Mcclintock
Another example of usage: awk '{print "'"'"'"$2"'"'"' => '"'"'"$3"'"'"'"}' outputs a string such as: 'value$2' => 'value$3'Haematoblast
Double quotations can also be escaped in the same way; simply interchange double and single quotations in '"'"'Ryter
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/49063038Epimenides
@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!Swot
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}'"'"Rico
this is THE ONLY way I got to make a grep alias to work correctly!Hill
This is horrible. How does a language based on manipulating strings not have a clean string interpolation function built in?Lilylilyan
if you just need to output this 'value$2' => 'value$3', I'd go for octals instead ::::::::::::: awk '{ print "\47" $2 "\47 => \47" $3 "\47" }' :::: or, better yet :::::: awk '{ _ = "\47" ; print _$2_ " => " _$3_ }' ::: doing it once up front, then directly surround field variables with a pair of them, no space gap needed……………..Unmitigated
…………(since user defined var. names can neither contain the $ dollar-sign-sigil, nor begin w/ a digit, so there's zero risk when it comes to code parsing) underscores are also helpful because it provides a tight visual clue to the encapsulation, it's syntactically unambiguous, its literal line-on-the-floor character profile fades it into the bkgrnd instead of competing w/ the quoted content 4 your attention (as opposed to to naming the variable like squote = "\47"), so as long as the octal gets preserved between the layers, it can go as deep as you like w/o becoming a \\\\\\\\ nightmareUnmitigated
A
380

TL;DR

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.

More in-depth on converting string-with-embedded-single-quotes

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'\''\'\'''\'''\'''
Allot answered 22/8, 2009 at 5:30 Comment(5)
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.Netti
@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 quotesAllot
The first paragraph by itself is the answer I was looking for.Burtburta
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)Sinuation
If you are trying to do this directly in bash (not in a function) then this is what it looks like for the given example (you just need some extra escaping): alias rxvt='urxvt -fg '\\\''#111111'\\\'' -bg '\\\''#111111'\\\'''Macrospore
C
331

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.)

A 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 & Linux' How to use a special character as a normal one?, it should work (with some variations) in Bash, Z shell, mksh, ksh93, FreeBSD and the BusyBox sh.

Crier answered 8/8, 2009 at 22:50 Comment(6)
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 foundWernsman
On my machine > echo $BASH_VERSION 4.2.47(1)-release > echo $'foo\'b!ar' foo'b!arCrier
Yes, that's the reason for "may", I had it on a Red hat 6.4, certainly an older bash version.Wernsman
Bash ChangeLog contains a lot of bug fixes related to $' so probably easiest way is to try it yourself on older systems.Crier
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.Maryrosemarys
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'Parang
J
61

I don't see the entry on his blog (link, please?), 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
Jackiejackinoffice answered 8/8, 2009 at 23:7 Comment(6)
muffinresearch.co.uk/archives/2007/01/30/…Toweling
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.Champlain
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.Germicide
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.Kreisler
No need to quote a single quote in a double quote string.Blotch
@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).Thanasi
V
52

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 answers. 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.

Vulgarism answered 3/8, 2011 at 7:54 Comment(5)
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 stringsCherian
@Cherian 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.Winkelman
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.Vi
@mikhail_b, yes, '\'' works for bash. Could you point out which sections of gnu.org/software/bash/manual/bashref.html specify such a behavior?Sacramentalist
@Vi Your example works exactly as it should. You forgot that '\'' is meant to be used inside a single-quoted string, so it becomes STR=''\'''; echo $STR -- which prints a single single-quote '.Draftsman
O
29

Both versions are working, either with concatenation by using the escaped single quote character ('), or with concatenation by enclosing the single quote character within double quotes ("'").

The author of the question did not notice that there was an extra single quote (') at the end of his last escaping attempt:

alias rxvt='urxvt -fg'\''#111111'\'' -bg '\''#111111'\''
           │         │┊┊|       │┊┊│     │┊┊│       │┊┊│
           └─STRING──┘┊┊└─STRIN─┘┊┊└─STR─┘┊┊└─STRIN─┘┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      ┊┊         ┊┊       ┊┊         ┊┊│
                      └┴─────────┴┴───┰───┴┴─────────┴┘│
                          All escaped single quotes    │
                                                       │
                                                       ?

As you can see in the previous nice piece of ASCII/Unicode art, the last escaped single quote (') is followed by an unnecessary single quote ('). Using a syntax-highlighter like the one present in Notepad++ can prove very helpful.

The same is true for another example like the following one:

alias rc='sed '"'"':a;N;$!ba;s/\n/, /g'"'"
alias rc='sed '\'':a;N;$!ba;s/\n/, /g'\'

These two beautiful instances of aliases show in a very intricate and obfuscated way how a file can be lined down. That is, from a file with a lot of lines you get only one line with commas and spaces between the contents of the previous lines. In order to make sense of the previous comment, the following is an example:

$ cat Little_Commas.TXT
201737194
201802699
201835214

$ rc Little_Commas.TXT
201737194, 201802699, 201835214
Ownership answered 20/2, 2015 at 8:8 Comment(2)
Upwoted for the ASCII Table illustration :)Mavilia
How did you generate that unicode art? It's beautiful!Echols
B
28

For most cases, the main answer with the '"'"' markers really is the best answer. But, for cases where it's not, here's my answer:

How to escape single quotes (') and double quotes (") with hex and octal chars

If using something like echo, I've had some really complicated and really weird and hard-to-escape (think: very nested) cases where the only thing I could get to work was using octal or hex codes!

Here are some basic examples just to demonstrate how it works. Be sure to add the -e option to echo to process these escape sequences:

1. Single quote example, where ' is escaped with hex \x27 or octal \047 (its corresponding ASCII code):

  1. hex \x27

    echo -e "Let\x27s get coding!"
    # OR
    echo -e 'Let\x27s get coding!'
    

    Result:

    Let's get coding!
    
  2. octal \047

    echo -e "Let\047s get coding!"
    # OR
    echo -e 'Let\047s get coding!'
    

    Result:

    Let's get coding!
    

2. Double quote example, where " is escaped with hex \x22 or octal \042 (its corresponding ASCII code).

Note: bash is nuts! Sometimes even the ! char has special meaning, and must either be removed from within the double quotes and then escaped "like this"\! or put entirely within single quotes 'like this!', rather than within double quotes.

# 1. hex; also escape `!` by removing it from within the double quotes 
# and escaping it with `\!`
$ echo -e "She said, \x22Let\x27s get coding"\!"\x22"
She said, "Let's get coding!"

# OR put it all within single quotes:
$ echo -e 'She said, \x22Let\x27s get coding!\x22'
She said, "Let's get coding!"


# 2. octal; also escape `!` by removing it from within the double quotes 
$ echo -e "She said, \042Let\047s get coding"\!"\042"
She said, "Let's get coding!"

# OR put it all within single quotes:
$ echo -e 'She said, \042Let\047s get coding!\042'
She said, "Let's get coding!"


# 3. mixed hex and octal, just for fun
# also escape `!` by removing it from within the double quotes when it is followed by
# another escape sequence
$ echo -e "She said, \x22Let\047s get coding! It\x27s waaay past time to begin"\!"\042"
She said, "Let's get coding! It's waaay past time to begin!"

# OR put it all within single quotes:
$ echo -e 'She said, \x22Let\047s get coding! It\x27s waaay past time to begin!\042'
She said, "Let's get coding! It's waaay past time to begin!"

Note that if you don't properly escape !, when needed, as I've shown two ways to do above, you'll get some weird errors, like this:

$ echo -e "She said, \x22Let\047s get coding! It\x27s waaay past time to begin!\042"
bash: !\042: event not found

OR:

$ echo -e "She said, \x22Let\x27s get coding!\x22"
bash: !\x22: event not found

One more alternative: this allows mixed expansion and non-expansion all within the same bash string

Here is another demo of an alternative escaping technique.

First, read the main answer by @liori to see how the 2nd form below works. Now, read these two alternative ways of escaping characters. Both examples below are identical in their output:

CMD="gs_set_title"

# 1. 1st technique: escape the $ symbol with a backslash (\) so it doesn't 
# run and expand the command following it
echo "$CMD '\$(basename \"\$(pwd)\")'"

# 2. 2nd technique (does the same thing in a different way): escape the 
# $ symbol using single quotes around it, and the single quote (') symbol
# using double quotes around it
echo "$CMD ""'"'$(basename "$(pwd)")'"'"

Sample output:

gs_set_title '$(basename "$(pwd)")'
gs_set_title '$(basename "$(pwd)")'

Note: for my gs_set_title bash function, which I have in my ~/.bash_aliases file somewhere around here, see my other answer here.

References:

  1. Wikipedia: ASCII: Printable characters
  2. Server Fault: What is "-bash: !": event not found"
  3. See also my other answer here [now deleted by others :( ]: How do I write non-ASCII characters using echo?
Benilda answered 25/1, 2021 at 4:28 Comment(6)
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"Mite
@cokedude, try asking a new question. Paste a link to your new question here so I can help you on your question.Benilda
Is it better to post in the regular stackoverlow or the Unix stackoverflow?Mite
@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.Benilda
Probably don't recommend echo -e. Instead, try printf, or perhaps regular echo with Bash's $'c-style' strings which support basically the same escapes (so \047 encodes an escaped literal single quote).Sinistrodextral
According @tripleee's comment, Have a look at wiki.wooledge: Avoid using echo on modern shell. And you could present %b and %q parameters for printf command!Samurai
P
21

A simple example of escaping quotes in shell:

$ echo 'abc'\''abc'
abc'abc

$ echo "abc"\""abc"
abc"abc

It's done by finishing an already opened one ('), placing the escaped one (\'), then opening another one ('). This syntax works for all commands. It's a very similar approach to the first answer.

Phonology answered 28/2, 2015 at 20:59 Comment(0)
N
18

I'm not specifically addressing the quoting issue because, well, sometimes, it's just reasonable to consider an alternative approach.

rxvt() { urxvt -fg "#${1:-000000}" -bg "#${2:-FFFFFF}"; }

which you can then call as:

rxvt 123456 654321

The idea being that you can now alias this without concern for quotes:

alias rxvt='rxvt 123456 654321'

Or, if you need to include the # in all calls for some reason:

rxvt() { urxvt -fg "${1:-#000000}" -bg "${2:-#FFFFFF}"; }

which you can then call as:

rxvt '#123456' '#654321'

Then, of course, an alias is:

alias rxvt="rxvt '#123456' '#654321'"

(Oops, I guess I kind of did address the quoting :)

Netti answered 22/8, 2009 at 4:59 Comment(3)
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.Hoary
I'm 5 years late, but aren't you missing a single quote in your last alias?Tallboy
@Tallboy I don't see a problem ;-)Netti
F
16

A minimal answer is needed, so that people can get going without spending a lot of time as I had to sifting through people waxing eloquent.

There isn't any way to escape single quotes or anything else within single quotes.

The following is, perhaps surprisingly, a complete command:

echo '\'

whose output is:

\

Backslashes, surprisingly to even long-time users of Bash, have no meaning inside single quotes. Nor does anything else.

Footpound answered 30/12, 2021 at 10:59 Comment(2)
Writing '"'"' instead of ' seems to work just fine so one could argue that's the correct encoding of that character in single quote strings. (Technically it ends the string, starts another double quote encoded string with a single quote which is combined by the shell into previous string and then re-opens another single quote string which is again combined with the two previous parts by the shell.)Mofette
One could argue, but two, rather many, would disagree :) It is better for one to know -- and many don't actually -- that bash concatenates adjacent strings, and, independently, that bash disinterprets a ' within "" , because as a programmer one can use these two independent techniques in other situations. This answer disabuses the notion that there is any magical escaping or encoding, and nudges the coder towards knowing that those two things, independently useful in general, are the only way to accomplish this. This is code, not poetry to be interpreted from various perspectives.Footpound
U
14

I just use shell codes.. e.g. \x27 or \\x22 as applicable. No hassle, ever really.

Ultrasound answered 16/12, 2014 at 16:15 Comment(3)
Could you show an example of this in operation? For me it just prints a literal x27 (on Centos 6.6)Marcenemarcescent
@WillSheppard echo -e "\x27 \\x22" prints ' "Masticate
@WillSheppard and others, here are a bunch of examples of this I just wrote: https://mcmap.net/q/45284/-how-to-escape-single-quotes-within-single-quoted-strings.Benilda
P
12

Since one cannot put single quotes within single quoted strings, the simplest and most readable option is to use a HEREDOC string:

command=$(cat <<'COMMAND'
urxvt -fg '#111111' -bg '#111111'
COMMAND
)

alias rxvt=$command

In the code above, the HEREDOC is sent to the cat command and the output of that is assigned to a variable via the command substitution notation $(..)

Putting a single quote around the HEREDOC is needed since it is within a $().

Pita answered 12/2, 2016 at 9:53 Comment(2)
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.Spook
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.Montgomery
C
11

IMHO, the real answer is that you can't escape single-quotes within single-quoted strings.

It’s impossible.

If we presume we are using Bash.

From the Bash 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.

You need to use one of the other string escape mechanisms, " or \.

There is nothing magic about alias that demands it use single quotes.

Both the following work in Bash.

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

The latter is using \ to escape the space character.

There is also nothing magic about #111111 that requires single quotes.

The following options achieves the same result the other two options, in that the rxvt alias works as expected.

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

You can also escape the troublesome # directly:

alias rxvt="urxvt -fg \#111111 -bg \#111111"
Cherian answered 9/1, 2016 at 23:47 Comment(2)
"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/49063038Epimenides
Not by escaping, only by concatenation.Cherian
K
7

Most of these answers hit on the specific case you're asking about. There is a general approach that a friend and I have developed that allows for arbitrary quoting in case you need to quote Bash commands through multiple layers of shell expansion, e.g., through ssh, su -c, bash -c, etc. There is one core primitive you need, here in native Bash:

quote_args() {
    local sq="'"
    local dq='"'
    local space=""
    local arg
    for arg; do
        echo -n "$space'${arg//$sq/$sq$dq$sq$dq$sq}'"
        space=" "
    done
}

This does exactly what it says: it shell-quotes each argument individually (after bash expansion, of course):

$ quote_args foo bar
'foo' 'bar'
$ quote_args arg1 'arg2 arg2a' arg3
'arg1' 'arg2 arg2a' 'arg3'
$ quote_args dq'"'
'dq"'
$ quote_args dq'"' sq"'"
'dq"' 'sq'"'"''
$ quote_args "*"
'*'
$ quote_args /b*
'/bin' '/boot'

It does the obvious thing for one layer of expansion:

$ bash -c "$(quote_args echo a'"'b"'"c arg2)"
a"b'c arg2

(Note that the double quotes around $(quote_args ...) are necessary to make the result into a single argument to bash -c.) And it can be used more generally to quote properly through multiple layers of expansion:

$ bash -c "$(quote_args bash -c "$(quote_args echo a'"'b"'"c arg2)")"
a"b'c arg2

The above example:

  1. shell-quotes each argument to the inner quote_args individually and then combines the resulting output into a single argument with the inner double quotes.
  2. shell-quotes bash, -c, and the already once-quoted result from step 1, and then combines the result into a single argument with the outer double quotes.
  3. sends that mess as the argument to the outer bash -c.

That's the idea in a nutshell. You can do some pretty complicated stuff with this, but you have to be careful about order of evaluation and about which substrings are quoted. For instance, the following do the wrong things (for some definition of "wrong"):

$ (cd /tmp; bash -c "$(quote_args cd /; pwd 1>&2)")
/tmp
$ (cd /tmp; bash -c "$(quote_args cd /; [ -e *sbin ] && echo success 1>&2 || echo failure 1>&2)")
failure

In the first example, Bash immediately expands quote_args cd /; pwd 1>&2 into two separate commands, quote_args cd / and pwd 1>&2, so the CWD is still /tmp when the pwd command is executed. The second example illustrates a similar problem for globbing. Indeed, the same basic problem occurs with all Bash expansions. The problem here is that a command substitution isn't a function call: it's literally evaluating one Bash script and using its output as part of another Bash script.

If you try to simply escape the shell operators, you'll fail because the resulting string passed to bash -c is just a sequence of individually-quoted strings that aren't then interpreted as operators, which is easy to see if you echo the string that would have been passed to Bash:

$ (cd /tmp; echo "$(quote_args cd /\; pwd 1\>\&2)")
'cd' '/;' 'pwd' '1>&2'
$ (cd /tmp; echo "$(quote_args cd /\; \[ -e \*sbin \] \&\& echo success 1\>\&2 \|\| echo failure 1\>\&2)")
'cd' '/;' '[' '-e' '*sbin' ']' '&&' 'echo' 'success' '1>&2' '||' 'echo' 'failure' '1>&2'

The problem here is that you're over-quoting. What you need is for the operators to be unquoted as input to the enclosing bash -c, which means they need to be outside the $(quote_args ...) command substitution.

Consequently, what you need to do in the most general sense is to shell-quote each word of the command not intended to be expanded at the time of command substitution separately, and not apply any extra quoting to the shell operators:

$ (cd /tmp; echo "$(quote_args cd /); $(quote_args pwd) 1>&2")
'cd' '/'; 'pwd' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")
/
$ (cd /tmp; echo "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
'cd' '/'; [ -e *'sbin' ] && 'echo' 'success' 1>&2 || 'echo' 'failure' 1>&2
$ (cd /tmp; bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")
success

Once you've done this, the entire string is fair game for further quoting to arbitrary levels of evaluation:

$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")"
/
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")"
/
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); $(quote_args pwd) 1>&2")")")"
/
$ bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")"
success
$ bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *sbin ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")"
success
$ bash -c "$(quote_args bash -c "$(quote_args bash -c "$(quote_args cd /tmp); $(quote_args bash -c "$(quote_args cd /); [ -e *$(quote_args sbin) ] && $(quote_args echo success) 1>&2 || $(quote_args echo failure) 1>&2")")")"
success

etc.

These examples may seem overwrought given that words like success, sbin, and pwd don't need to be shell-quoted, but the key point to remember when writing a script taking arbitrary input is that you want to quote everything you're not absolutely sure doesn't need quoting, because you never know when a user will throw in a Robert'; rm -rf /.

To better understand what is going on under the covers, you can play around with two small helper functions:

debug_args() {
    for (( I=1; $I <= $#; I++ )); do
        echo -n "$I:<${!I}> " 1>&2
    done
    echo 1>&2
}

debug_args_and_run() {
    debug_args "$@"
    "$@"
}

That will enumerate each argument to a command before executing it:

$ debug_args_and_run echo a'"'b"'"c arg2
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)"
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2

$ bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run bash -c "$(quote_args debug_args_and_run echo a'"'b"'"c arg2)")")")"
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'bash'"'"' '"'"'-c'"'"' '"'"''"'"'"'"'"'"'"'"'debug_args_and_run'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'echo'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'a"b'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'c'"'"'"'"'"'"'"'"' '"'"'"'"'"'"'"'"'arg2'"'"'"'"'"'"'"'"''"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'bash' '-c' ''"'"'debug_args_and_run'"'"' '"'"'echo'"'"' '"'"'a"b'"'"'"'"'"'"'"'"'c'"'"' '"'"'arg2'"'"''>
1:<bash> 2:<-c> 3:<'debug_args_and_run' 'echo' 'a"b'"'"'c' 'arg2'>
1:<echo> 2:<a"b'c> 3:<arg2>
a"b'c arg2
Kyat answered 29/12, 2015 at 16:57 Comment(1)
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!!!Diacetylmorphine
E
5

Obviously, it would be easier simply to surround with double quotes, but where's the challenge in that? Here is the answer using only single quotes. I'm using a variable instead of alias so that's it's easier to print for proof, but it's the same as using alias.

$ rxvt='urxvt -fg '\''#111111'\'' -bg '\''#111111'\'
$ echo $rxvt
urxvt -fg '#111111' -bg '#111111'

Explanation

The key is that you can close the single quote and re-open it as many times as you want. For example foo='a''b' is the same as foo='ab'. So you can close the single quote, throw in a literal single quote \', then reopen the next single quote.

Breakdown diagram

This diagram makes it clear by using brackets to show where the single quotes are opened and closed. Quotes are not "nested" like parentheses can be. You can also pay attention to the color highlighting, which is correctly applied. The quoted strings are maroon, whereas the \' is black.

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'    # Original
[^^^^^^^^^^] ^[^^^^^^^] ^[^^^^^] ^[^^^^^^^] ^    # Show open/close quotes
 urxvt -fg   ' #111111  '  -bg   ' #111111  '    # Literal characters remaining

(This is essentially the same answer as Adrian's, but I feel this explains it better. Also his answer has two superfluous single quotes at the end.)

Epimenides answered 2/3, 2018 at 4:53 Comment(1)
+1 for using the '\'' method I recommend over the '"'"' method which is often harder for humans to read.Mcclintock
M
4

In the given example, simply use double quotes instead of single quotes as the outer escape mechanism:

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

This approach is suited for many cases where you just want to pass a fixed string to a command: Just check how the shell will interpret the double-quoted string through an echo, and escape characters with backslash if necessary.

In the example, you'd see that double quotes are sufficient to protect the string:

echo "urxvt -fg '#111111' -bg '#111111'"

Output:

urxvt -fg '#111111' -bg '#111111'
Mccollough answered 25/11, 2013 at 14:36 Comment(0)
C
4

Here is an elaboration on The One True Answer referenced in the previous answers:

Sometimes I will be downloading using rsync over SSH and have to escape a filename with a ' in it twice! (OMG!) Once for Bash and once for ssh. The same principle of alternating quotation delimiters is at work here.

For example, let's say we want to get: Louis Theroux's LA Stories ...

  1. First you enclose Louis Theroux in single quotes for Bash and double quotes for ssh: '"Louis Theroux"'
  2. Then you use single quotes to escape a double quote '"'
  3. The use double quotes to escape the apostrophe "'"
  4. Then repeat #2, using single quotes to escape a double quote '"'
  5. Then enclose LA Stories in single quotes for bash and double quotes for ssh: '"LA Stories"'

And behold! You wind up with this:

rsync -ave ssh '"Louis Theroux"''"'"'"'"''"s LA Stories"'

which is an awful lot of work for one little '—but there you go.

Cavallaro answered 7/4, 2014 at 23:39 Comment(0)
T
4

In addition to JasonWoof's perfect answer I want to show how I solved a related problem.

In my case, encoding single quotes with '\'' will not always be sufficient. For example, if a string must be quoted with single quotes, but the total count of quotes results in an odd amount:

#!/bin/bash

# No closing quote
string='alecxs\'solution'

# This works for string
string="alecxs'solution"
string=alecxs\'solution
string='alecxs'\''solution'

Let's assume the string is a file name and we need to save quoted file names in a list (like stat -c%N ./* > list):

echo "'$string'" > "$string"
cat "$string"

But processing this list will fail (depending on how many quotes the string does contain in total):

while read file
  do
    ls -l "$file"
    eval ls -l "$file"
done < "$string"

Workaround: encode quotes with string manipulation

string="${string//$'\047'/\'\$\'\\\\047\'\'}"

# Result
echo "$string"

Now it works because quotes are always balanced:

echo "'$string'" > list
while read file
  do
    ls -l "$file"
    eval ls -l "$file"
done < list
Touchwood answered 14/6, 2020 at 16:25 Comment(1)
use '$'\047'' or '$'\\047'' as replacement for '\'' depending on shellTouchwood
M
3

Another way to fix the problem of too many layers of nested quotation:

You are trying to cram too much into too tiny a space, so use a Bash function.

The problem is you are trying to have too many levels of nesting, and the basic alias technology is not powerful enough to accommodate it. Use a Bash function like this to make it so the single, double quotes back ticks and passed in parameters are all handled normally as we would expect:

lets_do_some_stuff() {
    tmp=$1                       # Keep a passed in parameter.
    run_your_program $@          # Use all your passed parameters.
    echo -e '\n-------------'    # Use your single quotes.
    echo `date`                  # Use your back ticks.
    echo -e "\n-------------"    # Use your double quotes.
}
alias foobarbaz=lets_do_some_stuff

Then you can use your $1 and $2 variables and single, double quotes and back ticks without worrying about the alias function wrecking their integrity.

This program prints:

cd ~/code
foobarbaz alien Dyson ring detected @grid 10385

Output:

alien Dyson ring detected @grid 10385
-------------
Mon Oct 26 20:30:14 EDT 2015
-------------
Melyndamem answered 27/10, 2015 at 0:36 Comment(0)
E
3
shell_escape () {
    printf '%s' "'${1//\'/\'\\\'\'}'"
}

Implementation explanation:

  • double quotes so we can easily output wrapping single quotes and use the ${...} syntax

  • bash's search and replace looks like: ${varname//search/replacement}

  • we're replacing ' with '\''

  • '\'' encodes a single ' like so:

    1. ' ends the single quoting

    2. \' encodes a ' (the backslash is needed because we're not inside quotes)

    3. ' starts up single quoting again

    4. bash automatically concatenates strings with no white space between

  • there's a \ before every \ and ' because that's the escaping rules for ${...//.../...} .

string="That's "'#@$*&^`(@#'
echo "original: $string"
echo "encoded:  $(shell_escape "$string")"
echo "expanded: $(bash -c "echo $(shell_escape "$string")")"

P.S. Always encode to single quoted strings because they are way simpler than double quoted strings.

Ephialtes answered 25/4, 2019 at 20:27 Comment(0)
W
3

Here are my two cents—in the case if one wants to be sh-portable, not just Bash-specific (the solution is not too efficient, though, as it starts an external program—sed):

  • put this in quote.sh (or just quote) somewhere on your PATH:
# This works with standard input (stdin)
quote() {
  echo -n "'" ;
  sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g' ;
  echo -n "'"
}

case "$1" in
 -) quote ;;
 *) echo "usage: cat ... | quote - # single-quotes input for Bourne shell" 2>&1 ;;
esac

An example:

$ echo -n "G'day, mate!" | ./quote.sh -
'G'"'"'day, mate!'

And, of course, that converts back:

$ echo 'G'"'"'day, mate!'
G'day, mate!

Explanation: basically we have to enclose the input with quotes ', and then also replace any single quote within with this micro-monster: '"'"' (end the opening quote with a pairing ', escape the found single quote by wrapping it with double quotes -- "'", and then finally issue a new opening single quote ', or in pseudo-notation: ' + "'" + ' == '"'"')

One standard way to do that would be to use sed with the following substitution command:

s/\(['][']*\)/'"\1"'/g

One small problem, though, is that in order to use that in shell one needs to escape all these single quote characters in the sed expression itself—what leads to something like

sed 's/\(['"'"']['"'"']*\)/'"'"'"\1"'"'"'/g'

(and one good way to build this result is to feed the original expression s/\(['][']*\)/'"\1"'/g to Kyle Rose' or George V. Reilly's scripts).

Finally, it kind of makes sense to expect the input to come from stdin—since passing it through command-line arguments could be already too much trouble.

(Oh, and maybe we want to add a small help message, so that the script does not hang when someone just runs it as ./quote.sh --help wondering what it does.)

Washer answered 31/1, 2020 at 10:39 Comment(0)
T
2

If you're generating the shell string within Python 2 or Python 3, the following may help to quote the arguments:

#!/usr/bin/env python

from __future__ import print_function

try:  # py3
    from shlex import quote as shlex_quote
except ImportError:  # py2
    from pipes import quote as shlex_quote

s = """foo ain't "bad" so there!"""

print(s)
print(" ".join([shlex_quote(t) for t in s.split()]))

This will output:

foo ain't "bad" so there!
foo 'ain'"'"'t' '"bad"' so 'there!'
Tremolite answered 22/6, 2017 at 20:58 Comment(1)
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>..."))Kharkov
M
2

If you have GNU parallel installed, you can use its internal quoting:

$ parallel --shellquote
L's 12" record
<Ctrl-D>
'L'"'"'s 12" record'
$ echo 'L'"'"'s 12" record'
L's 12" record

From version 20190222 you can even --shellquote multiple times:

$ parallel --shellquote --shellquote --shellquote
L's 12" record
<Ctrl-D>
'"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
$ eval eval echo '"'"'"'"'"'"'L'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'"'s 12" record'"'"'"'"'"'"'
L's 12" record

It will quote the string in all supported shells (not only Bash).

Mariannemariano answered 11/7, 2019 at 5:48 Comment(0)
S
1

This function:

quote () 
{ 
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

allows quoting of ' inside '. Use as this:

$ quote "urxvt -fg '#111111' -bg '#111111'"
'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''

If the line to quote gets more complex, like double quotes mixed with single quotes, it may become quite tricky to get the string to quote inside a variable. When such cases show up, write the exact line that you need to quote inside an script (similar to this).

#!/bin/bash

quote ()
{
    local quoted=${1//\'/\'\\\'\'};
    printf "'%s'" "$quoted"
}

while read line; do
    quote "$line"
done <<-\_lines_to_quote_
urxvt -fg '#111111' -bg '#111111'
Louis Theroux's LA Stories
'single quote phrase' "double quote phrase"
_lines_to_quote_

Will output:

'urxvt -fg '\''#111111'\'' -bg '\''#111111'\'''
'Louis Theroux'\''s LA Stories'
''\''single quote phrase'\'' "double quote phrase"'

All correctly quoted strings inside single quotes.

Stowell answered 21/11, 2015 at 11:1 Comment(0)
A
0

Here is another solution. This function will take a single argument and appropriately quote it using the single-quote character, just as the voted answer above explains:

single_quote() {
  local quoted="'"
  local i=0
  while [ $i -lt ${#1} ]; do
    local ch="${1:i:1}"
    if [[ "$ch" != "'" ]]; then
      quoted="$quoted$ch"
    else
      local single_quotes="'"
      local j=1
      while [ $j -lt ${#1} ] && [[ "${1:i+j:1}" == "'" ]]; do
        single_quotes="$single_quotes'"
        ((j++))
      done
      quoted="$quoted'\"$single_quotes\"'"
      ((i+=j-1))
    fi
    ((i++))
  done
  echo "$quoted'"
}

So, you can use it this way:

single_quote "1 2 '3'"
'1 2 '"'"'3'"'"''

x="this text is quoted: 'hello'"
eval "echo $(single_quote "$x")"
this text is quoted: 'hello'
Allseed answered 27/11, 2015 at 18:41 Comment(1)
What is "the voted answer above"? Which answer?Corvin

© 2022 - 2024 — McMap. All rights reserved.