ค้นหา -exec ฟังก์ชั่นเชลล์ใน Linux?


185

มีวิธีการfindดำเนินการฟังก์ชั่นฉันกำหนดในเปลือกหรือไม่ ตัวอย่างเช่น:

dosomething () {
  echo "doing something with $1"
}
find . -exec dosomething {} \;

ผลลัพธ์ที่ได้คือ:

find: dosomething: No such file or directory

มีวิธีที่จะได้รับfindของ-execที่จะเห็นdosomething?

คำตอบ:


262

เนื่องจากเชลล์เท่านั้นที่รู้วิธีเรียกใช้ฟังก์ชันเชลล์คุณต้องใช้เชลล์เพื่อเรียกใช้ฟังก์ชัน คุณต้องทำเครื่องหมายฟังก์ชันของคุณเพื่อส่งออกด้วยexport -fมิฉะนั้น subshell จะไม่รับช่วงต่อ:

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;

7
คุณชนะฉัน $0โดยวิธีการที่คุณสามารถใส่เครื่องมือจัดฟันด้านในคำพูดแทนการใช้
หยุดชั่วคราวจนกว่าจะมีการแจ้งให้ทราบต่อไป

20
ถ้าคุณใช้find . -exec bash -c 'dosomething "$0"' {} \;มันจะจัดการกับช่องว่าง (และตัวอักษรแปลกอื่น ๆ ) ในชื่อไฟล์ ...
กอร์ดอน Davisson

3
@alxndr: นั่นจะล้มเหลวในชื่อไฟล์ที่มีเครื่องหมายคำพูดคู่, backquotes, สัญญาณดอลลาร์, คอมโบหลบหนีอื่น ๆ ...
Gordon Davisson

4
โปรดทราบว่าฟังก์ชั่นใด ๆ ที่ฟังก์ชั่นของคุณอาจจะเรียกใช้จะไม่สามารถใช้งานได้จนกว่าคุณจะส่งออก -f ฟังก์ชันเหล่านั้นเช่นกัน
hraban

3
export -fจะทำงานเฉพาะในบางรุ่นของทุบตี ไม่ใช่ posix ไม่ใช่ crossplatforn /bin/shจะมีข้อผิดพลาดกับมัน
РоманКоптев

120
find . | while read file; do dosomething "$file"; done

10
ทางออกที่ดี ไม่ต้องการส่งออกฟังก์ชั่นหรือ messing รอบหนีข้อโต้แย้งและสันนิษฐานว่ามีประสิทธิภาพมากขึ้นเพราะมันไม่ได้วางไข่ subshells เพื่อรันแต่ละฟังก์ชั่น
Tom

19
อย่างไรก็ตามโปรดจำไว้ว่ามันจะแตกชื่อไฟล์ที่มีการขึ้นบรรทัดใหม่
chepner

3
นี่คือ "shell'ish" มากกว่านี้เนื่องจากตัวแปรและฟังก์ชั่นทั่วโลกของคุณจะพร้อมใช้งานโดยไม่ต้องสร้างเชลล์ / สภาพแวดล้อมใหม่ทั้งหมดในแต่ละครั้ง เรียนรู้สิ่งนี้อย่างหนักหลังจากลองใช้วิธีการของอดัมและพบปัญหาสิ่งแวดล้อมทุกประเภท วิธีนี้ยังไม่ทำลายเชลล์ของผู้ใช้ปัจจุบันของคุณด้วยการเอ็กซ์พอร์ตทั้งหมดและต้องการความพิถีพิถันน้อยลง
Ray Foss

เร็วกว่าคำตอบที่ยอมรับโดยไม่มีเชลล์ย่อย! NICE! ขอบคุณ!
Bill Heller

2
ฉันยังแก้ไขปัญหาของฉันโดยเปลี่ยนwhile readfor for loop for item in $(find . ); do some_function "${item}"; done
user5359531

22

คำตอบของจักรเป็นคำตอบที่ยอดเยี่ยม แต่มีข้อผิดพลาดสองข้อที่เอาชนะได้ง่าย:

