Tìm kiếm đệ quy một mẫu / văn bản chỉ trong tên tệp được chỉ định của một thư mục?


16

Tôi có một thư mục (ví dụ abc/def/efg:) với nhiều thư mục con (ví dụ abc/def/efg/(1..300):). Tất cả các thư mục con này có một tệp chung (ví dụ file.txt:). Tôi muốn tìm kiếm một chuỗi chỉ trong này file.txtkhông bao gồm các tập tin khác. Tôi có thể làm cái này như thế nào?

Tôi đã sử dụng grep -arin "pattern" *, nhưng nó rất chậm nếu chúng ta có nhiều thư mục con và tập tin.


Câu trả lời:


21

Trong thư mục mẹ, bạn có thể sử dụng findvà sau đó chỉ chạy greptrên các tệp đó:

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

2
Tôi cũng đề nghị chuyển qua -Hđể greptrong trường hợp chỉ có một đường dẫn được truyền tới nó, đường dẫn đó vẫn được in (chứ không chỉ là các dòng khớp từ tệp).
Eliah Kagan

24

Bạn cũng có thể sử dụng globalstar.

Xây dựng grepcác lệnh với find, như trong câu trả lời của Zanna , là một cách rất mạnh mẽ, linh hoạt và di động để làm điều này (xem thêm câu trả lời của sudodus ). Và muru đã đăng một cách tiếp cận tuyệt vời của việc sử dụng grepcủa --includetùy chọn . Nhưng nếu bạn chỉ muốn sử dụng greplệnh và shell của mình, có một cách khác để thực hiện - bạn có thể làm cho chính shell thực hiện đệ quy cần thiết :

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

Các -Hlàm cho lá cờ grephiển thị tên tập tin ngay cả khi chỉ có một tập tin phù hợp được tìm thấy. Bạn có thể vượt qua -a, -i-ncờ (từ ví dụ của bạn) để greplà tốt, nếu đó là những gì bạn cần. Nhưng đừng vượt qua -rhoặc -Rkhi sử dụng phương pháp này. Nó là shell mà đệ quy các thư mục trong việc mở rộng mẫu toàn cầu có chứa **, và khônggrep .

Các hướng dẫn này là cụ thể cho vỏ Bash. Bash là vỏ người dùng mặc định trong Ubuntu (và hầu hết các hệ điều hành GNU / Linux khác), vì vậy nếu bạn đang sử dụng Ubuntu và không biết shell của bạn là gì, thì gần như chắc chắn là Bash. Mặc dù các shell phổ biến thường hỗ trợ các giao diện ngang qua thư mục **, chúng không luôn hoạt động theo cùng một cách. Để biết thêm thông tin, xem Stéphane Chazelas 's câu trả lời xuất sắc để Kết quả của ls *, ls ** và ls *** trên Unix.SE .

Làm thế nào nó hoạt động

Bật globstar bash tùy chọn vỏ làm cho **đường dẫn phù hợp có chứa các dấu phân cách thư mục ( /). Do đó, nó là một thư mục đệ quy toàn cầu. Cụ thể, như man bashgiải thích:

Khi globstar tùy chọn vỏ được kích hoạt, và * được sử dụng trong một bối cảnh mở rộng tên đường dẫn, hai liền kề * s sử dụng như một mô hình duy nhất sẽ phù hợp với tất cả các file và số không hoặc nhiều thư mục và thư mục con. Nếu theo sau là a, hai * s liền kề sẽ chỉ khớp với các thư mục và thư mục con.

Bạn nên cẩn thận với điều này, vì bạn có thể chạy các lệnh sửa đổi hoặc xóa nhiều tệp hơn bạn dự định, đặc biệt nếu bạn viết **khi bạn có ý định viết *. (Nó an toàn trong lệnh này, không thay đổi bất kỳ iles nào.) shopt -u globstarTắt tùy chọn shell globalstar trở lại.

Có một vài sự khác biệt thực tế giữa globalstar và find.

