คำตอบนี้มาในส่วนต่าง ๆ ต่อไปนี้:
- การใช้งานขั้นพื้นฐานของ
-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 -c
sh -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 {} +