25

In order to remind myself when I try to use shopt in Zsh instead of setopt, I created the following alias, testing it first at a shell prompt:

$ alias shopt='echo "You\'re looking for setopt. This is Z shell, man, not Bash."'

Despite the outer single quotes matching and the inner double quotes matching, and the apostrophe being escaped, I was prompted to finish closing the quotes with:

dquote > _

What's going on?

It appeared that the escaping was being ignored, or that it needed to be double-escaped because of multiple levels of interpretation... So, just to test this theory, I tried double-escaping it (and triple-escaping it, and so on) all the way up until:

alias shopt='echo "You\\\\\\\\\\\\\\\\\\\\\\'re looking for setopt. This is Z shell, man, not Bash." '

and never saw any different behavior. This makes no sense to me. What kind of weird voodoo is preventing the shell from behaving as I expect?

The practical solution is to not use quotes for echo, since it doesn't really need any, and to use double quotes for alias, and to escape the apostrophe so it is ignored when the text is echoed. Then all of the practical problems go away.

Can you help me? I need resolution to this perplexing problem.

Charles Duffy
  • 1,732
  • 15
  • 22
iconoclast
  • 9,198
  • 13
  • 57
  • 97
  • 4
    @muru: I would never in a million years see that other question and think it would provide an answer to my question. The first paragraph in and I have no idea what he's talking about. Wrapping a command for another command??? What does that even mean? There's too much noise from the specifics of his situation to use that as a general question for all quoting problems involving both single and double quotes. – iconoclast Apr 16 '18 at 04:16
  • 1
    @styrofoamfly I'm not asking for a mnemonic... how can it be considered the same question? Having the same subject matter (quotes) does not make it the same question. – iconoclast Apr 16 '18 at 16:40
  • those answers answer your question if you cared enough to read them – styrofoam fly Apr 16 '18 at 16:54
  • 1
    There is nothing about those answers that give anyone with this same question a reason to expect to find an answer there. Whether the answer to this question is buried somewhere in the answers to those questions is irrelevant. What matters is are those the same questions? and they most certainly are not. – iconoclast Apr 16 '18 at 17:21
  • 4
    @iconoclast, if it's a single underlying question dressed up in two entirely different ways, that's exactly why we have the close-as-duplicate mechanism: To guide people to a single source for canonical answers, from multiple paths, thus providing multiple sets of keywords that lead to the same destination. This is why questions closed as duplicates can still be upvoted, aren't automatically deleted, etc -- duplicates are valuable and help the site, when they use sufficiently different terms to describe the same problem as to bring a different audience to that same destination. – Charles Duffy Apr 16 '18 at 19:53
  • 3
    @iconoclast, ...not closing a question as a duplicate just because it's asked in different ways leads to a place where there's no community consensus on a single set of answer, but rather competition between the two. That's unfortunate -- it spreads out the answers, and the folks who review/comment/upvote those answers, between those distinct yet equivalent-at-their-core questions. – Charles Duffy Apr 16 '18 at 19:56
  • @CharlesDuffy: you're the first person I've ever heard articulate even a slightly strong case for your approach to closing questions, but I still disagree. I think you're confusing two questions as the same just because the same bit of information can answer both. Those are not the same question in different "clothes". They are quite literally different questions. I think you're looking at this backwards. These sites are organized by questions, not by answers. You perceive duplication because you're trying to organize by answer (or information in the answer). – iconoclast Apr 17 '18 at 17:32
  • If the SE sites were intended to be organized based on the information in the answer then they would need a different data model Each question would have to be classified by the information in the answer, and each question/answer pair would be an instance of the class. But there are no classes in SE sites. The data model doesn't support the kind of organization that you want. I'm not saying it would be bad to do it the way you propose if the site actually supported it with it's UI and data model and that intent were explicitly stated, but it is bad in the current site. – iconoclast Apr 17 '18 at 17:38
  • The page with the "class" for the questions would need to have the "naked" question (to use your metaphor) without being dressed up at all. Then the multiple questions "dressed up in different clothes" could be linked to from the page for the class of questions that they belong to. The class would have the most stripped down version of the question, and the most stripped-down version of the answer. – iconoclast Apr 17 '18 at 17:40

