90

A command like mv foo* ~/bar/ produces this message in stderr if there are no files matching foo*.

mv: cannot stat `foo*': No such file or directory

However, in the script I'm working on that case would be completely fine, and I'd like to omit that message from our logs.

Is there any nice way to tell mv to be quiet even if nothing was moved?

Jonik
  • 1,480

12 Answers12

83

Are you looking for this?

$ mv  file dir/
mv: cannot stat ‘file’: No such file or directory
$ mv  file dir/ 2>/dev/null
# <---- Silent ----->
  • 40
    note, return value still != 0, therefore any set -e (which should be used in every shell script) will fail. you can add a || true to deactivate the check for a single command. – reto Aug 21 '13 at 16:28
  • 14
    @reto set -e should not be used in every shell script, in many cases it makes error management even more complex than it is without. – Chris Down Nov 04 '13 at 09:12
  • 3
    @ChrisDown I see your point. It is usually a choice between two evils. But if in doubt, I prefer a failed successful run, than a successful failure. (which doesn't get noticed until it's getting visible to a customer/user).

    Shell scripts are a big mine field for beginners, it's so easy to miss a an error and your script goes haywire!

    – reto Nov 04 '13 at 10:25
  • @reto Much better to gracefully deal with errors than just dump out of the script. – Tripp Kinetics May 14 '21 at 18:31
  • 1
    @TrippKinetics if you use -e and forget about a corner case, then the script will just fail with a generic message. If you don't use it and forget about a corner case, then your script continous and you have a very strange bug half a year later. But just use the appropriate technique for your task at hand, there's no "wrong" way. – reto May 17 '21 at 11:28
18

Actually, I don't think muting mv is a good approach (remember it might report you also on other things which might be of interest ... eg. missing ~/bar). You want to mute it only in case your glob expression doesn't return results. In fact rather not execute it at all.

[ -n "$(shopt -s nullglob; echo foo*)" ] && mv foo* ~/bar/

Doesn't look very appealing, and works only in bash.

OR

[ 'foo*' = "$(echo foo*)" ] || mv foo* ~/bar/

only except you are in bash with nullglob set. You pay a price of 3x repetition of glob pattern though.

  • 2
    Minor issue with the second form: it fails to move a file named foo* when it is the only one in the current directory which matches the glob foo*. This can be worked around with a glob expression that doesn't match itself literally, tough. E.g. [ 'fo[o]*' = "$(echo fo[o]*)" ] || mv fo[o]* ~/bar/. – fra-san Jun 01 '19 at 07:50
14

find . -maxdepth 1 -name 'foo*' -type f -print0 | xargs -0r mv -t ~/bar/

— GNU's mv has nice "destination first" option (-t) and xargs can skip running its command if there's no input at all (-r). Using of -print0 and -0 correspondingly makes sure there wouldn't be a mess when filenames contain spaces and other "funny" stuff.

poige
  • 6,231
  • 2
    Note that -maxdepth, -print0, -0 and -r are also GNU extensions (though some of them are found in other implementations nowadays). – Stéphane Chazelas Jun 01 '19 at 06:47
  • 2
    Poige, a lot of our users don't use Linux. It is standard practice here, and very helpful, to point out what parts of an answer are not portable. The site is called "Unix & Linux", and many people use some form of BSD or Unix or AIX or macOS or embedded systems. Don't take it personally. – terdon Jun 03 '19 at 08:24
  • 1
    I'm not taking anything personally. I though have an opinion that you've removed as I see now. So I repeat: I find those comments "note it's GNU" pretty pointless. They're not worth adding at all, first of all since my answer has clear indication that it's based on GNU version of mv. – poige Jun 03 '19 at 11:52
12

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:

3

It's probably not the best but you can use find command to check if the folder is empty or not:

find "foo*" -type f -exec mv {} ~/bar/ \;
Matt
  • 147
3

I'm assuming that you are using bash, because this error depends on bash's behavior to expand unmatched globs to themselves. (By comparison, zsh raises an error when trying to expand an unmatched glob.)

So, what about the following workaround?

ls -d foo* >/dev/null 2>&1 && mv foo* ~/bar/

This will silently ignore the mv if ls -d foo* fails, while still logging errors if ls foo* succeeds but the mv fails. (Beware, ls foo* could fail for other reasons than foo* not existing, e.g., insufficient rights, problem with the FS, etc., so such conditions would be silently ignored by this solution.)

a3nm
  • 9,207
  • I'm mostly interested in bash, yeah (and I did tag the question as bash). +1 for taking into account the fact that mv might fail for other reasons than foo* not existing. – Jonik Aug 22 '13 at 12:27
  • 1
    (In practice I probably won't be using this since it's kind of verbose, and the point of the ls command wouldn't be very clear for future readers, at least without a comment.) – Jonik Aug 22 '13 at 12:28
  • ls -d foo* could return with a non-zero exit status for other reasons as well, like after a ln -s /nowhere foobar (at least with some ls implementations). – Stéphane Chazelas Jun 01 '19 at 08:39
2

You can do for example

mv 1>/dev/null 2>&1 foo* ~/bar/ or mv foo* ~/bar/ 1&>2

For more details see: http://mywiki.wooledge.org/BashFAQ/055

  • mv foo* ~/bar/ 1&>2 does not silence the command, it sends what would have been on stdout to also be on stderr. – Chris Down Aug 21 '13 at 12:15
  • and if you're using mingw under windows (like me) replace /dev/null with NUL – Rian Sanderson Mar 10 '16 at 18:46
  • How does this command work? What do the ampersands do? What do 1 and 2 mean? – Aaron Franke Dec 15 '16 at 20:20
  • @AaronFranke 1 is stdout and 2 is stderr pipe by default when a Linux process get created. Know more on pipe in Linux commands. You can start here -- https://www.digitalocean.com/community/tutorials/an-introduction-to-linux-i-o-redirection – Mithun B May 31 '19 at 10:21
2

You can cheat (portably) with perl:

perl -e 'system "mv foo* ~/bar/" if glob "foo*"'
Joseph R.
  • 39,549
2

If you're going to use Perl, you might as well go all the way:

#!/usr/bin/perl
use strict;
use warnings;
use File::Copy;

my $target = "$ENV{HOME}/bar/";

foreach my $file (<foo*>) {
    move $file, $target  or warn "Error moving $file to $target: $!\n";
}

or as a one-liner:

perl -MFile::Copy -E 'move $_, "$ENV{HOME}/bar/" or warn "$_: $!\n" for <foo*>'

(For details of the move command, see the documentation for File::Copy.)

1

This code worked for me for in case of a file, when you are in the source location.

[ -f ${fileName} ] && mv ${source}/${fileName} ${destination}

In case of a directory

[ -d ${source} ] && mv ${source} ${destination}
0
mv foo* ~/bar/ 2>/dev/null

Whether Above command is successfull or not we can find by exit status of previous command

command: echo $?

if output of echo $? is other than 0 means command unsucessfull if output is 0 means command is successfull

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
-1

Instead

mv foo* ~/bar/

you can do

cp foo* ~/bar/
rm foo* 

Simple, readable :)

Abhik Bose
  • 2,118
  • 6
    Copying, then removing, takes longer than a simple move. It also will not work if a file is larger than the available free disc space. – Anthon Apr 14 '14 at 07:14
  • 11
    The OP wants a solution that is silent. Neither cp nor rm are silent if foo* does not exist. – ron rothman Oct 03 '14 at 13:10