วิธีการส่งไฟล์ที่พบโดยหาเป็นข้อโต้แย้ง?


9

ก่อนอื่นให้ตัดคำตอบที่ไม่สำคัญ แต่ไม่เหมาะสม: ฉันสามารถใช้เคล็ดลับfind+ xargsหรือชุดรูปแบบ (เช่นเดียวfindกับ-exec) เพราะฉันต้องใช้การแสดงออกเช่นนี้ต่อการโทรเล็กน้อย ฉันจะกลับมาที่นี่ในตอนท้าย


ตอนนี้เป็นตัวอย่างที่ดีกว่าลองพิจารณา:

$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

ฉันจะส่งต่อสิ่งเหล่านี้เป็นอาร์กิวเมนต์ไปยังได้programอย่างไร

เพียงทำมันไม่ได้ทำเคล็ดลับ

$ ./program $(find -L some/dir -name \*.abc | sort)

ล้มเหลวเนื่องจากprogramได้รับอาร์กิวเมนต์ดังนี้

[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc

ดังที่เห็นได้เส้นทางที่มีช่องว่างถูกแยกออกและprogramพิจารณาว่าเป็นอาร์กิวเมนต์ที่ต่างกันสองรายการ

อ้างถึงมันใช้งานได้

ดูเหมือนว่าผู้ใช้มือใหม่เช่นตัวฉันเองเมื่อเผชิญกับปัญหาดังกล่าวมักจะเพิ่มคำพูดแบบสุ่มจนกว่าจะได้ผลในที่สุด - เฉพาะที่นี่ดูเหมือนจะไม่ช่วย ...

"$(…)"

$ ./program "$(find -L some/dir -name \*.abc | sort)"
[0]: ./program
[1]: some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

เนื่องจากเครื่องหมายคำพูดป้องกันการแยกคำไฟล์ทั้งหมดจึงถูกส่งเป็นอาร์กิวเมนต์เดียว

การอ้างอิงแต่ละเส้นทาง

แนวทางที่มีแนวโน้ม:

$ ./program $(find -L some/dir -name \*.abc -printf '"%p"\n' | sort)
[1]: "some/dir/1.abc"
[2]: "some/dir/2.abc"
[3]: "some/dir/a
[4]: space.abc"

คำพูดอยู่ที่นั่นแน่นอน แต่พวกเขาจะไม่ถูกตีความอีกต่อไป พวกเขาเป็นเพียงส่วนหนึ่งของสตริง ดังนั้นไม่เพียง แต่พวกเขาไม่ได้ป้องกันการแยกคำเท่านั้น แต่พวกเขายังมีข้อโต้แย้ง!

เปลี่ยน IFS

IFSแล้วฉันพยายามเล่นรอบกับ ฉันชอบfindที่มี-print0และsortที่มี-zอยู่แล้ว - เพื่อว่าพวกเขาจะมีปัญหาไม่มีใน "เส้นทางสาย" ตัวเอง เหตุใดจึงไม่บังคับให้แยกคำกับnullตัวละครและมีทั้งหมด

$ ./program $(IFS=$'\0' find -L some/dir -name \*.abc -print0 | sort -z)
[0]: ./program
[1]: some/dir/1.abcsome/dir/2.abcsome/dir/a
[2]: space.abc

nullดังนั้นจึงยังคงแยกในพื้นที่และไม่ได้แยกบน

ฉันพยายามทำการIFSมอบหมายทั้งใน$(…)(ตามที่แสดงด้านบน) และก่อนหน้า./programนี้ นอกจากนี้ผมพยายามไวยากรณ์อื่น ๆ เช่น\0, \x0, \x00ทั้งยกมาด้วย'และเช่นเดียวกับที่มีและไม่มี" $ไม่มีสิ่งใดที่สร้างความแตกต่างได้ ...


และที่นี่ฉันไม่มีความคิด ฉันลองอีกสองสามอย่าง แต่ทุกอย่างดูเหมือนจะหมดไปกับปัญหาแบบเดียวกับที่ระบุไว้

ฉันจะทำอะไรได้อีก เป็นไปได้หรือไม่?

แน่นอนว่าฉันสามารถprogramยอมรับรูปแบบและทำการค้นหาตัวเอง แต่มันเป็นงานสองอย่างมากในขณะที่แก้ไขเป็นไวยากรณ์ที่เฉพาะเจาะจง (เช่นการให้ไฟล์โดยgrepตัวอย่างเช่น?)

นอกจากนี้ฉันสามารถทำให้programยอมรับไฟล์ที่มีรายการเส้นทาง จากนั้นฉันสามารถดัมพ์findนิพจน์ไปยังไฟล์ temp บางไฟล์และระบุพา ธ ไปยังไฟล์นั้นเท่านั้น สิ่งนี้สามารถรองรับได้ตามเส้นทางโดยตรงดังนั้นหากผู้ใช้มีเพียงเส้นทางง่ายๆก็สามารถให้ได้โดยไม่ต้องมีไฟล์ระดับกลาง แต่สิ่งนี้ดูไม่ดีนักเราจำเป็นต้องสร้างไฟล์เพิ่มเติมและดูแลไฟล์เหล่านั้นไม่ต้องพูดถึงการติดตั้งเพิ่มเติมที่จำเป็น (ในด้านบวกอย่างไรก็ตามมันอาจช่วยชีวิตสำหรับกรณีที่จำนวนไฟล์ที่เป็นอาร์กิวเมนต์เริ่มต้นที่จะทำให้เกิดปัญหากับความยาวบรรทัดคำสั่ง ... )


ในตอนท้ายให้ฉันเตือนคุณอีกครั้งว่าเทคนิคfind+ xargs(และเหมือนกัน) จะไม่ทำงานในกรณีของฉัน เพื่อความเรียบง่ายของคำอธิบายฉันแสดงเพียงอาร์กิวเมนต์เดียวเท่านั้น แต่กรณีจริงของฉันมีลักษณะเช่นนี้:

$ ABC_FILES=$(find -L some/dir -name \*.abc | sort)
$ XYZ_FILES=$(find -L other/dir -name \*.xyz | sort)
$ ./program --abc-files $ABC_FILES --xyz-files $XYZ_FILES

ดังนั้นการxargsทำการค้นหาแบบหนึ่งยังทำให้ฉันมีวิธีการจัดการอีกวิธีหนึ่ง ...

คำตอบ:


13

ใช้อาร์เรย์

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

mapfile -t ABC_FILES < <(find -L some/dir -name \*.abc | sort)
mapfile -t XYZ_FILES < <(find -L other/dir -name \*.xyz | sort)

แล้วก็

./program --abc-files "${ABC_FILES[@]}" --xyz-files "${XYZ_FILES[@]}"

ถ้าคุณทำจำเป็นที่จะต้องขึ้นบรรทัดใหม่จับภายในชื่อไฟล์และมีการทุบตี> = 4.4 คุณสามารถใช้-print0และ-d ''ไปด้วย null ยุติชื่อในระหว่างการก่อสร้างอาร์เรย์:

mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)

