Paul L. Snyder on 20 Sep 2010 12:01:14 -0700


[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

Re: [PLUG] Building a command from variables in BASH


On Mon, 20 Sep 2010, Gavin W. Burris wrote:

> Passing variables to sed bit me recently.  I finally figured it out,
> proper use of the quotes and escapes, which seems painfully obvious now:
> http://idolinux.blogspot.com/2010/08/passing-variables-to-sed.html

sh (and bash) word-splitting conventions are atrocious: word splitting
happens after expansion (brace, parameter, and arithmetic) and substitution
(process and command), but before globbing.  During word splitting, the
command-line is divided up based on the delimiters in the IFS environment
variable (by default: space, tab, and newline).

  bash% f() { echo "First: -$1-   Second: -$2-"; }
  bash% A="1 2"
  bash% f $A
  First: -1-   Second: -2-

Word splitting applies only to the text resulting from command
substitution, parameter expansion, or arithmetic expansion.  This seems
like it might be vaguely sane until you start tossing quotes into the mix.

  bash% B="\"3 4\""
  bash% f $B
  First: -"3-   Second: -4"-

Yech!

Quote removal happens after all expansions, but text that is the result
of an expansion or substitution will not have quotes removed, giving us the
hideous result above.

zsh breaks with traditional Bourne shell word-splitting, and introduces a
number of features that make dealing with this sort of thing much easier.

  zsh% setopt NO_SH_WORD_SPLIT # The zsh default
  zsh% g() { echo "First: -$1-   Second: -$2-"; }
  zsh% A="1 2"
  zsh% f $A
  First: -1 2-   Second: --

zsh will NOT split on whitespace by default.  As with bash, quotes are not
removed:

  zsh% B="\"3 4\""
  zsh% g $B
  First: -"3 4"-   Second: --

You get fine-grained control over what you want to happen with your
expansions.  Rather than having to lobotomize your shell by setting
SH_WORD_SPLIT, you can split a single expansion:

  zsh% g ${=A}
  First: -1-   Second: -2-

As with sh/bash, splitting is based on the contents of IFS.  The braces are
usually optional but help keep thing clear when the parsing gets hairy.
The result with the quoted contents of B still isn't very nice, though:

  zsh% g ${=B}
  First: -"3-   Second: -4"-

Fortunately, we can control this with the parameter expansion flag 'Q',
which removes a level of quoting when the envvar is expanded (note the
nested expansion, since we're splitting the result of the first expansion.

  zsh% g ${=${(Q)B}}
  First: -3-   Second: -4-

Or, we could have quoting go the other way, using 'q', which adds a level
of quoting (using backquotes):

  zsh% g ${(q)A}
  First: -3\ 4-   Second: --

We can easily split on a different character:
  zsh% C="5 6/7"
  zsh% g ${(s:/:)C}
  First: -5 6-   Second: -7-

And for a last example, we can perform sensible splitting based on the
quoting inside the envvar:

  zsh% D="8 \"9 0\""
  zsh% g ${(z)D}
  First: -8-   Second: -"9 0"-
  zsh% g ${(Q)${(z)D}
  First: -9'   Second: -9 0-

Here endeth today's zsh evangelism.  It won't do you any good if you have
to script for bash, but if you've got enough control over your target
environment that you can ensure zsh is present, it can make your life much
easier.

Paul
___________________________________________________________________________
Philadelphia Linux Users Group         --        http://www.phillylinux.org
Announcements - http://lists.phillylinux.org/mailman/listinfo/plug-announce
General Discussion  --   http://lists.phillylinux.org/mailman/listinfo/plug