Bash Quoting
Quoting things in bash is quite simple... until it isn't. I've written scripts where I'd swear 90% of the effort was getting bash to quote things correctly.
The basics of quoting are simple, use single or double quotes when a value contains spaces:
a="hello world"
a='hello world'
But single and double quotes are different. Single quotes don't support any kind of variable substitution or escaping of special characters inside the quoted string. Double quotes, on the other hand, support both:
a="hello \"there\" world"
a='hello "there" world'
a='hello \'there\' world' # causes an error
a="hello 'there' world"
b="there"
a='hello \"$b\" world' # a is >>hello \"$b\" world
a="hello \"$b\" world" # a is >>hello "there" world
One way around the problem that single quotes don't support variable substitution is to quote the individual literal parts of the string and then put the variable substitutions between them:
b='"there"'
a='"hello" '$b' "world"' # a is: >>"hello" "there" "world"<<
Note that "$b" is not actually inside a quoted part of the string. Since there's no space between the two literal parts of the string and the "$b", bash puts the final result into a single string and then assigns it to a.
One place where quoting problems often occur is when you're dealing with files that have spaces in their names. For example, if we have a file named "file with spaces" and we run the following script:
#/bin/bash
for i in $(find .)
do
echo $i
done
We don't get what we want:
$ bash t1.sh
.
./file
with
spaces
./t2.sh
./t1.sh
...
One solution is to put the file names into a bash array when the Internal Field Separator is set to just a newline. Then we use the value "${array[@]}" as the list for the for loop.
#/bin/bash
newline='
'
OIFS=$IFS
IFS=$newline
files=($(find .))
IFS=$OIFS
for i in "${files[@]}"
do
echo $i
done
This produces the correct output:
$ bash t2.sh
.
./file with spaces
./t2.sh
./t1.sh
...
The difference between "${array[*]}" and "${array[@]}" is analagous to the difference between "$*" and "$@", namely that in the later case the result is that each word becomes separately quoted rather than the entire thing being quoted as a single string.
Another option, which avoids special quoting is to just set the Internal Field Separator variable before the for loop runs and reset it as the first statement in the loop body:
#/bin/bash
newline='
'
OIFS=$IFS
IFS=$newline
for i in $(find .)
do
IFS=$OIFS
echo $i
done
Another quoting problem is when you have a string which you'd like to break up into separate words but you'd also like to honor any quoted substrings in the string. Consider the string:
a="hello \"there big\" world"
and suppose you'd like to break that into its three parts:
- hello
- there big
- world
If you run this:
#!/bin/bash
a="hello \"there big\" world"
for i in $a
do
echo $i
done
It produces this:
$ bash t4.sh
hello
"there
big"
world
The trick here is to use a special form of the set command to reset $* (and therefore, $1, $2, etc). My first version is always like this:
#!/bin/bash
a="hello \"there big\" world"
set -- $a
for i in "$@"
do
echo $i
done
And of course it doesn't work:
$ bash t5.sh
hello
"there
big"
world
Then I usually remember you have to eval the set statement:
#!/bin/bash
a="hello \"there big\" world"
eval set -- $a
for i in "$@"
do
echo $i
done
At which point it works:
$ bash t6.sh
hello
there big
world
at this point I'm usually tired of quoting things.
Mitch Frazier is an Associate Editor for Linux Journal.
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Sponsored by AMD
Built-in forensics, incident response, and security with Red Hat Enterprise Linux 6
Every security policy provides guidance and requirements for ensuring adequate protection of information and data, as well as high-level technical and administrative security requirements for a system in a given environment. Traditionally, providing security for a system focuses on the confidentiality of the information on it. However, protecting the data integrity and system and data availability is just as important. For example, when processing United States intelligence information, there are three attributes that require protection: confidentiality, integrity, and availability.
Learn more about catching the bad guy in this free white paper.
Sponsored by DLT Solutions
| Dynamic DNS—an Object Lesson in Problem Solving | May 21, 2013 |
| Using Salt Stack and Vagrant for Drupal Development | May 20, 2013 |
| Making Linux and Android Get Along (It's Not as Hard as It Sounds) | May 16, 2013 |
| Drupal Is a Framework: Why Everyone Needs to Understand This | May 15, 2013 |
| Home, My Backup Data Center | May 13, 2013 |
| Non-Linux FOSS: Seashore | May 10, 2013 |
- Dynamic DNS—an Object Lesson in Problem Solving
- Making Linux and Android Get Along (It's Not as Hard as It Sounds)
- Using Salt Stack and Vagrant for Drupal Development
- New Products
- A Topic for Discussion - Open Source Feature-Richness?
- RSS Feeds
- Drupal Is a Framework: Why Everyone Needs to Understand This
- Validate an E-Mail Address with PHP, the Right Way
- Readers' Choice Awards
- The Secret Password Is...
- All the articles you talked
2 hours 17 min ago - All the articles you talked
2 hours 21 min ago - All the articles you talked
2 hours 22 min ago - myip
6 hours 47 min ago - Keeping track of IP address
8 hours 38 min ago - Roll your own dynamic dns
13 hours 51 min ago - Please correct the URL for Salt Stack's web site
17 hours 2 min ago - Android is Linux -- why no better inter-operation
19 hours 18 min ago - Connecting Android device to desktop Linux via USB
19 hours 46 min ago - Find new cell phone and tablet pc
20 hours 44 min ago
Enter to Win an Adafruit Pi Cobbler Breakout Kit for Raspberry Pi

It's Raspberry Pi month at Linux Journal. Each week in May, Adafruit will be giving away a Pi-related prize to a lucky, randomly drawn LJ reader. Winners will be announced weekly.
Fill out the fields below to enter to win this week's prize-- a Pi Cobbler Breakout Kit for Raspberry Pi.
Congratulations to our winners so far:
- 5-8-13, Pi Starter Pack: Jack Davis
- 5-15-13, Pi Model B 512MB RAM: Patrick Dunn
- 5-21-13, Prototyping Pi Plate Kit: Philip Kirby
- Next winner announced on 5-27-13!
Free Webinar: Hadoop
How to Build an Optimal Hadoop Cluster to Store and Maintain Unlimited Amounts of Data Using Microservers
Realizing the promise of Apache® Hadoop® requires the effective deployment of compute, memory, storage and networking to achieve optimal results. With its flexibility and multitude of options, it is easy to over or under provision the server infrastructure, resulting in poor performance and high TCO. Join us for an in depth, technical discussion with industry experts from leading Hadoop and server companies who will provide insights into the key considerations for designing and deploying an optimal Hadoop cluster.
Some of key questions to be discussed are:
- What is the “typical” Hadoop cluster and what should be installed on the different machine types?
- Why should you consider the typical workload patterns when making your hardware decisions?
- Are all microservers created equal for Hadoop deployments?
- How do I plan for expansion if I require more compute, memory, storage or networking?



Comments
When there are too long or too many filenames
Very interesting! I always forget $@ and eval. Regarding filenames, I prefer while/read to avoid the case of extra long or too many filenames (because * expands to the full list!):
This way I have the full filename in a variable without too much hassle. To process only the current directory, I add -maxdepth 1 to the find command.
There is a problem with this approach: FILE is only valid inside the while loop, because is run as a separate bash process (Eats some memory, but is slightly faster). When the input comes from a file, we can do this to avoid the extra process:
Regards,
Fjor
This is sweet. I've been
This is sweet. I've been looking for a over an hour for a way to treat quoted strings with spaces in them as a single token; many articles I have read come close but none hit the nail on the head like yours did. The magic is all in the "eval set..." Thanks so much!
IFS
In bash it is possible to specify the field separator using control characters. The "newline" var in the above examples can be written as:
newline=$'\n'
which is more readable.
Yes it is
Thanks, I didn't know that. I see it now in the man page.
Mitch Frazier is an Associate Editor for Linux Journal.
This is madness
Does anyone know of a shell without all this quoting complications?
Or do I have to stick to Python and Ruby?
In the
In the program
#/bin/bash
newline='
'
OIFS=$IFS
IFS=$newline
for i in $(find .)
do
IFS=$OIFS
echo $i
done
why we need IFS=$OIFS in the loop?
Can't we write this once after loop?
IFS
In this simple case, yes you could put IFS=$OIFS outside the loop, but that might not always be the case if other parts of the loop expect a "normal" IFS value. This example is a bit contrived, but suppose the loop looked like this:
for i in $(find .) do for j in $(echo a b c) do echo $j done echo $i doneHere the variable j only takes on one value, "a b c", because IFS contains only newline, and not tab or space.
Mitch Frazier is an Associate Editor for Linux Journal.
Quoting is fine but why not
Quoting is fine but why not use python instead?
Can you elaborate on the
Can you elaborate on the Python advantage? Not familiar w/ it.
Thanks!
Ouch, brain hurts. Funny you
Ouch, brain hurts.
Funny you should bring this up - I was struggling with a super-stupid-simple issue this week that didn't work because of quoting. Thanks for the article!
What about files passed on the command line?
Thanks very much for the IFS tip. That seems a good way around this problem.
However, what about passing in filenames on the command line? I'd like to call my script as so:
$ script "file 1 with spaces" "file 2 with spaces"
.. and be able to parse them correctly. Suggestions?
Use "$@"
Something like this should work:
for i in "$@" do echo "$i" doneThe secret is with quoting $@, rather than getting all the command line arguments quoted as a single string (which is what "$*" gets you), when you use "$@" you get each individual argument separately quoted, and if you quoted things on the command line they will continue to be quoted. When I say quoted, I really mean it keeps them together as you specified them and doesn't break them at spaces.
Mitch Frazier is an Associate Editor for Linux Journal.
Doh! Thanks!
Wow! All this time I've been misusing $* and $@. I've tried various combinations of quoting $@, $*, $1, and $i (in a for loop), and I'd never gotten this to work right before now. Thanks so much!
I finally have a way to quickly, quietly, reliably unzip a bunch of zip files with spaces in their names, from the command line.
Thanks.
Re: Use "$@"
The following is slightly simpler and does the same thing:
for i do echo "$i" doneWith no in, the for just iterates over all the positional parameters.
Very useful article! I have
Very useful article! I have run into these issues too and I these tips help a lot.