Apakah ada cara untuk membuat "mv" gagal diam-diam?

61

Perintah like mv foo* ~/bar/menghasilkan pesan ini di stderr jika tidak ada file yang cocok foo*.

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

Namun, dalam skrip saya sedang mengerjakan kasus itu akan baik-baik saja, dan saya ingin menghilangkan pesan itu dari log kami.

Apakah ada cara yang bagus untuk mengatakan mvdiam meskipun tidak ada yang bergerak?

Jonik
sumber
13
mv foo* ~/bar/ 2> /dev/null?
Thomas Nyman
1
Yah begitulah. Saya kira saya memiliki sesuatu yang "lebih baik" dalam pikiran, seperti beberapa pergantian mv. :) Tapi itu akan dilakukan, tentu saja.
Jonik
3
Saya telah melihat beberapa mvimplementasi mendukung -qopsi untuk menenangkan mereka, tetapi itu bukan bagian dari spesifikasi POSIX mv. Dalam mvGNU coreutils, misalnya, tidak memiliki opsi seperti itu.
Thomas Nyman
Yah, saya tidak berpikir masalahnya adalah bagaimana membuat "mv" tetap tenang, tetapi bagaimana melakukannya dengan lebih baik. Memeriksa apakah ada file / dir foo *, dan pengguna memiliki izin yang dapat ditulisi, akhirnya jalankan "mv", mungkin?
Shâu Shắc

Jawaban:

47

Apakah Anda mencari ini?

$ mv  file dir/
mv: cannot stat file’: No such file or directory
$ mv  file dir/ 2>/dev/null
# <---- Silent ----->
dunia yang tidak bersalah
sumber
20
perhatikan, nilai kembali masih! = 0, oleh karena itu setiap set -e(yang harus digunakan dalam setiap skrip shell) akan gagal. Anda dapat menambahkan || trueuntuk menonaktifkan pemeriksaan untuk satu perintah.
reto
10
@reto set -etidak boleh digunakan di setiap skrip shell, dalam banyak kasus ini membuat manajemen kesalahan bahkan lebih kompleks daripada tanpa.
Chris Down
1
@ ChrisDown, saya mengerti maksud Anda. Ini biasanya merupakan pilihan antara dua kejahatan. Tetapi jika ragu, saya lebih suka lari yang gagal, daripada yang gagal. (yang tidak diketahui sampai terlihat oleh pelanggan / pengguna). Skrip shell adalah bidang tambang besar untuk pemula, sangat mudah untuk melewatkan kesalahan dan skrip Anda menjadi berantakan!
reto
14

Sebenarnya, saya tidak berpikir bahwa mematikan mvadalah pendekatan yang baik (ingat itu mungkin melaporkan Anda juga tentang hal-hal lain yang mungkin menarik ... misalnya hilang ~/bar). Anda ingin membisukannya hanya jika ekspresi glob Anda tidak memberikan hasil. Bahkan lebih tepatnya tidak menjalankannya sama sekali.

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

Tidak terlihat sangat menarik, dan hanya berfungsi di bash.

ATAU

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

hanya kecuali Anda berada bashdengan nullglobset. Anda membayar harga 3x pengulangan pola glob.

