Existe-t-il un moyen de faire échouer «mv» en silence?

61

Une commande telle que mv foo* ~/bar/produit ce message dans stderr si aucun fichier ne correspond foo*.

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

Cependant, dans le scénario sur lequel je travaille, tout irait bien, et j'aimerais supprimer ce message de nos journaux.

Y a-t-il un bon moyen de dire mvde rester silencieux même si rien n'a été déplacé?

Jonik
la source
13
mv foo* ~/bar/ 2> /dev/null?
Thomas Nyman
1
Ben ouais. Je suppose que j'avais quelque chose de "plus gentil" en tête, comme un interrupteur mv. :) Mais ça ira, bien sûr.
Jonik
3
J'ai vu que certaines mvimplémentations prennent en charge une -qoption pour les calmer, mais cela ne fait pas partie de la spécification POSIX pour mv. Les mvcoreutils in GNU, par exemple, n’ont pas cette option.
Thomas Nyman
Eh bien, je ne pense pas que le problème est de savoir comment garder "mv" être silencieux, mais comment le faire plus correctement. Pour vérifier s’il existe un fichier / répertoire foo * et que l’utilisateur dispose d’une autorisation d’inscription, exécutez enfin "mv", peut-être?
Shâu Shắc Le

Réponses:

47

Vous cherchez cela?

$ mv  file dir/
mv: cannot stat file’: No such file or directory
$ mv  file dir/ 2>/dev/null
# <---- Silent ----->
monde innocent
la source
20
remarque, la valeur de retour reste! = 0, donc tout set -e(qui doit être utilisé dans chaque script shell) échouera. vous pouvez ajouter un || truepour désactiver la vérification pour une seule commande.
Reto
10
@reto set -ene doit pas être utilisé dans tous les scripts shell, car dans de nombreux cas, la gestion des erreurs est encore plus complexe qu’elle ne l’est.
Chris Down
1
@ChrisDown je vois votre point. C'est généralement un choix entre deux maux. Mais en cas de doute, je préfère une exécution réussie à un échec. (qui ne sera remarqué que lorsqu'il sera visible par un client / utilisateur). Les scripts Shell sont un gros terrain miné pour les débutants, il est si facile de rater une erreur et votre script se dégonfle!
Reto
14

