มีวิธีที่จะทำให้ "mv" ล้มเหลวอย่างเงียบ ๆ หรือไม่?


61

คำสั่งเช่นmv foo* ~/bar/ผลิตข้อความนี้ในการ stderr foo*ถ้ามีไฟล์ที่ตรงกับ

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

อย่างไรก็ตามในสคริปต์ฉันกำลังทำงานในกรณีนั้นจะไม่เป็นไรและฉันต้องการละเว้นข้อความนั้นจากบันทึกของเรา

มีวิธีใดที่ดีที่จะบอกmvให้เงียบแม้ว่าจะไม่มีสิ่งใดเคลื่อนไหว


13
mv foo* ~/bar/ 2> /dev/null?
โทมัสนีแมน

1
ใช่แล้ว ผมคิดว่าผมมีอะไรบางอย่าง "ดีกว่า" mvในใจเช่นสวิทช์บางอย่างสำหรับ :) แน่นอนว่าจะต้องทำ
Jonik

3
ผมเคยเห็นบางmvการใช้งานที่สนับสนุน-qตัวเลือกที่จะเงียบพวกเขาลง mvแต่ที่ไม่ได้เป็นส่วนหนึ่งของสเปคสำหรับ mvใน coreutils GNU เช่นไม่ได้มีตัวเลือกดังกล่าว
Thomas Nyman

ฉันไม่คิดว่าปัญหาคือทำอย่างไรให้ "mv" เงียบ แต่จะทำอย่างไรให้ถูกต้องมากขึ้น กำลังตรวจสอบว่ามีไฟล์ / dir foo * และผู้ใช้มีสิทธิ์ในการเขียนได้หรือไม่ในที่สุดก็เรียกใช้ "mv" หรือไม่?
ShâuShắc

คำตอบ:


46

คุณกำลังมองหาสิ่งนี้อยู่ใช่ไหม

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

20
หมายเหตุส่งคืนค่ายัง! = 0 ดังนั้นใด ๆset -e(ซึ่งควรใช้ในทุกเชลล์สคริปต์) จะล้มเหลว คุณสามารถเพิ่ม a || trueเพื่อปิดการตรวจสอบสำหรับคำสั่งเดียว
reto

10
@reto set -eไม่ควรใช้ในทุกเชลล์สคริปต์ในหลาย ๆ กรณีมันทำให้การจัดการข้อผิดพลาดมีความซับซ้อนยิ่งกว่าที่ไม่มี
Chris Down

1
@ ChrisDown ฉันเห็นจุดของคุณ มันมักจะเป็นทางเลือกระหว่างสองสิ่งชั่วร้าย แต่ถ้ามีข้อสงสัยฉันชอบการวิ่งที่ล้มเหลวมากกว่าความล้มเหลวที่ประสบความสำเร็จ (ซึ่งจะไม่ถูกสังเกตจนกว่าจะปรากฏต่อลูกค้า / ผู้ใช้) เชลล์สคริปเป็นแหล่งข้อมูลเหมืองที่ยิ่งใหญ่สำหรับผู้เริ่มต้นมันง่ายที่จะพลาดข้อผิดพลาดและสคริปต์ของคุณจะยุ่งเหยิง!
reto

14

ที่จริงแล้วฉันไม่คิดว่าการปิดเสียงmvเป็นวิธีการที่ดี (โปรดจำไว้ว่ามันอาจแจ้งให้คุณทราบถึงสิ่งอื่น ๆ ที่อาจเป็นที่สนใจของคุณ ... เช่นหายไป~/bar) คุณต้องการปิดเสียงเฉพาะในกรณีที่นิพจน์ glob ของคุณไม่ส่งคืนผลลัพธ์ ในความเป็นจริงค่อนข้างไม่รันเลย

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

bashไม่ได้ดูน่าสนใจมากและจะทำงานเฉพาะใน

หรือ

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

เพียงยกเว้นคุณจะอยู่ในbashกับnullglobชุด คุณต้องจ่ายราคาซ้ำ 3x ของรูปแบบ glob แม้ว่า


