Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filename expansion in "a=b"-like arguments of Bash built-in commands

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)

like image 693
ebk Avatar asked Jul 14 '15 11:07

ebk


People also ask

What is the file extension for bash files?

Normally, a Bash script file has . sh extension to make it clear that it is a shell script file.

How do I get filename in bash?

Using Bash, there's also ${file%. *} to get the filename without the extension and ${file##*.} to get the extension alone. That is, file="thisfile.

How do I remove a filename extension in bash?

`sed` command is used in this example to remove any type of extension from the filename. If you run the script, the output will be 'average' after removing the extension 'py'.

What does [[ ]] mean in bash?

The [[ ... ]] part allows to test a condition using operators. Think of it as an if statement. In your example, you're using the -s operator, which tests that the referenced file is not empty. Copy link CC BY-SA 3.0.


1 Answers

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).

like image 119
rici Avatar answered Sep 24 '22 21:09

rici