Search code examples
shellunixcommand-linezshls

Why does ls not subset by filetype recursively (with -R)?


I want to list all .jpg files in all subdirectories using ls.

For the same directory this works fine:

ls *.jpg

However, when using the -R for recursiveness:

ls -R *.jpg

I get:

zsh:no matches found: *.jpg

Why does this not work?

Note: I know it can be done using find or grep but I want to know why the above does not work.


Solution

  • The shell first expands any glob patterns, and then runs the command. So from ls's perspective, ls *.jpg is exactly the same as if you had typed ls one.jpg two.jpg. The -R flag to ls only makes sense if you use it on a directory, which you're not doing here.

    This is also why mv *.jpg *.png doesn't work as expected on Unix systems, since mv never sees those patterns but just the filenames it expanded to (it does on e.g. Windows, where the globbing is done by the program rather than the shell; there are advantages and disadvantages to both approaches).


    * matches all characters except a /, so *.jpg only expands to patterns in the current directory. **/ is similar, but also matches /, so it expands to patterns in any directory. This is supported by both bash and zsh.

    So ls **/*.jpg will do what you want; you don't need to use find or grep. In zsh, especially you rarely need to use find since globbing is so much more powerful than in the standard Bourne shell or bash.

    In zsh you can also use setopt glob_star_short and then **.jpg will work as well, which is a shortcut for **/*.jpg.