0

tl;dir

In bash, brace expansion occurs before tilde expansion. However, tilde prefixes seem to expand prior to variable assignment and brace expansion doesn't. Why?

Details and examples

Brace expansion in bash is documented as occurring prior to tilde expansion:

Brace expansion is performed before any other expansions

As I understand it, tilde expansion occurs prior to variable assignment, demonstrated by the following example:

# UNQUOTED TILDE PREFIX, TILDE EXPANSION EXPECTED
tilde=~
echo "$tilde"
# /Users/DeNovo
[[ $tilde == "$HOME" ]] && echo "literal match"
# literal match

# QUOTED TILDE PREFIX, TILDE EXPANSION DOES NOT OCCUR
lit_tilde='~'
echo "$lit_tilde"
# ~
# UNQUOTED TILDE PREFIX PRODUCED AND EVALUATED, TILDE EXPANSION OCCURS
# AS EXPECTED
eval echo "$lit_tilde"
# /Users/DeNovo
[[ $lit_tilde == "~" ]] && echo "literal match"
# literal match

These examples would seem to suggest that the right operand of left=right undergoes tilde expansion before being bound as the value to the left operand.

Brace expansion, however, does NOT occur before variable assignment, with similar behavior to binding a literal tilde to a variable (vs. a special character/prefix that is expanded)

# UNQUOTED BRACE, BUT NO EXPANSION. BRACE LITERAL IS PRESERVED
brace={a..c}
echo "$brace"
# {a..c}
[[ $brace = "a b c" ]] || echo "doesn't match"
# doesn't match
[[ $brace = "{a..c}" ]] && echo "literal match"
# literal match

# SIMILAR TO THE LITERAL TILDE, EVALUATING THE VARIABLE VALUE 
# RESULTS IN BRACE EXPANSION
eval echo "$brace"
# a b c

Combining the two expansions into one example, you can see tilde expansion occurs and brace expansion does not:

combined=~/dir_{a..c}
echo "$combined"
# /Users/DeNovo/dir_{a..c}

And braces prevent internal tilde's from expanding during assignment

internal_tilde={~,~+}/new_dir
echo "$internal_tilde"
# {~,~+}/new_dir

# vs the same output, not in a variable
echo {~,~+}/new_dir
/Users/DeNovo/new_dir /Your/Working/Directory/new_dir

Just to clarify, I'm not asking how to get a variable to contain the result of brace expansion, but why, despite occurring before tilde expansion, brace expansion doesn't occur before variable assignment. There are several ways to get the desired behavior. Here is one:

# PROCESS SUBSTITUTION GIVES THE DESIRED BEHAVIOR
brace_exp="$(echo {a..c})"
echo "$brace_exp"
a b c
[[ $brace_exp = "a b c" ]] && echo "literal match"
# literal match

So again, why does tilde expansion occur before variable assignment, but brace expansion (which is supposed to occur before tilde expansion) doesn't occur before variable assignment. Presumably assignment is special in some way that I'm not aware of.

4

In your test, {a..c} isn’t expanded because it’s part of a variable assignment, and handled separately:

  1. The words that the parser has marked as variable assignments (those preceding the command name) and redirections are saved for later processing.
  2. The words that are not variable assignments or redirections are expanded (see Shell Expansions). If any words remain after expansion, the first word is taken to be the name of the command and the remaining words are the arguments.

Assigned values don’t undergo brace expansion:

  1. The text after the ‘=’ in each variable assignment undergoes tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal before being assigned to the variable.
  • @DeNovo Assignments are different, see man bash on assignments: "All values undergo tilde expansion, parameter and variable expansion, command substitution, arithmetic expansion, and quote removal". No brace expansion will be done during assignments. – nohillside Dec 4 '18 at 19:08
  • Thank you for pointing me to the relevant section of the documentation (+1). I should have been looking in the general information about command expansion, rather than specifically at tilde and brace expansion. I think I've come up with the "why" (beyond "that's how it's documented"). See my self answer – De Novo Dec 4 '18 at 23:40
0

@StephenKitt's answer points to the relevant documentation (+1), indicating that the behavior is expected (why do tilde prefixes expand prior to assignment but braces don't? Because that's how they are defined).

Re: A possible reason brace expansion is special, let's look at what doesn't happen on variable assignment:

  • brace expansion
  • word splitting (the parser splits the line, but doesn't split the result of expansion within that word)
  • filename expansion

These are exactly the operations that can change the number of words of the expansion:

Shell Expansion:

Only brace expansion, word splitting, and filename expansion can change the number of words of the expansion

In the example cases, the variable is a string rather than an array, and should contain only one word.

Brace expansion does occur when assigning an array, as in brace=({a..c}). To use the more complicated example:

internal_tilde=({~,~+}/new_dir)
echo "${internal_tilde[@]}"
# /Users/DeNovo/new_dir /Your/Working/Directory/new_dir

So, I would propose this design makes sense (rather than being a historical oddity). Tilde expansion doesn't change the number of words. Brace expansion does. If you want your variable to contain more than one word, use a data structure that is designed for that.

  • As an additional twist, brace expansion is applied after a redirection operator. Bash reports an error if any expansion results in more than one word after a redirection operator. If changing the number of words was a design consideration, it wasn’t applied consistently :-(. – Stephen Kitt Dec 5 '18 at 7:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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