Czy istnieje sposób, aby cicho „mv” zawiodło?


61

Polecenie typu mv foo* ~/bar/produkuje ten komunikat w stderr, jeśli nie ma pasujących plików foo*.

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

Jednak w skrypcie, nad którym pracuję, ta sprawa byłaby całkowicie w porządku i chciałbym pominąć tę wiadomość w naszych dziennikach.

Czy jest jakiś fajny sposób, aby powiedzieć mvcicho, nawet jeśli nic nie zostało poruszone?


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

1
No tak Wydaje mi się, że miałem na myśli coś „ładniejszego”, jak na przykład przełącznik mv. :) Ale tak się stanie.
Jonik

3
Widziałem, że niektóre mvimplementacje obsługują -qopcję ich wyciszenia, ale nie jest to część specyfikacji POSIX mv. Na przykład mvw jądrach GNU nie ma takiej opcji.
Thomas Nyman

Cóż, nie sądzę, że problemem jest to, jak zachować „mv” cicho, ale jak to zrobić bardziej poprawnie. Sprawdzając, czy jest jakiś plik / dir foo *, a użytkownik ma uprawnienia do zapisu, może w końcu wykonać „mv”?
Shâu Shắc

Odpowiedzi:


46

Szukasz tego?

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

20
Uwaga: wciąż zwracaj wartość! = 0, dlatego dowolny set -e(który powinien być użyty w każdym skrypcie powłoki) zawiedzie. możesz dodać a, || trueaby wyłączyć sprawdzanie pojedynczego polecenia.
reto

10
@reto set -enie powinno być używane w każdym skrypcie powłoki, w wielu przypadkach sprawia, że ​​zarządzanie błędami jest jeszcze bardziej złożone niż bez niego.
Chris Down,

1
@ChrisDown Rozumiem twój punkt widzenia. Zazwyczaj jest to wybór między dwoma złami. Ale jeśli masz wątpliwości, wolę nieudane udane uruchomienie niż udane niepowodzenie. (co nie jest zauważane, dopóki nie stanie się widoczne dla klienta / użytkownika). Skrypty powłoki to duże pole minowe dla początkujących, tak łatwo przeoczyć błąd, a twój skrypt oszalał!
reto

14

Właściwie nie sądzę, aby wyciszanie mvbyło dobrym podejściem (pamiętaj, że może zgłosić ci również inne rzeczy, które mogą być interesujące ... np. Brakujące ~/bar). Chcesz go wyciszyć tylko w przypadku, gdy wyrażenie glob nie zwróci wyników. W rzeczywistości raczej wcale go nie wykonuje.

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

Nie wygląda zbyt atrakcyjnie i działa tylko w bash.

LUB

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

tylko chyba jesteś w bashze nullglobzbioru. Płacisz jednak trzykrotnie za powtórzenie wzoru globu.