5 Answers5

46

This is zsh, man, not fish.

In zsh, like in every Bourne-like shell (and also csh), single quotes are strong quotes, there is no escaping within them (except by using the rcquotes options as hinted by @JdeBP where zsh emulates rc quotes¹). You cannot have a single quote inside a single-quoted string, you need to first close the single quoted string and enter the literal single quote using another quoting mechanism (like \ or "):

alias shopt='echo "You'\''re looking for setopt. This is Z shell, man, not Bash."'

Or:

alias shopt='echo "You'"'"'re looking for setopt. This is Z shell, man, not Bash."'

Though you could also do:

alias shopt="echo \"You're looking for setopt. This is Z shell, man, not Bash.\""

("..." are weaker quotes inside which several characters, including \ (here used to escape the embedded ") are still special).

Or:

alias shopt=$'echo "You\'re looking for setopt. This is Z shell, man, not Bash."'

($'...' is yet another kind of quotes from ksh93, where the ' can be escaped with \').

(and BTW, you can also use the standard set -o in place of setopt in zsh. bash, for historical reasons, has two sets of options, one that you set with set -o one with shopt; zsh like most other shells has only one set of options).


¹ In `rc`, the shell of Plan9, with a version for unix-likes also available, [single quotes are the only quoting mechanism](/a/296147) (backslash and double quotes are ordinary characters there), the only way to enter a literal single-quote there is with `''` inside single quotes, so with `zsh -o rcquotes`, you could do:
alias shopt='echo "You''re looking for setopt. This is Z shell, man, not Bash."'
iconoclast
  • 9,198
  • 13
  • 57
  • 97
14
shopt='echo "You\'

This is not voodoo. This is normal POSIX shell quoting in action. There is no escaping mechanism within single-quoted strings. They always terminate at the next single quote. There is a Z shell extension that makes two successive single-quoted strings get a single quote placed between them, which you could employ. Or you could just terminate the single-quoted string, use an escaped (or indeed non-single-quote quoted) single quote, and then start a second single-quoted string.

Or you could not use contractions in your messages. ☺

Further reading

  • "Single quotes". Shell command language. Base Specifications. IEEE 1003.1:2017. The Open Group
  • "Quoting". Shell Grammar. The Z Shell manual.
JdeBP
  • 68,745
  • Do you have any reference for that "extension"? 'foo''bar' -> foo'bar is how you get literal single quotes in rc (where single quotes are the only quoting operator). – Stéphane Chazelas Apr 15 '18 at 22:20
  • Ah! set -o rcquotes in zsh. – Stéphane Chazelas Apr 15 '18 at 22:27
  • 1
    Nitpick: I wouldn't use the word "terminate" because the next single quote doesn't terminate the argument, though that is what many people mistakenly believe. The crucial point to understand is that in the shell, quotes are never delimiters. Only whitespace (usually space characters) are delimiters. – Wildcard Apr 16 '18 at 19:48
14

The other answers do a good job of explaining why you're seeing this behavior. But if I may make a suggestion for how to actually solve this problem:

Don't use aliases for anything even remotely complicated.

Sure, you can tie your brain up in knots trying to figure out how to nest N layers of quotes, but it's rarely worth it for an alias. When an alias is complicated enough that its quoting becomes nontrivial, just switch to a shell function:

shopt(){
    echo "You're looking for setopt. This is Z shell, man, not Bash."
}

This removes an entire layer of quotes, allows you to add more code to the shell function later, and is generally much easier to read. It also allows much finer control over how and where arguments are inserted, instead of the alias approach of "just replace the beginning of the line and let the remaining words fall where they may." For example, with your (corrected) alias, if you type this:

shopt -s some_opt

...then you will get this output:

You're looking for setopt. This is Z shell, man, not Bash. -s some_opt

That's probably not what you wanted. The shell function will consume whatever arguments you pass to it, and silently discard them.

Kevin
  • 766
  • 3
    Yes, very good practical advice. It doesn't actually answer the question, but I'm upvoting it anyway because it's still a useful addition to the conversation. – iconoclast Apr 16 '18 at 16:45
5

For days when you don't feel like fighting the quotes, use another character like Unicode U+2019 instead of ' for apostrophes.

Press and hold Ctrl+Shift and type u2019 and this character will appear (er... depending on your locale?).

% alias shopt='echo You’re looking for setopt. This is a Z shell, woman, not Bash.'
% shopt -s dotglob
You’re looking for setopt. This is a Z shell, woman, not Bash. -s dotglob

Such usage of (U+2019) is correct because this character is officially intended to be used as an apostrophe. The Unicode Standard, Version 10.0 explicitly recommends such usage. The standard acknowledges that ' (U+0027) is commonly used as an apostrophe due to its presence in ASCII and on keyboards, and appears to permit that. But it then states that (U+2019) is preferred for apostrophes used as punctuation (and gives a contraction as an example). The only apostrophes for which (U+2019) is not preferred are those used as diacritic marks rather than punctuation marks; those are best written ʼ (U+02BC). From Apostrophes (p. 276) in section 6.2 of the standard:

Apostrophes

U+0027 APOSTROPHE is the most commonly used character for apostrophe. For historical reasons, U+0027 is a particularly overloaded character. In ASCII, it is used to represent a punctuation mark (such as right single quotation mark, left single quotation mark, apostrophe punctuation, vertical line, or prime) or a modifier letter (such as apostrophe modifier or acute accent). Punctuation marks generally break words; modifier letters generally are considered part of a word.

When text is set, U+2019 RIGHT SINGLE QUOTATION MARK is preferred as apostrophe, but only U+0027 is present on most keyboards. Software commonly offers a facility for automatically converting the U+0027 APOSTROPHE to a contextually selected curly quotation glyph. In these systems, a U+0027 in the data stream is always represented as a straight vertical line and can never represent a curly apostrophe or a right quotation mark.

Letter Apostrophe. U+02BC MODIFIER LETTER APOSTROPHE is preferred where the apostrophe is to represent a modifier letter (for example, in transliterations to indicate a glottal stop). In the latter case, it is also referred to as a letter apostrophe.

Punctuation Apostrophe. U+2019 RIGHT SINGLE QUOTATION MARK is preferred where the character is to represent a punctuation mark, as for contractions: “We’ve been here before.” In this latter case, U+2019 is also referred to as a punctuation apostrophe.

An implementation cannot assume that users’ text always adheres to the distinction between these characters. The text may come from different sources, including mapping from other character sets that do not make this distinction between the letter apostrophe and the punctuation apostrophe/right single quotation mark. In that case, all of them will generally be represented by U+2019.

The semantics of U+2019 are therefore context dependent. For example, if surrounded by letters or digits on both sides, it behaves as an in-text punctuation character and does not separate words or lines.

Eliah Kagan
  • 4,155
Zanna
  • 3,571
  • @StéphaneChazelas: It is my understanding that ' is also the straight quote (for when you don't know which way it goes). – Joshua Apr 16 '18 at 15:57
  • Thanks @EliahKagan. I stand corrected. I'll remove my comments. – Stéphane Chazelas Apr 19 '18 at 16:08
  • 1
    @StéphaneChazelas Thanks. Although your previous comments were the impetus for my edit, I think the blockquoted text I added is also generally beneficial (even in the absence of those comments) and worth the length increase. Of course, as the editor who added it, I would think that, wouldn't I! Zanna: So, although I suggest keeping the full quote, I will not object if you end up deciding to shorten or remove/replace it. – Eliah Kagan Apr 19 '18 at 16:15
4

A now-deleted comment tipped me off and got me half-way to the answer.

It's impossible to escape a single quote inside a single-quoted string.

This limitation is not present in double quoted strings, as I'm certainly escaping a single quotation mark inside a double-quoted string in my final solution:

alias shopt="echo You\'re looking for setopt. This is Z shell, man, not Bash."
slm
  • 369,824
iconoclast
  • 9,198
  • 13
  • 57
  • 97