(และในทำนองเดียวกันสำหรับXYZ_FILES) หากคุณไม่มี bash ที่ใหม่กว่าคุณสามารถใช้ลูปการอ่านที่สิ้นสุดด้วยค่า null เพื่อต่อท้ายชื่อไฟล์ต่อท้ายอาร์เรย์เช่น

ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)

ยอดเยี่ยม ฉันกำลังคิดเกี่ยวกับอาร์เรย์ แต่อย่างใดฉันไม่พบอะไรเลยmapfile(หรือคำพ้องความหมายreadarray) แต่มันใช้งานได้!
Adam Badura

แต่คุณสามารถปรับปรุงได้เล็กน้อย รุ่น Bash <4.4 (ซึ่งฉันมี ... ) ที่มีการwhileวนซ้ำไม่ได้ล้างอาร์เรย์ ซึ่งหมายความว่าหากไม่พบไฟล์อาเรย์จะไม่ได้กำหนด แม้ว่าจะมีการกำหนดไฟล์ใหม่ไว้แล้วจะถูกต่อท้าย (แทนที่จะแทนที่ไฟล์เก่า) ดูเหมือนว่าการเพิ่มdeclare -a ABC_FILES='()';ก่อนwhileจะหลอกลวง (ในขณะที่การเพิ่มABC_FILES='()';ไม่ได้)
Adam Badura

นอกจากนี้ยังมีความ< <หมายอะไรที่นี่ มันเหมือนกัน<<ไหม? ฉันไม่คิดว่าการเปลี่ยนเป็น<<ข้อผิดพลาดทางไวยากรณ์ ("โทเค็นที่ไม่คาดคิด` ('") ดังนั้นมันทำงานอย่างไรและมันทำงานอย่างไร
Adam Badura