findlinh hoạt hơn nhiều so với thế giới. Bất cứ điều gì bạn có thể làm với globalstar, bạn cũng có thể làm với findlệnh. Tôi thích Glostar, và đôi khi nó thuận tiện hơn, nhưng Glostar không phải là sự thay thế chung cho find.

Phương thức trên không nhìn vào bên trong các thư mục có tên bắt đầu bằng a .. Đôi khi bạn không muốn lặp lại các thư mục như vậy, nhưng đôi khi bạn làm như vậy.

Như với một quả cầu thông thường, shell xây dựng một danh sách tất cả các đường dẫn phù hợp và chuyển chúng dưới dạng đối số cho lệnh của bạn ( grep) thay cho chính quả cầu đó. Nếu bạn có quá nhiều tệp được gọi là file.txtlệnh kết quả sẽ quá dài để hệ thống thực thi, thì phương thức trên sẽ thất bại. Trong thực tế, bạn cần (ít nhất) hàng ngàn tệp như vậy, nhưng nó có thể xảy ra.

Các phương thức sử dụng findkhông phải chịu sự hạn chế này, bởi vì:

  • Cách của Zanna xây dựng và chạy một greplệnh với nhiều đối số đường dẫn. Nhưng nếu nhiều tệp được tìm thấy nhiều hơn có thể được liệt kê trong một đường dẫn, thì hành động bị +hủy -execbỏ sẽ chạy lệnh với một số đường dẫn, sau đó chạy lại nó với một số đường dẫn khác, v.v. Trong trường hợp greping cho một chuỗi trong nhiều tệp, điều này tạo ra hành vi chính xác.

    Giống như phương pháp sao được đề cập ở đây, phương pháp này in tất cả các dòng khớp, với các đường dẫn được đặt trước mỗi đường.

  • cách của sudodus chạy grepriêng cho từng file.txttìm thấy. Nếu có nhiều tệp, nó có thể chậm hơn một số phương thức khác, nhưng nó hoạt động.

    Phương thức đó tìm các tệp và in đường dẫn của chúng, theo sau là các dòng khớp nếu có. Đây là một định dạng đầu ra khác với định dạng được tạo bởi phương pháp của tôi, của Zannamuru .

Bắt màu với find

Một trong những lợi ích trước mắt của việc sử dụng globalstar là, theo mặc định trên Ubuntu, grepsẽ tạo ra đầu ra được tô màu. Nhưng bạn có thể dễ dàng có được điều này với find, quá .

Tài khoản người dùng trong Ubuntu được tạo ra với một bí danh mà làm grepthực sự chạy grep --color=auto(chạy alias grepđể xem). Một điều tốt là các bí danh được mở rộng khá nhiều khi bạn phát hành chúng một cách tương tác , nhưng điều đó có nghĩa là nếu bạn muốn findgọi grepbằng --colorcờ, bạn sẽ phải viết nó một cách rõ ràng. Ví dụ:

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

Bạn có thể muốn nói rõ hơn rằng bạn cần sử dụng bashshell để nó hoạt động. Bạn nói rằng nó hoàn toàn nằm trong "tùy chọn bash shell starstar" nhưng nó có thể dễ dàng bị bỏ qua bởi những người đọc quá nhanh.
Stig Hemmer

Tôi đã loại bỏ câu trả lời của mình vì nó gây ra rất nhiều bình luận chỉ trích. Vì vậy, bạn nên loại bỏ các tham chiếu đến nó trong câu trả lời của bạn.
sudodus

@StigHemmer Cảm ơn - Tôi đã làm rõ rằng không phải tất cả các shell đều có tính năng này. Mặc dù nhiều shell (không chỉ bash) hỗ trợ các giao diện ngang qua thư mục **, nhưng phê bình cốt lõi của bạn là chính xác: cách trình bày **trong câu trả lời này là dành riêng cho bash, với shopt chỉ là bash và thuật ngữ "continstar" là (tôi nghĩ) bash và chỉ tcsh. Tôi đã che đậy điều này ban đầu vì những sự phức tạp đó, nhưng bạn nói đúng rằng nó hơi khó hiểu. Thay vì thảo luận dài dòng trong câu trả lời này, tôi đã liên kết với một bài đăng khác (khá kỹ lưỡng) thực hiện việc nâng vật nặng.
Eliah Kagan

@sudodus Tôi đã làm như vậy, nhưng tôi hy vọng điều này là tạm thời. Tôi, và những người khác, đã tìm thấy câu trả lời của bạn có giá trị. Điều đó đúng -ekhông nên được áp dụng cho các đường dẫn, nhưng điều này dễ dàng được sửa. Đối với lệnh đầu tiên, chỉ cần bỏ qua -e. Đối với thứ hai, sử dụng find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;hoặc find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;. Người dùng đôi khi sẽ thích cách của bạn (với -eviệc sử dụng cố định) so với những người khác, in một đường dẫn trên mỗi dòng khớp ; của bạn in một đường dẫn cho mỗi tệp được tìm thấy theo grepkết quả.
Eliah Kagan

@sudodus Vì vậy, grepsẽ không làm những gì bạn đang làm. Một số lời chỉ trích khác cũng sai. grep -Hchạy bằng cách -execkhông tô màu mà không --color(hoặc GREP_COLOR). IEEE 1003.1-2008 không đảm bảo {}mở rộng ##### {}:, nhưng Ubuntu có tìm thấy GNU . Nếu nó ổn với bạn, tôi sẽ chỉnh sửa bài đăng của mình để sửa -elỗi (và làm rõ trường hợp sử dụng của nó) và bạn có thể xem bạn có muốn phục hồi không. (Tôi có đại diện để xem / chỉnh sửa các bài đăng đã bị xóa.)
Eliah Kagan

18

Bạn không cần findđiều này; grepcó thể tự xử lý việc này hoàn toàn tốt

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

Từ 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).

Nice - đây có vẻ là cách tốt nhất. Đơn giản và hiệu quả. Tôi ước tôi đã biết về (hoặc suy nghĩ để kiểm tra trang chủ cho) phương pháp này. Cảm ơn!
Eliah Kagan

@EliahKagan Tôi ngạc nhiên hơn khi Zanna không đăng bài này - Tôi đã đưa ra một ví dụ về tùy chọn này cho một câu trả lời khác một thời gian trước đây. :)
muru

2
Người học chậm, than ôi, nhưng cuối cùng tôi cũng đến đó, những lời dạy của bạn không hoàn toàn lãng phí đối với tôi;)
Zanna

Điều này rất đơn giản và dễ nhớ. Cảm ơn bạn.
Rajesh Keladimath

Tôi đồng ý, rằng đây là câu trả lời tốt nhất. Tôi có nên xóa câu trả lời của mình để giảm sự nhầm lẫn hay để nó ở lại để cho thấy rằng có những lựa chọn thay thế, và những gì có thể được thực hiện vớifind?
sudodus

8

Phương pháp được đưa ra trong câu trả lời của muru , chạy grepvới --includecờ để chỉ định tên tệp, thường là lựa chọn tốt nhất. Tuy nhiên, điều này cũng có thể được thực hiện với find.

Cách tiếp cận trong câu trả lời này sử dụng findđể chạy grepriêng cho từng tệp được tìm thấy và in đường dẫn đến từng tệp chính xác một lần , bên trên các dòng khớp được tìm thấy trong mỗi tệp. (Các phương thức in đường dẫn phía trước mỗi dòng khớp được bao phủ trong các câu trả lời khác.)


Bạn có thể thay đổi thư mục lên đầu cây thư mục nơi bạn có các tệp đó. Sau đó chạy:

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

Điều đó in đường dẫn (liên quan đến thư mục hiện tại, . và bao gồm cả tên tệp) của mỗi tệp được đặt tên file.txt, theo sau là tất cả các dòng khớp trong tệp. Điều này hoạt động vì {}là một giữ chỗ cho các tập tin được tìm thấy. Đường dẫn của mỗi tệp được đặt tách biệt với nội dung của nó bằng cách được thêm tiền tố #####và chỉ được in một lần, trước các dòng khớp từ tệp đó. (Các tệp được gọi file.txtkhông chứa kết quả khớp vẫn có đường dẫn được in.) Bạn có thể thấy đầu ra này ít lộn xộn hơn so với những gì bạn nhận được từ các phương thức in một đường dẫn ở đầu mỗi dòng khớp.

