I was playing around in my terminal earlier and discovered that I can execute single word commands (‘ls’,’cat’,’python2.7′,’exit’) by making a file or directory named the same thing as the command. However, I can’t execute ‘multi-word’ commands (‘rm -rf *’,’ls -a’, ‘python2.7 test.py’) (which also led me to discover that you can’t remove directories named ‘-rf’ with ‘rm -rf *’) UNLESS the arguments are in alphabetic order. Seemingly, when you just pass in * to bash it reads the names of the files/directories in alphabetical order, passing each successive name in as an argument to the previous command. Example:
$ mkdir cat $ touch dog $ vim dog # at this point I put 'Hello!' into dog $ * Hello!
Why does the wildcard character not allow me to execute commands of files with spaces in them but does allow me to execute commands of multiple file names? Thanks!
EDIT: Also aliases don’t work for some reason. Example:
$ alias lsa='ls -a' $ mkdir lsa $ * No command 'lsa' found, did you mean: ... lsa: command not found
Anyone know why this is?
Advertisement
Answer
Every command bash processes is subject to several varieties of expansions before bash interprets and acts on the result. The last of those is pathname expansion, wherein bash examines the words of the command (as produced by previous expansions and word splitting) for any that contain any of the unquoted special characters *
, ?
, and [
, and interprets those words as patterns representing file names. After all expansions are performed, any redirections and variable assignments in the expanded command are performed and removed from the command. If any words remain, the first is taken as the name of a command to run. The provenance of that word does not matter.
In pathname expansion, the *
matches any string, including the empty string. When bash performs pathname expansion on the word consisting of only *
, it expands to the names of all the files in the working directory, except those beginning with a dot (.
).
Thus, if your working directory contains just a single file named ls
, and you execute the command *
, bash expands that command to ls
and executes it, producing the output ls
. Similarly, if the contents of the directory are echo
, Hello,
, and World!
, then when you execute the command *
bash expands it to the equivalent of echo Hello, 'World!'
, and the command outputs Hello, World!
.
That doesn’t work as you thought it would for filenames with spaces in them because each file name matching a pattern is expanded to a single word. These are not subsequently split. (Bash does perform “word splitting” as part of the expansion process, but that happens before pathname expansion.) This is normally what you want, for otherwise commands such as rm *
would not reliably do what you expect.
That doesn’t work with aliases because bash
expands aliases when it reads commands, not when it executes them. That has several implications, but one of them is that aliases are interpreted before any expansions are performed. Another is that the replacement text of an alias is subject to all the normal expansions.