ทำให้ xargs จัดการกับชื่อไฟล์ที่มีช่องว่าง


252
$ ls *mp3 | xargs mplayer  

Playing Lemon.  
File not found: 'Lemon'  
Playing Tree.mp3.  
File not found: 'Tree.mp3'  

Exiting... (End of file)  

คำสั่งของฉันล้มเหลวเนื่องจากไฟล์ "Lemon Tree.mp3" มีช่องว่างดังนั้น xargs จึงคิดว่าเป็นสองไฟล์ ฉันสามารถค้นหา + xargs ใช้งานชื่อไฟล์ได้ไหม


แทนที่จะls |grep mp3 |sed -n "7p"ใช้ก็สามารถecho "Lemon Tree.mp3"ใช้ได้
Micha Wiedenmann


คำถามนี้ยังตอบโดยstackoverflow.com/a/33528111/94687
imz - Ivan Zakharyaschev

คำตอบ:


256

xargsคำสั่งต้องใช้ตัวอักษรสีขาวพื้นที่ (แท็บช่องว่างสายใหม่) เป็นตัวคั่น คุณสามารถ จำกัด ให้แคบลงสำหรับอักขระบรรทัดใหม่ ('\ n') ด้วย-dตัวเลือกดังนี้:

ls *.mp3 | xargs -d '\n' mplayer

ใช้งานได้กับ GNU xargs เท่านั้น สำหรับระบบ BSD ใช้-0ตัวเลือกดังนี้:

ls *.mp3 | xargs -0 mplayer

วิธีนี้ง่ายกว่าและทำงานกับ GNU xargs ได้เช่นกัน


6
คำตอบที่ดีที่สุดสำหรับการใช้งานทั่วไป! สิ่งนี้ใช้ได้แม้ว่าคำสั่งก่อนหน้าของคุณจะไม่ "ค้นหา"
nexayq

28
น่าเสียดายที่ตัวเลือกนี้ไม่สามารถใช้ได้กับ OS X
Thomas Tempelmann

25
@Thomas สำหรับ OS X การตั้งค่าสถานะคือ-Eอดีต:xargs -E '\n'

30
บน OS X, -E '\ n' ไม่มีผลสำหรับฉันและฉันจะไม่คาดหวังว่ามันจะทำการปรับเปลี่ยน eofstr ไม่ใช่ตัวคั่นเรคคอร์ด อย่างไรก็ตามฉันสามารถใช้แฟล็ก -0 เป็นวิธีแก้ปัญหาแม้ว่าคำสั่งก่อนหน้านี้จะไม่ 'ค้นหา' โดยจำลองผลกระทบของแฟล็ก -print0 ของ find ในอินพุตของฉันเช่น: ls * mp3 | tr '\ n' '\ 0' | xargs -0 mplayer
biomiker

10
สำหรับ OS X คุณสามารถ "BREW ติดตั้ง findutils" ซึ่งจะช่วยให้คุณคำสั่ง "gxargs" ที่จะมีสวิทช์ -d
Tom De Leu

213

ยูทิลิตี xargs อ่านพื้นที่แท็บบรรทัดใหม่และสตริงที่คั่นด้วยจุดสิ้นสุดไฟล์จากอินพุตมาตรฐานและเรียกใช้งานยูทิลิตี้ด้วยสตริงเป็นอาร์กิวเมนต์

คุณต้องการหลีกเลี่ยงการใช้พื้นที่เป็นตัวคั่น สิ่งนี้สามารถทำได้โดยการเปลี่ยนตัวคั่นสำหรับ xargs ตามคู่มือ:

 -0      Change xargs to expect NUL (``\0'') characters as separators,
         instead of spaces and newlines.  This is expected to be used in
         concert with the -print0 function in find(1).

เช่น:

 find . -name "*.mp3" -print0 | xargs -0 mplayer

เพื่อตอบคำถามเกี่ยวกับการเล่น mp3 ที่เจ็ด; มันง่ายกว่าที่จะเรียกใช้

 mplayer "$(ls *.mp3 | sed -n 7p)"

10
นี่คือการใช้ GNU findและ GNU xargs; ไม่ใช่ทุกเวอร์ชันของโปรแกรมเหล่านั้นที่สนับสนุนตัวเลือกเหล่านั้น (แม้ว่าจะมีกรณีที่ควรทำ)
Jonathan Leffler