ปัญหาเล็ก ๆ น้อย ๆ แบบที่สอง: มันล้มเหลวที่จะย้ายไฟล์ชื่อfoo*เมื่อมันเป็นเพียงคนเดียวในไดเรกทอรีปัจจุบันซึ่งตรง foo*glob สิ่งนี้สามารถแก้ไขได้ด้วยนิพจน์กลมที่ไม่ตรงกับตัวเองอย่างแท้จริง [ 'fo[o]*' = "$(echo fo[o]*)" ] || mv fo[o]* ~/bar/เช่น
รานซาน

13

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

- GNU mvมีตัวเลือก "ปลายทางแรก" ที่ดี ( -t) และxargsสามารถข้ามการรันคำสั่งหากไม่มีการป้อนข้อมูลเลย ( -r) การใช้-print0และ-0สอดคล้องกันทำให้แน่ใจว่าจะไม่เป็นระเบียบเมื่อชื่อไฟล์มีช่องว่างและสิ่งอื่น ๆ "ตลก"


2
โปรดทราบว่า-maxdepth, -print0, -0และ-rนอกจากนี้ยังมีส่วนขยายของกนู (แม้ว่าบางส่วนของพวกเขาจะพบในการใช้งานอื่น ๆ ในปัจจุบัน)
Stéphane Chazelas

1
Poige ผู้ใช้จำนวนมากของเราไม่ใช้ Linux มันเป็นแบบฝึกหัดมาตรฐานที่นี่และมีประโยชน์มากในการชี้ให้เห็นว่าส่วนใดของคำตอบที่ไม่สามารถพกพาได้ ไซต์นี้มีชื่อว่า " Unix & Linux" และหลาย ๆ คนใช้ BSD หรือ Unix หรือ AIX หรือ macOS หรือระบบฝังตัวบางรูปแบบ อย่าใช้มันเป็นการส่วนตัว
terdon

ฉันไม่ได้ทำอะไรเป็นการส่วนตัว ฉันมีความเห็นว่าคุณได้ลบออกอย่างที่เห็น ดังนั้นฉันซ้ำ: ฉันพบความคิดเห็นเหล่านั้น "ทราบว่าเป็น GNU" ไม่มีจุดหมายสวย พวกเขาไม่ได้เพิ่มมูลค่าmvเลยประการแรกเพราะคำตอบของฉันมีข้อบ่งชี้ชัดเจนว่าเป็นไปตาม GNU เวอร์ชัน
poige

5

สิ่งสำคัญคือต้องตระหนักว่าจริงๆแล้วมันคือเชลล์ที่ขยายfoo*ไปยังรายการของชื่อไฟล์ที่ตรงกันดังนั้นจึงมีความmvสามารถเล็กน้อยที่จะทำเอง

ปัญหาตรงนี้คือเมื่อ glob ไม่ตรงกันเชลล์บางตัวbash(และเชลล์คล้ายบอร์นอื่น ๆ ส่วนใหญ่พฤติกรรมการบั๊กกี้นั้นถูกนำเสนอโดยบอร์นเชลล์ในช่วงปลายยุค 70) ผ่านรูปแบบคำต่อคำไปยังคำสั่ง

ดังนั้นที่นี่เมื่อfoo*ไม่ตรงกับไฟล์ใด ๆ แทนการยกเลิกคำสั่ง (เช่นก่อนบอร์นเปลือกหอยและเปลือกหอยทันสมัยหลายอย่างทำ) เปลือกผ่านคำต่อคำfoo*ไฟล์mvดังนั้นโดยทั่วไปขอให้ย้ายไฟล์ที่เรียกว่าmvfoo*

ไฟล์นั้นไม่มีอยู่ ถ้าเป็นเช่นนั้นจริง ๆ แล้วมันจะตรงกับรูปแบบดังนั้นmvรายงานข้อผิดพลาด หากรูปแบบได้รับการfoo[xy]แทนที่mvอาจมีการย้ายไฟล์ที่เรียกโดยไม่ได้ตั้งใจfoo[xy]แทนfooxและfooyไฟล์

ตอนนี้แม้ในเชลล์เหล่านั้นที่ไม่มีปัญหา (pre-Bourne, csh, tcsh, ปลา, zsh, bash -O failglob) คุณจะยังคงได้รับข้อผิดพลาดmv foo* ~/barแต่คราวนี้โดยเชลล์

