จัดการชื่อไฟล์ piped จากคำสั่ง find


10

ฉันค่อนข้างใหม่กับ Bash และฉันพยายามทำบางสิ่งที่บนพื้นผิวนั้นค่อนข้างตรงไปตรงมา - ลองค้นหาลำดับชั้นของไดเรกทอรีเพื่อรับไฟล์ * .wma ทั้งหมดไปป์ที่เอาต์พุตไปยังคำสั่งที่ฉันแปลงเป็น mp3 และ บันทึกไฟล์ที่แปลงเป็น. mp3 ฉันคิดว่าคำสั่งควรมีลักษณะดังต่อไปนี้ (ฉันได้ละทิ้งคำสั่งการแปลงเสียงและฉันใช้ echo แทนภาพประกอบ):

$ find ./ -name '*.wma' -type f -print0 | xargs -0 -I f echo ${f%.*}.mp3

ตามที่ฉันเข้าใจแล้ว -print0 arg จะให้ฉันจัดการกับชื่อไฟล์ที่มีช่องว่าง ฉันคาดหวังว่า (เนื่องจาก xargs) ว่าแต่ละพา ธ ไฟล์จาก find ถูกดักจับใน f และการใช้ซับสตริงจับคู่ / ลบจากจุดสิ้นสุดของสตริงนั้นฉันควรจะสะท้อนพา ธ ไฟล์ดั้งเดิมด้วย mp3 ส่วนขยายแทน wma อย่างไรก็ตามแทนที่จะเป็นผลลัพธ์นี้ฉันเห็นสิ่งต่อไปนี้:

*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
*.mp3
...

ดังนั้นคำถามของฉัน (นอกเหนือจาก 'สิ่งที่ฉันทำผิดที่นี่') คือสิ่งนี้ - ทำค่าที่เป็นผลลัพธ์ของการดำเนินการไปป์จำเป็นต้องได้รับการปฏิบัติที่แตกต่างกันในการดำเนินการจัดการสตริงที่แตกต่างจากการกำหนดตัวแปร ?


1
ไม่มีเหตุผลที่จะใช้ไม่ได้กับxargs findมันมาพร้อมกับ-execตัวเลือก คุณสามารถเพิ่มคำสั่งที่คุณจะใช้กับคำถามของคุณและใครบางคนสามารถแสดงfindคำสั่งที่ถูกต้องให้คุณได้หรือไม่?
ixtmixilix

ใช่ (ตามที่ระบุไว้ด้านล่าง) ตราบใดที่ฉันยังคงสามารถจัดการสตริงในแต่ละผลลัพธ์ของคำสั่ง find (เช่น{}สมาชิก)
Howard Dierking

จริงมีกรณีขอบที่มีความเหมาะสมมากกว่าxargs execดู stackpost stackoverflow.com/questions/896808/find-exec-cmd-vs-xargsสำหรับกรณีและปัญหา
d34dh0r53

@ d34dh0r53 กรณีขอบอะไร? เธรดที่คุณลิงก์ไปไม่ได้ชี้ไปที่ใด
Gilles 'หยุดความชั่วร้าย'

คำตอบ:


8

ตามที่คำตอบอื่น ๆ ระบุไว้แล้ว${f%.*}จะถูกขยายโดยเชลล์ก่อนที่จะรันxargsคำสั่ง คุณต้องใช้การขยายนี้จะเกิดขึ้นหนึ่งครั้งสำหรับชื่อไฟล์แต่ละไฟล์โดยที่ตัวแปร shell fตั้งค่าเป็นชื่อไฟล์ (การส่งผ่าน-I fไม่ได้ทำเช่นนั้น: xargsไม่มีความคิดของตัวแปรเชลล์มันจะค้นหาสตริงfในคำสั่งดังนั้นหากคุณต้องการ ใช้เช่นxargs -I e echo …มันจะมีคำสั่งดำเนินการเช่น./somedir/somefile.wmacho .mp3)

ใช้วิธีนี้xargsต่อไปบอกให้เรียกใช้เชลล์ที่สามารถทำการขยายได้ Better, tell find- xargsเป็นเครื่องมือที่ล้าสมัยและยากต่อการใช้อย่างถูกต้องเนื่องจาก find รุ่นที่ทันสมัยมีโครงสร้างที่ทำงานเหมือนกัน (และอื่น ๆ ) ที่มีปัญหาเรื่องท่อประปาน้อยลง แทนการวิ่งfind … -print0 | xargs -0 command …find … -exec command … {} +

find . -name '*.wma' -type f -exec sh -c 'for f; do echo "${f%.*}.mp3"; done' _ {} +

อาร์กิวเมนต์_อยู่$0ในเชลล์ ชื่อไฟล์จะถูกส่งเป็นอาร์กิวเมนต์ตำแหน่งที่for f; do …วนซ้ำ เวอร์ชันที่ง่ายกว่าของคำสั่งนี้เรียกใช้งานเชลล์แยกต่างหากสำหรับแต่ละไฟล์ซึ่งเทียบเท่า แต่ช้ากว่าเล็กน้อย:

find . -name '*.wma' -type f -exec sh -c 'echo "${0%.*}.mp3"' {} \;

คุณไม่จำเป็นต้องใช้findที่นี่จริง ๆสมมติว่าคุณใช้เชลล์ล่าสุดอย่างสมเหตุสมผล (ksh93, bash ≥4.0หรือ zsh) ในทุบตีใส่shopt -s globstarในของคุณ.bashrcเพื่อเปิดใช้งาน**/รูปแบบ glob เพื่อชดเชยในไดเรกทอรีย่อย (ใน ksh นั่นคือset -o globstar) จากนั้นคุณสามารถเรียกใช้

for f in **/*.wma; do
  echo "${f%.*}.mp3"
done

(ถ้าคุณมีไดเรกทอรีที่เรียกว่า*.wmaเพิ่ม[ -f "$f" ] || continueที่จุดเริ่มต้นของลูป)


คำอธิบายที่ดีรวมทั้งชี้ให้ฉันในทิศทางที่ฉันไม่ได้ตระหนักถึง (ยกระดับเปลือกหอยของฉันเพื่อรับฟังก์ชั่น globstar) - ในที่สุดการวนซ้ำแบบซ้ำก็เป็นวิธีแก้ปัญหาที่ทำให้คำสั่งง่ายและสำเร็จทุกอย่างที่ฉันพยายามทำ . ขอบคุณ!
Howard Dierking

6

ในกรณีของการประเมินผลการแก้ปัญหาของคุณ $ {F%. *}. mp3 จะทำในเปลือกที่คุณกำลังเรียกใช้คำสั่งทั้งในไม่ได้อยู่ในเปลือกคดเคี้ยวโดยxargs และในเชลล์ของคุณไม่มีตัวแปรfมันถูกแทนที่ด้วยสตริงว่าง

โซลูชันที่ใช้xargsด้วย-I f :

% cat 1.sh 
#!/bin/sh
f=$1
echo ${f%.*}.mp3
% /bin/ls -1
1.sh
aaa.wma
bbbb.wma
ccccc.wma
% find ./ -name '*.wma' -type f -print0 | xargs -0 -I f ./1.sh f
./aaa.mp3
./ccccc.mp3
./bbbb.mp3

แต่ฉันจะทำแบบนี้:

% find ./ -name '*.wma' -type f | sed 's,\.wma$,.mp3,'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.