แปลง glob เป็น `find`


11

ฉันได้อีกครั้งและอีกครั้งมีปัญหานี้: ฉันมี glob ว่าการแข่งขันว่าไฟล์ที่ถูกต้อง Command line too longแต่สาเหตุ ทุกครั้งที่ฉันแปลงเป็นชุดค่าผสมบางอย่างfindและใช้grepงานได้กับสถานการณ์เฉพาะ แต่ไม่เทียบเท่า 100%

ตัวอย่างเช่น:

./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg

มีเครื่องมือสำหรับแปลง globs เป็นfindนิพจน์ที่ฉันไม่ทราบหรือไม่? หรือมีตัวเลือกสำหรับfindจับคู่กลมโดยไม่ต้องจับคู่กลมเดียวกันในส่วนย่อย (เช่นfoo/*.jpgไม่อนุญาตให้จับคู่bar/foo/*.jpg)?


ขยายรั้งและคุณควรจะสามารถที่จะใช้การแสดงออกผลด้วยหรือ-path ควรจะทำงาน - ยกเว้นว่ามันจะตรงกับ นั่นจะเป็นปัญหาหรือไม่? -ipathfind . -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg'/fooz/blah/bar/quuxA/pic1234d.jpg
muru

ใช่นั่นจะเป็นปัญหา จะต้องเทียบเท่า 100%
Ole Tange

ปัญหาคือเราไม่มีความคิดอะไรคือสิ่งที่แตกต่างอย่างแน่นอน ลวดลายของคุณค่อนข้างโอเค
peterh - Reinstate Monica

ฉันเพิ่มโพสต์ส่วนขยายของคุณเป็นคำตอบสำหรับคำถาม ฉันหวังว่ามันจะไม่เลว
peterh - Reinstate Monica

คุณไม่สามารถทำได้echo <glob> | catถ้าสมมติว่าฉันมีความรู้เรื่องทุบตีเสียงก้องอยู่ในตัวและดังนั้นจึงไม่มีขีด จำกัด สูงสุดของคำสั่ง
Ferrybig

คำตอบ:


15

หากปัญหาคือคุณได้รับข้อผิดพลาด argument-list-is-long-long ให้ใช้ loop หรือ shell ในตัว ในขณะที่command glob-that-matches-too-muchสามารถผิดพลาดfor f in glob-that-matches-too-muchไม่ได้ดังนั้นคุณสามารถทำได้:

for f in foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg
do
    something "$f"
done

ห่วงอาจช้าเลือดตาแทบกระเด็น แต่มันควรจะทำงาน

หรือ:

printf "%s\0" foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg |
  xargs -r0 something

( printfถูก buildin ในเชลล์ส่วนใหญ่การทำงานด้านบนมีข้อ จำกัด ของการexecve()เรียกระบบ)

$ cat /usr/share/**/* > /dev/null
zsh: argument list too long: cat
$ printf "%s\n" /usr/share/**/* | wc -l
165606

ยังทำงานร่วมกับทุบตี ฉันไม่แน่ใจว่าเอกสารนี้ถูกต้องตรงไหน


ทั้งสองเป็นกลุ่มglob2regpat()และ ธfnmatch.translate()สามารถแปลง globs เพื่อ regexes แต่ทั้งสองยังใช้.*สำหรับการจับคู่ข้าม*/


หากเป็นจริงแล้วควรแทนที่somethingด้วยสิ่งที่echoควรทำ
Ole Tange

1
@OleTange นั่นเป็นเหตุผลที่ฉันแนะนำprintf- มันจะเร็วกว่าการเรียกechoหลายพันครั้งและให้ความยืดหยุ่นมากขึ้น
muru

4
มีข้อ จำกัด เกี่ยวกับอาร์กิวเมนต์ที่สามารถส่งผ่านexecซึ่งใช้กับคำสั่งภายนอกเช่นcat; แต่วงเงินที่ไม่ได้นำไปใช้กับเปลือก builtin printfคำสั่งเช่น
Stephen Kitt

1
@OleTange บรรทัดไม่นานเกินไปเพราะprintfเป็น builtin forและเปลือกหอยคงจะใช้วิธีการเดียวกันในการจัดหาข้อโต้แย้งไปว่าพวกเขาใช้สำหรับแจงข้อโต้แย้ง catไม่ใช่ builtin
muru

1
ในทางเทคนิคมีเปลือกหอยเหมือนmkshที่printfไม่ได้อยู่ในตัวและเปลือกเหมือนksh93อยู่ในที่cat(หรือสามารถ) สร้างขึ้น ดูเพิ่มเติมzargsในzshการทำงานรอบ ๆ xargsได้โดยไม่ต้องรีสอร์ท
Stéphane Chazelas

9

find(สำหรับ-name/ เพรดิเคต-pathมาตรฐาน) ใช้รูปแบบสัญลักษณ์แทนเช่น globs (โปรดทราบว่า{a,b}ไม่ใช่ตัวดำเนินการแบบวงกลมหลังจากการขยายคุณจะได้รับสอง globs) ความแตกต่างที่สำคัญคือการจัดการทับ (และจุดไฟล์และ dirs ไม่ได้รับการปฏิบัติเป็นพิเศษในfind) *ใน globs จะไม่ครอบคลุมหลายไดเรกทอรี */*/*จะทำให้รายการไดเรกทอรีมากถึง 2 ระดับ การเพิ่ม-path './*/*/*'จะตรงกับไฟล์ใด ๆ ที่มีความลึกอย่างน้อย 3 ระดับและจะไม่หยุดการแสดงfindรายการเนื้อหาของไดเรกทอรีใด ๆ ที่ระดับความลึกใด ๆ

สำหรับเรื่องนั้นโดยเฉพาะ

./foo*bar/quux[A-Z]{.bak,}/pic[0-9][0-9][0-9][0-9]?.jpg

คู่ของ globs มันง่ายต่อการแปลคุณต้องการไดเรกทอรีที่ระดับความลึก 3 ดังนั้นคุณสามารถใช้:

find . -mindepth 3 -maxdepth 3 \
       \( -path './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' -o \
          -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg' \) \
       -exec cmd {} +

(หรือ-depth 3มีfindการใช้งานบางอย่าง) หรือ POSIXly:

find . -path './*/*/*' -prune \
       \( -path './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' -o \
          -path './foo*bar/quux[A-Z]/pic[0-9][0-9][0-9][0-9]?.jpg' \) \
       -exec cmd {} +

ซึ่งจะรับรองได้ว่าสิ่งเหล่านั้น*และ?ไม่สามารถจับคู่/ตัวละคร

( findตรงกันข้ามกับ globs จะอ่านเนื้อหาของไดเรกทอรีอื่นนอกเหนือจากfoo*barที่อยู่ในไดเรกทอรีปัจจุบัน and และไม่เรียงลำดับรายการไฟล์ แต่ถ้าเราทิ้งปัญหาที่สิ่งที่ถูกจับคู่[A-Z]หรือพฤติกรรมของ*/ ?เกี่ยวกับอักขระที่ไม่ถูกต้องคือ ไม่ได้ระบุคุณจะได้รับรายชื่อไฟล์เดียวกัน)

แต่ไม่ว่าในกรณีใด ๆ ตามที่@muru ได้แสดงขึ้นไม่จำเป็นต้องหันไปใช้findถ้าเพียงเพื่อแยกรายการของไฟล์ออกเป็นหลายการทำงานเพื่อหลีกเลี่ยงข้อ จำกัด ของการexecve()เรียกระบบ บางเชลล์เช่นzsh(พร้อมzargs) หรือksh93(พร้อมcommand -x) ยังมีการสนับสนุนในตัวด้วย

ด้วยzsh(ซึ่ง globs ยังมีภาคเทียบเท่า-type fและภาคอื่น ๆ ส่วนใหญ่find) เช่น:

autoload zargs # if not already in ~/.zshrc
zargs ./foo*bar/quux[A-Z](|.bak)/pic[0-9][0-9][0-9][0-9]?.jpg(.) -- cmd

( (|.bak)เป็นผู้ประกอบการทางตรงกันข้าม glob เพื่อ{,.bak}ที่(.)glob คัดเลือกเทียบเท่าของfind's -type fเพิ่มoNในการมีที่จะข้ามการเรียงลำดับเช่นเดียวกับfind, Dจะรวมจุดไฟล์ (ใช้ไม่ได้กับ glob นี้))


¹สำหรับfindการรวบรวมข้อมูลไดเรกทอรีต้นไม้อย่าง globs คุณจะต้องมีสิ่งต่อไปนี้:

find . ! -name . \( \
  \( -path './*/*' -o -name 'foo*bar' -o -prune \) \
  -path './*/*/*' -prune -name 'pic[0-9][0-9][0-9][0-9]?.jpg' -exec cmd {} + -o \
  \( ! -path './*/*' -o -name 'quux[A-Z]' -o -name 'quux[A-Z].bak' -o -prune \) \)

นั่นคือการตัดไดเรกทอรีทั้งหมดที่ระดับ 1 ยกเว้นไดเรกทอรีfoo*barและที่ระดับ 2 ยกเว้นquux[A-Z]หรือไดเรกทอรีquux[A-Z].bakจากนั้นเลือกpic...ไดเรกทอรีที่ระดับ 3 (และตัดไดเรกทอรีทั้งหมดที่ระดับนั้น)


3

คุณสามารถเขียน regex เพื่อค้นหาความต้องการของคุณ:

find . -regextype egrep -regex './foo[^/]*bar/quux[A-Z](\.bak)?/pic[0-9][0-9][0-9][0-9][^/]?\.jpg'

มีเครื่องมือที่ใช้ในการแปลงนี้เพื่อหลีกเลี่ยงข้อผิดพลาดของมนุษย์?
Ole Tange

ไม่มี แต่เพียงการเปลี่ยนแปลงที่ผมทำก็จะหลบหนี.เพิ่มทางเลือกสำหรับการแข่งขัน.bakและการเปลี่ยนแปลง*เพื่อ[^/]*ที่จะไม่ตรงกับเส้นทางเช่น / foo / foo / บาร์ ฯลฯ
sebasth

แต่ถึงกระนั้นการแปลงของคุณก็ผิด ? จะไม่เปลี่ยนเป็น [^ /] นี่เป็นข้อผิดพลาดของมนุษย์ที่ฉันต้องการหลีกเลี่ยง
Ole Tange

1
ฉันคิดว่าด้วย egrep คุณสามารถตัด[0-9][0-9][0-9][0-9]?ให้สั้นลง[0-9]{3,4}
wjandrea


0

การจดบันทึกในคำตอบอื่น ๆ ของฉันเพื่อเป็นการตอบคำถามของคุณโดยตรงคุณสามารถใช้shสคริปต์POSIX นี้เพื่อแปลง glob ให้เป็นfindนิพจน์:

#! /bin/sh -
glob=${1#./}
shift
n=$#
p='./*'

while true; do
  case $glob in
    (*/*)
      set -- "$@" \( ! -path "$p" -o -path "$p/*" -o -name "${glob%%/*}" -o -prune \)
      glob=${glob#*/} p=$p/*;;
    (*)
      set -- "$@" -path "$p" -prune -name "$glob"
      while [ "$n" -gt 0 ]; do
        set -- "$@" "$1"
        shift
        n=$((n - 1))
      done
      break;;
  esac
done
find . "$@"

วิธีใช้กับglob มาตรฐานหนึ่งอันsh (ไม่ใช่สองตัวอย่างของคุณซึ่งใช้การขยายรั้ง ):

glob2find './foo*bar/quux[A-Z].bak/pic[0-9][0-9][0-9][0-9]?.jpg' \
  -type f -exec cmd {} +

(ที่ไม่ละเว้นไฟล์จุดหรือจุด -dirs ยกเว้น.และ..และไม่เรียงลำดับรายการไฟล์)

หนึ่งนั้นทำงานร่วมกับ globs สัมพันธ์กับไดเรกทอรีปัจจุบันโดยไม่มี.หรือ..ส่วนประกอบ ด้วยความพยายามบางอย่างคุณสามารถขยายไปยัง glob ใด ๆ ได้มากกว่า glob ... ที่สามารถปรับให้เหมาะสมเพื่อที่glob2find 'dir/*'จะไม่มองหาdirเหมือนกันก็คือมันจะเป็นรูปแบบ

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