การเรียกใช้งานฟังก์ชั่นที่ผู้ใช้กำหนดในการค้นหา -exec call


25

ฉันใช้ Solaris 10 และฉันได้ทดสอบสิ่งต่อไปนี้ด้วย ksh (88), bash (3.00) และ zsh (4.2.1)

รหัสต่อไปนี้ไม่ได้ให้ผลลัพธ์ใด ๆ :

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

การค้นหาไม่ตรงกับหลายไฟล์ (ดังแสดงโดยแทนที่-exec ...ด้วย-print) และฟังก์ชั่นการทำงานอย่างสมบูรณ์แบบเมื่อถูกเรียกออกไปข้างนอกจากการfindโทร

นี่คือสิ่งที่man findหน้าพูดเกี่ยวกับ-exec:

 คำสั่ง -exec จริงถ้าคำสั่งดำเนินการส่งกลับ
                     ค่าศูนย์เป็นสถานะออก จุดจบของ
                     คำสั่งจะต้องคั่นด้วยการหลบหนี
                     อัฒภาค (;) อาร์กิวเมนต์คำสั่ง {} คือ
                     แทนที่ด้วยชื่อพา ธ ปัจจุบัน หากว่า
                     อาร์กิวเมนต์สุดท้ายของ -exec คือ {} และคุณ
                     ระบุ + มากกว่าเครื่องหมายอัฒภาค (;)
                     คำสั่งถูกเรียกใช้ครั้งน้อยลงด้วย
                     {} ถูกแทนที่ด้วยกลุ่มชื่อพา ธ ถ้า
                     การเรียกใช้คำสั่งใด ๆ ส่งคืน a
                     ค่าที่ไม่ใช่ศูนย์เป็นสถานะออก, ค้นหา
                     ส่งคืนสถานะทางออกที่ไม่ใช่ศูนย์

ฉันอาจจะออกไปทำอะไรแบบนี้:

for f in $(find somedir); do
    foo
done

แต่ฉันกลัวว่าจะจัดการกับปัญหาตัวคั่นฟิลด์

เป็นไปได้ไหมที่จะเรียกใช้ฟังก์ชั่นเชลล์ (กำหนดไว้ในสคริปต์เดียวกันอย่าไปสนใจปัญหาการกำหนดขอบเขต) จากการfind ... -exec ...โทร?

ฉันพยายามมันมีทั้ง/usr/bin/findและ/bin/findและได้ผลเหมือนกัน


คุณลองส่งออกฟังก์ชั่นหลังจากคุณประกาศหรือไม่ export -f foo
h3rrmiller

2
PATHคุณจะต้องทำให้การทำงานสคริปต์ภายนอกและวางไว้ใน อีกทางเลือกหนึ่งคือใช้sh -c '...'และกำหนดและเรียกใช้ฟังก์ชันใน...บิต มันอาจช่วยให้เข้าใจความแตกต่างระหว่างการทำงานและสคริปต์
jw013

คำตอบ:


27

A functionเป็นโลคัลสำหรับเชลล์ดังนั้นคุณต้องfind -execวางไข่เชลล์และกำหนดฟังก์ชันนั้นไว้ในเชลล์นั้นก่อนจึงจะสามารถใช้งานได้ สิ่งที่ต้องการ:

find ... -exec ksh -c '
  function foo {
    echo blan: "$@"
  }
  foo "$@"' ksh {} +

bashอนุญาตให้หนึ่งส่งออกฟังก์ชั่นผ่านสภาพแวดล้อมด้วยexport -fดังนั้นคุณสามารถทำ (ในทุบตี):

foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +

ksh88มีtypeset -fxฟังก์ชั่นการส่งออก (ไม่ผ่านสิ่งแวดล้อม) แต่ที่สามารถนำมาใช้โดยเธอปังน้อยสคริปต์ดำเนินการโดยเพื่อให้ไม่ได้ด้วยkshksh -c

ตัวเลือกอื่นคือ:

find ... -exec ksh -c "
  $(typeset -f foo)"'
  foo "$@"' ksh {} +

นั่นคือใช้typeset -fเพื่อดัมพ์นิยามของfooฟังก์ชันภายในสคริปต์แบบอินไลน์ โปรดทราบว่าหากfooใช้ฟังก์ชั่นอื่นคุณจะต้องทิ้งไฟล์เหล่านั้นด้วย


2
คุณช่วยอธิบายได้ไหมว่าทำไมถึงเกิดขึ้นสองครั้งkshหรือbashใน-execคำสั่ง? ฉันเข้าใจเหตุการณ์แรก แต่ไม่ใช่เหตุการณ์ที่สอง
daniel kullmann

6
@danielkullmann In bash -c 'some-code' a b c, $0คือa, $1คือb... ดังนั้นหากคุณต้องการที่$@จะเป็นa, b, cคุณต้องแทรกบางสิ่งบางอย่างก่อน เนื่องจาก$0ยังใช้เมื่อแสดงข้อความแสดงข้อผิดพลาดคุณควรใช้ชื่อของเชลล์หรือสิ่งที่เหมาะสมในบริบทนั้น
Stéphane Chazelas

@ StéphaneChazelasขอบคุณสำหรับคำตอบที่ดีเช่นนี้
User9102d82

5

สิ่งนี้ไม่สามารถใช้ได้เสมอไป แต่เมื่อเป็นเช่นนี้มันเป็นวิธีแก้ปัญหาง่ายๆ ตั้งค่าglobstarตัวเลือก ( set -o globstarใน ksh93 shopt -s globstarใน bash ≥4โดยค่าเริ่มต้นจะเป็น zsh) จากนั้นใช้**/เพื่อจับคู่ไดเรกทอรีปัจจุบันและไดเรกทอรีย่อยซ้ำ

ตัวอย่างเช่นแทนที่จะเป็นfind . -name '*.txt' -exec somecommand {} \;คุณสามารถเรียกใช้

for x in **/*.txt; do somecommand "$x"; done

แทนที่จะfind . -type d -exec somecommand {} \;คุณสามารถเรียกใช้

for d in **/*/; do somecommand "$d"; done

แทนที่จะfind . -newer somefile -exec somecommand {} \;คุณสามารถเรียกใช้

for x in **/*; do
  [[ $x -nt somefile ]] || continue
  somecommand "$x"
done

เมื่อ**/ไม่ได้ผลสำหรับคุณ (เพราะเปลือกของคุณไม่ได้มีมันหรือเพราะคุณจำเป็นต้องมีfindตัวเลือกที่ไม่ได้มีอนาล็อกเปลือก) กำหนดฟังก์ชั่นในfind -execการโต้แย้ง


ฉันไม่พบglobstarตัวเลือกในเวอร์ชันของ ksh ( ksh88) ที่ใช้อยู่
rahmu

@rahmu แน่นอนมันใหม่ใน ksh93 Solaris 10 ไม่มี ksh93 อยู่ที่ไหนสักแห่ง?
Gilles 'SO- หยุดความชั่วร้าย'

ตามโพสต์บล็อกนี้ ksh93 ได้รับการแนะนำใน Solaris 11 เพื่อแทนที่ทั้ง Bourne Shell และ ksh88 ...
rahmu

@rahmu Solaris มี ksh93 อยู่พักหนึ่งdtksh(ksh93 ที่มีนามสกุล X11 บางส่วน) แต่เป็นรุ่นเก่าและอาจเป็นส่วนหนึ่งของแพ็คเกจเสริมที่ CDE เป็นตัวเลือก
Stéphane Chazelas

ควรสังเกตว่าการวนซ้ำแบบวนซ้ำนั้นแตกต่างจากการfindแยก dotfiles และไม่ลงไปสู่ ​​dotdirs และเรียงลำดับรายการไฟล์ (ทั้งสองแบบสามารถอยู่ใน zsh ผ่านตัวระบุแบบหมุนได้) นอกจากนี้ยังมี**/*ตรงข้ามกับชื่อไฟล์อาจเริ่มต้นด้วย./**/* -
Stéphane Chazelas

4

ใช้ \ 0 เป็นตัวคั่นและอ่านชื่อไฟล์ในกระบวนการปัจจุบันจากคำสั่งที่ปรากฏดังนี้:

foo() {
  printf "Hello {%s}\n" "$1"
}

while read -d '' filename; do
  foo "${filename}" </dev/null
done < <(find . -maxdepth 2 -type f -print0)

เกิดอะไรขึ้นที่นี่:

  • read -d '' อ่านจนถึง \ 0 ไบต์ถัดไปดังนั้นคุณไม่ต้องกังวลเกี่ยวกับตัวละครแปลก ๆ ในชื่อไฟล์
  • ในทำนองเดียวกัน-print0ใช้ \ 0 เพื่อยุติชื่อไฟล์ที่สร้างขึ้นแต่ละรายการแทนที่จะเป็น \ n
  • cmd2 < <(cmd1)เป็นเช่นเดียวกับcmd1 | cmd2ยกเว้นว่า cmd2 จะทำงานในเปลือกหลักและไม่ใช่ subshell
  • มีการเปลี่ยนเส้นทางการเรียกไปที่ foo /dev/nullเพื่อให้แน่ใจว่าไม่ได้อ่านจากไพพ์โดยไม่ตั้งใจ
  • $filename ถูกยกมาเพื่อเชลล์จะไม่พยายามแบ่งชื่อไฟล์ที่มีช่องว่าง

ตอนนี้read -dและ<(...)อยู่ใน zsh, bash และ ksh 93u แต่ฉันไม่แน่ใจเกี่ยวกับ ksh รุ่นก่อนหน้านี้


4

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

หมายเหตุ: export -fเฉพาะทุบตี

เนื่องจากเชลล์เท่านั้นที่สามารถเรียกใช้ฟังก์ชันเชลล์ :

find / -exec /bin/bash -c 'function "$1"' bash {} \;

แก้ไข: โดยพื้นฐานแล้วสคริปต์ของคุณควรมีลักษณะดังนี้:

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;

1
export -fเป็นข้อผิดพลาดทางไวยากรณ์ในและพิมพ์นิยามฟังก์ชันไปยังหน้าจอในksh zshในทุกksh, zshและbashก็ไม่ได้แก้ปัญหา
rahmu

1
find / -exec /bin/bash -c 'function' \;
h3rrmiller

ในขณะนี้การทำงาน bashแต่เฉพาะกับ ขอขอบคุณ!
rahmu

4
ไม่เคยฝัง{}ในรหัสเปลือก! นั่นหมายความว่าชื่อไฟล์ถูกตีความว่าเป็นรหัสเชลล์ดังนั้นจึงเป็นสิ่งที่อันตรายมาก
Stéphane Chazelas
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.