1
@JonathanLeffler s / GNU / FreeBSD / g; POSIX เศร้าที่กลัวอักขระ NUL ในไฟล์ข้อความและยังไม่ได้รับการบำบัดที่เพียงพอ :-) คำแนะนำของฉันในความเป็นจริงตัวเลือกที่ไม่ใช่พกพา
Jens

6
และ Mac OS X (อนุพันธ์ BSD) มีfindด้วย-print0และมีxargs -0AFAIK, HP-UX, AIX และ Solaris ทำไม่ได้ (แต่ฉันยืนเพื่อแก้ไข: HP-UX 11i ไม่ได้, Solaris 10 ไม่ได้, AIX 5.x ไม่ได้ แต่พวกเขาไม่ใช่เวอร์ชันปัจจุบัน ) มันไม่ยากที่จะเปลี่ยนแปลงsedตัวอย่างเช่นการใช้ 'lines' ลงท้ายด้วย'\0'แทนที่จะเป็น'\n'และ POSIX 2008 getdelim()จะทำให้ง่ายต่อการจัดการ
Jonathan Leffler

2
เคล็ดลับ +1 +1 สำหรับใช้กับพา ธ ไฟล์ที่มีไฟล์รายการ: cat $ file_paths_list_file | perl -ne 's | \ n | \ 000 | g; print' | xargs -0 zip $ zip_package
Yordan Georgiev

2
ความคิดที่ดีในการแทนที่บรรทัดใหม่ด้วย NUL - ฉันต้องทำสิ่งนี้บนระบบฝังตัวที่ไม่มี GNU find หรือ GNU xargs หรือ perl - แต่คำสั่ง tr สามารถใช้ประโยชน์ในการทำสิ่งเดียวกัน: cat $ file_paths_list_file | tr '\ n' '\ 0' | xargs -0 du -hms
joensson


16

xargs บน MacOS ไม่มีตัวเลือก -d ดังนั้นวิธีนี้จึงใช้ -0 แทน

รับ ls เพื่อส่งออกหนึ่งไฟล์ต่อบรรทัดจากนั้นแปล newlines เป็น nulls และบอก xargs ให้ใช้ nulls เป็นตัวคั่น:

ls -1 *mp3 | tr "\n" "\0" | xargs -0 mplayer


8
find . -name 'Lemon*.mp3' -print0 | xargs 0 -i mplayer '{}' 

สิ่งนี้ช่วยฉันในการลบไฟล์ต่าง ๆ ด้วยช่องว่าง ควรทำงานกับ mplayer ด้วย เคล็ดลับที่จำเป็นคือคำพูด (ทดสอบบน Linux Xubuntu 14.04)


7

Dick.Guertin คำตอบ [1] แนะนำว่าใคร ๆ ก็สามารถหลบหนีช่องว่างในชื่อไฟล์เป็นทางเลือกที่มีค่าสำหรับโซลูชันอื่น ๆ ที่แนะนำที่นี่ (เช่นการใช้อักขระ null เป็นตัวคั่นแทนที่จะเป็นช่องว่าง) แต่มันอาจจะง่ายกว่า - คุณไม่จำเป็นต้องมีตัวละครที่พิเศษ คุณสามารถเพิ่มช่องว่างที่หลบหนีได้โดยตรง:

ls | grep ' ' | sed 's| |\\ |g' | xargs ...

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

ls | sed 's| |\\ |g' | xargs ...

แน่นอนว่าชื่อไฟล์อาจมีช่องว่างอื่นที่ไม่ใช่ช่องว่าง (เช่นแท็บ):

ls | sed -r 's|[[:blank:]]|\\\1|g' | xargs ...

สมมติว่าคุณมี sed ที่รองรับ -r (Extended regex) เช่น GNU sed หรือ bsd sed เวอร์ชันล่าสุด (เช่น FreeBSD ซึ่งดั้งเดิมสะกดคำว่า "-E" ก่อน FreeBSD 8 และรองรับทั้ง -r & -E เพื่อความเข้ากันได้ ผ่าน FreeBSD 11 เป็นอย่างน้อย) มิฉะนั้นคุณสามารถใช้นิพจน์คลาสวงเล็บอักขระพื้นฐาน regex และป้อนอักขระช่องว่างและอักขระแท็บใน[]ตัวคั่นด้วยตนเอง

