C'è un modo per far fallire silenziosamente "mv"?


61

Un comando come mv foo* ~/bar/produce questo messaggio in stderr se non ci sono file corrispondenti foo*.

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

Tuttavia, nella sceneggiatura sto lavorando su quel caso andrebbe benissimo, e vorrei omettere quel messaggio dai nostri registri.

C'è un modo carino per dire mvdi stare zitti anche se nulla è stato spostato?


13
mv foo* ~/bar/ 2> /dev/null?
Thomas Nyman,

1
Bene sì. Immagino di avere in mente qualcosa di "più bello", come un passaggio per mv. :) Ma questo lo farà, ovviamente.
Jonik,

3
Ho visto che alcune mvimplementazioni supportano -qun'opzione per tranquillizzarle, ma non fa parte delle specifiche POSIX mv. I mvcoreutils in GNU, ad esempio, non hanno tale opzione.
Thomas Nyman,

Bene, non penso che il problema sia come mantenere "mv" in silenzio, ma come farlo in modo più corretto. Verifica se ci sono file / dir foo * e l'utente ha i permessi di scrittura, finalmente esegui "mv", forse?
Shâu Shắc,

Risposte:


47

Stai cercando questo?

$ mv  file dir/
mv: cannot stat file’: No such file or directory
$ mv  file dir/ 2>/dev/null
# <---- Silent ----->

20
nota, restituisce ancora il valore! = 0, quindi qualsiasi set -e(che dovrebbe essere usato in ogni script di shell) fallirà. puoi aggiungere a || trueper disattivare il controllo per un singolo comando.
reto

10
@reto set -enon dovrebbe essere usato in ogni script di shell, in molti casi rende la gestione degli errori ancora più complessa di quanto non lo sia senza.
Chris Down,

1
@ChrisDown Vedo il tuo punto. Di solito è una scelta tra due mali. Ma in caso di dubbio, preferisco una corsa fallita, piuttosto che una riuscita. (che non viene notato fino a quando non diventa visibile a un cliente / utente). Gli script Shell sono un grande campo minato per i principianti, è così facile perdere un errore e il tuo script va in tilt!
reto

14

In realtà, non penso che il silenziamento mvsia un buon approccio (ricorda che potrebbe riferirti anche su altre cose che potrebbero essere di interesse ... ad esempio, la mancanza ~/bar). Si desidera disattivare l'audio solo nel caso in cui l'espressione glob non restituisca risultati. In realtà piuttosto non eseguirlo affatto.

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

Non sembra molto attraente e funziona solo in bash.

O

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

solo tranne che sei dentro bashcon nullglobset. Tuttavia, si paga un prezzo di 3 volte ripetizione del modello glob.