En fait, je ne pense pas que la mise en sourdine mvsoit une bonne approche (rappelez-vous que cela pourrait aussi vous rapporter d'autres choses qui pourraient être intéressantes ... par exemple manquantes ~/bar). Vous souhaitez le désactiver uniquement si votre expression globale ne renvoie pas de résultats. En fait plutôt ne pas l'exécuter du tout.

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

Ne semble pas très attrayant, et ne fonctionne que dans bash.

OU

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

sauf que vous êtes bashavec nullglobensemble. Vous payez un prix de 3 fois la répétition du motif glob.

Miroslav Koškár
la source
Problème mineur avec le second formulaire: il ne parvient pas à déplacer un fichier nommé foo*lorsqu'il est le seul du répertoire en cours qui correspond au glob foo*. Cela peut être contourné avec une expression globale qui ne correspond pas à elle-même, difficile. Par exemple [ 'fo[o]*' = "$(echo fo[o]*)" ] || mv fo[o]* ~/bar/.
fra-san
13

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

- GNU mva une belle option "destination en premier" ( -t) et xargspeut ignorer l'exécution de sa commande s'il n'y a aucune entrée ( -r). L' utilisation de -print0et -0fait en conséquence assurer qu'il n'y aurait pas un gâchis lorsque les noms de fichiers contiennent des espaces et d' autres choses « drôle ».

poige
la source
2
Notez que -maxdepth, -print0, -0et -rsont également des extensions GNU (bien que certains d'entre eux se trouvent dans d' autres implémentations de nos jours).
Stéphane Chazelas
1
Poige, beaucoup de nos utilisateurs n’utilisent pas Linux. Ici, il est pratique courante et très utile de signaler les éléments d’une réponse qui ne sont pas portables. Le site s'appelle " Unix et Linux", et beaucoup de gens utilisent une forme quelconque de BSD, Unix, AIX ou macOS ou des systèmes intégrés. Ne le prenez pas personnellement.
terdon
Je ne prends rien personnellement. J'ai une opinion que vous avez supprimée comme je le vois maintenant. Je répète donc: je trouve ces commentaires "notez que c'est GNU" plutôt inutiles. Tout d’abord, ils ne valent pas la peine d’être ajoutés, car ma réponse indique clairement qu’elle est basée sur la version GNU de mv.
Poige le
5

Il est important de réaliser que c'est en fait le shell qui étend foo*la liste des noms de fichiers correspondants, de sorte que rien ne mvpourrait se faire.

Le problème ici est que, lorsqu'un glob ne correspond pas, certains shell ressemblent bash(et la plupart des autres shell Bourne-like, ce comportement buggy a été introduit par le shell Bourne à la fin des années 70) et transmet le motif à la commande.

Donc ici, quand foo*ne correspond à aucun fichier, au lieu d'abandonner la commande (comme le font les shells pre-Bourne et plusieurs shells modernes), le shell passe un foo*fichier in extenso à mv, demandant donc mvde déplacer le fichier appelé foo*.

Ce fichier n'existe pas. Si c'était le cas, il aurait correspondu au modèle, mvsignale donc une erreur. Si le motif avait été à la foo[xy]place, vous mvauriez pu déplacer accidentellement un fichier appelé à la foo[xy]place des fichiers fooxet fooy.

Maintenant, même dans les obus qui n'ont pas ce problème (pré-Bourne, csh, tcsh, poisson, zsh, bash -O failglob), vous obtiendrez toujours une erreur mv foo* ~/bar, mais cette fois par l'obus.

Si vous voulez considérer que ce n'est pas une erreur s'il n'y a pas de correspondance de fichier foo*et dans ce cas, ne déplacez rien, vous voudriez créer d'abord la liste des fichiers (d'une manière qui ne provoque pas d'erreur, comme en utilisant l' nullgloboption de certains shells), et alors seulement appeler si mvla liste est non vide.

Ce serait mieux que de cacher toutes les erreurs de mv(comme l'ajoutant 2> /dev/null) comme si cela mvéchouait pour une autre raison, vous voudriez probablement encore savoir pourquoi.

en zsh

files=(foo*(N)) # where the N glob qualifier activates nullglob for that glob
(($#files == 0)) || mv -- $files ~/bar/

Ou utilisez une fonction anonyme pour éviter d'utiliser une variable temporaire:

() { (($# == 0)) || mv -- "$@" ~/bar/; } foo*(N)

zshest un de ces shells qui n’a pas le bogue Bourne et rapporte une erreur sans exécuter la commande quand un glob ne correspond pas (et que l’ nullgloboption n’a pas été activée), alors ici, vous pouvez masquer zshl’erreur et restaurer stderr pendant mvsi vous voyez toujours les mverreurs s'il y en a, mais pas l'erreur concernant les globs non correspondants:

(mv 2>&3 foo* ~/bar/) 3>&2 2>&-

Ou vous pourriez utiliser zargsce qui éviterait également les problèmes si le foo*glob devenait trop volumineux.

autoload zargs # best in ~/.zshrc
zargs -r -- foo* -- mv -t ~/bar # here assuming GNU mv for its -t option

En ksh93:

files=(~(N)foo*)
((${#files[#]} == 0)) || mv -- "${files[@]}" ~/bar/

En bash:

bashn'a pas de syntaxe à activer nullglobpour un seul glob, et l' failgloboption est annulée nullglob, vous aurez donc besoin d'éléments comme:

saved=$(shopt -p nullglob failglob) || true
shopt -s nullglob
shopt -u failglob
files=(foo*)
((${#files[@]} == 0)) || mv -- "${files[@]}" ~/bar/
eval "$saved"

ou définissez les options dans un sous-shell pour enregistrer devez les enregistrer avant et les restaurer ensuite.

(
  shopt -s nullglob
  shopt -u failglob
  files=(foo*)
  ((${#files[@]} == 0)) || mv -- "${files[@]}" ~/bar/
)

Dans yash

(
  set -o nullglob
  files=(foo*)
  [ "${#files[@]}" -eq 0 ] || mv -- "${files[@]}" ~/bar/
)

Dans fish

Dans la coquille de poisson, le comportement nullglob est le comportement par défaut de la setcommande, il s'agit donc simplement de:

set files foo*
count $files > /dev/null; and mv -- $files ~/bar/

POSIXly

Il n'y a aucune nullgloboption dans POSIX shet aucun tableau autre que les paramètres de position. Il existe une astuce que vous pouvez utiliser pour détecter si un glob correspond ou non:

set -- foo[*] foo*
if [ "$1$2" != 'foo[*]foo*' ]; then
  shift
  mv -- "$@" ~/bar/
fi

En utilisant a foo[*]et foo*glob, nous pouvons différencier le cas où il n'y a pas de fichier correspondant à celui où il y a un fichier appelé foo*(ce qui set -- foo*n'a pas pu être fait).

Plus de lecture:

Stéphane Chazelas
la source
3

Ce n'est probablement pas le meilleur, mais vous pouvez utiliser la findcommande pour vérifier si le dossier est vide ou non:

find "foo*" -type f -exec mv {} ~/bar/ \;
Mat
la source
1
Cela donnerait un foo*chemin de recherche littéral à find.
Kusalananda
3

Je suppose que vous utilisez bash, car cette erreur dépend du comportement de bash pour développer des globs sans correspondance entre eux. (En comparaison, zsh génère une erreur lorsqu’on tente de développer un glob sans correspondance.)

Alors, qu'en est-il de la solution de contournement suivante?

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

Cela ignorera silencieusement le mvsi ls -d foo*échoue, tout en enregistrant les erreurs si ls foo*réussit mais mvéchoue. (Attention, ls foo*peut échouer pour des raisons autres foo*qu'existantes, par exemple des droits insuffisants, un problème lié au service fixe, etc., de sorte que de telles conditions seraient ignorées en silence par cette solution.)

a3nm
la source
Je suis surtout intéressé par bash, oui (et j'ai étiqueté la question comme bash). +1 pour prendre en compte le fait que cela mvpourrait échouer pour d'autres raisons que foo*non existantes.
Jonik
1
(En pratique, je n'utiliserai probablement pas cela car c'est un peu verbeux et le but de la lscommande ne serait pas très clair pour les futurs lecteurs, du moins sans commentaire.)
Jonik
ls -d foo*pourrait retourner avec un statut de sortie non nul pour d'autres raisons également, comme après un ln -s /nowhere foobar(au moins avec certaines lsimplémentations).
Stéphane Chazelas
3

Vous pouvez faire par exemple

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

Pour plus de détails, voir: http://mywiki.wooledge.org/BashFAQ/055

Valentin Bajrami
la source
mv foo* ~/bar/ 1&>2ne fait pas taire la commande, il envoie ce qui aurait été sur stdout pour être également sur stderr.
Chris Down
et si vous utilisez mingw sous les fenêtres (comme moi) remplacer /dev/nullparNUL
Rian Sanderson
Comment fonctionne cette commande? Que font les esperluettes? Que font 1et 2veulent dire?
Aaron Franke
@AaronFranke 1 est stdout et 2 est stderr pipe par défaut lorsqu'un processus Linux est créé. En savoir plus sur le pipe dans les commandes Linux. Vous pouvez commencer ici - digitalocean.com/community/tutorials/…
Mithun B
2

Vous pouvez tricher (portable) avec perl:

perl -e 'system "mv foo* ~/bar/" if glob "foo*"'
Joseph R.
la source
Veuillez commenter avant de voter.
Joseph R.
2

Si vous utilisez Perl, vous pouvez aussi bien aller jusqu'au bout:

#!/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";
}

ou comme one-liner:

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

(Pour plus de détails sur la movecommande, consultez la documentation de File :: Copy .)

Ilmari Karonen
la source
-1

Au lieu

mv foo* ~/bar/

tu peux faire

cp foo* ~/bar/
rm foo* 

Simple, lisible :)

Michał Króliczek
la source
6
Copier, puis retirer, prend plus de temps qu'un simple mouvement. Cela ne fonctionnera pas non plus si un fichier est plus volumineux que l'espace disque disponible.
Anthon
11
Le PO veut une solution silencieuse. Ni cpne rmse taisent si foo*n'existe pas.
ron rothman
-1
mv foo* ~/bar/ 2>/dev/null

Si la commande précédente est réussie ou non, nous pouvons trouver le statut de sortie de la commande précédente

command: echo $?

si sortie de echo $? est différent de 0 signifie que la commande est non fructueuse si la sortie est 0, cela signifie que la commande est réussie

Praveen Kumar BS
la source