find . -print0 | while IFS= read -r -d '' file; do dosomething "$file"; done

สิ่งนี้ใช้ null เป็นตัวคั่นแทนการป้อนบรรทัดดังนั้นชื่อไฟล์ที่มี linefeeds จะทำงาน นอกจากนี้ยังใช้การ-rตั้งค่าสถานะซึ่งปิดใช้งานเครื่องหมายแบคสแลชยกเว้นว่าเครื่องหมายแบ็กสแลชในชื่อไฟล์จะไม่ทำงาน นอกจากนี้ยังล้างข้อมูลIFSเพื่อให้ช่องว่างที่อาจเกิดขึ้นต่อท้ายชื่อไม่ถูกละทิ้ง


1
มันเป็นเรื่องดีสำหรับแต่จะไม่ทำงานใน/bin/bash /bin/shน่าสงสารจัง.
РоманКоптев

@ РоманКоптевโชคดีที่อย่างน้อยทำงานใน / bin / bash
sdenham

21

เพิ่มคำพูดใน{}ภาพด้านล่าง:

export -f dosomething
find . -exec bash -c 'dosomething "{}"' \;

สิ่งนี้จะแก้ไขข้อผิดพลาดเนื่องจากอักขระพิเศษที่ส่งคืนโดยfindตัวอย่างเช่นไฟล์ที่มีวงเล็บในชื่อ


1
{}นี้ไม่ได้เป็นวิธีที่ถูกต้องในการใช้งาน สิ่งนี้จะแตกสำหรับชื่อไฟล์ที่มีเครื่องหมายคำพูดคู่ touch '"; rm -rf .; echo "I deleted all you files, haha. อุ่ย
gniourf_gniourf

ใช่มันแย่มาก มันสามารถใช้ประโยชน์จากการฉีด ไม่ปลอดภัยมาก อย่าใช้สิ่งนี้!
Dominik

1
@kdubs: ใช้ $ 0 (ไม่ได้ใส่เครื่องหมายคำพูด) ไว้ใน command-string และส่งชื่อไฟล์เป็นอาร์กิวเมนต์แรก: -exec bash -c 'echo $0' '{}' \;โปรดทราบว่าเมื่อใช้bash -cงาน $ 0 จะเป็นอาร์กิวเมนต์แรกไม่ใช่ชื่อสคริปต์
sdenham

10

การประมวลผลผลลัพธ์เป็นกลุ่ม

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

โปรดทราบว่าวิธีนี้อาจมาพร้อมกับคำเตือนบางอย่างเช่นข้อกำหนดใน POSIX- findเพื่อให้ได้{}ที่ส่วนท้ายของคำสั่ง

export -f dosomething
find . -exec bash -c 'for f; do dosomething "$f"; done' _ {} +

findจะส่งผ่านผลลัพธ์จำนวนมากเป็นอาร์กิวเมนต์ไปยังการเรียกใช้ครั้งเดียวbashและfor-loop วนซ้ำผ่านการขัดแย้งเหล่านั้นการเรียกใช้ฟังก์ชันdosomethingในแต่ละรายการนั้น

วิธีการแก้ปัญหาข้างต้นเริ่มขัดแย้งที่$1ซึ่งเป็นสาเหตุที่มี_(ซึ่งหมายถึง$0)

การประมวลผลผลลัพธ์หนึ่งต่อหนึ่ง

ในทำนองเดียวกันฉันคิดว่าคำตอบที่ได้รับการยอมรับควรได้รับการแก้ไขให้เป็น

export -f dosomething
find . -exec bash -c 'dosomething "$1"' _ {} \;

สิ่งนี้ไม่เพียง แต่มีสติมากขึ้นเท่านั้นเพราะข้อโต้แย้งควรเริ่มต้นเสมอ$1แต่การใช้$0อาจทำให้เกิดพฤติกรรมที่ไม่คาดคิดได้หากชื่อไฟล์ที่ส่งคืนโดยfindมีความหมายพิเศษต่อเชลล์


