xargs พร้อมการเปลี่ยนเส้นทาง stdin / stdout


21

ฉันต้องการที่จะทำงาน:

./a.out < x.dat > x.ans

สำหรับแต่ละ * .datไฟล์ในไดเรกทอรี

แน่นอนว่ามันสามารถทำได้โดย bash / python / สคริปต์ใด ๆ แต่ฉันชอบเขียนเซ็กซี่หนึ่งซับ ทั้งหมดที่ฉันสามารถเข้าถึงคือ (ยังไม่มี stdout ใด ๆ ):

ls A/*.dat | xargs -I file -a file ./a.out

แต่-a in xargs ไม่เข้าใจแทนที่ 'ไฟล์' str

ขอบคุณสำหรับความช่วยเหลือ.


นอกเหนือจากคำตอบที่ดีอื่น ๆ ที่นี่คุณสามารถใช้ตัวเลือก -a ของ GNU xargs
James Youngman

คำตอบ:


30

ครั้งแรกของทั้งหมดไม่ได้ใช้lsออกเป็นรายชื่อไฟล์ findใช้การขยายตัวของเปลือกหรือ ดูด้านล่างสำหรับผลที่อาจเกิดขึ้นจากการใช้ls + xargsในทางที่ผิดและตัวอย่างของการxargsใช้งานที่เหมาะสม

1. วิธีง่ายๆ: สำหรับลูป

หากคุณต้องการประมวลผลไฟล์ภายใต้A/การforวนรอบแบบง่ายควรจะเพียงพอ:

for file in A/*.dat; do ./a.out < "$file" > "${file%.dat}.ans"; done

2. pre1ทำไมไม่   ls | xargs ?

นี่คือตัวอย่างของวิธีการที่สิ่งเลวร้ายอาจเปลี่ยนไปถ้าคุณใช้lsกับxargsงาน พิจารณาสถานการณ์สมมติต่อไปนี้:

  • ก่อนอื่นมาสร้างไฟล์เปล่า:

    $ touch A/mypreciousfile.dat\ with\ junk\ at\ the\ end.dat
    $ touch A/mypreciousfile.dat
    $ touch A/mypreciousfile.dat.ans
    
  • ดูไฟล์และพวกเขาไม่มีอะไร:

    $ ls -1 A/
    mypreciousfile.dat
    mypreciousfile.dat with junk at the end.dat
    mypreciousfile.dat.ans
    
    $ cat A/*
    
  • ใช้คำสั่ง magic โดยใช้xargs:

    $ ls A/*.dat | xargs -I file sh -c "echo TRICKED > file.ans"
    
  • ผลลัพธ์:

    $ cat A/mypreciousfile.dat
    TRICKED with junk at the end.dat.ans
    
    $ cat A/mypreciousfile.dat.ans
    TRICKED
    

เพื่อให้คุณได้มีการจัดการเพียงเพื่อแทนที่ทั้งสองและmypreciousfile.dat mypreciousfile.dat.ansหากมีเนื้อหาใด ๆ ในไฟล์เหล่านั้นแสดงว่าถูกลบไปแล้ว


2. การใช้   xargs : วิธีที่เหมาะสมด้วย  find 

หากคุณต้องการที่จะยืนยันการใช้xargsให้ใช้-0(ชื่อที่ลงท้ายด้วย null):

find A/ -name "*.dat" -type f -print0 | xargs -0 -I file sh -c './a.out < "file" > "file.ans"'

สังเกตเห็นสองสิ่ง:

  1. วิธีนี้คุณจะสร้างไฟล์โดย.dat.ansสิ้นสุด
  2. สิ่งนี้จะแตกถ้าชื่อไฟล์บางอันมีเครื่องหมายคำพูด ( ")

ปัญหาทั้งสองสามารถแก้ไขได้ด้วยวิธีต่าง ๆ ของการเรียกใช้เชลล์:

find A/ -name "*.dat" -type f -print0 | xargs -0 -L 1 bash -c './a.out < "$0" > "${0%dat}ans"'

3. ทั้งหมดทำภายใน find ... -exec

 find A/ -name "*.dat" -type f -exec sh -c './a.out < "{}" > "{}.ans"' \;

นี้อีกครั้งผลิตไฟล์และจะทำลายถ้าชื่อไฟล์ประกอบด้วย.dat.ans "หากต้องการทำสิ่งนั้นให้ใช้bashและเปลี่ยนวิธีการเรียกใช้:

 find A/ -name "*.dat" -type f -exec bash -c './a.out < "$0" > "${0%dat}ans"' {} \;

+1 สำหรับการกล่าวถึงไม่แยกวิเคราะห์ผลลัพธ์ของ ls
rahmu

2
ตัวเลือกที่ 2 พักเมื่อชื่อไฟล์มี ".
thiton

2
เป็นจุดที่ดีมากขอบคุณ! ฉันจะอัปเดตตามนั้น
rozcietrzewiacz

ฉันต้องการเพียงแค่พูดถึงว่าถ้าzshใช้เป็นเชลล์ (และไม่ได้ตั้ง SH_WORD_SPLIT) ทุกกรณีพิเศษที่น่ารังเกียจ (ช่องว่างสีขาว "ในชื่อไฟล์ ฯลฯ ) ไม่จำเป็นต้องพิจารณาfor file in A/*.dat; do ./a.out < $file > ${file%.dat}.ans ; doneงานเล็ก ๆ น้อย ๆในทุกกรณี
jofel

-1 เพราะฉันพยายามที่จะคิดออกว่าจะทำ xargs กับ stdin findและคำถามของฉันมีอะไรจะทำอย่างไรกับไฟล์หรือ
Mehrdad

2

ลองทำสิ่งนี้ (ไวยากรณ์อาจแตกต่างกันเล็กน้อยขึ้นอยู่กับเชลล์ที่คุณใช้):

$ for i in $(find A/ -name \*.dat); do ./a.out < ${i} > ${i%.dat}.ans; done


ที่จะไม่ทำงาน มันจะพยายามทำงานกับสิ่งต่าง ๆ ที่ชอบsomefile.dat.datและเปลี่ยนเส้นทางเอาต์พุตทั้งหมดเป็นไฟล์เดียว
rozcietrzewiacz

คุณถูก. ฉันแก้ไขวิธีแก้ไขเพื่อแก้ไข
rahmu

ตกลง - เกือบจะดี :) แค่somefile.dat.ansเอาท์พุทก็คงจะดูไม่ค่อยดี
rozcietrzewiacz

1
แก้ไข! ฉันไม่รู้เกี่ยวกับ '%' มันใช้งานได้อย่างมีเสน่ห์ขอบคุณสำหรับเคล็ดลับ
rahmu

1
การเพิ่ม-type fileจะดี (ไม่สามารถทำได้< directory) และสิ่งนี้ทำให้ชื่อไฟล์ - นางฟ้าผิดปกติเศร้า
Mat

2

สำหรับรูปแบบที่ง่ายห่วงสำหรับมีความเหมาะสม:

for file in A/*.dat; do
    ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

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

find A -name '*.dat' -print | while IFS= read -r file; do
   ./a.out < "${file}" > "${file%.dat}.ans" # Never forget the QUOTES!
done

สิ่งนี้จะจัดการช่องว่างเนื่องจากการอ่านคือ (โดยค่าเริ่มต้น) การวางสาย มันจะไม่จัดการกับการขึ้นบรรทัดใหม่และจะไม่จัดการแบ็กสแลชเนื่องจากโดยค่าเริ่มต้นแล้วจะตีความลำดับของการหลีกเลี่ยง (ที่จริง ๆ แล้วอนุญาตให้คุณผ่านในการขึ้นบรรทัดใหม่ เชลล์จำนวนมากมี-0ตัวเลือกให้อ่านดังนั้นคุณสามารถจัดการอักขระทั้งหมด แต่น่าเสียดายที่ไม่ใช่ POSIX



1

ฉันคิดว่าคุณต้องการอย่างน้อยเปลือกหอยใน xargs:

ls A/*.dat | xargs -I file sh -c "./a.out < file > file.ans"

แก้ไข: ควรสังเกตว่าวิธีการนี้ใช้ไม่ได้เมื่อชื่อไฟล์มีช่องว่าง ไม่สามารถทำงานได้ แม้ว่าคุณจะใช้ find -0 และ xargs -0 เพื่อให้ xargs เข้าใจช่องว่างอย่างถูกต้องการเรียกเชลล์ -c จะทำให้เกิดข้อผิดพลาด อย่างไรก็ตาม OP ได้ขอคำตอบอย่างชัดเจนสำหรับโซลูชัน xargs และนี่คือโซลูชัน xargs ที่ดีที่สุดที่ฉันคิดไว้ หากช่องว่างในชื่อไฟล์อาจเป็นปัญหาให้ใช้ find -exec หรือ shell loop



@rozcietrzewiacz: โดยทั่วไปแน่นอน แต่ฉันคิดว่ามีคนพยายามที่จะทำวูดู xargs รู้สิ่งนี้ เนื่องจากชื่อไฟล์เหล่านี้ผ่านการขยายตัวของเชลล์อีกครั้งพวกเขาจะต้องมีความประพฤติดีอยู่แล้ว
thiton

1
คุณผิดเกี่ยวกับการขยายตัวของเปลือก lsส่งออกสิ่งต่าง ๆ เช่นช่องว่างโดยไม่ต้องหลบหนีและนั่นคือปัญหา
rozcietrzewiacz

@rozcietrzewiacz: ใช่ฉันเข้าใจปัญหานั้น ตอนนี้สมมติว่าคุณจะหนีจากช่องว่างเหล่านี้อย่างเหมาะสมและนำไปไว้ใน xargs: มันถูกแทนที่ด้วยสตริงของ sh -c, sh tokenizes สตริง -c และทุกอย่างแตก
thiton

ใช่. คุณเห็นแล้วการแยกวิเคราะห์lsไม่ดี แต่xargs สามารถใช้กับการค้นหาได้อย่างปลอดภัย - ดูคำแนะนำข้อ 2 ในคำตอบของฉัน
rozcietrzewiacz

1

ไม่จำเป็นต้องทำให้ซับซ้อนไม่ได้ คุณสามารถทำได้ด้วยการforวนซ้ำ:

for file in A/*.dat; do
  ./a.out < "$file" >"${file%.dat}.ans"
done

${file%.dat}.ansบิตจะลบ.datคำต่อท้ายชื่อไฟล์ชื่อไฟล์จากใน$fileและแทนที่จะเพิ่ม.ansไปยังจุดสิ้นสุด

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