Using zsh
, I get a "No match found" message when choosing a pattern that does not fit with rm
and that even when redirecting the output.
# rm * > /dev/zero 2>&1
zsh: no matches found: *
How can I get rid of this message?
This behaviour is controlled by several of Zsh's globbing options. By default, if a command line contains a globbing expression which doesn't match anything, Zsh will print the error message you're seeing, and not run the command at all. You can disable this in three different ways:
setopt +o nomatch
will leave globbing expressions which don't match anything as-is, and you'll get an error message from rm
(which you can disable using -f
, although that's a bad idea since it will force removals in other situations where you might not want to);
setopt +o nullglob
will delete patterns which don’t match anything (so they will be effectively ignored);
setopt +o cshnullglob
will delete patterns which don’t match anything, and if all patterns in a command are removed, report an error.
The last two override nomatch
. All these options can be unset with setopt -o …
.
nullglob
can be enabled for a single pattern using the N
glob qualifier, e.g.:
rm -f -- *(N)
nomatch
is less than ideal, that's what Bourne-like shells do. If the pattern doesn't match, it is passed as-is to rm
(!) upon which rm
will give an error, or worse could delete the wrong file for a pattern like *.[ch]
for instance!
Commented
Sep 22, 2017 at 12:59
*.sh(N)
, not *(N).sh
. SO ref, manual ref
Commented
Nov 11, 2022 at 14:49
What would you want it to do instead? Not run rm
at all (1)? Run it with a literal *
argument like in other Bourne-like shells (2)? Run it with no argument at all (3)?
files=(*(N)); (($#files)) && rm -- $files
. Or (rm -- *) 2> /dev/null
but that would also hide genuine errors by rm
which would be silly. You could discard the zsh
error but restore stderr for the rm
command though with (rm -- * 2>&3 3>&-) 3>&2 2> /dev/null
emulate sh -c 'rm -- *' 2> /dev/null
. Then like in sh
which zsh
now emulated for that single command line, the non-matching *
is passed as-is to rm
and rm
complains as that *
file doesn't exist. We suppress rm
's stderr as you would do in sh
to suppress that error message, but again, that's silly as it would hide genuine errors by rm
as opposed to the error incurred by the misbehaviour of sh
passing a literal *
to rm
. rm -f '*'
would not complain about an un-existing *
file though, so you could do emulate sh -c 'rm -f -- *'
rm -- *(N)
. rm
would complain though when not passed any argument, though again, not rm -f
: rm -f -- *(N)
.Generally, rm -f
is the command you want to use if you want all the files gone and only get an error if files could not be removed or IOW are still there after rm
has returned. You also generally want to use -f
in scripts to avoid the user being prompted under some situations.
Here, calling rm
when the glob doesn't match is wrong. The sh
1 behaviour is wrong. It's harmless for a pattern like *
, but for one like *.[ch]
, passing *.[ch]
as-is when it doesn't match could cause the *.[ch]
file to be removed by mistake:
$ ls
*.[ch] foo.txt
$ zsh -c 'rm *.[ch]'
zsh:1: no matches found: *.[ch]
$ ls
*.[ch] foo.txt
$ sh -c 'rm *.[ch]'
$ ls
foo.txt
Failing with an error is the most sensible thing to do and is what zsh
(and fish
, csh
, tcsh
, bash -o failglob
and the original Unix shell) does.
And if you want to take care yourself of that special case, zsh
makes it easy with its (N)
glob qualifier (for nullglob) like in case (1) above. fish
(at least in recent version) makes it even easier as its globs are expanded in a nullglob fashion when in arguments to the set
command (the one that assigns variables). So, the equivalent there, would be:
set files *
if count $files > /dev/null
rm -f -- $files
end
See Why is nullglob not default for more details.
1. Strictly speaking it's only sh
since the Bourne shell (since Unix V7 in 1979); earlier versions of sh
(which did call /etc/glob
upon unquoted wildcards which is where the glob name comes from) did behave like csh
or zsh -o cshnullglob
, that is /etc/glob
would abort the command if none of the globs had any match (and would suppress the non-matching globs if at least one of them had any match). The behaviour was broken by the Bourne shell.
rm
and not from the shell. Something like "rm: cannot remove `*': No such file or directory".
emulate sh -c 'rm -- *'
to get the (buggy IMO) behaviour of the Bourne shell.
Commented
Sep 29, 2016 at 13:19
rm
errors. That's something you would do in other shells to suppress the error when there's no matching file and that causes the suppression of genuine rm errors as well. My answer outlines that and hopefully shows how zsh behaviour is preferable.
Commented
Sep 29, 2016 at 14:57
(N)
qualifies, i.e. changes, the preceding *
, so the full pattern is *(N)
. The qualifier changes the way the pattern behaves, and in this case the qualifier makes the pattern disappear if there is no match.
Type this in interactive zsh:
setopt no_nomatch
rm * > /dev/null 2>&1
setopt nomatch # `nomatch` is default, see `man zshoptions`
For me, nullglob
and cshnullglob
do not work
setopt nomatch
again after the removal? Can we not add setopt no_nomatch
to .zshrc
? please share drawbacks of doing so if there are any.
Commented
Apr 7, 2023 at 11:25
I could not comment due to lack of reputation. As a complement to Stephen Kitt's answer, I have to use setopt
instead of setopt +o
for the option to work. I am using zsh 5.9 .
setopt nullglob
declare -a files=(${SOME_PATH}/*.log)
for f in "${files[@]}"; do
printf "Removing ${f}...\n"
rm -f "${f}" || printf "Failed to remove ${f}\n"
done
If no match is found, the files
array is empty, and the loop will not be entered, which meets my need perfectly.
setopt extended_glob
(or even plainrm * >/dev/null 2>&1
) but really you should make a workaround to not needrm *
, that's outright dangerous/dev/null
rather thendev/zero
. Also, your stderr redirect is missing an&
; it should be2>&1
.zsh
during evaluation of the command and not during the runtime of the command itself (due to the error the command is not even run). The output redirections here would only affect the output of the command and not the shell itself.