Bash – shopt -s extglob not working as expected

bashlswildcards

I am trying to recursively change file permissions in a wordpress installation affecting everything below the public_html document root folder, excluding certain folders.

I have set extglob on with shopt -s extglob

When I run the following command it fails to exclude the folder I want it to exclude
ls -R -alh ./public_html/!(*uploads*) | grep uploads

The above lists numerous files in the uploads folder — it should not list any.

Of course the command above is only a test; I'll be using chmod in the actual script.

I have tried numerous different combinations and all have failed; for example, the following:

ls -R -alh ./public_html/!(wp-content/uploads*)

ls: cannot access ./public_html/!(wp-content/uploads*): No such file or directory

This is my first attempt to use exclusions with shell commands and I might be making a simple mistake.

Any ideas what I am doing wrong?

Please note: the purpose of the question is to better understand extglob rather than find alternatives. I can script alternatives but as I have never used extglob before I am having trouble understanding the syntax.

Best Answer

First, the extglob controls what ls sees on its command line. It does not control what ls does with what it sees on the command line. This is important because the -R option to ls tells ls to explore recursively any directories it sees on the command line. So, even if the *uploads* directories are not given explicitly on the command line, ls will find them when it explores their parent directories.

Second, as you know, don't parse ls. The output of ls is not meant for use in pipelines or scripts. Trying to use it that way eventually leads to unhappiness.

Third, to get the files that you want, try:

find ./public_html ! -path '*uploads*'

To explain:

  • The ./public_html tells find to start looking in the ./public_html directory.

  • By itself, the option -path '*uploads*' matches on any path that contains the pattern *uploads*. (-path is similar to find's -name option but path includes the directory names.) The preceding !, however, indicates negation. So, the option ! -path '*uploads*' excludes any path matching *uploads*.

To get ls style output while still using the features of find, consider:

find ./public_html ! -path '*uploads*' -exec ls -dalh {} +
Related Question