This blog has been archived.
Visit my home page at zhimingwang.org.

Bash: the special slash character in filename expansion

It is well-known and common sense that the slash character (/) serves a special role in Bash filename expansion. For instance, the asterisk * certainly won't match / or . when used in filename expansion; otherwise, a standalone * would match everything in the filesystem.

However, it is less clear how a literal slash character1 is treated in extended glob patterns. Naively one would expect it to just match a literal slash, but the real situtation is more complicated than that. Consider the following examples:

bash-4.3$ shopt -s extglob nullglob
bash-4.3$ echo /usr/@(bin|lib)
/usr/bin /usr/lib
bash-4.3$ echo /[email protected](/bin|/lib)

bash-4.3$ [[ /usr/bin == /[email protected](/bin|/lib) ]] && echo matching
matching

As seen from this example, patterns with slash simply doesn't work (in filename expansion) when placed in an extended glob pattern list, and there's no error whatsoever. I looked up the Bash Reference Manual and the Bash Guide but neither mentioned this behavior. One might need to delve into the source code to say for sure what exactly is going on.

In comparison, Zsh and its docs are much more up front about this issue:

Note that grouping cannot extend over multiple directories: it is an error to have a ‘/’ within a group (this only applies for patterns used in filename generation). ...

And when we run equivalent code in Zsh:

zsh-5.0.5$ setopt NULL_GLOB
zsh-5.0.5$ echo /usr/(bin|lib)
/usr/bin /usr/lib
zsh-5.0.5$ echo /usr(/bin|/lib)
zsh: bad pattern: /usr(/bin|/lib)
zsh-5.0.5$ [[ /usr/bin == /usr(/bin|/lib) ]] && echo matching
matching

The lesson? Be careful not to use a pattern like @(path1|path2|path3) in Bash when the paths are absolute, or relative but contain the slash. Unlike Zsh, Bash just silently fails on a pattern like this, which is rather dangerous in scripts.


  1. Here, "a literal slash character" also applies to one that comes from tilde expansion, parameter expansion or command substitution, since they are performed before filename expansion in Bash.↩ī¸Ž