Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

In my Bash environment I use variables containing spaces, and I use these variables within command substitution. Unfortunately I cannot find the answer on SE.

What is the correct way to quote my variables? And how should I do it if these are nested?

DIRNAME=$(dirname "$FILE")

or do I quote outside the substitution?

DIRNAME="$(dirname $FILE)"

or both?

DIRNAME="$(dirname "$FILE")"

or do I use back-ticks?

DIRNAME=`dirname "$FILE"`

What is the right way to do this? And how can I easily check if the quotes are set right?

share|improve this question
    
See also - unix.stackexchange.com/questions/97560/… –  Graeme Mar 6 at 14:48
    
    
This is a good question, but given all the issues with embedded blanks, why would you make life hard on yourself by using them on purpose? –  Joe Mar 8 at 4:03
1  
@Joe, with embedded blanks you mean space in the filenames? Personally I do not use them that often, but I am working with other peoples directories and files of which I am not certain if they contain spaces. Furthermore, I think it is better to get it right at once so I do not have to worry in the future. –  CousinCocaine Mar 8 at 11:18
2  
Yes. What are we going to do with those "other people"? <G> –  Joe Mar 9 at 16:24

2 Answers 2

up vote 29 down vote accepted

In order from worst to best:

  • DIRNAME="$(dirname $FILE)" will not do what you want if $FILE contains whitespace or globbing characters \[?*.
  • DIRNAME=`dirname "$FILE"` is technically correct, but backticks are not recommended for command expansion because of the extra complexity when nesting them.
  • DIRNAME=$(dirname "$FILE") is correct, but only because this is an assignment. If you use the command substitution in any other context, such as export DIRNAME=$(dirname "$FILE") or du $(dirname "$FILE"), the lack of quotes will cause trouble if the result of the expansion contain whitespace or globbing characters.
  • DIRNAME="$(dirname "$FILE")" is the recommended way. You can replace DIRNAME= with a command and a space without changing anything else, and dirname receives the correct string.

To improve even further:

  • DIRNAME="$(dirname -- "$FILE")" works if $FILE starts with a dash.
  • DIRNAME="$(dirname -- "$FILE"; printf x)" && DIRNAME="${DIRNAME%?x}" works even if $FILE ends with a newline, since $() chops off newlines at the end of output and dirname outputs a newline after the result. Sheesh dirname, why you gotta be different?

You can nest command expansions as much as you like. With $() you always create a new quoting context, so you can do things like this:

foo "$(bar "$(baz "$(ban "bla")")")"

You do not want to try that with backticks.

share|improve this answer
2  
Clear answer to my question. When I nest these variables, can I just keep on quoting like I do now? –  CousinCocaine Mar 6 at 15:07

You can always show the effects of variable quoting with printf.

Word splitting done on var1:

$ var1="hello     world"
$ printf '[%s]\n' $var1
[hello]
[world]

var1 quoted, so no word splitting:

$ printf '[%s]\n' "$var1"
[hello     world]

Word splitting on var1 inside $(), equivalent to echo "hello" "world":

$ var2=$(echo $var1)
$ printf '[%s]\n' "$var2"
[hello world]

No word splitting on var1, no problem with not quoting the $():

$ var2=$(echo "$var1")
$ printf '[%s]\n' "$var2"
[hello     world]

Word splitting on var1 again:

$ var2="$(echo $var1)"
$ printf '[%s]\n' "$var2"
[hello world]

Quoting both, easiest way to be sure.

$ var2="$(echo "$var1")"
$ printf '[%s]\n' "$var2"
[hello     world]

Globbing problem

Not quoting a variable can also lead to glob expansion of its contents:

$ mkdir test; cd test; touch file1 file2
$ var="*"
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Note this happens after the variable is expanded only. It is not necessary to quote a glob during assignment:

$ var=*
$ printf '[%s]\n' $var
[file1]
[file2]
$ printf '[%s]\n' "$var"
[*]

Use set -f to disable this behaviour:

$ set -f
$ var=*
$ printf '[%s]\n' $var
[*]

And set +f to re-enable it:

$ set +f
$ printf '[%s]\n' $var
[file1]
[file2]
share|improve this answer
2  
People tend to forget that word splitting is not the only problem, you may want to change your example to have var1='hello * world' to illustrate the globbing problem as well. –  Stéphane Chazelas Mar 6 at 15:15

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.