Drobny problem z drugą formą: nie przenosi pliku o nazwie, foo*gdy jest on jedynym w bieżącym katalogu, który pasuje do globu foo*. Można to obejść za pomocą wyrażenia globalnego, które nie pasuje dosłownie, trudne. Np [ '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 mvma ładną opcję „najpierw docelową” ( -t) i xargsmoże pominąć uruchamianie polecenia, jeśli nie ma żadnych danych wejściowych ( -r). Używanie -print0i -0odpowiednio zapewnia, że ​​nie będzie bałaganu, gdy nazwy plików zawierają spacje i inne „śmieszne” rzeczy.


2
Należy zauważyć, że -maxdepth, -print0, -0a -rtakże rozszerzenia GNU (chociaż niektóre z nich znajdują się w innych implementacjach w dzisiejszych czasach).
Stéphane Chazelas

1
Poige, wielu naszych użytkowników nie używa Linuksa. Jest to standardowa praktyka i bardzo pomocne, aby wskazać, które części odpowiedzi nie są przenośne. Strona nazywa się „ Unix i Linux”, a wiele osób korzysta z jakiejś formy BSD lub Unix lub AIX lub macOS lub systemów wbudowanych. Nie bierz tego osobiście.
terdon

Nie biorę niczego osobiście. Mam jednak opinię, którą usunąłeś, tak jak teraz. Powtarzam: uważam, że te komentarze „zauważ, że to GNU” są zupełnie bezcelowe. Nie warto ich wcale dodawać, przede wszystkim, ponieważ moja odpowiedź wyraźnie wskazuje, że jest oparta na wersji GNU mv.
poige

5

Ważne jest, aby zdać sobie sprawę, że tak naprawdę to powłoka rozszerza foo*ją na listę pasujących nazw plików, więc niewiele mvmożna zrobić.

Problem polega na tym, że gdy glob nie pasuje, niektóre powłoki takie jak bash(i większość innych powłok podobnych do Bourne'a, to zachowanie błędne zostało wprowadzone przez powłokę Bourne'a pod koniec lat 70.) przekazują wzorzec dosłownie do polecenia.

Więc tutaj, gdy foo*nie pasuje do żadnego pliku, zamiast przerywać polecenie (jak robią to wcześniejsze powłoki Bourne'a i kilka nowoczesnych powłok), powłoka przekazuje foo*plik dosłowny mv, więc w zasadzie prosi mvo przeniesienie pliku o nazwie foo*.

Ten plik nie istnieje. Gdyby tak było, rzeczywiście pasowałby do wzorca, więc mvzgłasza błąd. Gdyby foo[xy]zamiast tego wzorzec , mvmógł przypadkowo przenieść plik o nazwie foo[xy]zamiast plików fooxi fooy.

Teraz nawet w tych powłokach, które nie mają tego problemu (przed Bourne, csh, tcsh, fish, zsh, bash -O failglob), nadal pojawiał się błąd mv foo* ~/bar, ale tym razem przez powłokę.

Jeśli chcesz uznać to za błąd, jeśli nie ma pasującego pliku, foo*a w takim przypadku nie przenosić niczego, najpierw musisz zbudować listę plików (w sposób, który nie powoduje błędu, jak przy użyciu nullglobopcji niektóre muszle), a następnie tylko wywołanie mvjest takie, że lista nie jest pusta.

Byłoby to lepsze niż ukrywanie wszystkich błędów mv(tak jak w przypadku dodawania 2> /dev/null), jak gdyby mvnie powiodło się z jakiegokolwiek innego powodu, prawdopodobnie nadal chcesz wiedzieć, dlaczego.

w Zsh

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

Lub użyj funkcji anonimowej, aby uniknąć używania zmiennej tymczasowej:

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

zshjest jedną z tych powłok, które nie zawierają błędu Bourne'a i zgłaszają błąd bez wykonania polecenia, gdy glob nie pasuje (a nullglobopcja nie została włączona), więc tutaj możesz ukryć zshbłąd i przywrócić stderr dla, mvwięc nadal będziesz widzieć mvbłędy, jeśli takie istnieją, ale nie błąd dotyczący niepasujących globów:

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

Lub możesz użyć tego, zargsco uniknęłoby również problemów, gdyby foo*glob rozszerzył się do zbyt dużych plików.

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

W ksh93:

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

W bash:

bashnie ma składni, aby włączyć tylko nullglobdla jednego globu, a failglobopcja anuluje, nullglobwięc potrzebujesz takich rzeczy jak:

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

lub ustaw opcje w podpowłoce, aby zapisać trzeba je wcześniej zapisać, a następnie przywrócić.

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

W yash

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

W fish

W skorupce ryby zachowanie nullglob jest domyślnym setpoleceniem, więc jest to po prostu:

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

POSIXly

W nullglobPOSIX shnie ma opcji ani tablicy innej niż parametry pozycyjne. Istnieje jednak sztuczka, której można użyć do wykrycia, czy glob pasuje, czy nie:

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

Używając zarówno a, jak foo[*]i foo*glob, możemy rozróżnić przypadek, w którym nie ma pasującego pliku, i ten, w którym jest jeden plik, który jest wywoływany foo*(czego set -- foo*nie można zrobić).

Więcej lektur:


3

Prawdopodobnie nie jest to najlepsze, ale możesz użyć findpolecenia, aby sprawdzić, czy folder jest pusty, czy nie:

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

1
Dałoby to dosłowną foo*ścieżkę wyszukiwania do find.
Kusalananda

3

Zakładam, że używasz bash, ponieważ ten błąd zależy od zachowania bash, aby rozwinąć do siebie niedopasowane globusy. (Dla porównania, zsh zgłasza błąd przy próbie rozwinięcia niedopasowanego globu.)

A co z następującym obejściem?

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

To po cichu zignoruje błąd mvif ls -d foo*, a nadal będzie rejestrować błędy, jeśli się ls foo*powiedzie, ale się mvnie powiedzie. (Uwaga, ls foo*może się nie powieść z innych przyczyn niż foo*nieistniejących, np. Niewystarczających uprawnień, problemów z FS itp., Więc takie rozwiązanie zostanie po cichu zignorowane).


Najbardziej interesuje mnie bash, tak (i ​​otagowałem pytanie jako bash). +1 za wzięcie pod uwagę faktu, że mvmoże się nie powieść z innych powodów niż foo*nieistniejący.
Jonik

1
(W praktyce prawdopodobnie nie będę tego używał, ponieważ jest to rodzaj gadatliwy, a ls
cel

ls -d foo*może powrócić z niezerowym statusem wyjścia również z innych powodów, na przykład po ln -s /nowhere foobar(przynajmniej w niektórych lsimplementacjach).
Stéphane Chazelas

3

Możesz zrobić na przykład

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

Aby uzyskać więcej informacji, zobacz: http://mywiki.wooledge.org/BashFAQ/055


mv foo* ~/bar/ 1&>2nie wycisza polecenia, wysyła to, co byłoby na stdout, aby było również na stderr.
Chris Down,

i jeśli używasz MinGW pod oknami (jak ja) zastąpić /dev/nullzNUL
Rian Sanderson

Jak działa to polecenie? Co robią znaki handlowe? Co 1i co 2znaczy?
Aaron Franke,

@AaronFranke 1 jest domyślnie stdout, a 2 jest domyślnie potokiem stderr podczas tworzenia procesu Linux. Dowiedz się więcej na temat potoku w poleceniach Linuksa. Możesz zacząć tutaj - digitalocean.com/community/tutorials/…
Mithun B

2

Możesz oszukiwać (przenośnie) za pomocą perl:

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

Proszę skomentować przed oddaniem głosu.
Joseph R.

2

Jeśli zamierzasz używać Perla, równie dobrze możesz przejść całą drogę:

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

lub jako jedna linijka:

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

(Szczegółowe informacje na temat movepolecenia znajdują się w dokumentacji File :: Copy .)


-1

Zamiast

mv foo* ~/bar/

możesz to zrobić

cp foo* ~/bar/
rm foo* 

Prosty, czytelny :)


6
Kopiowanie, a następnie usuwanie trwa dłużej niż zwykły ruch. Nie działa również, jeśli plik jest większy niż dostępne wolne miejsce na dysku.
Anthon

11
OP chce cichego rozwiązania. Ani cpnie rmmilczą, jeśli foo*nie istnieje.
ron rothman

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

Niezależnie od tego, czy powyższe polecenie zakończyło się powodzeniem, czy nie, możemy sprawdzić status wyjścia poprzedniego polecenia

command: echo $?

jeśli wyjście echa $? jest inny niż 0 oznacza, że ​​polecenie nie powiodło się, jeśli wyjście ma wartość 0, oznacza, że ​​polecenie zakończyło się pomyślnie

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.