Home » The result of ls * , ls ** and ls ***

The result of ls * , ls ** and ls ***


ls lists the files and content of directories it is being passed as arguments, and if no argument is given, it lists the current directory. It can also be passed a number of options that affect its behaviour (see man ls for details).

If ls is being passed an argument called *, it will look for a file or directory called * in the current directory and list it just like any other. ls doesn’t treat the * character in any other way than any other one.

However if ls * is a shell command line, that is code in the language of a Unix shell, then the shell will expand that * according to its globbing (also referred to as Filename Generation or Filename/Pathname Expansion) rules.

While different shells support different globbing operators, most of them agree on the simplest one *. * as a pattern means any number of characters, so * as a glob will expand to the list of files in the current directories that match that pattern. There’s an exception however that a leading dot (.) character in a file name has to be matched explicitly, so * actually expands to the list of files and directories not starting with . (in lexical order).

For instance, if the current directory contains the files called ., .., .foo, -l and foo bar, * will be expanded by the shell to two arguments to pass to ls: -l and foo bar, so it will be as if you had typed:

ls -l "foo bar"


'ls' "-l" foo bar

Which are three ways to run exactly the same command. In all 3 cases, the ls command (which will probably be executed from /bin/ls from a lookup of directories mentioned in $PATH) will be passed those 3 arguments: “ls”, “-l” and “foo bar”.

Incidentally, in this case, ls will treat the first (strictly speaking second) one as an option.

Now, as I said, different shells have different globbing operators. A few decades ago, zsh introduced the **/ operator¹ which means to match any level of subdirectories, short for (*/)# and ***/ which is the same except that it follows symlinks while descending the directories.

A few years ago (July 2003, ksh93o+), ksh93 decided to copy that behaviour but decided to make it optional, and only covered the ** case (not ***). Also, while ** alone was not special in zsh² (just meant the same as * like in other traditional shells since ** means any number of character followed by any number of characters), in ksh93, ** meant the same as **/* (so any file or directory below the current one (excluding hidden files)³.

bash copied ksh93 a few years later (February 2009, bash 4.0), with the same syntax but an unfortunate difference: bash’s ** was like zsh‘s ***, that is it was following symlinks when recursing into sub-directories which is generally not what you want it do and can have nasty side effects. It was partly fixed in bash-4.3 in that symlinks were still followed, but recursion stopped there. It was fully fixed in 5.0.

yash added ** in version 2.0 in 2008, enabled with the extended-glob option. Its implementation is closer to zsh‘s in that ** alone is not special. In version 2.15 (2009), it added *** like in zsh and two of its own extensions: .** and .*** to include hidden dirs when recursing (in zsh, the D glob qualifier (as in **/*(D)) will consider hidden files and directories, but if you only want to traverse hidden dirs but not expand hidden files, you need ((*|.*)/)#* or **/[^.]*(D)).

The fish shell also supports **. Like earlier version of bash, it follows symlinks when descending the directory tree. In that shell however **/* is not the same as **. ** is more an extension of * that can span several directories. In fish, **/*.c will match a/b/c.c but not a.c, while a**.c will match a.c and ab/c/d.c and zsh‘s **/.* for instance has to be written .* **/.*. There, *** is understood as ** followed by * so the same as **.

tcsh also added a globstar option in V6.17.01 (May 2010) and supports both ** and *** à la zsh.

So in tcsh, bash and ksh93, (when the corresponding option is enabled (globstar)) or fish, ** expands all the files and directories below the current one, and *** is the same as ** for fish, a symlink traversing ** for tcsh with globstar, and the same as * in bash and ksh93 (though it’s not impossible that future versions of those shells will also traverse symlinks).

Above, you’ll have noticed the need to make sure none of the expansions is interpreted as an options. For that, you’d do:

ls -- *


