matching files using curly brace expansion in zsh

5,402

Solution 1

You don't get a warning in bash, you get an error by ls (you'll find that ls exit status is non-0 which indicates it has failed).

In both zsh and bash, {...} is not a globbing operator, it's an expansion that occurs before globbing.

In:

ls -d -- *.{dot,svg,err}

(you forgot the -d and -- btw), the shell expands the {...} first:

ls -d -- *.dot *.svg *.err

and then does the glob. bash like most Bourne-like shells has that misfeature that non-matching globs are passed as-is. While on zsh, a non-matching glob is an error.

See how rm -f [ab].c in bash could delete the [ab].c file if there was no file called a.c nor b.c. In zsh, you'd get a no match error instead. See the failglob option in bash to get a similar behaviour.

ls -d -- *.{dot,svg,err}(N)

in zsh would enable the nullglob option on all 3 globs, so that if the globs don't match, they are removed, but that's probably not what you want, because if none of the globs match any file, the command will become:

ls -d --

Which will list . (the current directory) instead.

Best here is to use one glob that matches files with either of the 3 extensions:

ls -d -- *.(dot|svg|err)

That will give a sorted list of files to ls, ls will be run unless there's no file found matching that one pattern.

You also have the option to enable the sh/bash (bogus IMO) behaviour with emulate sh or with unsetopt nomatch. A slightly better approach is to enable the csh behaviour (which was also the behaviour of Unix shells before the Bourne shell was released):

setopt cshnullglob

With that option, the command is cancelled only if all the globs on the command line fail to match. If at least one matches, all the ones that don't match are removed so:

ls -d -- *.{dot,svg,err}

Will expand the dot, svg and err in turn, omitting the missing ones.

If you want to compare the effect on the order of the arguments of the different approaches, you need a command that (contrary to ls) doesn't sort them before displaying. With GNU ls, you can pass the -U option for that, or since ls does only print its arguments here, just use printf '%s\n' *... instead.

Solution 2

Tack on (N) to the pattern; this tells zsh to not be upset about missing matching.

rm -f *.{dot,svg,err}(N)

It's a zsh glob feature. See man zshexpn for more details; under "Glob Qualifiers". It's annotated as "sets the NULL_GLOB option for the current pattern".

Share:
5,402

Related videos on Youtube

Peter Uhnak
Author by

Peter Uhnak

Author/lead dev of the open-source OpenPonk modeling platform aimed at providing modeling support for business and software engineering for students, researchers, and professionals. The work is backed by the CCMi (Centre for Conceptual Modelling and Implementation) research group of Czech Technical University.

Updated on September 18, 2022

Comments

  • Peter Uhnak
    Peter Uhnak over 1 year

    In bash I can use curly braces to pattern match several files, e.g.

    ls *.{dot,svg,err}
    

    If there's no file for a particular extension, I get a warning, but the remaining files will be listed.

    ls: cannot access '*.err': No such file or directory

    However, in zsh I get an error instead and no output is produced for the existing files.

    zsh: no matches found: *.err

    This is also applies for removing, where attempting to remove files with this:

    rm -f *.{dot,svg,err}
    

    Will remove all matching files in bash without any error or warning, but in zsh it will err out and not remove anything at all.

    Is there a way to make zsh behave in the same way as bash, or is there another way to remove/list the existing files with similar ease¹?


    ¹Obviously I can do something like find . -name '*.dot' -or -name '*.svg' -or -name '.err' | xargs rm but that's not particularly easy/practical.

    • Rahul
      Rahul almost 8 years
      do setopt BRACE_CCL first then give it one more try
    • Peter Uhnak
      Peter Uhnak almost 8 years
      @Rahul setting it didn't affect it in any way.