3

When you do something like this:

echo 'Hello World'

Or like this:

x=12345
echo "x is: $x"

In the first example, does the echo command receive 'Hello World', or does it receive Hello World?

And in the second example, does the echo command receive "x is: $x", or does it receive x is: 12345?

So basically my question is: Are the single quotes and the double quotes handled by bash or by echo?

2 Answers2

3

Quotes, variable expansion, wildcards, and everything else mentioned in the bash manual section on expansion is handled by bash.

For example, when you run the bash command echo "x is: $x", bash parses this to find out that it needs to run the command echo with one argument which is x is: 12345. Given echo "x is" "$x", bash runs echo with two arguments: x is and 12345 — this is why the two spaces inside the quotes are preserved (they're part of the argument which echo prints unmodified), but the two spaces outside the quotes are not (for the shell, two spaces to separate arguments are as good as a single one, and echo always prints a single space in between).

The echo command (or any other command) has no way to know which shell command produced the argument x is: 12345, or indeed whether a shell was involved at all. Here are a few sample commands that produce this argument:

echo "x is: 12345"
echo 'x is: 12345'
echo x\ is:\ 12345
echo "x is: $x"
echo 'x is:'\ "$x"
echo "$(echo "x ")is: $x"
# Assume there is no other file whose name begins with x in the current directory
touch "x is: $x"; echo x*

Or it could be exec "echo", "x is: 12345" in Perl, or execlp("echo", "echo", "x is: 12345") in C, etc.

This holds for every command. And the same principle holds for other shells, although they each have a slightly (or significantly) different set of expansions.

On the other hand, options are handled by the command. For example, ls -l -t and ls -l "-t" in bash (or any similar shell) both run ls in exactly the same way, with the two arguments -l and -t. This is why no form of shell quoting won't help you if you want to run ls to display information about a file called -t. You can do that with ls -l -- -t, i.e. by passing -- as an argument. This is a convention followed by most commands: nothing after -- is parsed as an option. Once again, this is a feature of the command; for the shell, a leading dash is nothing special.

One thing that can get tricky is backslashes. In bash and other shells, backslash outside of any quotes means that the next character loses its special meaning. For example, echo \"x\ is:\ 12345\" prints "x is: 12345", because each of the characters that are preceded by a backslash lose their special meaning (quote syntax for ", word separator for spaces). But some commands also interpret backslashes. When they do, you need to ensure that the backslash gets to them. For example, in bash, the echo command prints backslashes literally by default, but if you pass the option -e or run shopt xpg_echo first, then echo has its own escape sequences (this is for bash, the echo command in different shells and on different systems have different rules about whether they treat backslashes specially). For example, echo 'a\nb' prints a\nb while echo -e 'a\nb' prints a-newline-b because \n means “newline” to echo -e. On the other hand, echo a\nb prints anb since \n is expanded by the shell and there \ means “quote the next character”.

Also, be careful when multiple shells are involved, e.g. with SSH. When you run ssh in a shell, the local shell parses the command as usual. The SSH client joins its non-option arguments with spaces in between, and sends the resulting string to the SSH server. The SSH server passes this string to the remote shell. This means that there are two full rounds of shell expansion between what you type in a terminal or in a shell script, and what gets executed on the remote machine.

ilkkachu
  • 138,973
0

In your examples: the quotes are used by the shell.

The first step that the shell (all shells) take is to split the command line into tokens.

The first step on Chapter 7.3: Command-line Processing is to split. Please also take a look at the "Figure 7.1: Steps in Command-line Processing".

Each token is some part of the line. Each token may be some expansion or other tokens.

Splitting is mostly done on meta-characters (space is a meta-character, the most commonly used), quoting does modify the splitting. Single quotes group all characters (including spaces) as one token.

You can "see" the splitting with printf:

$ printf '<%s>\n' echo 'Hello World'
<echo>
<Hello World>

Double quotes also group characters but allow some internal expansions.

$ x=12345
$ echo "x is: $x"
x is: 12345
$ printf '%s' echo "x is: $x"
<echo>
<x is: 12345>

Quotes may also appear as:

$ echo $(printf '"%s" ' "Hello World")
"Hello World"

However, the splitting done by the shell adds an interesting twist:

$ printf '<%s>\n' echo $(printf '"%s" ' "Hello World")
<echo>
<"Hello>
<World">

Splitting is done on the result of the command expansion. Quoting the command expansion avoids the splitting:

$ printf '<%s>\n' echo "$(printf '"%s" ' "Hello World")"
<echo>
<"Hello World" >

Other from of quoting is the backslash. Quoting the space makes it act as a normal character (not an splitting meta-character):

$ printf '<%s>\n' echo Hello\ World
<echo>
<Hello World>