Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I force bash to expand a variable to pass it as an argument?

I found a weird behavior that I don't know how to workaround.

$ var1=*

$ echo $var1
Audiobooks Downloads Desktop (etc.)

$ ls $var1
Audiobooks:

Downloads:
(etc)

All seems OK. At declaration, the variable gets expanded and everything else works. But see this:

$ var2=~/rpmbuild/{SRPMS,RPMS/*}/enki-*.rpm

$ echo $var2
/home/yajo/rpmbuild/{SRPMS,RPMS/*}/enki-*.rpm

$ ls $var2
ls: no se puede acceder a /home/yajo/rpmbuild/{SRPMS,RPMS/*}/enki-*.rpm: No existe el fichero o el directorio

$ ls /home/yajo/rpmbuild/{SRPMS,RPMS/*}/enki-*.rpm
/home/yajo/rpmbuild/RPMS/noarch/enki-12.10.3-1.fc18.noarch.rpm  /home/yajo/rpmbuild/SRPMS/enki-12.10.3-1.fc18.src.rpm
/home/yajo/rpmbuild/RPMS/noarch/enki-12.10.3-1.fc19.noarch.rpm  /home/yajo/rpmbuild/SRPMS/enki-12.10.3-1.fc19.src.rpm

This time, at declaration only ~ gets expanded, which produces that I cannot pass it as an argument to ls. However, passing the same string literally produces the expected results.

Questions are:

  • Why sometimes expand and sometimes not?
  • How to mimic the behavior of $var1 with $var2?

Thanks.

Extra notes:

I tried the same with double and single quotes, but with the same bad results.

like image 594
Yajo Avatar asked Jul 07 '13 08:07

Yajo


1 Answers

The order in which the shell parses various aspects of the command line is not obvious, and it matters for things like this.

First, the wildcards aren't expanded at declaration, they're expanded after the variable value is substituted (note: in these examples I'll pretend I have your filesystem):

$ var1=*
$ echo "$var1"   # double-quotes prevent additional parsing of the variable's value
*
$ echo $var1     # without double-quotes, variable value undergoes wildcard expansion and word splitting
Audiobooks:

Downloads:
(etc)

BTW, the ~ is expanded at declaration, confusing things even further:

$ var2=~
$ echo "$var2"   # again, double-quotes let me see what's actually in the variable
/home/yajo

The problem with ~/rpmbuild/{SRPMS,RPMS/*}/enki-*.rpm1 is that while the shell does wildcard expansion (*) on the value after substitution, it doesn't do brace expansion ({SRPMS,RPMS/*}), so it's actually looking for directory names with braces and commas in the name... and not finding any.

The best way to handle this is generally to store the file list as an array; if you do this right, everything gets expanded at declaration:

$ var2=(~/rpmbuild/{SRPMS,RPMS/*}/enki-*.rpm)
$ echo "${var2[@]}"   # This is the proper way to expand an array into a word list
/home/yajo/rpmbuild/RPMS/noarch/enki-12.10.3-1.fc18.noarch.rpm  etc...

Note that arrays are a bash extension, and will not work in plain POSIX shells. So be sure to start your script with #!/bin/bash, not #!/bin/sh.

like image 167
Gordon Davisson Avatar answered Oct 14 '22 10:10

Gordon Davisson