59

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?

  • 13
    mv foo* ~/bar/ 2> /dev/null ? – Thomas Nyman Aug 21 '13 at 11:14
  • 1
    Well yeah. I guess I had something "nicer" in mind, like some switch for mv. :) But that will do, of course. – Jonik Aug 21 '13 at 11:34
  • 3
    I've seen some mv implementations support a -q option to quiet them down, but that is not part of the POSIX specification for mv. The mv in GNU coreutils, for instance, does not have such an option. – Thomas Nyman Aug 21 '13 at 11:53
  • Well, I dont think the problem is how to keep "mv" be quiet, but how to do it more properly. Checking if there is any file/dir foo*, and user has writable permission, finally execute "mv", perhaps? – Shâu Shắc Aug 22 '13 at 7:00

11 Answers 11

45

Are you looking for this?

$ mv  file dir/
mv: cannot stat ‘file’: No such file or directory
$ mv  file dir/ 2>/dev/null
# <---- Silent ----->
  • 20
    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
  • 10
    @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 4 '13 at 9:12
  • 1
    @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 4 '13 at 10:25
14

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.

  • 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 1 at 7:50
13

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.

  • 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 1 at 6:47
  • 1
    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 3 at 8:24
  • 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 3 at 11:52
5

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/ \;
  • 1
    This would give a literal foo* search path to find. – Kusalananda Jun 28 at 6:32
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.)

  • 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 1 at 8:39
3

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 -- digitalocean.com/community/tutorials/… – Mithun B May 31 at 10:21
2

You can cheat (portably) with perl:

perl -e 'system "mv foo* ~/bar/" if glob "foo*"'
  • Please comment before downvoting. – Joseph R. Aug 21 '13 at 18:40
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

Instead

mv foo* ~/bar/

you can do

cp foo* ~/bar/
rm foo* 

Simple, readable :)

  • 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 7:14
  • 11
    The OP wants a solution that is silent. Neither cp nor rm are silent if foo* does not exist. – ron rothman Oct 3 '14 at 13:10
-1
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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.