หากคุณต้องการพิจารณาว่าไม่ใช่ข้อผิดพลาดหากไม่มีการจับคู่ไฟล์foo*และในกรณีนั้นไม่ย้ายอะไรคุณจะต้องสร้างรายการไฟล์ก่อน (ในลักษณะที่ไม่ทำให้เกิดข้อผิดพลาดเช่นโดยใช้nullglobตัวเลือกของ เชลล์บางตัว) และจากนั้นเรียกใช้เท่านั้นmvคือรายการไม่ว่างเปล่า

นั่นจะดีกว่าการซ่อนข้อผิดพลาดทั้งหมดของmv(เช่นการเพิ่ม2> /dev/nullจะ) ราวกับว่าmvล้มเหลวด้วยเหตุผลอื่นใดคุณอาจยังคงต้องการที่จะรู้ว่าทำไม

ใน zsh

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

หรือใช้ฟังก์ชันที่ไม่ระบุชื่อเพื่อหลีกเลี่ยงการใช้ตัวแปรชั่วคราว:

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

zshเป็นหนึ่งในเชลล์เหล่านั้นที่ไม่มีบัก Bourne และรายงานข้อผิดพลาดโดยไม่ต้องดำเนินการคำสั่งเมื่อ glob ไม่ตรงกัน (และnullglobตัวเลือกไม่ได้เปิดใช้งาน) ดังนั้นที่นี่คุณสามารถซ่อนzshข้อผิดพลาดและเรียกคืนได้ stderr ทำmvเช่นนั้นคุณจะยังคงเห็นmvข้อผิดพลาดถ้ามี แต่ไม่ใช่ข้อผิดพลาดเกี่ยวกับ globs ที่ไม่ตรงกัน:

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

หรือคุณสามารถใช้zargsซึ่งจะหลีกเลี่ยงปัญหาหากfoo*glob จะขยายเป็นไฟล์คนเกินไป

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

ใน ksh93:

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

ในทุบตี:

bashไม่มีไวยากรณ์ที่จะเปิดใช้งานnullglobสำหรับหนึ่ง glob เท่านั้นและfailglobตัวเลือกยกเลิกnullglobดังนั้นคุณต้องมีสิ่งต่าง ๆ เช่น:

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

หรือตั้งค่าตัวเลือกใน subshell เพื่อบันทึกต้องบันทึกก่อนและเรียกคืนหลังจากนั้น

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

ใน yash

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

ใน fish

ในเชลล์ปลาพฤติกรรม nullglob เป็นค่าเริ่มต้นสำหรับsetคำสั่งดังนั้นมันเป็นเพียง:

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

POSIXly

ไม่มีnullglobตัวเลือกใน POSIX shและไม่มีอาร์เรย์อื่นนอกเหนือจากพารามิเตอร์ตำแหน่ง มีเคล็ดลับที่คุณสามารถใช้เพื่อตรวจสอบว่ากลมจับคู่หรือไม่:

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

โดยการใช้ทั้ง a foo[*]และfoo*glob เราสามารถแยกความแตกต่างระหว่างเคสที่ไม่มีไฟล์ที่ตรงกันและอีกไฟล์หนึ่งที่มีไฟล์เดียวที่เรียกว่าfoo*(ซึ่งset -- foo*ไม่สามารถทำได้)

อ่านเพิ่มเติม:


3

อาจไม่ดีที่สุด แต่คุณสามารถใช้findคำสั่งเพื่อตรวจสอบว่าโฟลเดอร์ว่างเปล่าหรือไม่:

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

1
นี้จะให้ตัวอักษรเส้นทางการค้นหาเพื่อfoo* find
Kusalananda

3

ฉันสมมติว่าคุณกำลังใช้ bash เพราะข้อผิดพลาดนี้ขึ้นอยู่กับพฤติกรรมของ bash เพื่อขยาย globs ที่ไม่ตรงกันให้กับตัวเอง (โดยการเปรียบเทียบ zsh ทำให้เกิดข้อผิดพลาดเมื่อพยายามที่จะขยาย glob ที่ไม่ตรงกัน)

ดังนั้นวิธีแก้ไขปัญหาต่อไปนี้เป็นอย่างไร

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