[1] นี่อาจจะเหมาะสมกว่าในฐานะความคิดเห็นหรือการแก้ไขคำตอบนั้น แต่ในขณะนี้ฉันไม่มีชื่อเสียงพอที่จะแสดงความคิดเห็นและสามารถแนะนำการแก้ไขได้เท่านั้น เนื่องจากรูปแบบหลังดังกล่าวข้างต้น (ไม่มี grep) เปลี่ยนแปลงพฤติกรรมของคำตอบเดิมของ Dick.Guertin การแก้ไขโดยตรงอาจไม่เหมาะสมอยู่ดี


1
พวกยูนิกซ์ที่คลั่งไคล้ที่เรียกใช้สคริปต์ที่ตั้งชื่อไฟล์โดยไม่คำนึงถึงผลลัพธ์ของพวกเขานั่นคือใคร
andrew lorien

4

ls | grep mp3 | sed -n "7p" | xargs -i mplayer {}

โปรดทราบว่าในคำสั่งข้างต้นxargsจะเรียกmplayerใหม่สำหรับแต่ละไฟล์ สิ่งนี้อาจไม่เป็นที่พึงปรารถนาmplayerแต่อาจไม่เป็นผลสำหรับเป้าหมายอื่น ๆ


1
นอกจากนี้ยังมีประโยชน์สำหรับคำตอบที่มีอยู่ แต่มันจะคุ้มค่าที่จะสังเกตว่าสิ่งนี้จะทำให้เกิดmplayerการเรียกว่าใหม่สำหรับแต่ละไฟล์ มันเป็นเรื่องสำคัญถ้าคุณลองเช่น... | xargs -I{} mplayer -shuffle {}: -shuffleนี้จะเล่นในลำดับที่กำหนดอย่างสมบูรณ์แม้จะมี

1
มันมักจะไม่ตั้งใจ xargsส่วนใหญ่จะใช้กับคำสั่งที่ยอมรับรายชื่อไฟล์ (ตัวอย่างง่าย ๆ :) rmและพยายามที่จะส่งชื่อไฟล์ให้มากที่สุดเท่าที่จะเหมาะสมกับการร้องขอแต่ละครั้งโดยแยกออกเป็นหลาย ๆ การร้องขอหากจำเป็นเท่านั้น คุณสามารถเห็นความแตกต่างเมื่อคุณใช้คำสั่งที่มองเห็นการร้องขอแต่ละครั้งเช่นecho(ค่าเริ่มต้น): seq 0 100000 | xargsพิมพ์ตัวเลขทั้งหมดจาก 0 ถึง 23695 (เฉพาะแพลตฟอร์ม แต่นั่นคือสิ่งที่เกิดขึ้นในระบบของฉัน) ในบรรทัดแรกถึง 45539 ในบรรทัดที่ 2 ฯลฯ และคุณพูดถูกสำหรับคำสั่งส่วนใหญ่มันจะไม่สำคัญ

4

บน macOS 10.12.x (Sierra) หากคุณมีช่องว่างในชื่อไฟล์หรือไดเรกทอรีย่อยคุณสามารถใช้สิ่งต่อไปนี้:

find . -name '*.swift' -exec echo '"{}"' \; |xargs wc -l

2

ขึ้นอยู่กับ (ก) ว่าคุณแนบหมายเลข 7 อย่างไรเมื่อเทียบกับพูดเลม่อนและ (ข) ชื่อไฟล์ใด ๆ ของคุณมีการขึ้นบรรทัดใหม่ (และคุณเต็มใจเปลี่ยนชื่อพวกเขาหรือไม่)

มีหลายวิธีที่จะจัดการกับมัน แต่บางคนก็คือ:

mplayer Lemon*.mp3

find . -name 'Lemon*.mp3' -exec mplayer {} ';'

i=0
for mp3 in *.mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

for mp3 in *.mp3
do
    case "$mp3" in
    (Lemon*) mplayer "$mp3";;
    esac
done

i=0
find . -name *.mp3 |
while read mp3
do
    i=$((i+1))
    [ $i = 7 ] && mplayer "$mp3"
done