Sử dụng findnhư thế này hầu như sẽ luôn nhanh hơn chạygrep trên mọi tệp ( grep -arin "pattern" *), vì findtìm kiếm các tệp có tên chính xác và bỏ qua tất cả các tệp khác.

Ubuntu sử dụng GNU find , nó luôn mở rộng {}ngay cả khi nó xuất hiện trong một chuỗi lớn hơn , như thế ##### {}:. Nếu bạn cần lệnh của bạn để làm việc với findcác hệ thống có thể không hỗ trợ điều này hoặc bạn chỉ muốn sử dụng -exechành động khi thực sự cần thiết, bạn có thể sử dụng:

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

Để làm cho đầu ra dễ đọc hơn , bạn có thể sử dụng các chuỗi thoát ANSI để lấy tên tệp màu. Điều này làm cho tiêu đề đường dẫn của mỗi tệp nổi bật hơn so với các dòng phù hợp được in bên dưới nó:

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

Điều đó làm cho trình bao của bạn biến mã thoát cho màu xanh lục thành chuỗi thoát thực tế tạo ra màu xanh lục trong một thiết bị đầu cuối và thực hiện tương tự với mã thoát cho màu bình thường. Những lối thoát này được chuyển đến find, sử dụng chúng khi nó in tên tệp. ($' ' Báo giá là cần thiết ở đây vì find's -printfhành động không công nhận \eđể giải thích mã thoát ANSI.)

Nếu bạn thích, thay vào đó bạn có thể sử dụng -execvới lệnh của hệ thốngprintf (hỗ trợ \e). Vì vậy, một cách khác để làm điều tương tự là:

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

tôi sẽ tạo ra một "vòng lặp for" với một mảng và tôi đã không nghĩ về tùy chọn thực thi gốc từ find. Tốt một! Nhưng tôi nghĩ rằng việc sử dụng dấu chấm sẽ xác định vị trí của bạn trong thư mục mà bạn đã ở đó. Đúng nếu tôi đã sai lầm. Sẽ không tốt hơn nếu chỉ định phân tích trực tiếp để phân tích theo thứ tự tìm? find abc/def/efg -name "file.txt" -type f -exec echo -e "##### {}:" \; -exec grep -i "pattern" {} \;
kcdtv

Chắc chắn, điều đó sẽ loại bỏ lệnh cd abc/def/efg'thay đổi thư mục' :-)
sudodus

(1) Tại sao bạn chỉ định -etùy chọn echo? Điều đó sẽ khiến nó xáo trộn bất kỳ tên tệp nào có dấu gạch chéo ngược. (2) Sử dụng {}như một phần của đối số không được đảm bảo để hoạt động. Nó sẽ tốt hơn để nói -exec echo "#####" {} \;hay -exec printf "##### %s:\n" {} \;. (3) Tại sao không chỉ sử dụng -printhay -printf? (4) Cũng xem xét grep -H.
G-Man nói 'Phục hồi Monica'

@ G-man, 1) Bởi vì ban đầu tôi đã sử dụng màu ANSI: find . -name "file.txt" -type f -exec echo -e "\0033[32m{}:\0033[0m" \; -exec grep -i "pattern" {} \;2) Bạn có thể đúng, nhưng cho đến nay điều này vẫn hiệu quả với tôi. 3) -print và -printf cũng là những lựa chọn thay thế. 4) Điều này đã có trong câu trả lời chính. - Dù sao, bạn được chào đón với câu trả lời của riêng bạn :-)
sudodus

Bạn không cần hai -execcuộc gọi. Chỉ cần sử dụng grep -Hvà điều đó sẽ in tên tệp (màu) cũng như văn bản phù hợp.
terdon

0

Chỉ cần chỉ ra rằng nếu các điều kiện của câu hỏi có thể được sử dụng theo nghĩa đen, bạn có thể sử dụng grep trực tiếp:

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

hoặc là

grep 'pattern' abc/def/efg/{1..300}/file.txt
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.