Problema minore con il secondo modulo: non riesce a spostare un file denominato foo*quando è l'unico nella directory corrente che corrisponde al glob foo*. Questo può essere risolto con un'espressione glob che non si combina letteralmente, dura. Es [ '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 mvha una bella opzione "destinazione prima" ( -t) e xargspuò saltare l'esecuzione del suo comando se non c'è alcun input ( -r). L'uso di -print0e di -0conseguenza si assicura che non ci sarebbe un disordine quando i nomi dei file contengono spazi e altre cose "divertenti".


2
Si noti che -maxdepth, -print0, -0e -rsono anche le estensioni GNU (anche se alcuni di loro si trovano in altre implementazioni al giorno d'oggi).
Stéphane Chazelas,

1
Poige, molti dei nostri utenti non usano Linux. È pratica standard qui, e molto utile, sottolineare quali parti di una risposta non sono portatili. Il sito si chiama " Unix e Linux" e molte persone usano una qualche forma di BSD o Unix o AIX o macOS o sistemi integrati. Non prenderlo sul personale.
Terdon

Non prendo nulla sul personale. Tuttavia ho un'opinione che hai rimosso come vedo ora. Quindi ripeto: trovo inutili quei commenti "nota che è GNU". Non vale la pena aggiungere nulla, prima di tutto poiché la mia risposta ha una chiara indicazione che si basa sulla versione GNU di mv.
poige

5

È importante rendersi conto che in realtà è la shell che lo espande foo*all'elenco dei nomi di file corrispondenti, quindi c'è poco che mvpotrebbe fare da solo.

Il problema qui è che quando un glob non corrisponde, alcune shell come bash(e la maggior parte delle altre shell Bourne-like, quel comportamento buggy è stato effettivamente introdotto dalla shell Bourne alla fine degli anni '70) passano il pattern alla lettera al comando.

Quindi qui, quando foo*non corrisponde a nessun file, invece di interrompere il comando (come fanno le shell pre-Bourne e diverse shell moderne), la shell passa un foo*file testuale a mv, quindi sostanzialmente chiede mvdi spostare il file chiamato foo*.

Quel file non esiste. In tal caso, avrebbe effettivamente adattato il modello, quindi mvsegnala un errore. Se il modello fosse stato foo[xy]invece, mvavrebbe potuto spostare accidentalmente un file chiamato al foo[xy]posto dei file fooxe fooy.

Ora, anche in quelle shell che non hanno questo problema (pre-Bourne, csh, tcsh, fish, zsh, bash -O failglob), avresti ancora un errore mv foo* ~/bar, ma questa volta dalla shell.

Se vuoi considerarlo non un errore se non c'è corrispondenza dei file foo*e in quel caso, non spostare nulla, ti consigliamo di costruire prima l'elenco dei file (in un modo che non causi un errore come usando l' nullglobopzione di alcune shell), quindi solo call mvè l'elenco non vuoto.

Sarebbe meglio che nascondere tutti gli errori di mv(come 2> /dev/nullfarebbe l' aggiunta ) come se mvfallissero per qualsiasi altra ragione, probabilmente vorrai comunque sapere perché.

in zsh

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

Oppure usa una funzione anonima per evitare di usare una variabile temporanea:

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

zshè una di quelle shell che non hanno il bug Bourne e riportano un errore senza eseguire il comando quando un glob non corrisponde (e l' nullglobopzione non è stata abilitata), quindi qui puoi nascondere zshl'errore e ripristinare stderr per mvcosì vedresti ancora gli mveventuali errori, ma non l'errore relativo ai globs non corrispondenti:

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

Oppure potresti usare zargsciò che eviterebbe anche problemi se il foo*glob si espandesse in file troppo man.

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:

bashnon ha sintassi da abilitare nullglobper un solo glob, e l' failglobopzione annulla nullglobquindi avresti bisogno di cose come:

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

oppure impostare le opzioni in una subshell per salvare devono salvarle prima e ripristinarle successivamente.

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

Nel yash

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

Nel fish

Nella shell di pesce, il comportamento nullglob è il valore predefinito per il setcomando, quindi è solo:

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

POSIXly

Non esiste alcuna nullglobopzione in POSIX she nessun array oltre ai parametri posizionali. C'è un trucco che puoi usare per rilevare se un glob corrisponde o meno:

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

Usando sia a foo[*]che foo*glob, possiamo distinguere tra il caso in cui non esiste un file corrispondente e quello in cui esiste un file che sembra essere chiamato foo*(cosa che set -- foo*non si può fare).

Più lettura:


3

Probabilmente non è il massimo, ma puoi usare il findcomando per verificare se la cartella è vuota o meno:

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

1
Ciò darebbe un foo*percorso di ricerca letterale a find.
Kusalananda

3

Suppongo che tu stia usando bash, perché questo errore dipende dal comportamento di bash di espandere a se stessi globi senza pari. (In confronto, zsh genera un errore quando si tenta di espandere un glob senza pari.)

Quindi, per quanto riguarda la seguente soluzione alternativa?

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

Ciò ignorerà silenziosamente il mvcaso if ls -d foo*, mentre continuerà a registrare gli errori in caso di esito ls foo*positivo, ma in caso mvcontrario. (Attenzione, ls foo*potrebbe non riuscire per ragioni diverse da quelle foo*inesistenti, ad esempio diritti insufficienti, problemi con la FS, ecc., Quindi tali condizioni verrebbero silenziosamente ignorate da questa soluzione.)


Sono principalmente interessato a bash, sì (e ho taggato la domanda come bash). +1 per tenere conto del fatto che mvpotrebbe non riuscire per motivi diversi da quelli foo*inesistenti.
Jonik,

1
(In pratica probabilmente non lo userò poiché è un po 'prolisso e il punto del lscomando non sarebbe molto chiaro per i futuri lettori, almeno senza un commento.)
Jonik,

ls -d foo*potrebbe tornare con uno stato di uscita diverso da zero anche per altri motivi, come dopo un ln -s /nowhere foobar(almeno con alcune lsimplementazioni).
Stéphane Chazelas,

3

Puoi fare per esempio

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

Per maggiori dettagli, consultare: http://mywiki.wooledge.org/BashFAQ/055


mv foo* ~/bar/ 1&>2non mette a tacere il comando, invia ciò che sarebbe stato su stdout per essere anche su stderr.
Chris Down,

e se stai usando mingw sotto windows (come me) sostituiscilo /dev/nullconNUL
Rian Sanderson il

Come funziona questo comando? Cosa fanno le e commerciali? Cosa significano 1e cosa 2significano?
Aaron Franke,

@AaronFranke 1 è stdout e 2 è stderr pipe per impostazione predefinita quando viene creato un processo Linux. Scopri di più sulla pipe nei comandi Linux. Puoi iniziare qui - digitalocean.com/community/tutorials/…
Mithun B

2

Puoi barare (portabile) con perl:

perl -e 'system "mv foo* ~/bar/" if glob "foo*"'

Si prega di commentare prima del downvoting.
Joseph R.

2

Se hai intenzione di usare Perl, potresti anche andare fino in fondo:

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

o come one-liner:

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

(Per i dettagli del movecomando, consultare la documentazione per File :: Copia .)


-1

Anziché

mv foo* ~/bar/

tu puoi fare

cp foo* ~/bar/
rm foo* 

Semplice, leggibile :)


6
La copia, quindi la rimozione, richiede più tempo di una semplice mossa. Inoltre, non funzionerà se un file è più grande dello spazio libero disponibile sul disco.
Anthon,

11
L'OP vuole una soluzione silenziosa. Né cp, né rmsono in silenzio, se foo*non esiste.
ron rothman,

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

Se il comando Above ha esito positivo o meno, possiamo trovare in base allo stato di uscita del comando precedente

command: echo $?

se l'output di echo $? è diverso da 0 significa comando non riuscito se l'output è 0 significa che il comando ha esito positivo

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.