Bash – extglob behaves different in bash of Fedora and Debian

bashwildcards

Fedora 31 and Debian 10 behaves different when I run the following command:

/bin/ls /bin/!(znew) | /bin/grep znew

Fedora it outputs nothing, but Debian it does:

znew

znew exists on Fedora, if I run /bin/ls /bin/znew | /bin/grep znew I get:

/bin/znew

(On Debian it happens the same thing)

I've checked bash configuration on both distros, the only difference I've found is that on Fedora I have:

cdable_vars     on
cdspell         on
dirspell        on
login_shell     on

On Debian those options are disabled, and extglob is on on both distros

Note: Command grep it's only used here to make the listing shorter

Versions:

  • On Fedora: 5.0.11

  • On Debian: 5.0-4

Why does it exist such a difference?

UPDATE 1

/bin is a symlink to /usr/bin on both Debian and Fedora.

This is the output of searching for znew string in filenames:

updatedb && locate  znew
/usr/bin/znew
/usr/share/man/man1/znew.1.gz
/var/lib/flatpak/runtime/org.freedesktop.Platform/x86_64/19.08/5a35247ad1c941455f2f9c4139d9136c6c0662e1b04e5b3c56121e7f67ba0100/files/bin/znew
/var/lib/lxc/centos/rootfs/usr/bin/znew
/var/lib/lxc/centos/rootfs/usr/share/man/man1/znew.1.gz
/var/lib/lxc/opensuse/rootfs/usr/bin/xznew
/var/lib/lxc/opensuse/rootfs/usr/bin/znew
/var/lib/lxc/opensuse/rootfs/usr/share/man/man1/xznew.1.gz
/var/lib/lxc/opensuse/rootfs/usr/share/man/man1/znew.1.gz

UPDATE 2

I've find something interesting, there was in Debian a symlink of /bin/X11 pointing to . after removing it behaves as in Fedora.

Best Answer

Given that the extglob shell option is set in your interactive bash session, the command

ls /bin/!(znew) | grep znew

would first run ls with all the names in /bin that is not znew as arguments. If this list of names includes names of subdirectories, ls would output the contents of those subdirectories (since -d was not used with ls). If one of these subdirectories contain the name znew, then the grep would match and output that name.

The contents of the unknown subdirectory of /bin would be listed by ls without prepending the names in it with any directory path. Hence, if a subdirectory contains the znew name, it would be outputted as znew rather than as /bin/some-dir/znew.

When ls is used as in

ls /bin/znew | grep znew

it would output the string /bin/znew rather than znew (if that pathname exists). It does this because it is outputting the specific pathname of a file given as a command line argument, not the contents of a directory given as an argument.

A situation like this could possibly have occurred if someone tried to create a symbolic link for /usr/bin called /bin while /bin was already a symbolic link to /usr/bin, or while /bin was still a directory.

In an update to the question, it is revealed that /bin/X11 was a symbolic link to . (the current directory). This means that znew would have been accessible through the pathname /bin/X11/znew.

So, in conclusion, what is happening is that the /bin/!(znew) globbing pattern expanded to a list of pathnames, one of these being /bin/X11 (but not /bin/znew). The ls utility would then get all pathnames, including /bin/X11 as arguments. When ls gets to list /bin/X11, the contents of the /bin directory is listed, due to /bin/X11 pointing there. The grep utility then picks out znew, which will part of the output of ls.


As Stéphane Chazelas points out in comments, you would get the same effect if there is a file in /bin on your Debian system that has a name containing a newline followed by the string znew (possibly followed by another newline and maybe further characters).

Since the name is not exactly the string znew, it would match the pattern /bin/!(znew) and ls would output this single name as

/bin/something
znew
maybe more

and grep would extract znew from that output.

Related Question