Did you know? DZone has great portals for Python, Cloud, NoSQL, and HTML5!
DevOps Zone is brought to you in partnership with:

Geoff Papilion has made a living running infrastructure for the past 15 years. He is currently employeed at Wikia.com, scaling the infrastructure to 1.5 billion request per day. Geoffrey is a DZone MVB and is not an employee of DZone and has posted 10 posts at DZone. You can read more from them at their website. View Full User Profile

3 Bash Expansion Tricks

02.17.2012
Email
Views: 3868
  • submit to reddit
The DevOps Zone is presented by DZone with partners including ThoughtWorks Studios and UrbanCode to bring you the most interesting and relevant content on the DevOps movement.  See today's top DevOps content and be sure to check out ThoughtWorks Studio's Continuous Delivery Whitepapers and UrbanCode's Webinars.

I got asked a question regarding filename expansion in bash the other day, and was stumped. It turns out to be something I should have considered a long time ago, and will always keep in mind when writing a script.

Question 1:

What does the following script do if there is a file abc in the current directory

#!/bin/bash
for i in a*
do
  echo $i
done
Answer:

This a* matches abc and expands to abc, and the script outputs:
abc

Question 2:

What if you run the same script in a directory without any files?

Answer:

The script outputs:

a*
Why?

According to The Bash Reference Manual:

Bash scans each word for the characters ‘*’, ‘?’, and ‘[’. If one of these characters appears, then the word is regarded as a pattern, and replaced with an alphabetically sorted list of file names matching the pattern. If no matching file names are found, and the shell option nullglob is disabled, the word is left unchanged.


So bash will output ‘a*’, because that is how filename expansion works.

Question 3:

What if you run the following script and in a directory with no filename beginning with a:

#!/bin/bash
for i in a*
do
  echo /usr/bin/$i
done

Answer:

The script outputs:


/usr/bin/a2p /usr/bin/a2p5.10.0 /usr/bin/a2p5.8.9 /usr/bin/aaf_install /usr/bin/aclocal /usr/bin/aclocal-1.10 /usr/bin/addftinfo /usr/bin/afconvert /usr/bin/afinfo /usr/bin/afmtodit /usr/bin/afplay /usr/bin/afscexpand /usr/bin/agvtool /usr/bin/alias /usr/bin/allmemory /usr/bin/amavisd /usr/bin/amavisd-agent /usr/bin/amavisd-nanny /usr/bin/amavisd-release /usr/bin/amlint /usr/bin/ant /usr/bin/applesingle /usr/bin/appletviewer /usr/bin/apply /usr/bin/apr-1-config /usr/bin/apropos /usr/bin/apt /usr/bin/apu-1-config /usr/bin/ar /usr/bin/arch /usr/bin/as /usr/bin/asa /usr/bin/at /usr/bin/atos /usr/bin/atq /usr/bin/atrm /usr/bin/atsutil /usr/bin/autoconf /usr/bin/autoheader /usr/bin/autom4te /usr/bin/automake /usr/bin/automake-1.10 /usr/bin/automator /usr/bin/autoreconf /usr/bin/autoscan /usr/bin/autospec /usr/bin/autoupdate /usr/bin/auval /usr/bin/auvaltool /usr/bin/awk

Why?

Because you’re re-evaluating ‘/usr/bin/$i’ which is now ‘/usr/bin/a*’, which expands to the order list above due to shell filename expansion rules. If you want to avoid this you need to protect your variables using quotes. Here is the safe version of the script:

#!/bin/bash
for i in a*
do
  echo /usr/bin/"$i"
done

Just something simple to think about when writing your bash scripts. Expect to enter loops on globs that don't match anything, always protect your variables, and consider setting the failglob option in your scripts.


Source:  http://blog.hypergeometric.com/2012/02/01/stupid-bash-expansion-trick/
Published at DZone with permission of Geoffrey Papilion, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

ThoughtWorks Studios and UrbanCode, the sponsors of the DevOps Zone, are champions of the DevOps movement.  Their deployment tooling solutions focus on the entire software development lifecycle, involving all parts of an organization, which helps facilitate a migration to the DevOps philosophy.