readห่วงไม่ทำงานถ้าชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่; คนอื่นทำงานอย่างถูกต้องแม้จะมีการขึ้นบรรทัดใหม่ในชื่อ (เว้นวรรคอย่างเดียว) สำหรับเงินของฉันถ้าคุณมีชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่คุณควรเปลี่ยนชื่อไฟล์โดยไม่ต้องขึ้นบรรทัดใหม่ การใช้เครื่องหมายคำพูดคู่ล้อมรอบชื่อไฟล์คือกุญแจสำคัญในการวนซ้ำทำงานอย่างถูกต้อง

หากคุณมี GNU findและ GNU xargs(หรือ FreeBSD (* BSD?) หรือ Mac OS X) คุณสามารถใช้ตัวเลือก-print0และ-0ตัวเลือกเช่นใน:

find . -name 'Lemon*.mp3' -print0 | xargs -0 mplayer

สิ่งนี้ใช้ได้ผลโดยไม่คำนึงถึงเนื้อหาของชื่อ (อักขระสองตัวเท่านั้นที่ไม่สามารถปรากฏในชื่อไฟล์คือเครื่องหมายทับและ NUL และเครื่องหมายทับทำให้ไม่มีปัญหาในเส้นทางไฟล์ดังนั้นการใช้ NUL เป็นตัวคั่นชื่อครอบคลุมทุกอย่าง) อย่างไรก็ตามถ้าคุณต้องการกรอง 6 รายการแรกคุณต้องมีโปรแกรมที่จัดการ 'lines' ซึ่งจบลงด้วย NUL แทนที่จะขึ้นบรรทัดใหม่ ... และฉันไม่แน่ใจว่ามี

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


2

ฉันรู้ว่าฉันไม่ได้ตอบxargsคำถามโดยตรงแต่ก็คุ้มค่าการกล่าวขวัญfindของ-execตัวเลือก

รับระบบไฟล์ต่อไปนี้:

[root@localhost bokeh]# tree --charset assci bands
bands
|-- Dream\ Theater
|-- King's\ X
|-- Megadeth
`-- Rush

0 directories, 4 files

คำสั่ง find สามารถสร้างขึ้นเพื่อจัดการพื้นที่ใน Dream Theatre และ King's X ดังนั้นเพื่อค้นหามือกลองของแต่ละวงโดยใช้ grep:

[root@localhost]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

ใน-execตัวเลือก{}ย่อมาจากชื่อไฟล์รวมถึงเส้นทาง โปรดทราบว่าคุณไม่จำเป็นต้องหลบหนี

ความแตกต่างระหว่าง-execterminators ของ ( +และ\;) คือ+กลุ่มที่เป็นชื่อไฟล์จำนวนมากที่มันสามารถลงบนบรรทัดคำสั่งเดียว โดยที่\;จะรันคำสั่งสำหรับแต่ละชื่อไฟล์

ดังนั้นfind bands/ -type f -exec grep Drums {} +จะส่งผลให้:

grep Drums "bands/Dream Theater" "bands/Rush" "bands/King's X" "bands/Megadeth"

และfind bands/ -type f -exec grep Drums {} \;จะส่งผลให้:

grep Drums "bands/Dream Theater"
grep Drums "bands/Rush"
grep Drums "bands/King's X"
grep Drums "bands/Megadeth"

ในกรณีgrepนี้มีผลข้างเคียงของการพิมพ์ชื่อไฟล์หรือไม่

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} \;
Drums:Mike Mangini
Drums: Neil Peart
Drums:Jerry Gaskill
Drums:Dirk Verbeuren

[root@localhost bokeh]# find bands/ -type f -exec grep Drums {} +
bands/Dream Theater:Drums:Mike Mangini
bands/Rush:Drums: Neil Peart
bands/King's X:Drums:Jerry Gaskill
bands/Megadeth:Drums:Dirk Verbeuren

แน่นอนgrepเป็นตัวเลือก-hและ-Hจะควบคุมหรือไม่ว่าชื่อไฟล์จะถูกพิมพ์โดยไม่คำนึงถึงวิธีการที่grepเรียกว่า


xargs

xargs ยังสามารถควบคุมว่าไฟล์ Man อยู่บนบรรทัดคำสั่งได้อย่างไร

xargsโดยกลุ่มเริ่มต้นข้อโต้แย้งทั้งหมดลงในหนึ่งบรรทัด เพื่อที่จะทำสิ่งเดียวที่ไม่ใช้-exec \; xargs -lโปรดทราบว่า-tตัวเลือกบอกxargsให้พิมพ์คำสั่งก่อนดำเนินการ

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n' -l -t grep Drums
grep Drums ./bands/Dream Theater 
Drums:Mike Mangini
grep Drums ./bands/Rush 
Drums: Neil Peart
grep Drums ./bands/King's X 
Drums:Jerry Gaskill
grep Drums ./bands/Megadeth 
Drums:Dirk Verbeuren

ดูว่า-lตัวเลือกนั้นบอกให้ xargs เรียกใช้ grep สำหรับทุกชื่อไฟล์

กับค่าเริ่มต้น (เช่นไม่มี-lตัวเลือก):

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush ./bands/King's X ./bands/Megadeth 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren

xargsมีการควบคุมที่ดีขึ้นเกี่ยวกับจำนวนไฟล์ที่สามารถอยู่ในบรรทัดคำสั่ง ให้-lตัวเลือกจำนวนไฟล์สูงสุดต่อคำสั่ง

[root@localhost bokeh]# find ./bands -type f  | xargs -d '\n'  -l2 -t grep Drums
grep Drums ./bands/Dream Theater ./bands/Rush 
./bands/Dream Theater:Drums:Mike Mangini
./bands/Rush:Drums: Neil Peart
grep Drums ./bands/King's X ./bands/Megadeth 
./bands/King's X:Drums:Jerry Gaskill
./bands/Megadeth:Drums:Dirk Verbeuren
[root@localhost bokeh]# 

เห็นว่ากำลังดำเนินการมีสองชื่อไฟล์เพราะgrep-l2


1

ให้ชื่อเฉพาะของโพสต์นี้นี่คือคำแนะนำของฉัน:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g'

แนวคิดคือการแปลงช่องว่างเป็นอักขระเฉพาะเช่น '<' จากนั้นเปลี่ยนเป็น '\', แบ็กสแลชตามด้วยช่องว่าง จากนั้นคุณสามารถไพพ์นั้นลงในคำสั่งที่คุณต้องการเช่น:

ls | grep ' ' | tr ' ' '<' | sed 's|<|\\ |g' | xargs -L1 GetFileInfo

กุญแจที่นี่อยู่ในคำสั่ง 'tr' และ 'sed' และคุณสามารถใช้อักขระใด ๆ นอกเหนือจาก '<' เช่น '?' หรือแม้แต่อักขระแท็บ


จุดประสงค์ของอ้อมtrคืออะไร ทำไมไม่เพียงls *.mp3 | sed -n '7!b;s/\([[:space:]]\)/\\\1/g;p'?
tripleee

1
ฉันพบว่า "tr '' '?'" ไม่จำเป็นต้องใช้ "sed" ซิงเกิ้ล "?" อักขระไม่ว่างเปล่า แต่ตรงกับอักขระเดี่ยวใด ๆ ในกรณีนี้: ว่างเปล่า อัตราต่อรองของการเป็นอย่างอื่นนั้นค่อนข้างเล็กและยอมรับได้เนื่องจากคุณพยายามประมวลผลไฟล์ทั้งหมดที่ลงท้ายด้วย. mp3: "ls | grep '' | tr '' '?' | xargs -L1 GetFileInfo "
Dick Guertin

นอกจากนี้คุณยังสามารถจัดการ "แท็บ" ในเวลาเดียวกัน: tr '\ t' '??' จัดการทั้งสอง
Dick Guertin

1

โซลูชันทางเลือกมีประโยชน์ ...

นอกจากนี้คุณยังสามารถเพิ่มตัวละครโมฆะในตอนท้ายของบรรทัดของคุณโดยใช้ Perl แล้วใช้-0ตัวเลือกใน xargs ไม่เหมือนกับ xargs -d '\ n' (ในคำตอบที่ได้รับการอนุมัติ) - ใช้ได้ทุกที่รวมถึง OS X

ตัวอย่างเช่นในการทำรายการซ้ำ (เรียกใช้ย้าย ฯลฯ ) ไฟล์ MPEG3 ซึ่งอาจมีช่องว่างหรือตัวละครตลกอื่น ๆ - ฉันต้องการใช้:

find . | grep \.mp3 | perl -ne 'chop; print "$_\0"' | xargs -0  ls

(หมายเหตุ: สำหรับการกรองฉันชอบไวยากรณ์ "| grep" ที่ง่ายต่อการจำได้ง่ายกว่าเพื่อหาอาร์กิวเมนต์ "find's" --name)

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