คำตอบนี้มาในส่วนต่าง ๆ ต่อไปนี้:
- การใช้งานขั้นพื้นฐานของ
-exec
- ใช้
-execร่วมกับsh -c
- การใช้
-exec ... {} +
- การใช้
-execdir
การใช้งานขั้นพื้นฐานของ -exec
-execตัวเลือกที่จะใช้เวลาสาธารณูปโภคภายนอกที่มีข้อโต้แย้งที่ไม่จำเป็นเป็นอาร์กิวเมนต์และรัน
หากสตริง{}มีอยู่ที่ใดก็ได้ในคำสั่งที่กำหนดแต่ละอินสแตนซ์ของมันจะถูกแทนที่ด้วยชื่อพา ธ ที่กำลังดำเนินการในขณะนี้ (เช่น./some/path/FILENAME) ในเชลล์ส่วนใหญ่{}ไม่จำเป็นต้องอ้างอิงอักขระสองตัว
คำสั่งจะต้องถูกยกเลิกด้วย;เพื่อfindให้ทราบว่ามันจะจบลงที่ใด (เนื่องจากอาจมีตัวเลือกเพิ่มเติมภายหลัง) เพื่อปกป้อง;เชลล์จากนั้นจะต้องมีการยกมาเป็น\;หรือ';'มิฉะนั้นเชลล์จะเห็นว่ามันเป็นจุดสิ้นสุดของfindคำสั่ง
ตัวอย่าง ( \ในตอนท้ายของสองบรรทัดแรกนั้นมีไว้เพื่อต่อเนื่องของบรรทัด):
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} ';'
นี้จะค้นหาไฟล์ปกติทั้งหมด ( -type f) ที่มีชื่อตรงกับรูปแบบ*.txtในหรือด้านล่างไดเรกทอรีปัจจุบัน จากนั้นจะทดสอบว่าสตริงhelloเกิดขึ้นในไฟล์ใด ๆ ที่พบโดยใช้grep -q(ซึ่งไม่ได้สร้างเอาต์พุตใด ๆ เพียงแค่สถานะออก) สำหรับไฟล์ที่มีสตริงcatจะถูกดำเนินการเพื่อส่งออกเนื้อหาของไฟล์ไปยังสถานี
แต่ละคน-execยังทำหน้าที่เหมือน "การทดสอบ" ในชื่อพา ธ ที่พบfindเช่นเดียวกับ-typeและ-nameไม่ หากคำสั่งส่งคืนสถานะจบการทำงานเป็นศูนย์ (หมายถึง "สำเร็จ") ส่วนถัดไปของfindคำสั่งจะถูกพิจารณามิฉะนั้นfindคำสั่งจะดำเนินการกับชื่อพา ธ ถัดไป ใช้ในตัวอย่างด้านบนเพื่อค้นหาไฟล์ที่มีสตริงhelloแต่ไม่ต้องสนใจไฟล์อื่นทั้งหมด
ตัวอย่างข้างต้นแสดงให้เห็นถึงสองกรณีการใช้งานที่พบบ่อยที่สุดของ-exec:
- เป็นการทดสอบเพื่อ จำกัด การค้นหาเพิ่มเติม
- หากต้องการดำเนินการบางอย่างกับชื่อพา ธ ที่พบ (โดยทั่วไป แต่ไม่จำเป็นในตอนท้ายของ
findคำสั่ง)
ใช้-execร่วมกับsh -c
คำสั่งที่-execสามารถดำเนินการถูก จำกัด ไว้ที่ยูทิลิตี้ภายนอกที่มีอาร์กิวเมนต์ที่เป็นตัวเลือก ในการใช้เชลล์บิวด์อินฟังก์ชันเงื่อนไขไพพ์ไลน์การเปลี่ยนทิศทางและอื่น ๆ โดยตรง-execไม่สามารถทำได้นอกจากจะมีการห่อหุ้มบางอย่างเช่นsh -cเปลือกลูก
ถ้าbashคุณสมบัติจะต้องแล้วใช้ในสถานที่ของbash -csh -c
sh -cทำงาน/bin/shกับสคริปต์ที่กำหนดในบรรทัดคำสั่งตามด้วยอาร์กิวเมนต์บรรทัดคำสั่งเผื่อเลือกสำหรับสคริปต์นั้น
ตัวอย่างง่ายๆของการใช้งานsh -cโดยไม่มีfind:
sh -c 'echo "You gave me $1, thanks!"' sh "apples"
สิ่งนี้จะส่งผ่านสองอาร์กิวเมนต์ไปยังสคริปต์เปลือกลูก:
shสตริง สิ่งนี้จะพร้อมใช้งาน$0ภายในสคริปต์และหากเชลล์ภายในแสดงข้อความแสดงข้อผิดพลาดมันจะขึ้นต้นด้วยสตริงนี้
อาร์กิวเมนต์applesสามารถใช้ได้เป็น$1ในสคริปต์และเคยมีการขัดแย้งมากขึ้นแล้วเหล่านี้จะได้รับการบริการเป็น$2, $3ฯลฯ และยังจะมีในรายการ"$@"(ยกเว้น$0ซึ่งจะไม่เป็นส่วนหนึ่งของ"$@")
นี้จะเป็นประโยชน์ในการทำงานร่วมกับ-execมันช่วยให้เราเพื่อให้สคริปต์ที่ซับซ้อนโดยพลที่ทำหน้าที่ใน pathnames findที่พบโดย
ตัวอย่าง: ค้นหาไฟล์ปกติทั้งหมดที่มีคำต่อท้ายชื่อไฟล์ที่แน่นอนและเปลี่ยนคำต่อท้ายชื่อไฟล์เป็นคำต่อท้ายอื่น ๆ โดยที่คำต่อท้ายจะถูกเก็บไว้ในตัวแปร:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c 'mv "$3" "${3%.$1}.$2"' sh "$from" "$to" {} ';'
ภายในสคริปต์ภายใน$1จะเป็นสตริงtext, $2จะเป็นสตริงtxtและ$3จะเป็นสิ่งที่ชื่อพา ธfindได้พบสำหรับเรา การขยายพารามิเตอร์${3%.$1}จะใช้ชื่อพา ธ และลบคำต่อท้าย.textออกจากมัน
หรือใช้dirname/ basename:
find . -type f -name "*.$from" -exec sh -c '
mv "$3" "$(dirname "$3")/$(basename "$3" ".$1").$2"' sh "$from" "$to" {} ';'
หรือด้วยตัวแปรเพิ่มเติมในสคริปต์ภายใน:
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2; pathname=$3
mv "$pathname" "$(dirname "$pathname")/$(basename "$pathname" ".$from").$to"' sh "$from" "$to" {} ';'
โปรดทราบว่าในรูปแบบที่เปลี่ยนแปลงล่าสุดนี้ตัวแปรfromและtoในเปลือกเด็กจะแตกต่างจากตัวแปรที่มีชื่อเดียวกันในสคริปต์ภายนอก
ข้างต้นเป็นวิธีที่ถูกต้องของการเรียกสคริปต์ที่ซับซ้อนโดยพลการจากที่มี-exec findใช้findในวงเหมือน
for pathname in $( find ... ); do
เป็นข้อผิดพลาดและไม่เหมาะสม (ความเห็นส่วนตัว) มันคือการแยกชื่อไฟล์บนช่องว่างเรียกใช้ชื่อไฟล์ globbing และยังบังคับให้เปลือกเพื่อขยายผลที่สมบูรณ์ของfindก่อนที่จะใช้ซ้ำซ้ำครั้งแรกของวง
ดูสิ่งนี้ด้วย:
การใช้ -exec ... {} +
ในตอนท้ายอาจถูกแทนที่ด้วย; +ซึ่งทำให้findการดำเนินการคำสั่งที่กำหนดด้วยอาร์กิวเมนต์มากที่สุด (พบชื่อพา ธ ) เป็นไปได้มากกว่าหนึ่งครั้งสำหรับแต่ละพา ธ ที่พบ สตริง{} จะต้องเกิดขึ้นก่อนที่สิ่ง+นี้จะใช้งานได้
find . -type f -name '*.txt' \
-exec grep -q 'hello' {} ';' \
-exec cat {} +
ที่นี่findจะรวบรวมชื่อพา ธ ผลลัพธ์และดำเนินการcatกับชื่อให้มากที่สุดในครั้งเดียว
find . -type f -name "*.txt" \
-exec grep -q "hello" {} ';' \
-exec mv -t /tmp/files_with_hello/ {} +
เช่นเดียวกันที่นี่mvจะถูกประหารชีวิตสองสามครั้งเท่าที่จะทำได้ ตัวอย่างสุดท้ายนี้ต้องใช้ GNU mvจาก coreutils (ซึ่งรองรับ-tตัวเลือก)
การใช้-exec sh -c ... {} +เป็นวิธีที่มีประสิทธิภาพในการวนลูปมากกว่าชุดของชื่อพา ธ ด้วยสคริปต์ที่ซับซ้อนโดยพลการ
ข้อมูลเบื้องต้นเหมือนกับเมื่อใช้-exec sh -c ... {} ';'แต่ตอนนี้สคริปต์ใช้รายการอาร์กิวเมนต์ที่ยาวกว่ามาก สิ่งเหล่านี้สามารถวนซ้ำโดยวนซ้ำ"$@"ภายในสคริปต์
ตัวอย่างจากส่วนสุดท้ายที่เปลี่ยนคำต่อท้ายชื่อไฟล์:
from=text # Find files that have names like something.text
to=txt # Change the .text suffix to .txt
find . -type f -name "*.$from" -exec sh -c '
from=$1; to=$2
shift 2 # remove the first two arguments from the list
# because in this case these are *not* pathnames
# given to us by find
for pathname do # or: for pathname in "$@"; do
mv "$pathname" "${pathname%.$from}.$to"
done' sh "$from" "$to" {} +
การใช้ -execdir
นอกจากนี้ยังมี-execdir(ใช้งานโดยfindตัวแปรส่วนใหญ่แต่ไม่ใช่ตัวเลือกมาตรฐาน)
นี้ทำงานเหมือน-execมีความแตกต่างที่ว่าคำสั่งของเชลล์ที่ได้รับจะถูกดำเนินการกับไดเรกทอรีของพา ธ ที่พบเป็นไดเรกทอรีการทำงานในปัจจุบันและที่{}จะมี basename ของพา ธ ที่พบโดยไม่ต้องเส้นทางของมัน ( แต่ GNU findจะยังคงนำหน้า basename ด้วย./ในขณะที่ BSD findจะไม่ทำอย่างนั้น)
ตัวอย่าง:
find . -type f -name '*.txt' \
-execdir mv {} done-texts/{}.done \;
นี้จะย้ายพบกัน*.txtแฟ้ม: การที่มีอยู่ก่อนdone-textsไดเรกทอรีย่อยในไดเรกทอรีเดียวกันเป็นที่ไฟล์นั้นถูกพบ ไฟล์ที่จะได้รับการเปลี่ยนชื่อโดยการเพิ่มคำต่อท้าย.doneไป
นี่อาจเป็นเรื่องที่ยุ่งยากกว่า-execเพราะเราจะต้องได้รับชื่อไฟล์ที่ค้นพบออกมา{}เพื่อสร้างชื่อใหม่ของไฟล์ นอกจากนี้เรายังต้องการชื่อไดเรกทอรีจาก{}เพื่อค้นหาdone-textsไดเรกทอรีอย่างถูกต้อง
ด้วย-execdirบางสิ่งเช่นนี้จะง่ายขึ้น
การดำเนินการที่สอดคล้องกันโดยใช้-execแทนที่จะ-execdirต้องใช้เปลือกลูก:
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "$( dirname "$name" )/done-texts/$( basename "$name" ).done"
done' sh {} +
หรือ,
find . -type f -name '*.txt' -exec sh -c '
for name do
mv "$name" "${name%/*}/done-texts/${name##*/}.done"
done' sh {} +