52
  • when specifying ls --directory a* it should list only directories starting with a*
  • BUT it lists files AND directories starting with a

Questions:

  • where might I find some documentation on this, other than man and info where I think I thoroughly looked?
  • does this work in BASH only?
2

7 Answers 7

93

The a* and *a* syntax is implemented by the shell, not by the ls command.

When you type

ls a*

at your shell prompt, the shell expands a* to a list of existing all files in the current directory whose names start with a. For example, it might expand a* to the sequence a1 a2 a3, and pass those as arguments to ls. The ls command itself never sees the * character; it only sees the three arguments a1, a2, and a3.

For purposes of wildcard expansion, "files" refers to all entities in the current directory. For example, a1 might be a normal file, a2 might be a directory, and a3 might be a symlink. They all have directory entries, and the shell's wildcard expansion doesn't care what kind of entity those entries refer to.

Practically all shells you're likely to run across (bash, sh, ksh, zsh, csh, tcsh, ...) implement wildcards. The details may vary, but the basic syntax of * matching zero or more characters and ? matching any single character is reasonably consistent.

For bash in particular, this is documented in the "Filename expansion" section of the bash manual; run info bash and search for "Filename expansion", or see here.

The fact that this is done by the shell, and not by individual commands, has some interesting (and sometimes surprising) consequences. The best thing about it is that wildcard handling is consistent for (very nearly) all commands; if the shell didn't do this, inevitably some commands wouldn't bother, and others would do it in subtly different ways that the author thought was "better". (I think the Windows command shell has this problem, but I'm not familiar enough with it to comment further.)

On the other hand, it's difficult to write a command to rename multiple files. If you write:

mv *.log *.log.bak

it will probably fail, since*.log.bak is expanded based on the files that already exist in the current directory. There are commands that do this kind of thing, but they have to use their own syntax to specify how the files are to be renamed. Some commands (such as find) can do their own wildcard expansion; you have to quote the arguments to suppress the shell's expansion:

find . -name '*.txt' -print

The shell's wildcard expansion is based entirely on the syntax of the command-line argument and the set of existing files. It can't be affected by the meaning of the command. For example, if you want to move all .log files up to the parent directory, you can type:

mv *.log ..

If you forget the .. :

mv *.log

and there happen to be exactly two .log files in the current directory, it will expand to:

mv one.log two.log

which will rename one.log and clobber two.log.

EDIT: And after 52 upvotes, an accept, and a Guru badge, maybe I should actually answer the question in the title.

The -d or --directory option to ls doesn't tell it to list only directories. It tells it to list directories just as themselves, not their contents. If you give a directory name as an argument to ls, by default it will list the contents of the directory, since that's usually what you're interested in. The -d option tells it to list just the directory itself. This can be particularly useful when combined with wildcards. If you type:

ls -l a*

ls will give you a long listing of each file whose name starts with a, and of the contents of each directory whose name starts with a. If you just want a list of the files and directories, one line for each, you can use:

ls -ld a*

which is equivalent to:

ls -l -d a*

Remember again that the ls command never sees the * character.

As for where this is documented, man ls will show you the documentation for the ls command on just about any Unix-like system. On most Linux-based systems, the ls command is part of the GNU coreutils package; if you have the info command, either info ls or info coreutils ls should give you more definitive and comprehensive documentation. Other systems, such as MacOS, may use different versions of the ls command, and may not have the info command; for those systems, use man ls. And ls --help will show a relatively short usage message (117 lines on my system) if you're using the GNU coreutils implementation.

And yes, even experts need to consult the documentation now and then. See also this classic joke.

12
  • 8
    @Thomas: a* expands to a list of all files in the current directory whose names start with a. Commented May 8, 2013 at 1:58
  • 2
    @Thomas: Or in whatever directory you specify:ls subdir/a* Commented May 8, 2013 at 6:26
  • 1
    To really see the command executed after the shell expansion, echo it. So if you want to know what is happening when you do ls a*, first execute echo ls a* Commented May 8, 2013 at 10:04
  • 1
    or just echo a* ... the shell does glob expansion anyway
    – Useless
    Commented May 8, 2013 at 16:47
  • 2
    @sendmoreinfo: That depends on the shell and the settings. In csh and tcsh, a failing glob expansion is an error. In bash, shopt -s failglob causes the same behavior. Setting nullglob but not failglob causes, for example, *nosuchfile* to expand to an empty string. Commented May 8, 2013 at 18:34
27

See Keith Thompson’s answer; but to explain why ls --directory a* shows files and directories: The --directory option does not suppress non-directory files. Instead, it lists the directories as such, while it would otherwise list their content. Example:

$ mkdir foo
$ touch foo/bar
$ ls foo
bar
$ ls --directory foo
foo
1
  • 3
    that example is more compelling with the -F option Commented May 8, 2013 at 2:18
6

To be very explicit, it is documented in the ls(1) manual page:

-d, --directory list directory entries instead of contents, and do not dereference symbolic links

to be fair, "entries instead of contents" could probably be more explanatory:

If FILE is a directory, show the directory entry itself instead of listing the contents of that directory. If FILE is a symbolic link, show the link entry itself instead of the file pointed to by the link.

2
  • To be pedantic, while the -d option may be explained (if tersely) in the man page, argument expansion and wildcarding is not documented in the ls man page since it's done by the shell. If you turned off filename expansion in your shell, "ls a*" would only show you a file that's literally named 'a*'.
    – Johnny
    Commented May 8, 2013 at 6:10
  • To be pedantic, while shell expansion was answered by other respondents, none of them addresses the scant explanation and how it might be misread. If you should like me to repeat that which was already well said, please be more direct.
    – msw
    Commented May 8, 2013 at 9:45
4

Globbing

As has been explained, the expansion of * (and similar expansions) is called globbing in Unix jargon, and is usually a feature of the command processor (known as the "shell" in Unix parlance). So globbing can be used in many other places too; type man 7 glob at the shell of a classic Linux distribution (or see this) for more about globbing.

In early Unix, glob was actually implemented by a separate program called /etc/glob (see page 10 of this old UNIX manual for documentation for that). Nowadays, it is a code routine supplied by code libraries, and is commonly used by shells. Source: Wikipedia.

Directories

As to why ls -d lists files as well as directories...

The ls man page explains why this happens, but with a very terse explanation. So here is an attempt at an expanded explanation:

By default ls lists the contents of directories when given their names, and will do the same thing for symlinks to directories. The -d option means, "when given name(s) of directory(ies)" (which can also be given implicitly by globbing), only show the _name_s of the directory(ies), but not their contents. Similarly, when provided with (explicitly or implicitly) name(s) of symlinks to directories, show the file name of the symlink, not the contents of the directory which it references.

The -d option has nothing to do, as far as I can tell, with which items are listed; this can be done (source: here) with find, like so: find . -maxdepth 1 -type d. I am not sure if there is a good way to do so only with GNU ls. Here are some examples of how to use the find command.

So to sum up: By default ls shows the contents of directories when given their path name. -d changes this behavior, only showing the directory name itself, as would be done for a standard file.

3

Documentation doesn't say it lists only directory entries bur rather when ls receives directory name it lists dir entry instead of it's content. Best way to understand is by example:

> {ice} ~ :10:47 % ls -l / 
total 97
drwxr-xr-x   2 root root  4096 May  3 00:27 bin
drwxr-xr-x   4 root root  1024 May  3 14:17 boot
drwxr-xr-x   2 root root  4096 Apr 29 13:44 cdrom
drwxr-xr-x  18 root root  4420 May  9 09:58 dev
...
lrwxrwxrwx   1 root root    33 May  3 14:16 vmlinuz -> boot/vmlinuz-3.9.0-030900-generic
lrwxrwxrwx   1 root root    29 May  3 11:07 vmlinuz.old -> boot/vmlinuz-3.8.0-19-generic
> {ice} ~ :10:47 % ls -ld /
drwxr-xr-x 25 root root 4096 May  3 14:16 /
2

The documentation is part of the shell. In particular the shell performs various expansions and substitutions in a particular order before executing a command.

The sequence of word expansions for a Bourne compatible shell is

  1. Tilde Expansion
  2. Parameter Expansion
  3. Command Substitution
  4. Arithmetic Expansion
  5. Field Splitting
  6. Pathname Expansion
  7. Quote Removal

So, the ls command doesn't even see the * characters, the arguments are already expanded with all files matching the a* glob pattern. This is unlike on Windows, where each command needing file name globbing has to implement this feature itself.

1

The key word you need (man bash) is "Pathname Expansion".

2
  • 3
    more like "Pathname Expansion" or "Filename Generation". Related to "pattern matching", but not the same. Commented May 7, 2013 at 23:18
  • @StephaneChazelas Indeed, but "pattern matching" is a subsection of "pathname expansion" in the man page whereas "Filename Generation" doesn't occur there at all (just in the info document). Commented May 8, 2013 at 0:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .