ค้นหารูปแบบ / ข้อความซ้ำในชื่อไฟล์ที่ระบุของไดเรกทอรีซ้ำหรือไม่


16

ฉันมีไดเรกทอรี (เช่นabc/def/efg) กับหลายไดเรกทอรีย่อย (เช่น ,: abc/def/efg/(1..300)) ไดเรกทอรีย่อยทั้งหมดเหล่านี้มีไฟล์ทั่วไป (เช่นfile.txt) ฉันต้องการค้นหาสตริงในรายการนี้เท่านั้นfile.txtไม่รวมไฟล์อื่น ๆ ฉันจะทำสิ่งนี้ได้อย่างไร

ฉันใช้ grep -arin "pattern" *แต่มันช้ามากถ้าเรามีไดเรกทอรีย่อยและไฟล์จำนวนมาก


คำตอบ:


21

ในไดเรกทอรีหลักคุณสามารถใช้findและเรียกใช้grepไฟล์เหล่านั้นเท่านั้น:

find . -type f -iname "file.txt" -exec grep -Hi "pattern" '{}' +

2
ฉันขอแนะนำให้ส่งผ่าน-Hไปยังgrepเพื่อให้ในกรณีที่มีเพียงหนึ่งพา ธ ที่ถูกส่งผ่านไปยังพา ธ นั้นจะยังคงถูกพิมพ์
Eliah Kagan

24

คุณสามารถใช้ globstar ได้เช่นกัน

การสร้างgrepคำสั่งด้วยfindเช่นเดียวกับในคำตอบของ Zannaเป็นวิธีที่มีความทนทานสูงอเนกประสงค์และพกพาได้ในการทำเช่นนี้ (ดูคำตอบของ sudodus ด้วย ) และMuru ได้โพสต์วิธีการที่ดีเยี่ยมของการใช้grepของ--includeตัวเลือก แต่ถ้าคุณต้องการใช้เพียงแค่grepคำสั่งและเชลล์ของคุณมีวิธีอื่นในการทำมัน - คุณสามารถทำให้เชลล์ตัวเองทำการเรียกซ้ำที่จำเป็น :

shopt -s globstar   # you can skip this if you already have globstar turned on
grep -H 'pattern' **/file.txt

การ-Hตั้งค่าสถานะทำให้grepแสดงชื่อไฟล์แม้ว่าจะพบไฟล์ที่ตรงกันเพียงหนึ่งไฟล์ คุณสามารถส่งต่อ-aและ-i, -n(จากตัวอย่างของคุณ) ไปยังgrepเช่นกันหากนั่นคือสิ่งที่คุณต้องการ แต่อย่าผ่าน-rหรือ-Rเมื่อใช้วิธีนี้ มันเป็นเปลือกที่ recurses ไดเรกทอรีในการขยายรูปแบบ glob ที่มี**และไม่ได้grep

คำแนะนำเหล่านี้ใช้เฉพาะกับ Bash shell Bash เป็นเชลล์ผู้ใช้เริ่มต้นใน Ubuntu (และระบบปฏิบัติการ GNU / Linux อื่น ๆ ส่วนใหญ่) ดังนั้นถ้าคุณอยู่บน Ubuntu และไม่รู้ว่าเชลล์ของคุณคืออะไร ถึงแม้ว่าเชลล์ยอดนิยมมักจะสนับสนุนการโกรฟไดเรคทอรี่ที่ข้ามเส้นทาง**แต่มันก็ไม่ได้ทำงานในลักษณะเดียวกัน สำหรับข้อมูลเพิ่มเติมโปรดดูที่Stéphane Chazelas 's คำตอบที่ดีเพื่อผลมาจากคำสั่ง ls *, ** การ LS และ LS ***ในUnix.SE

มันทำงานอย่างไร

การเปิดGLOBSTARทุบตีตัวเลือกเปลือกทำให้**เส้นทางการแข่งขันที่มีคั่นไดเรกทอรี ( /) มันจึงเป็นไดเรกทอรีเรียกซ้ำ โดยเฉพาะตามที่man bashอธิบาย:

เมื่อเปิดใช้งานตัวเลือกglobstar shell และ * ถูกใช้ในบริบทการขยายชื่อพา ธ * s ที่อยู่ติดกันสองตัวที่ใช้เป็นรูปแบบเดียวจะจับคู่ไฟล์ทั้งหมดและไดเรกทอรีหรือไดเรกทอรีย่อยหรือไดเรกทอรีศูนย์หรือมากกว่า หากตามด้วย a / สอง * s ที่อยู่ติดกันจะจับคู่เฉพาะไดเรกทอรีและไดเรกทอรีย่อย

คุณควรจะระมัดระวังเกี่ยวกับเรื่องนี้เนื่องจากคุณสามารถเรียกใช้คำสั่งที่ปรับเปลี่ยนหรือลบไฟล์ที่ไกลมากขึ้นกว่าที่คุณตั้งใจโดยเฉพาะอย่างยิ่งถ้าคุณเขียนเมื่อคุณหมายถึงการเขียน** *(ปลอดภัยในคำสั่งนี้ซึ่งไม่เปลี่ยน iles ใด ๆ ) shopt -u globstarปิดตัวเลือก globstar shell ออก

มีความแตกต่างในทางปฏิบัติระหว่าง globstar และfind.

findมีความหลากหลายมากขึ้นกว่า globstar ทุกสิ่งที่คุณสามารถทำได้ด้วย globstar คุณสามารถทำได้ด้วยfindคำสั่งเช่นกัน ฉันชอบ GLOBSTAR และบางครั้งก็สะดวกมากขึ้น แต่ GLOBSTAR ไม่ได้เป็นทั่วไปfindทางเลือกในการ

.วิธีการดังกล่าวข้างต้นไม่ได้ดูภายในไดเรกทอรีที่มีชื่อขึ้นต้นด้วย บางครั้งคุณไม่ต้องการเรียกคืนโฟลเดอร์ดังกล่าว แต่บางครั้งคุณก็ทำ

เช่นเดียวกับ glob ทั่วไปเปลือกสร้างรายการของเส้นทางที่ตรงกันทั้งหมดและส่งผ่านพวกเขาเป็นอาร์กิวเมนต์ไปยังคำสั่งของคุณ ( grep) แทน glob ตัวเอง หากคุณมีไฟล์จำนวนมากที่เรียกfile.txtว่าคำสั่งผลลัพธ์จะยาวเกินไปสำหรับระบบที่จะเรียกใช้งานดังนั้นวิธีการด้านบนจะล้มเหลว ในทางปฏิบัติคุณต้องการไฟล์อย่างน้อยหลายพันไฟล์ แต่มันอาจเกิดขึ้นได้

วิธีการที่ใช้findไม่อยู่ภายใต้ข้อ จำกัด นี้เนื่องจาก:

  • วิธีของ Zannaสร้างและเรียกใช้grepคำสั่งด้วยอาร์กิวเมนต์ของพา ธ จำนวนมาก แต่หากพบไฟล์มากเกินกว่าที่จะแสดงรายการไว้ในพา ธ เดียวแอ็คชัน+-minminated -execจะรันคำสั่งด้วยบางพา ธจากนั้นรันไฟล์นั้นอีกครั้งด้วยพา ธ เพิ่มเติมและอื่น ๆ ในกรณีของgrepไอเอ็นจีสำหรับสตริงในหลายไฟล์สิ่งนี้ก่อให้เกิดพฤติกรรมที่ถูกต้อง

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

  • วิธี sudodusทำงานgrepแยกกันสำหรับแต่ละfile.txtพบ หากมีไฟล์จำนวนมากมันอาจช้ากว่าวิธีอื่น ๆ แต่ใช้งานได้

    เมธอดนั้นค้นหาไฟล์และพิมพ์พา ธ ของพวกเขาตามด้วยบรรทัดที่ตรงกันหากมี นี่คือรูปแบบที่แตกต่างกันออกจากรูปแบบที่ผลิตโดยวิธีการของฉันZanna ของและMuru ของ

รับสีด้วย find

ข้อดีอย่างหนึ่งของการใช้ globstar คือโดยค่าเริ่มต้นบน Ubuntu grepจะให้ผลลัพธ์ที่เป็นสี แต่คุณสามารถรับสิ่งนี้ได้ง่ายfindเช่นกัน

บัญชีผู้ใช้ใน Ubuntu ถูกสร้างขึ้นด้วยนามแฝงที่ทำให้grepทำงานจริงๆgrep --color=auto(วิ่งalias grepเพื่อดู) มันเป็นสิ่งที่ดีที่จะนามแฝงสวยมากขยายเฉพาะเมื่อคุณออกพวกเขาโต้ตอบแต่มันหมายความว่าถ้าคุณต้องการที่findจะก่อให้เกิดgrepกับ--colorธงคุณจะต้องเขียนไว้อย่างชัดเจน ตัวอย่างเช่น:

find . -name file.txt -exec grep --color=auto -H 'pattern' {} +

คุณอาจต้องการระบุให้ชัดเจนยิ่งขึ้นว่าคุณต้องใช้bashเชลล์เพื่อให้ทำงานได้ คุณจะบอกว่ามันโดยปริยายในตัวเลือก "เปลือก GLOBSTAR ทุบตี" แต่ก็สามารถพลาดได้อย่างง่ายดายโดยคนที่อ่านเร็วเกินไป
เฮมเมอร์

ฉันลบคำตอบของฉันเพราะมันทำให้เกิดความคิดเห็นที่สำคัญมากมาย ดังนั้นคุณควรลบการอ้างอิงถึงมันในคำตอบของคุณ
sudodus

@StigHemmer ขอบคุณ - ฉันชี้แจงว่าเชลล์บางตัวเท่านั้นที่มีคุณสมบัตินี้ แม้ว่าเปลือกหอยจำนวนมาก (ไม่ใช่แค่ทุบตี) ทำหน้าที่สนับสนุน**globs สำรวจเส้นทางแต่คำวิจารณ์หลักของคุณนั้นถูกต้อง: การนำเสนอของ**คำตอบนี้เฉพาะกับทุบตีเท่านั้นโดยเฉพาะการทุบตี shopt และคำว่า "globstar" คือการทุบตีและ tcsh เท่านั้น ฉันเคยขัดเรื่องนี้เพราะความซับซ้อนเหล่านั้น แต่คุณพูดถูกว่ามันค่อนข้างสับสน แทนที่จะพูดถึงเรื่องนี้ในคำตอบนี้ฉันได้เชื่อมโยงกับโพสต์อื่น (ค่อนข้างละเอียด) ที่ยกของหนัก
Eliah Kagan

@ Sudodus ฉันได้ทำไปแล้ว แต่ฉันหวังว่านี่จะเป็นการชั่วคราว ฉันและคนอื่น ๆ พบว่าคำตอบของคุณมีค่า -eไม่ควรใช้ความจริงกับเส้นทาง แต่สิ่งนี้ได้รับการแก้ไขอย่างง่ายดาย -eสำหรับคำสั่งแรกเพียงงด สำหรับที่สองการใช้งานหรือfind . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \; find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;บางครั้งผู้ใช้จะชอบวิธีของคุณ (ด้วย-eการใช้งานคงที่) กับผู้อื่นซึ่งพิมพ์หนึ่งเส้นทางต่อบรรทัดที่ตรงกัน ; คุณพิมพ์หนึ่งเส้นทางต่อไฟล์ที่พบตามด้วยgrepผลลัพธ์
Eliah Kagan

@ Sudodus ดังนั้นgrepตัวมันเองจะไม่ทำในสิ่งที่คุณกำลังทำอยู่ การวิพากษ์วิจารณ์อื่น ๆ ก็ผิดเช่นกัน grep -Hดำเนินการโดย-execจะไม่เปลี่ยนสีหากไม่มี--color(หรือGREP_COLOR) IEEE 1,003.1-2008ไม่ได้รับประกันการ{}ขยายตัวใน##### {}:แต่อูบุนตูมีGNU พบซึ่งจะ หากคุณตกลงฉันจะแก้ไขโพสต์เพื่อแก้ไข-eข้อบกพร่อง (และชี้แจงกรณีการใช้งาน) และคุณสามารถดูว่าคุณต้องการยกเลิกการลบหรือไม่ (ฉันมีตัวแทนเพื่อดู / แก้ไขโพสต์ที่ถูกลบ)
Eliah Kagan

18

คุณไม่ต้องการfindสิ่งนี้ grepสามารถจัดการกับมันได้อย่างสมบูรณ์แบบด้วยตัวเอง:

grep "pattern" . -airn --include="file.txt"

จากman grep:

--exclude=GLOB
      Skip  files  whose  base  name  matches  GLOB  (using   wildcard
      matching).   A  file-name  glob  can  use  *,  ?,  and [...]  as
      wildcards, and \ to quote  a  wildcard  or  backslash  character
      literally.

--exclude-from=FILE
      Skip  files  whose  base name matches any of the file-name globs
      read from FILE  (using  wildcard  matching  as  described  under
      --exclude).

--exclude-dir=DIR
      Exclude  directories  matching  the  pattern  DIR from recursive
      searches.

--include=GLOB
      Search  only  files whose base name matches GLOB (using wildcard
      matching as described under --exclude).

นีซ - ดูเหมือนว่าเป็นวิธีที่ดีที่สุด ง่ายและมีประสิทธิภาพ ฉันหวังว่าฉันรู้ (หรือคิดว่าจะตรวจสอบ manpage) วิธีนี้ ขอบคุณ!
Eliah Kagan

@EliahKagan ฉันแปลกใจมากที่ Zanna ไม่ได้โพสต์สิ่งนี้ - ฉันได้แสดงตัวอย่างของตัวเลือกนี้สำหรับคำตอบอื่นในช่วงเวลาที่ผ่านมา :)
muru

2
ผู้เรียนช้า, อนิจจา, แต่ฉันไปถึงที่นั่นในที่สุด, คำสอนของคุณจะไม่สูญเปล่าอย่างสมบูรณ์กับฉัน;)
Zanna

ง่ายมากและง่ายต่อการจดจำ ขอขอบคุณ.
Rajesh Keladimath

ฉันเห็นด้วยว่านี่คือคำตอบที่ดีที่สุด ฉันควรลบคำตอบของฉันเพื่อลดความสับสนหรือปล่อยให้มันอยู่เพื่อแสดงให้เห็นว่ามีทางเลือกและสิ่งที่สามารถทำได้ด้วยfind?
sudodus

8

วิธีการที่ระบุไว้ในคำตอบของ muru การรันgrepด้วย--includeแฟล็กเพื่อระบุชื่อไฟล์มักเป็นตัวเลือกที่ดีที่สุด findแต่สามารถนี้ทำได้ด้วย

วิธีการในคำตอบนี้ใช้findเพื่อเรียกใช้grepแยกกันสำหรับแต่ละไฟล์ที่พบและพิมพ์เส้นทางไปยังแต่ละไฟล์อย่างแน่นอนหนึ่งครั้งเหนือเส้นจับคู่ที่พบในแต่ละไฟล์ (วิธีการที่พิมพ์เส้นทางด้านหน้าของทุกบรรทัดที่ตรงกันจะครอบคลุมในคำตอบอื่น ๆ )


คุณสามารถเปลี่ยนไดเรกทอรีเป็นด้านบนของแผนผังไดเรกทอรีที่คุณมีไฟล์เหล่านั้น จากนั้นเรียกใช้:

find . -name "file.txt" -type f -exec echo "##### {}:" \; -exec grep -i "pattern" {} \;

ที่พิมพ์เส้นทาง (สัมพันธ์กับไดเรกทอรีปัจจุบัน.และรวมถึงชื่อไฟล์ของตัวเอง) ของแต่ละไฟล์ที่มีชื่อfile.txtตามด้วยบรรทัดที่ตรงกันทั้งหมดในไฟล์ ใช้งานได้เพราะ{}เป็นตัวยึดตำแหน่งสำหรับไฟล์ที่พบ พา ธ ของไฟล์แต่ละไฟล์จะถูกแยกออกจากเนื้อหาของมันโดยการขึ้นต้นด้วย#####และจะพิมพ์เพียงครั้งเดียวก่อนที่จะจับคู่บรรทัดจากไฟล์นั้น (ไฟล์ที่เรียกfile.txtว่าไม่มีการจับคู่ยังคงมีการพิมพ์เส้นทางของพวกเขา) คุณอาจพบว่าผลลัพธ์นี้รกน้อยกว่าสิ่งที่คุณได้รับจากวิธีการที่พิมพ์เส้นทางที่จุดเริ่มต้นของทุกบรรทัดที่ตรงกัน

การใช้findวิธีนี้จะเร็วกว่าการเรียกใช้grepในทุกไฟล์ ( grep -arin "pattern" *) เนื่องจากfindการค้นหาไฟล์ด้วยชื่อที่ถูกต้องและข้ามไฟล์อื่นทั้งหมด

อูบุนตูใช้ GNU พบซึ่งมักจะขยาย{}แม้เมื่อมันปรากฏในสตริงที่มีขนาดใหญ่##### {}:เช่น หากคุณต้องการให้คำสั่งของคุณทำงานกับfindระบบที่อาจไม่รองรับสิ่งนี้หรือคุณต้องการใช้การ-execกระทำเมื่อจำเป็นเท่านั้นคุณสามารถใช้:

find . -name "file.txt" -type f -printf '##### %p:\n' -exec grep -i "pattern" {} \;

เพื่อให้ง่ายต่อการอ่านเอาต์พุตคุณสามารถใช้ ANSI escape sequences เพื่อรับชื่อไฟล์สี สิ่งนี้ทำให้ส่วนหัวของพา ธ ของแต่ละไฟล์โดดเด่นยิ่งขึ้นจากบรรทัดที่ตรงกันที่พิมพ์ภายใต้:

find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;

นั่นเป็นสาเหตุที่ทำให้เปลือกของคุณเพื่อเปิดรหัสหลบหนีสำหรับสีเขียวเป็นลำดับการหลบหนีที่เกิดขึ้นจริงที่ผลิตสีเขียวใน terminal และจะทำสิ่งเดียวกันกับรหัสหลบหนีสำหรับสีปกติ escapes เหล่านี้จะถูกส่งผ่านไปยังfindซึ่งจะใช้พวกเขาเมื่อมันพิมพ์ชื่อไฟล์ ( $' 'คำพูดเป็นสิ่งที่จำเป็นที่นี่เพราะfind's-printfการกระทำไม่รู้จัก\eสำหรับการตีความรหัสหนี ANSI.)

หากคุณต้องการคุณสามารถใช้-execกับระบบprintfคำสั่ง (ซึ่งไม่สนับสนุน\e) ดังนั้นวิธีอื่นในการทำสิ่งเดียวกันคือ:

find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;

ฉันจะทำ "for loop" กับ array และฉันไม่คิดเกี่ยวกับตัวเลือก native exec จากการค้นหา สิ่งที่ดี! แต่ฉันคิดว่าการใช้จุดจะค้นหาคุณในไดเรกทอรีที่คุณอยู่แล้ว ถูกต้องฉันถ้าฉันผิด จะเป็นการดีกว่าหรือไม่หากระบุการแยกวิเคราะห์โดยตรงในลำดับการค้นหา find abc/def/efg -name "file.txt" -type f -exec echo -e "##### {}:" \; -exec grep -i "pattern" {} \;
kcdtv

แน่นอนว่าจะกำจัดcd abc/def/efgคำสั่ง 'เปลี่ยนไดเรกทอรี' :-)
sudodus

(1) ทำไมคุณระบุ-eตัวเลือกที่จะecho? ซึ่งจะทำให้การรวมชื่อไฟล์ใด ๆ ที่มีแบ็กสแลชหายไป (2) การใช้{}เป็นส่วนหนึ่งของการโต้แย้งไม่รับประกันว่าจะทำงาน มันจะดีกว่าที่จะพูดหรือ-exec echo "#####" {} \; -exec printf "##### %s:\n" {} \;(3) ทำไมไม่เพียงใช้-printหรือ-printf? (4) พิจารณาgrep -Hด้วย
G-Man กล่าวว่า 'Reinstate Monica'

@ G-man, 1) เพราะฉันใช้สี ANSI ในตอนแรก: find . -name "file.txt" -type f -exec echo -e "\0033[32m{}:\0033[0m" \; -exec grep -i "pattern" {} \;2) คุณอาจพูดถูก แต่จนถึงตอนนี้มันใช้งานได้สำหรับฉัน 3) -print และ -printf ยังเป็นทางเลือก 4) นี่มีอยู่แล้วในคำตอบหลัก - อย่างไรก็ตามคุณยินดีต้อนรับด้วยคำตอบของคุณเอง :-)
sudodus

คุณไม่จำเป็นต้องใช้-execสายทั้งสอง เพียงใช้grep -Hและจะพิมพ์ชื่อไฟล์ (สี) รวมถึงข้อความที่ตรงกัน
terdon

0

เพียงเพื่อชี้ให้เห็นว่าหากเงื่อนไขของคำถามที่สามารถนำมาวรรณกรรมคุณสามารถใช้ grep โดยตรง:

grep 'pattern' abc/def/efg/*/file.txt

หรือ

grep 'pattern' abc/def/efg/{1..300}/file.txt
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.