การดำเนินการนี้จะเพิกเฉยต่อความล้มเหลวmvหากls -d foo*ล้มเหลวในขณะที่ยังคงบันทึกข้อผิดพลาดหากทำls foo*สำเร็จ แต่mvล้มเหลว (ระวังls foo*อาจล้มเหลวด้วยเหตุผลอื่นนอกเหนือจากที่foo*ไม่มีอยู่เช่นสิทธิ์ไม่เพียงพอปัญหากับ FS ฯลฯ ดังนั้นเงื่อนไขดังกล่าวจะถูกเพิกเฉยโดยวิธีนี้


ฉันส่วนใหญ่สนใจทุบตีใช่ (และฉันติดแท็กคำถามเป็นbash) +1 สำหรับการคำนึงถึงข้อเท็จจริงที่mvอาจล้มเหลวด้วยเหตุผลอื่นที่foo*ไม่ใช่ที่มีอยู่
Jonik

1
(ในทางปฏิบัติฉันอาจจะไม่ได้ใช้สิ่งนี้เพราะมันเป็นคำพูดที่ละเอียดและจุดlsคำสั่งจะไม่ชัดเจนสำหรับผู้อ่านในอนาคตอย่างน้อยก็ไม่มีความคิดเห็น)
Jonik

ls -d foo*สามารถกลับมาพร้อมกับสถานะออกไม่เป็นศูนย์ด้วยเหตุผลอื่นเช่นกันเช่นหลังจากln -s /nowhere foobar(อย่างน้อยก็มีlsการใช้งานบางอย่าง)
Stéphane Chazelas

3

คุณสามารถทำเช่น

mv 1>/dev/null 2>&1 foo* ~/bar/ หรือ mv foo* ~/bar/ 1&>2

ดูรายละเอียดเพิ่มเติมได้ที่: http://mywiki.wooledge.org/BashFAQ/055


mv foo* ~/bar/ 1&>2ไม่ได้เงียบคำสั่งมันส่งสิ่งที่จะได้รับใน stdout ไปยังยังอยู่ใน stderr
Chris Down

และหากคุณกำลังใช้ mingw ใต้ windows (เช่นฉัน) แทนที่/dev/nullด้วยNUL
Rian Sanderson

คำสั่งนี้ทำงานอย่างไร เครื่องหมายแอมเปอร์แซนด์ทำอะไร ทำอะไร1และ2หมายความว่าอย่างไร
Aaron Franke

@AaronFranke 1 คือ stdout และ 2 คือ stderr pipe โดยค่าเริ่มต้นเมื่อกระบวนการ Linux ได้ถูกสร้างขึ้น รู้เพิ่มเติมเกี่ยวกับไปป์ในคำสั่ง Linux คุณสามารถเริ่มต้นได้ที่นี่ - digitalocean.com/community/tutorials/…
Mithun B


2

หากคุณกำลังจะใช้ Perl คุณก็อาจไปตลอดทาง:

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

หรือเป็นหนึ่งซับ:

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

(สำหรับรายละเอียดของmoveคำสั่งดูเอกสารประกอบสำหรับไฟล์ :: คัดลอก )


-1

แทน

mv foo* ~/bar/

คุณทำได้

cp foo* ~/bar/
rm foo* 

ง่าย ๆ อ่านง่าย :)


6
การคัดลอกแล้วลบจะใช้เวลานานกว่าการย้ายที่เรียบง่าย และจะไม่ทำงานหากไฟล์มีขนาดใหญ่กว่าพื้นที่ว่างในดิสก์ที่มีอยู่
Anthon

11
OP ต้องการโซลูชันที่เงียบ ไม่ว่ากันcpหรือrmเงียบหากfoo*ไม่มีอยู่
Ron Rothman

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

ไม่ว่าคำสั่งดังกล่าวจะประสบความสำเร็จหรือไม่เราสามารถหาได้โดยออกจากสถานะของคำสั่งก่อนหน้า

command: echo $?

ถ้าผลลัพธ์ของ echo $? เป็นค่าอื่นที่ไม่ใช่ 0 หมายถึงคำสั่ง unsucessfull หากเอาต์พุตคือ 0 หมายถึงคำสั่งคือ successfull

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.