Reputation: 39138
For example:
FOO="BAR * BAR"
echo $FOO
Output:
BAR file1 file2 file3 file4 BAR
And using the \
escape character:
FOO="BAR \* BAR"
echo $FOO
Output:
BAR \* BAR
I'm obviously doing something stupid.
How do I get the output BAR * BAR
?
Upvotes: 170
Views: 160657
Reputation: 10820
If you don't want to bother with weird expansions from Bash, you can do this:
FOO="BAR \x2A BAR" # 2A is the hexadecimal code for *
echo -e $FOO
Output:
BAR * BAR
An explanation here why using -e
option of echo makes life easier:
Relevant quote from the man page here:
SYNOPSIS
echo [SHORT-OPTION]... [STRING]...
echo LONG-OPTION
DESCRIPTION
Echo the STRING(s) to standard output.
-n do not output the trailing newline
-e enable interpretation of backslash escapes
-E disable interpretation of backslash escapes (default)
--help display this help and exit
--version
output version information and exit
If -e is in effect, the following sequences are recognized:
\\ backslash
...
\0NNN byte with octal value NNN (1 to 3 digits)
\xHH byte with hexadecimal value HH (1 to 2 digits)
For the hexadecimal code, you can check the man ascii
page (first line in octal, second decimal, third hex):
051 41 29 ) 151 105 69 i
052 42 2A * 152 106 6A j
053 43 2B + 153 107 6B k
Upvotes: 4
Reputation: 841
Usually you would use
$ echo "$FOO"
However, I've had problems even with this syntax. Consider the following script.
#!/bin/bash
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1"
The *
needs to be passed verbatim to curl
, but the same problems will arise. The above example won't work (it will expand to filenames in the current directory) and neither will \*
. You also can't quote $curl_opts
because it will be recognized as a single (invalid) option to curl
.
curl: option -s --noproxy * -O: is unknown
curl: try 'curl --help' or 'curl --manual' for more information
Therefore I would recommend the use of the bash
variable $GLOBIGNORE
to prevent filename expansion altogether if applied to the global pattern, or use the set -f
built-in flag.
#!/bin/bash
GLOBIGNORE="*"
curl_opts="-s --noproxy * -O"
curl $curl_opts "$1" ## no filename expansion
Applying to your original example:
me$ FOO="BAR * BAR"
me$ echo $FOO
BAR file1 file2 file3 file4 BAR
me$ set -f
me$ echo $FOO
BAR * BAR
me$ set +f
me$ GLOBIGNORE=*
me$ echo $FOO
BAR * BAR
Upvotes: 69
Reputation: 1293
SHORT ANSWER
Like others have said, you should always quote the variables to prevent strange behaviour. So use echo "$foo" in instead of just echo $foo.
LONG ANSWER
I do think this example warrants further explanation, because there is more going on than it might seem on the face of it.
I can see where your confusion comes in because after you ran your first example you probably thought to yourself that the shell is obviously doing:
So from your first example:
FOO="BAR * BAR"
echo $FOO
After parameter expansion is equivalent to:
echo BAR * BAR
And after filename expansion is equivalent to:
echo BAR file1 file2 file3 file4 BAR
And if you just type echo BAR * BAR
into the command line you will see that they are equivalent.
So you probably thought to yourself "if I escape the *, I can prevent the filename expansion"
So from your second example:
FOO="BAR \* BAR"
echo $FOO
After parameter expansion should be equivalent to:
echo BAR \* BAR
And after filename expansion should be equivalent to:
echo BAR \* BAR
And if you try typing echo BAR \* BAR
directly into the command line it will indeed print BAR * BAR
because the filename expansion is prevented by the escape.
So why did using $FOO
not work?
It's because there is a third expansion that takes place: Quote Removal. From the Bash manual, quote removal is:
After the preceding expansions, all unquoted occurrences of the characters
\
,'
, and"
that did not result from one of the above expansions are removed.
When you type the command directly into the command line, the escape character is not the result of a previous expansion, so Bash removes it before sending it to the echo command, but in the second example, the "\" was the result of a previous parameter expansion, so it is not removed. As a result, echo receives "\" and that's what it prints.
Note the difference between the first example. "*" is not included in the characters that will be removed by Quote Removal.
I hope this makes sense. In the end the conclusion in the same: Just use quotes. I just thought I'd explain why escaping, which logically should work if only parameter and filename expansion are at play, didn't work.
For a full explanation of Bash expansions, refer to 3.5 Shell Expansions.
Upvotes: 128
Reputation: 48659
Quoting when setting $FOO
is not enough. You need to quote the variable reference as well:
me$ FOO="BAR * BAR"
me$ echo "$FOO"
BAR * BAR
Upvotes: 178
Reputation: 193814
It may be worth getting into the habit of using printf
rather then echo
on the command line.
In this example it doesn't give much benefit but it can be more useful with more complex output.
FOO="BAR * BAR"
printf %s "$FOO"
Upvotes: 4