การปรับปรุงอื่น (ตามการใช้งานของฉัน) คือการสร้างอาร์เรย์อื่น ABC_FILESดังนั้นเราจึงมีผู้ นั่นเป็นเรื่องปกติ แต่มันมีประโยชน์ที่จะทำให้ABS_ARGSมันเป็นอาเรย์ที่ว่างถ้าABC_FILESมันว่างเปล่ามิฉะนั้นมันก็เป็นอาเร('--abc-files' "${ABC_FILES[@]}")ย์ ด้วยวิธีนี้ในภายหลังฉันสามารถใช้มันเช่นนี้./program "${ABC_ARGS[@]}" "${XYZ_ARGS[@]}"และให้แน่ใจว่ามันจะทำงานได้อย่างถูกต้องโดยไม่คำนึงว่ากลุ่มใด (ถ้ามี) ว่างเปล่า หรือระบุว่ามันแตกต่าง: วิธีนี้--abc-files(และ--xyz-files) จะมีให้เฉพาะในกรณีที่มีการติดตามเส้นทางจริง
Adam Badura

1
@AdamBadura: while read ... done < <(find blah)คือการเปลี่ยนเส้นทางเปลือกปกติ<จากแฟ้มพิเศษที่สร้างขึ้นโดยกระบวนการทดแทน สิ่งนี้แตกต่างจากการไพพ์find blah | while read ... doneไลน์เนื่องจากไพพ์ไลน์จะเรียกใช้whileลูปใน subshell ดังนั้น var (s) ที่ตั้งค่าไว้จะไม่ถูกเก็บรักษาไว้สำหรับคำสั่งที่ตามมา
dave_thompson_085

3

คุณสามารถใช้ IFS = newline (สมมติว่าไม่มีชื่อไฟล์ที่มีขึ้นบรรทัดใหม่) แต่คุณต้องตั้งค่าในเปลือกนอกก่อนการทดแทน:

$ ls -1
a file with spaces
able
alpha
baker
boo hoo hoo
bravo
$ # note semicolon here; it's not enough to be in the environment passed
$ # to printf, it must be in the environment OF THE SHELL WHILE PARSING
$ IFS=$'\n'; printf '%s\n' --afiles $(find . -name 'a*') --bfiles $(find . -name 'b*')
--afiles
./able
./a file with spaces
./alpha
--bfiles
./bravo
./boo hoo hoo
./baker

ด้วยzshแต่ไม่ใช่bashคุณสามารถใช้ null ได้$'\0'เช่นกัน แม้แต่ในตัวbashคุณก็สามารถจัดการกับ newline ได้หากมีตัวละครแปลก ๆ ที่ไม่เคยใช้

 IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...

อย่างไรก็ตามวิธีการนี้ไม่ได้จัดการกับคำขอเพิ่มเติมที่คุณทำไว้ในความคิดเห็นเกี่ยวกับคำตอบของ @ steeldriver เพื่อละเว้น --afiles หากการค้นหาว่างเปล่า


ดังนั้นเมื่อฉันเข้าใจใน Bash ไม่มีทางที่จะบังคับIFSให้แยกจากกันnull?
Adam Badura

@ AdamBadura: ฉันไม่แน่ใจ bash ไม่อนุญาตให้ใช้ null null ในตัวแปรใด ๆ รวมถึง IFS หมายเหตุวิธีการที่read -d ''ใช้ในเครื่องมือของ steeldriver เป็นสตริงที่ว่างเปล่าไม่มีสตริงที่มีค่าว่าง (และตัวเลือกคำสั่งไม่ใช่ var เช่นนี้)
dave_thompson_085

คุณต้องปิดการใช้งาน globbing ( set -o noglob) ก่อนที่จะใช้ตัวดำเนินการแยก + glob (ยกเว้นในzsh)
Stéphane Chazelas


@ AdamBadura ใช่แล้วใน bash ค่า null จะเหมือนกับ$'\0'และเหมือน''กันทุกประการ
Isaac

1

xargsผมไม่แน่ใจว่าผมเข้าใจว่าทำไมคุณให้ขึ้น