ls ./*

There are some commands (it doesn’t matter for ls) where the second is preferable since even with the -- some filenames may be treated specially. It’s the case of - for most text utilities, cd and pushd and filenames that contain the = character for awk for instance. Prepending ./ to all the arguments removes their special meaning (at least for the cases mentioned above).

It should also be noted that most shells have a number of options that affect the globbing behaviour (like whether dot files are ignored or not, the sorting order, what to do if there’s no match…), see also the $FIGNORE parameter in ksh

Also, in every shell but csh, tcsh, fish and zsh, if the globbing pattern doesn’t match any file, the pattern is passed as an unexpanded argument which causes confusion and possibly bugs. For instance, if there’s no non-hidden file in the current directory

ls *

Will actually call ls with the two arguments ls and *. And as there’s no file at all, so none called * either, you’ll see an error message from ls (not the shell) like: ls: cannot access *: No such file or directory, which has been known to make people think that it was ls that was actually expanding the globs.

The problem is even worse in cases like:

rm -- *.[ab]

If there’s no *.a nor *.b file in the current directory, then you might end up deleting a file called *.[ab] by mistake (csh, tcsh, and zsh would report a no match error and wouldn’t call rm (and fish doesn’t support the [...] wildcards)).

If you do want to pass a literal * to ls, you have to quote that * character in some way as in ls * or ls '*' or ls "*". In POSIX-like shells, globbing can be disabled altogether using set -o noglob or set -f (the latter not working in zsh unless in sh/ksh emulation).

¹ While (*/)# was always supported, it was first short-handed as ..../ in zsh-2.0 (and potentially before), then ****/ in 2.1 before getting its definitive form **/ in 2.2 (early 1992)

² The globstarshort option, has since be added (in 2015) to allow ** and *** being used instead of **/* and ***/* respectively

³ See also these few more oddities with the ksh93 globstar design, some of which were copied by bash..

The command ls defaults to ls .: List all entries in the current directory.

The command ls * means ‘run ls on the expansion of the * shell pattern’

The * pattern is processed by the shell, and expands to all entries in the current directory, except those that start with a .. It will go one level deep.

The interpretation of double or triple * patterns depend on the actual shell used.

* is a wildcard that matches 0 or more characters. Some modern shells will recurse into subdirectories on seeing the ** pattern.

You can demystify the whole process by typing echo instead of ls first, to see what the command expands to:

$ echo *
Applications Downloads Documents tmp.html

So in this case, ls * expands to ls Applications Downloads Documents tmp.html

$ echo **
Applications Downloads Documents tmp.html

$ echo ***
Applications Downloads Documents tmp.html

So no change. This assumes you’re using bash as your shell — most people are, and different shells have different behavior. If you’re using ash or csh or ksh or zsh, you may expect things to work differently. That’s the point of having different shells.

So lets try something different (still with bash) so we get an idea of the the globbing (*) operator can do for us. For example, we can filter by part of the name:

$ echo D*
Downloads Documents

And interestingly, a trailing slash is an implicitly part of any directory name. So */ will yield only the directories (and symbolic links to directories):

$ echo */
Applications/ Downloads/ Documents/

And we can do some filtering at multiple levels by putting slashes in the middle:

$ echo D*/*/
Documents/Work/ /Documents/unfinished/

Since the Downloads directory doesn’t contain any subdirectories, it does not end up in the output. This is very useful for just examining the files you want. I use commands like this all the time:

$ ls -l /home/*/public_html/wp-config.php

This lists, if there are any, all the wp-config.php files that exist at the base level of any user’s public_html directory. Or perhaps to be more complete:

$ find /home/*/public_html/ -name wp-config.php

This will find any wp-config.php files in any user’s public_html directories or any of their subdirectories, but it will operate more efficiently than just find /home/ -name wp-config.php because it won’t examine anything but the public_html directories for each of the users.

Related Solutions

Joining bash arguments into single string with spaces

[*] I believe that this does what you want. It will put all the arguments in one string, separated by spaces, with single quotes around all: str="'$*'" $* produces all the scripts arguments separated by the first character of $IFS which, by default, is a space....

AddTransient, AddScoped and AddSingleton Services Differences

TL;DR Transient objects are always different; a new instance is provided to every controller and every service. Scoped objects are the same within a request, but different across different requests. Singleton objects are the same for every object and every...

How to download package not install it with apt-get command?

Use --download-only: sudo apt-get install --download-only pppoe This will download pppoe and any dependencies you need, and place them in /var/cache/apt/archives. That way a subsequent apt-get install pppoe will be able to complete without any extra downloads....

What defines the maximum size for a command single argument?

Answers Definitely not a bug. The parameter which defines the maximum size for one argument is MAX_ARG_STRLEN. There is no documentation for this parameter other than the comments in binfmts.h: /* * These are the maximum length and maximum number of strings...

Bulk rename, change prefix

I'd say the simplest it to just use the rename command which is common on many Linux distributions. There are two common versions of this command so check its man page to find which one you have: ## rename from Perl (common in Debian systems -- Ubuntu, Mint,...

Output from ls has newlines but displays on a single line. Why?

When you pipe the output, ls acts differently. This fact is hidden away in the info documentation: If standard output is a terminal, the output is in columns (sorted vertically) and control characters are output as question marks; otherwise, the output is...

mv: Move file only if destination does not exist

mv -vn file1 file2. This command will do what you want. You can skip -v if you want. -v makes it verbose - mv will tell you that it moved file if it moves it(useful, since there is possibility that file will not be moved) -n moves only if file2 does not exist....

Is it possible to store and query JSON in SQLite?

SQLite 3.9 introduced a new extension (JSON1) that allows you to easily work with JSON data . Also, it introduced support for indexes on expressions, which (in my understanding) should allow you to define indexes on your JSON data as well. PostgreSQL has some...

Combining tail && journalctl

You could use: journalctl -u service-name -f -f, --follow Show only the most recent journal entries, and continuously print new entries as they are appended to the journal. Here I've added "service-name" to distinguish this answer from others; you substitute...

how can shellshock be exploited over SSH?

One example where this can be exploited is on servers with an authorized_keys forced command. When adding an entry to ~/.ssh/authorized_keys, you can prefix the line with command="foo" to force foo to be run any time that ssh public key is used. With this...

Why doesn’t the tilde (~) expand inside double quotes?

The reason, because inside double quotes, tilde ~ has no special meaning, it's treated as literal. POSIX defines Double-Quotes as: Enclosing characters in double-quotes ( "" ) shall preserve the literal value of all characters within the double-quotes, with the...

What is GNU Info for?

GNU Info was designed to offer documentation that was comprehensive, hyperlinked, and possible to output to multiple formats. Man pages were available, and they were great at providing printed output. However, they were designed such that each man page had a...

Set systemd service to execute after fstab mount

a CIFS network location is mounted via /etc/fstab to /mnt/ on boot-up. No, it is not. Get this right, and the rest falls into place naturally. The mount is handled by a (generated) systemd mount unit that will be named something like mnt-wibble.mount. You can...

Merge two video clips into one, placing them next to each other

To be honest, using the accepted answer resulted in a lot of dropped frames for me. However, using the hstack filter_complex produced perfectly fluid output: ffmpeg -i left.mp4 -i right.mp4 -filter_complex hstack output.mp4 ffmpeg -i input1.mp4 -i input2.mp4...

How portable are /dev/stdin, /dev/stdout and /dev/stderr?

It's been available on Linux back into its prehistory. It is not POSIX, although many actual shells (including AT&T ksh and bash) will simulate it if it's not present in the OS; note that this simulation only works at the shell level (i.e. redirection or...

How can I increase the number of inodes in an ext4 filesystem?

It seems that you have a lot more files than normal expectation. I don't know whether there is a solution to change the inode table size dynamically. I'm afraid that you need to back-up your data, and create new filesystem, and restore your data. To create new...

Why doesn’t cp have a progress bar like wget?

The tradition in unix tools is to display messages only if something goes wrong. I think this is both for design and practical reasons. The design is intended to make it obvious when something goes wrong: you get an error message, and it's not drowned in...