7

I've learned that filename expansions are done prior to command executions when running commands in Bash. But when trying the commands below (with -x option):

touch foo=3    # Create a file with name "foo=3"
+ touch foo=3
declare foo=?
+ declare 'foo=?'
alias foo=*
+ alias 'foo=*'

I don't get what I expect because foo=? and foo=* aren't expanded to the filename "foo=3":

declare -p | grep 'foo='    # => foo='?'
alias | grep 'foo='         # => alias foo='*'

But if I run another built-in like cd or a function accepting an assignment as a parameter written by myself like show_rhs() { echo "${1%=*}='${1#*=}'"; } I gets what I expect (foo=? and foo=* are expanded).

cd foo=?            # => foo=3: Not a directory
show_rhs() foo=*    # => foo='3'

The only difference I can see here is declare and alias are built-ins AND accept an assignment as a parameter. It seems an pair of quotations is added to enclose the assignment before filename expansions according to the output of -x option.

But if the filename expansion does run before the command execution regardless of what the command is, the argument passed into declare and alias should be foo=3 rather than foo=? and foo=* due to the presence of the file "foo=3".

So does Bash do something special (maybe quoting wildcards?) to "a=b"-like arguments depending on commands before filename expansions?

(My environment: CentOS 5.8 64bit, GNU Bash 3.2.25)

ebk
  • 586
  • 5
  • 11
  • Aliases and variables live in different namespaces. You can absolutely have an alias and a variable with the same name. A variable created with a plain assignment and one created with `declare` are the same thing though so `foo=3; declare foo=?` leaves you with a variable `foo` with the value `?`. Are you sure you get the errors you claim for those last two lines? Because I don't see how that's possible. Neither of those contexts will evaluate variables at all (and the second isn't even a valid shell line and should give you an error about an unexpected token). – Etan Reisner Jul 14 '15 at 11:38
  • Sorry for the confusion. I didn't mean if it was possible to create a variable and alias with the same name. I mean both foo=? and foo=* should be expanded to foo=3 due to the presence of the file "foo=3" before being passed into those commands, so both the variable and alias are supposed to have a value of "3" rather than the orginal wildcard. The code of declare and alias are just examples. – ebk Jul 14 '15 at 11:51
  • Ah, ok. I almost commented about a `foo=3` file but thought that was unlikely (and you didn't mention that in the post). But yes, I think your guess is likely correct. The fact that those built-ins accept assignments directly means the otherwise normal glob expansion behavior doesn't happen there. (It doesn't happen for `foo=?` either.) There's probably something in the POSIX spec about this. I can try to look later. – Etan Reisner Jul 14 '15 at 12:21
  • I've put a line of "> foo=3" in the first line of the first code block :) But I think I should change it to "touch foo=3" with some explanatory comments to make it clear. Thank you. – ebk Jul 14 '15 at 13:10

1 Answers1

2

Bash parses some of its built-in commands in a way that is not strictly Posix compliant, and also not very well documented.

In particular, assignment arguments in commands which accept such arguments (alias, declare, export, local, readonly and typeset) are not subject to pathname expansion nor word splitting. (This is done internally by suppressing the expansions, not by quoting the metacharacters, although it's not easy to see how the implementation detail might become visible.)

This happens even if bash is started in Posix mode or as sh.

Note that the suppression of pathname expansion only applies to arguments which look like assignments. Extending the example from the question:

touch foo=3    # Create a file with name "foo=3"
+ touch foo=3
declare foo=?
+ declare 'foo=?'

bar="foo=?"    # Put the declare argument in a variable
+ bar='foo=?'
declare $bar
+ declare foo=3

As expected, dash pathname expands and word-splits arguments to alias and export, consistent with the Posix spec. So, apparently, does zsh.

Except in Posix mode, bash also tilde-expands the right-hand side of arguments which look like assignments. In Posix mode, it restricts this to assignment arguments of the builtins listed above, although Posix specifies tilde-expansion after = only in variable assignments before the command word. That's what dash does, but zsh extends this to "commands of the typeset family" (documented in the zsh manual).

rici
  • 234,347
  • 28
  • 237
  • 341
  • @ebk: the page you cite clearly says that variable assignments "come before the command name", which is also what i was talking about. (This includes assignments where there is no command word.) – rici Jul 18 '15 at 04:01
  • Sorry, I accidently deleted my previous comment since I haven't gotten used to stackoverflow yet. Just post [the page link](http://www.gnu.org/software/bash/manual/html_node/Simple-Command-Expansion.html) again as reference for others. I just can't picture how to put a variable assignment before a command name in a simple command except assignments without a command word. Could you give me an example? – ebk Jul 18 '15 at 04:25
  • 2
    @ebk: If you put an assignment before the command word, the assignment only applies to the environment passed to the command. The most common example is probably `IFS=, read -r a b c`, in which the assignment to `IFS` is not visible in the script, but is part of the environment used by the `read` command. There's a longer explanation with examples in the Apple shell scripting primer, https://developer.apple.com/library/mac/documentation/OpenSource/Conceptual/ShellScripting/shell_scripts/shell_scripts.html#//apple_ref/doc/uid/TP40004268-CH237-SW12 – rici Jul 18 '15 at 17:29
  • Thank you for your example. I didn't know putting assignments before an command like that was legal in Bash. I'll do some research on it. – ebk Jul 20 '15 at 15:39