It's important to realise that it's actually the shell that expands that foo*
to the list of matching file names, so there's little mv
could do itself.
The problem here is that when a glob doesn't match, some shells like bash
(and most other Bourne-like shells, that buggy behaviour was actually introduced by the Bourne shell in the late 70s) pass the pattern verbatim to the command.
So here, when foo*
doesn't match any file, instead of aborting the command (like pre-Bourne shells and several modern shells do), the shell passes a verbatim foo*
file to mv
, so basically asking mv
to move the file called foo*
.
That file doesn't exist. If it did, it would actually have matched the pattern, so mv
reports an error. If the pattern had been foo[xy]
instead, mv
could have accidentally move a file called foo[xy]
instead of the foox
and fooy
files.
Now, even in those shells that don't have that problem (pre-Bourne, csh, tcsh, fish, zsh, bash -O failglob), you would still get an error upon mv foo* ~/bar
, but this time by the shell.
If you want to consider it not an error if there's no file matching foo*
and in that case, not move anything, you would want to build the list of files first (in a way that doesn't cause an error like by using the nullglob
option of some shells), and then only call mv
is the list is non-empty.
That would be better than hiding all the errors of mv
(as adding 2> /dev/null
would) as if mv
fails for any other reason, you'd probably still want to know why.
in zsh
files=(foo*(N)) # where the N glob qualifier activates nullglob for that glob
(($#files == 0)) || mv -- $files ~/bar/
Or use an anonymous function to avoid using a temporary variable:
() { (($# == 0)) || mv -- "$@" ~/bar/; } foo*(N)
zsh
is one of those shells that don't have the Bourne bug and do report an error without executing the command when a glob doesn't match (and the nullglob
option has not been enabled), so here, you could hide zsh
's error and restore stderr for mv
so you would still see the mv
errors if any, but not the error about the non-matching globs:
(mv 2>&3 foo* ~/bar/) 3>&2 2>&-
Or you could use zargs
which would also avoid problems if the foo*
glob would expand to too man files.
autoload zargs # best in ~/.zshrc
zargs -r -- foo* -- mv -t ~/bar # here assuming GNU mv for its -t option
In ksh93:
files=(~(N)foo*)
((${#files[#]} == 0)) || mv -- "${files[@]}" ~/bar/
In bash:
bash
has no syntax to enable nullglob
for one glob only, and the failglob
option cancels nullglob
so you'd need things like:
saved=$(shopt -p nullglob failglob) || true
shopt -s nullglob
shopt -u failglob
files=(foo*)
((${#files[@]} == 0)) || mv -- "${files[@]}" ~/bar/
eval "$saved"
or set the options in a subshell to save have to save them before and restore them afterwards.
(
shopt -s nullglob
shopt -u failglob
files=(foo*)
((${#files[@]} == 0)) || mv -- "${files[@]}" ~/bar/
)
In yash
(
set -o nullglob
files=(foo*)
[ "${#files[@]}" -eq 0 ] || mv -- "${files[@]}" ~/bar/
)
In fish
In the fish shell, the nullglob behaviour is the default for the set
command, so it's just:
set files foo*
count $files > /dev/null; and mv -- $files ~/bar/
POSIXly
There's no nullglob
option in POSIX sh
and no array other than the positional parameters. There is a trick you can use though to detect whether a glob matched or not:
set -- foo[*] foo*
if [ "$1$2" != 'foo[*]foo*' ]; then
shift
mv -- "$@" ~/bar/
fi
By using both a foo[*]
and foo*
glob, we can differentiate between the case where there's no matching file and the one where there's one file which happens to be called foo*
(which a set -- foo*
couldn't do).
More reading:
mv foo* ~/bar/ 2> /dev/null
? – Thomas Nyman Aug 21 '13 at 11:14mv
. :) But that will do, of course. – Jonik Aug 21 '13 at 11:34mv
implementations support a-q
option to quiet them down, but that is not part of the POSIX specification formv
. Themv
in GNU coreutils, for instance, does not have such an option. – Thomas Nyman Aug 21 '13 at 11:53