Miroslav Koškár
sumber
Masalah kecil dengan bentuk kedua: gagal memindahkan file bernama foo*ketika itu adalah satu-satunya di direktori saat ini yang cocok dengan bola dunia foo*. Ini bisa diselesaikan dengan ekspresi gumpalan yang tidak cocok dengan dirinya sendiri secara harfiah, sulit. Misalnya [ '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 mvmemiliki opsi "tujuan pertama" yang bagus ( -t) dan xargsdapat melewatkan menjalankan perintahnya jika tidak ada input sama sekali ( -r). Penggunaan -print0dan juga -0memastikan tidak akan ada kekacauan ketika nama file mengandung spasi dan hal-hal "lucu" lainnya.

poige
sumber
2
Perhatikan bahwa -maxdepth, -print0, -0dan -rjuga ekstensi GNU (meskipun beberapa dari mereka ditemukan dalam implementasi lainnya saat ini).
Stéphane Chazelas
1
Poige, banyak pengguna kami tidak menggunakan Linux. Ini adalah praktik standar di sini, dan sangat membantu, untuk menunjukkan bagian mana dari jawaban yang tidak portabel. Situs ini disebut " Unix & Linux", dan banyak orang menggunakan beberapa bentuk BSD atau Unix atau AIX atau macOS atau sistem embedded. Jangan tersinggung.
terdon
Saya tidak mengambil apa pun secara pribadi. Meskipun saya berpendapat bahwa Anda telah menghapus seperti yang saya lihat sekarang. Jadi saya ulangi: Saya menemukan komentar itu "perhatikan itu GNU" cukup sia-sia. Mereka sama sekali tidak layak ditambahkan, pertama-tama karena jawaban saya memiliki indikasi yang jelas bahwa itu didasarkan pada versi GNU mv.
poige
5

Sangat penting untuk menyadari bahwa itu sebenarnya shell yang memperluas foo*ke daftar nama file yang cocok, jadi ada sedikit yang mvbisa dilakukan sendiri.

Masalahnya di sini adalah bahwa ketika gumpalan tidak cocok, beberapa cangkang seperti bash(dan kebanyakan cangkang mirip Bourne lainnya, bahwa perilaku buggy sebenarnya diperkenalkan oleh cangkang Bourne pada akhir 70-an) meneruskan pola kata demi kata ke perintah.

Jadi di sini, ketika foo*tidak cocok dengan file apa pun, alih-alih membatalkan perintah (seperti shell pra-Bourne dan beberapa shell modern lakukan), shell melewati foo*file kata demi kata mv, jadi pada dasarnya meminta mvuntuk memindahkan file yang disebut foo*.

File itu tidak ada. Jika ya, itu akan benar-benar cocok dengan polanya, jadi mvmelaporkan kesalahan. Jika polanya sudah foo[xy], mvbisa saja secara tidak sengaja memindahkan file yang disebut foo[xy]bukan fooxdan fooyfile.

Sekarang, bahkan di shell yang tidak memiliki masalah (pre-Bourne, csh, tcsh, fish, zsh, bash -O failglob), Anda masih akan mendapatkan kesalahan mv foo* ~/bar, tetapi kali ini oleh shell.

Jika Anda ingin menganggapnya bukan kesalahan jika tidak ada file yang cocok foo*dan dalam hal itu, tidak memindahkan apa pun, Anda ingin membuat daftar file terlebih dahulu (dengan cara yang tidak menyebabkan kesalahan seperti dengan menggunakan nullglobopsi beberapa kerang), dan kemudian hanya panggilan mvdaftar tidak kosong.

Itu akan lebih baik daripada menyembunyikan semua kesalahan mv(seperti menambahkan 2> /dev/nullakan) seolah-olah mvgagal karena alasan lain, Anda mungkin masih ingin tahu mengapa.

dalam zsh

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

Atau gunakan fungsi anonim untuk menghindari penggunaan variabel sementara:

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

zshadalah salah satu dari shell yang tidak memiliki bug Bourne dan melaporkan kesalahan tanpa menjalankan perintah ketika glob tidak cocok (dan nullglobopsi belum diaktifkan), jadi di sini, Anda bisa menyembunyikan zshkesalahan dan mengembalikan stderr untuk mvjadi Anda masih akan melihat mvkesalahan jika ada, tetapi bukan kesalahan tentang gumpalan yang tidak cocok:

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

Atau Anda dapat menggunakan zargsyang juga akan menghindari masalah jika foo*gumpalan akan berkembang menjadi terlalu banyak file.

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

Dalam ksh93:

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

Dalam bash:

bashtidak memiliki sintaks untuk mengaktifkan nullglobsatu bola saja, dan failglobopsi dibatalkan nullglobsehingga Anda akan memerlukan hal-hal seperti:

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

atau mengatur opsi dalam subkulit untuk menyimpan harus menyimpannya sebelum dan mengembalikannya sesudahnya.

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

Di yash

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

Di fish

Di shell ikan, perilaku nullglob adalah default untuk setperintah, jadi itu hanya:

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

POSIXly

Tidak ada nullglobopsi di POSIX shdan tidak ada array selain dari parameter posisi. Ada trik yang dapat Anda gunakan untuk mendeteksi apakah sebuah glob cocok atau tidak:

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

Dengan menggunakan a foo[*]dan foo*glob, kita dapat membedakan antara kasus di mana tidak ada file yang cocok dan yang di mana ada satu file yang disebut foo*(yang set -- foo*tidak bisa dilakukan).

Lebih banyak membaca:

Stéphane Chazelas
sumber
3

Ini mungkin bukan yang terbaik tetapi Anda dapat menggunakan findperintah untuk memeriksa apakah folder kosong atau tidak:

find "foo*" -type f -exec mv {} ~/bar/ \;
Mat
sumber
1
Ini akan memberikan foo*jalur pencarian literal ke find.
Kusalananda
3

Saya berasumsi bahwa Anda menggunakan bash, karena kesalahan ini tergantung pada perilaku bash untuk memperluas gumpalan yang tak tertandingi untuk diri mereka sendiri. (Sebagai perbandingan, zsh memunculkan kesalahan saat mencoba memperluas gumpalan yang tak tertandingi.)

Jadi, bagaimana dengan solusi berikut ini?

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

Ini akan diam-diam mengabaikan mvif ls -d foo*gagal, sementara masih mencatat kesalahan jika ls foo*berhasil tetapi mvgagal. (Hati-hati, ls foo*bisa gagal karena alasan lain selain foo*tidak ada, misalnya, hak yang tidak mencukupi, masalah dengan FS, dll., Sehingga kondisi seperti itu akan diabaikan dalam keheningan oleh solusi ini.)

a3nm
sumber
Saya sebagian besar tertarik pada bash, yeah (dan saya memang menandai pertanyaannya sebagai bash). +1 untuk memperhitungkan fakta yang mvmungkin gagal karena alasan lain selain foo*tidak ada.
Jonik
1
(Dalam praktiknya saya mungkin tidak akan menggunakan ini karena ini agak bertele-tele, dan inti dari lsperintah tidak akan terlalu jelas bagi pembaca di masa depan, setidaknya tanpa komentar.)
Jonik
ls -d foo*dapat kembali dengan status keluar non-nol untuk alasan lain juga, seperti setelah ln -s /nowhere foobar(setidaknya dengan beberapa lsimplementasi).
Stéphane Chazelas
3

Anda bisa melakukannya misalnya

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

Untuk lebih jelasnya lihat: http://mywiki.wooledge.org/BashFAQ/055

Valentin Bajrami
sumber
mv foo* ~/bar/ 1&>2tidak membungkam perintah, ia mengirimkan apa yang seharusnya ada di stdout juga pada stderr.
Chris Down
dan jika Anda menggunakan mingw di bawah windows (seperti saya) ganti /dev/nulldenganNUL
Rian Sanderson
Bagaimana cara kerja perintah ini? Apa yang dilakukan ampersand? Apa 1dan apa 2artinya?
Aaron Franke
@AaronFranke 1 adalah stdout dan 2 adalah pipa stderr secara default ketika proses Linux dibuat. Ketahui lebih banyak tentang pipa dalam perintah Linux. Anda bisa mulai di sini - digitalocean.com/community/tutorials/…
Mithun B
2

Anda dapat menipu (mudah dibawa) dengan perl:

perl -e 'system "mv foo* ~/bar/" if glob "foo*"'
Joseph R.
sumber
Silakan komentar sebelum downvoting.
Joseph R.
2

Jika Anda akan menggunakan Perl, Anda bisa melakukannya:

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

atau sebagai one-liner:

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

(Untuk perincian moveperintah, lihat dokumentasi untuk File :: Copy .)

Ilmari Karonen
sumber
-1

Sebagai gantinya

mv foo* ~/bar/

Anda dapat melakukan

cp foo* ~/bar/
rm foo* 

Sederhana, mudah dibaca :)

Michał Króliczek
sumber
6
Menyalin, lalu menghapus, membutuhkan waktu lebih lama daripada langkah sederhana. Ini juga tidak akan berfungsi jika file lebih besar dari ruang disk kosong yang tersedia.
Anthon
11
OP menginginkan solusi yang diam. Baik cpatau rmdiam jika foo*tidak ada.
ron rothman
-1
mv foo* ~/bar/ 2>/dev/null

Apakah perintah di atas berhasil atau tidak, kita dapat menemukan status keluar dari perintah sebelumnya

command: echo $?

jika output dari $ echo? selain 0 berarti perintah gagal jika output 0 berarti perintah berhasil

Praveen Kumar BS
sumber