9

ให้สคริปต์เรียกตัวเองโดยผ่านแต่ละรายการที่พบว่าเป็นอาร์กิวเมนต์:

#!/bin/bash

if [ ! $1 == "" ] ; then
   echo "doing something with $1"
   exit 0
fi

find . -exec $0 {} \;

exit 0

เมื่อคุณเรียกใช้สคริปต์ด้วยตนเองมันจะค้นหาสิ่งที่คุณกำลังค้นหาและเรียกตัวเองว่าผ่านการค้นหาแต่ละผลลัพธ์เป็นอาร์กิวเมนต์ เมื่อสคริปต์รันด้วยอาร์กิวเมนต์สคริปต์จะประมวลผลคำสั่งบนอาร์กิวเมนต์แล้วออก


ไอเดียเจ๋ง ๆ แต่มีสไตล์ไม่ดี: ใช้สคริปต์เดียวกันเพื่อวัตถุประสงค์สองประการ หากคุณต้องการลดจำนวนไฟล์ในถังขยะของคุณ / จากนั้นคุณสามารถรวมสคริปต์ทั้งหมดของคุณเป็นหนึ่งเดียวที่มีกรณีใหญ่ในตอนเริ่มต้น ทางออกที่สะอาดมากใช่ไหม
user829755

ไม่ต้องพูดถึงสิ่งนี้จะล้มเหลวfind: ‘myscript.sh’: No such file or directoryหากเริ่มเป็นbash myscript.sh...
Camusensei

5

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

toall(){
    find . -type f | while read file; do "$1" "$file"; done
}

โปรดทราบว่าชื่อไฟล์มีช่องว่าง (ดูด้านล่าง)

ตัวอย่างเช่นใช้ฟังก์ชันนี้:

world(){
    sed -i 's_hello_world_g' "$1"
}

บอกว่าฉันต้องการเปลี่ยนทุกกรณีของสวัสดีสู่โลกในทุกไฟล์ในไดเรกทอรีปัจจุบัน ฉันจะทำ:

toall world

เพื่อความปลอดภัยกับสัญลักษณ์ใด ๆ ในชื่อไฟล์ให้ใช้:

toall(){
    find . -type f -print0 | while IFS= read -r -d '' file; do "$1" "$file"; done
}

(แต่คุณต้องมีfindที่จัดการ-print0เช่น GNU find)


3

ฉันค้นหาวิธีที่ง่ายที่สุดดังนี้ทำซ้ำสองคำสั่งในหนึ่งเดียวทำ

func_one () {
  echo "first thing with $1"
}

func_two () {
  echo "second thing with $1"
}

find . -type f | while read file; do func_one $file; func_two $file; done

3

สำหรับการอ้างอิงฉันหลีกเลี่ยงสถานการณ์นี้โดยใช้:

for i in $(find $dir -type f -name "$name" -exec ls {} \;); do
  _script_function_call $i;
done;

รับเอาต์พุตของ find ในไฟล์สคริปต์ปัจจุบันและวนซ้ำเอาต์พุตตามที่คุณต้องการ ฉันเห็นด้วยกับคำตอบที่ยอมรับ แต่ฉันไม่ต้องการให้ฟังก์ชั่นภายนอกไฟล์สคริปต์ของฉัน


นี่มีการ จำกัด ขนาด
Richard

2

มันเป็นไปไม่ได้ที่จะเรียกใช้งานฟังก์ชันในลักษณะนั้น

เพื่อเอาชนะสิ่งนี้คุณสามารถวางฟังก์ชันของคุณในเชลล์สคริปต์และเรียกสิ่งนั้นได้ find

# dosomething.sh
dosomething () {
  echo "doing something with $1"
}
dosomething $1

ตอนนี้ใช้มันในการค้นหาเป็น:

find . -exec dosomething.sh {} \;

พยายามหลีกเลี่ยงไฟล์เพิ่มเติมใน ~ / bin ของฉัน ขอบคุณนะ!
alxndr

ฉันพิจารณาการ downvoting แต่วิธีแก้ปัญหาในตัวมันไม่เลว โปรดใช้ข้อความที่ถูกต้อง: dosomething $1=> dosomething "$1"และเริ่มต้นไฟล์ของคุณอย่างถูกต้องด้วยfind . -exec bash dosomething.sh {} \;
Camusensei

2

ใส่ฟังก์ชั่นในไฟล์แยกต่างหากและfindไปที่คำสั่งนั้น

ฟังก์ชั่นของเชลล์นั้นอยู่ภายในเชลล์ที่พวกมันนิยามไว้ findจะไม่สามารถเห็นพวกเขา


Gotcha; มีเหตุผล. พยายามหลีกเลี่ยงไฟล์เพิ่มเติมใน ~ / bin ของฉัน
alxndr

2

เพื่อให้การเพิ่มและการชี้แจงบางส่วนของคำตอบอื่น ๆ ถ้าคุณจะใช้ตัวเลือกจำนวนมากสำหรับการexecหรือexecdir( -exec command {} +) และต้องการที่จะดึงทุกข้อโต้แย้งตำแหน่งที่คุณต้องพิจารณาจัดการของด้วย$0 bash -cให้พิจารณาคำสั่งด้านล่างอย่างเป็นรูปธรรมยิ่งขึ้นซึ่งใช้bash -cตามที่แนะนำข้างต้นและเพียงแสดงเส้นทางของไฟล์ที่ลงท้ายด้วย '.wav' จากแต่ละไดเร็กทอรีที่พบ:

find "$1" -name '*.wav' -execdir bash -c 'echo $@' _ {} +

คู่มือทุบตีพูดว่า:

If the -c option is present, then commands are read from the first non-option argument command_string.  If there are arguments after the command_string, they  are  assigned  to  the
                 positional parameters, starting with $0.

นี่'check $@'คือสตริงคำสั่งและ_ {}เป็นอาร์กิวเมนต์หลังจากสตริงคำสั่ง ทราบว่า$@เป็นพารามิเตอร์ที่ตำแหน่งพิเศษในการทุบตีที่ขยายไปยังทุกพารามิเตอร์ตำแหน่งตั้งแต่วันที่ 1 นอกจากนี้ยังทราบว่ามีตัวเลือกอาร์กิวเมนต์แรกได้รับมอบหมายให้พารามิเตอร์ตำแหน่ง-c $0ซึ่งหมายความว่าหากคุณพยายามเข้าถึงพารามิเตอร์ตำแหน่งทั้งหมดด้วย$@คุณจะได้รับเฉพาะพารามิเตอร์ที่เริ่มต้น$1ขึ้นไป นั่นคือเหตุผลที่คำตอบของ Dominik มีอยู่_ซึ่งเป็นอาร์กิวเมนต์ที่ใช้ในการเติมพารามิเตอร์$0ดังนั้นอาร์กิวเมนต์ทั้งหมดที่เราต้องการจะมีให้ในภายหลังหากเราใช้$@การขยายพารามิเตอร์เช่นหรือสำหรับลูปในคำตอบนั้น

แน่นอนคล้ายกับคำตอบที่ยอมรับbash -c 'shell_function $0 $@'ก็จะทำงานโดยผ่านอย่างชัดเจน$0แต่อีกครั้งคุณจะต้องจำไว้ว่า$@จะไม่ทำงานตามที่คาดไว้


0

ไม่โดยตรงไม่ ค้นหากำลังดำเนินการในกระบวนการที่แยกต่างหากไม่ใช่ในเชลล์ของคุณ

สร้างเชลล์สคริปต์ที่ทำงานเหมือนกับฟังก์ชันของคุณและค้นหา-execได้


พยายามหลีกเลี่ยงไฟล์เพิ่มเติมใน ~ / bin ของฉัน ขอบคุณนะ!
alxndr

-2

ฉันจะหลีกเลี่ยงการใช้งานโดย-execสิ้นเชิง ทำไมไม่ใช้xargs?

find . -name <script/command you're searching for> | xargs bash -c

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