ดังนั้นการxargsทำการค้นหาแบบหนึ่งยังทำให้ฉันมีวิธีการจัดการอีกวิธีหนึ่ง ...

สตริง--xyz-filesเป็นเพียงหนึ่งในหลาย ๆ อาร์กิวเมนต์และไม่มีเหตุผลที่จะต้องพิจารณาเป็นพิเศษก่อนที่โปรแกรมของคุณจะถูกตีความ ฉันคิดว่าคุณสามารถผ่านมันไปได้xargsระหว่างfindผลลัพธ์ทั้งสอง:

{ find -L some/dir -name \*.abc -print0 | sort -z; echo -ne "--xyz-files\0"; find -L other/dir -name \*.xyz -print0 | sort -z; } | xargs -0 ./program --abc-files

คุณพูดถูก! ใช้งานได้ดีเช่นกัน! แต่สังเกตเห็นว่าคุณพลาดในครั้งที่สอง-print0 findนอกจากนี้หากฉันใช้วิธีนี้ฉันก็จะใส่ตัว--abc-filesบ่งชี้echoเช่นกัน - เพื่อความมั่นคง
Adam Badura

วิธีนี้ดูเหมือนง่ายกว่าและค่อนข้างซับกว่าวิธีการแบบอาเรย์ อย่างไรก็ตามมันจะต้องใช้เหตุผลพิเศษบางอย่างที่จะครอบคลุมกรณีที่ถ้าไม่มี.abcไฟล์ก็ควรจะไม่มี--abc-files(เหมือนกัน.xyz) แก้ปัญหาอาร์เรย์ตามโดยsteeldriverยังต้องใช้ตรรกะพิเศษสำหรับมัน แต่ตรรกะที่เป็นที่น่ารำคาญในขณะที่มีมันอาจจะไม่ดังน่ารำคาญนี่ทำลายประโยชน์หลักของการแก้ปัญหานี้ - ความเรียบง่าย
Adam Badura

นอกจากนี้ผมไม่แน่ใจจริงๆ แต่ฉันคิดว่าxargsจะไม่พยายามที่จะแยกการขัดแย้งและทำให้ไม่กี่คำสั่งแทนหนึ่งเว้นแต่มันจะชัดเจนได้รับคำสั่งให้ทำเช่นนั้นด้วย-L, --max-lines( -l) --max-args( -n) หรือ--max-chars( -s) ข้อโต้แย้ง ฉันถูกไหม? หรือมีค่าเริ่มต้นบ้างไหม? ในฐานะที่เป็นโปรแกรมของฉันจะไม่จัดการแยกดังกล่าวได้อย่างถูกต้องและผมค่อนข้างจะมีความล้มเหลวที่จะเรียกว่า ...
อดัม Badura

1
@ AdamBadura Missing -print0- แก้ไขขอบคุณ ฉันไม่ทราบคำตอบทั้งหมด แต่ฉันเห็นด้วยกับวิธีแก้ปัญหาของฉันทำให้ยากที่จะรวมตรรกะเพิ่มเติม ฉันอาจจะไปกับอาร์เรย์ตัวเองตอนนี้เมื่อฉันรู้วิธีการนี้ คำตอบของฉันไม่ได้มีไว้สำหรับคุณ คุณยอมรับคำตอบอื่นแล้วและฉันคิดว่าปัญหาของคุณจะได้รับการแก้ไข ฉันแค่อยากจะชี้ให้เห็นว่าคุณสามารถส่งผ่านข้อโต้แย้งจากหลาย ๆ แหล่งผ่านxargsซึ่งไม่ชัดเจนในตอนแรก คุณอาจถือว่ามันเป็นหลักฐานของแนวคิด ตอนนี้เราทุกคนรู้วิธีการที่แตกต่างกันเล็กน้อยและเราสามารถเลือกสิ่งที่เหมาะกับเราในทุกกรณี
Kamil Maciorowski

ใช่ฉันได้ติดตั้งโซลูชันที่ใช้อาเรย์แล้วและใช้งานได้ดี ฉันภูมิใจเป็นพิเศษเกี่ยวกับความสะอาดของตัวเลือก (ถ้าไม่มีไฟล์--abc-files) แต่คุณพูดถูก - เป็นการดีที่จะรู้ทางเลือกของคุณ! โดยเฉพาะอย่างยิ่งฉันคิดว่ามันเป็นไปไม่ได้
Adam Badura
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.