Secara rekursif mencari pola / teks hanya dalam nama file yang ditentukan direktori?


16

Saya memiliki direktori (mis., abc/def/efg) Dengan banyak sub-direktori (mis .::) abc/def/efg/(1..300). Semua sub-direktori ini memiliki file yang sama (misalnya, file.txt). Saya ingin mencari string hanya di ini file.txttermasuk file lain. Bagaimana saya bisa melakukan ini?

Saya menggunakan grep -arin "pattern" *, tetapi sangat lambat jika kita memiliki banyak subdirektori dan file.


Jawaban:


21

Di direktori induk, Anda bisa menggunakan finddan menjalankan grephanya file-file itu:

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

2
Saya sarankan juga beralih -Hke grepsehingga, dalam kasus ketika hanya satu jalur dilewatkan ke sana, jalur itu masih dicetak (bukan hanya garis yang cocok dari file).
Eliah Kagan

24

Anda juga bisa menggunakan globstar.

Membangun grepperintah dengan find, seperti dalam jawaban Zanna , adalah cara yang sangat kuat, fleksibel, dan portabel untuk melakukan ini (lihat juga jawaban sudodus ). Dan Muru telah diposting sebuah pendekatan yang sangat baik dari menggunakan grep's --includepilihan . Tetapi jika Anda hanya ingin menggunakan grepperintah dan shell Anda, ada cara lain untuk melakukannya - Anda dapat membuat shell itu sendiri melakukan rekursi yang diperlukan :

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

The -Hmerek Bendera grepmenunjukkan nama file bahkan jika hanya satu file yang cocok ditemukan. Anda dapat melewati -a, -idan -nbendera (dari contoh Anda) untuk grepjuga, jika itu yang Anda butuhkan. Tetapi jangan lulus -ratau -Rsaat menggunakan metode ini. Ini adalah shell yang mengulang direktori dalam memperluas pola glob yang mengandung **, dan tidakgrep .

Instruksi ini khusus untuk Bash shell. Bash adalah shell pengguna default di Ubuntu (dan sebagian besar sistem operasi GNU / Linux) lainnya, jadi jika Anda menggunakan Ubuntu dan tidak tahu apa shell Anda, itu hampir pasti Bash. Meskipun cangkang populer biasanya mendukung **gumpalan direktori- traverse, mereka tidak selalu bekerja dengan cara yang sama. Untuk informasi lebih lanjut, lihat Stéphane Chazelas 's jawaban yang sangat baik untuk Hasil ls *, ls ** dan ls *** di Unix.SE .

Bagaimana itu bekerja

Mengaktifkan opsi bash shell globstar membuat jalur yang cocok berisi pemisah direktori ( ). Dengan demikian, ini adalah glob yang berulang direktori. Secara khusus, seperti yang dijelaskan:**/man bash

Ketika opsi shell globstar diaktifkan, dan * digunakan dalam konteks ekspansi pathname, dua * berdekatan yang digunakan sebagai pola tunggal akan cocok dengan semua file dan nol atau lebih direktori dan subdirektori. Jika diikuti oleh a /, dua * yang berdekatan hanya akan cocok dengan direktori dan subdirektori.

Anda harus berhati-hati dengan ini, karena Anda dapat menjalankan perintah yang mengubah atau menghapus lebih banyak file daripada yang Anda inginkan, terutama jika Anda menulis **ketika Anda ingin menulis *. (Aman dalam perintah ini, yang tidak mengubah iles.) shopt -u globstarMematikan opsi shell globstar.

Ada beberapa perbedaan praktis antara globstar dan find.

findjauh lebih fleksibel daripada globstar. Apa pun yang dapat Anda lakukan dengan globstar, Anda dapat melakukannya dengan findperintah juga. Saya suka globstar, dan kadang lebih nyaman, tetapi globstar bukan alternatif umumfind .

Metode di atas tidak melihat ke dalam direktori yang namanya dimulai dengan a .. Terkadang Anda tidak ingin mengulang folder seperti itu, tetapi terkadang Anda melakukannya.

Seperti halnya bola biasa, shell membuat daftar semua jalur yang cocok dan meneruskannya sebagai argumen untuk perintah Anda ( grep) sebagai ganti bola itu sendiri. Jika Anda memiliki begitu banyak file yang dipanggil file.txtsehingga perintah yang dihasilkan akan terlalu lama untuk dieksekusi oleh sistem, maka metode di atas akan gagal. Dalam praktiknya Anda akan membutuhkan (setidaknya) ribuan file seperti itu, tetapi itu bisa terjadi.

Metode yang digunakan findtidak tunduk pada batasan ini, karena:

  • Cara Zanna membangun dan menjalankan grepperintah dengan argumen jalur yang berpotensi banyak. Tetapi jika lebih banyak file ditemukan daripada yang bisa didaftarkan dalam satu jalur, tindakan +-minminasi -execmenjalankan perintah dengan beberapa jalur, kemudian jalankan lagi dengan beberapa jalur lagi, dan sebagainya. Dalam hal grepmencari string dalam banyak file, ini menghasilkan perilaku yang benar.

    Seperti metode globstar yang dicakup di sini, ini mencetak semua garis yang cocok, dengan jalur yang masing-masingnya berurutan.

  • cara sudodus berjalan grepsecara terpisah untuk setiap file.txtditemukan. Jika ada banyak file, mungkin lebih lambat dari beberapa metode lain, tetapi berhasil.

    Metode itu menemukan file dan mencetak jalurnya, diikuti oleh garis yang cocok jika ada. Ini adalah format output yang berbeda dari format yang dihasilkan oleh metode saya, Zanna , dan muru .

Mendapatkan warna dengan find

Salah satu manfaat langsung dari menggunakan globstar adalah, secara default di Ubuntu, grepakan menghasilkan keluaran berwarna. Tapi Anda dapat dengan mudah mendapatkan ini dengan findjuga .

Akun pengguna di Ubuntu dibuat dengan alias yang menjadikannya grepbenar-benar berjalan grep --color=auto(run alias grepto see). Ini hal yang baik yang alias cukup banyak hanya diperluas ketika Anda mengeluarkan mereka secara interaktif , tetapi itu berarti bahwa jika Anda ingin finduntuk memohon grepdengan --colorbendera, Anda harus menulis secara eksplisit. Sebagai contoh:

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

Anda mungkin ingin menyatakan lebih jelas bahwa Anda harus menggunakan bashshell agar ini berfungsi. Anda jangan mengatakan itu secara implisit dalam "globstar shell bash pilihan" tapi dapat dengan mudah terjawab oleh orang-orang membaca terlalu cepat.
Stig Hemmer

Saya menghapus jawaban saya karena menyebabkan banyak komentar kritis. Jadi, Anda harus menghapus referensi untuk itu dalam jawaban Anda.
sudodus

@StigHemmer Terima kasih - Saya sudah mengklarifikasi bahwa tidak semua shell memiliki fitur ini. Meskipun banyak shell (bukan hanya bash) yang mendukung **gumpalan direktori- traverse, kritik inti Anda benar: presentasi **dalam jawaban ini khusus untuk bash, dengan shopt menjadi bash saja dan istilah "globstar" menjadi (saya pikir) bash dan tcsh saja. Saya awalnya mengabaikan ini karena kerumitan itu, tetapi Anda benar bahwa itu agak membingungkan. Daripada membahasnya secara panjang lebar dalam jawaban ini, saya telah menautkan ke pos lain (cukup teliti) yang melakukan pekerjaan berat.
Eliah Kagan

@sudodus saya sudah melakukannya, tapi saya harap ini bersifat sementara. Saya, dan yang lainnya, menganggap jawaban Anda berharga. Memang benar -etidak boleh diterapkan pada jalur, tetapi ini mudah diperbaiki. Untuk perintah pertama, abaikan saja -e. Untuk yang kedua, gunakan find . -name file.txt -printf $'\e[32m%p:\e[0m\n' -exec grep -i "pattern" {} \;atau find . -name file.txt -exec printf '\e[32m%s:\e[0m\n' {} \; -exec grep -i "pattern" {} \;. Pengguna kadang-kadang lebih suka jalan Anda (dengan -epenggunaan tetap) daripada yang lain, yang mencetak satu jalur per baris yang cocok ; Anda mencetak satu jalur per file yang ditemukan diikuti oleh grephasil.
Eliah Kagan

@sudodus Jadi grepitu sendiri tidak akan melakukan apa yang Anda lakukan. Beberapa kritik lain juga salah. grep -Hdijalankan oleh -exectidak akan berwarna tanpa --color(atau GREP_COLOR). IEEE 1003.1-2008 tidak menjamin {}ekspansi ##### {}:, tetapi Ubuntu memiliki GNU find . Jika tidak apa-apa dengan Anda, saya akan mengedit posting Anda untuk memperbaiki -ebug (dan mengklarifikasi kasus penggunaannya) dan Anda dapat melihat apakah Anda ingin membatalkan penghapusan. (Saya punya perwakilan untuk melihat / mengedit posting yang dihapus.)
Eliah Kagan

18

Anda tidak perlu finduntuk ini; grepdapat menangani ini dengan sangat baik sendiri:

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

Dari 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).

Bagus - sepertinya ini cara terbaik. Sederhana dan efisien. Saya berharap saya tahu tentang (atau berpikir untuk memeriksa halaman manual) metode ini. Terima kasih!
Eliah Kagan

@EliahKagan Saya lebih terkejut Zanna tidak memposting ini - saya telah menunjukkan contoh opsi ini untuk jawaban lain beberapa waktu lalu. :)
muru

2
pelajar yang lambat, sayangnya, tapi akhirnya saya sampai di sana, ajaran Anda tidak sepenuhnya menyia-nyiakan saya;)
Zanna

Ini sangat sederhana dan mudah diingat. Terima kasih.
Rajesh Keladimath

Saya setuju, bahwa ini adalah jawaban terbaik. Haruskah saya menghapus jawaban saya untuk mengurangi kebingungan, atau membiarkannya tetap menunjukkan bahwa ada alternatif, dan apa yang dapat dilakukan denganfind?
sudodus

8

Metode yang diberikan dalam jawaban muru , berjalan grepdengan --includebendera untuk menentukan nama file, seringkali merupakan pilihan terbaik. Namun, ini juga bisa dilakukan dengan find.

Pendekatan dalam jawaban ini digunakan finduntuk menjalankan grepsecara terpisah untuk setiap file yang ditemukan, dan mencetak path ke setiap file tepat sekali , di atas garis yang cocok ditemukan di setiap file. (Metode yang mencetak jalur di depan setiap baris yang cocok tercakup dalam jawaban lain.)


Anda dapat mengubah direktori ke atas pohon direktori tempat Anda menyimpan file-file itu. Lalu lari:

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

Itu mencetak path (relatif ke direktori saat ini .,, dan termasuk nama file itu sendiri) dari setiap file bernama file.txt, diikuti oleh semua baris yang cocok dalam file. Ini bekerja karena {}merupakan tempat untuk file yang ditemukan. Setiap jalur file dipisahkan dari isinya dengan diawali dengan #####, dan dicetak hanya sekali, sebelum baris yang cocok dari file itu. (File yang dipanggil file.txtyang tidak memiliki kecocokan masih memiliki jalurnya dicetak.) Anda mungkin menemukan output ini kurang berantakan daripada apa yang Anda dapatkan dari metode yang mencetak jalur di awal setiap baris yang cocok.

Menggunakan findseperti ini hampir selalu lebih cepat daripada menjalankan greppada setiap file ( grep -arin "pattern" *), karena findmencari file dengan nama yang benar dan melompati semua file lainnya.

Ubuntu menggunakan GNU find , yang selalu mengembang {}bahkan ketika muncul dalam string yang lebih besar , seperti ##### {}:. Jika Anda memerlukan perintah untuk bekerja dengan findsistem yang mungkin tidak mendukung ini , atau Anda lebih suka menggunakan -exectindakan hanya jika benar-benar diperlukan, Anda dapat menggunakan:

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

Untuk membuat output lebih mudah dibaca , Anda dapat menggunakan urutan pelarian ANSI untuk mendapatkan nama file berwarna. Ini membuat tajuk jalur masing-masing file lebih menonjol dari garis yang cocok yang dicetak di bawahnya:

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

Itu menyebabkan shell Anda mengubah kode escape untuk hijau menjadi urutan escape aktual yang menghasilkan green di terminal, dan untuk melakukan hal yang sama dengan kode escape untuk warna normal. Luput ini dilewatkan ke find, yang menggunakannya saat mencetak nama file. ( $' 'Kutipan diperlukan di sini karena find's -printftindakan tidak mengakui \euntuk menafsirkan kode melarikan diri ANSI.)

Jika Anda suka, Anda malah bisa menggunakan -execdengan sistem printfperintah (yang tidak mendukung \e). Jadi cara lain untuk melakukan hal yang sama adalah:

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

saya akan membuat "untuk loop" dengan array dan saya tidak berpikir tentang opsi asli exec dari find. Bagus Tapi saya pikir menggunakan dot akan menempatkan Anda di direktori tempat Anda berada. Koreksi saya jika saya salah. Bukankah lebih baik untuk menentukan langsung untuk mem-parsing dalam urutan pencarian? find abc/def/efg -name "file.txt" -type f -exec echo -e "##### {}:" \; -exec grep -i "pattern" {} \;
kcdtv

Tentu, itu akan menghilangkan perintah cd abc/def/efg'direktori perubahan' :-)
sudodus

(1) Mengapa Anda menentukan -eopsi echo? Itu akan menyebabkannya memotong-motong nama file yang mengandung garis miring terbalik. (2) Menggunakan {}sebagai bagian dari argumen tidak dijamin berfungsi. Akan lebih baik untuk mengatakan -exec echo "#####" {} \;atau -exec printf "##### %s:\n" {} \;. (3) Kenapa tidak pakai saja -printatau -printf? (4) Pertimbangkan juga grep -H.
G-Man Mengatakan 'Reinstate Monica'

@ G-man, 1) Karena saya menggunakan warna ANSI awalnya: find . -name "file.txt" -type f -exec echo -e "\0033[32m{}:\0033[0m" \; -exec grep -i "pattern" {} \;2) Anda mungkin benar, tapi sejauh ini ini bekerja untuk saya. 3) -print dan -printf juga merupakan alternatif. 4) Ini sudah ada di jawaban utama. - Bagaimanapun, Anda dipersilakan dengan jawaban Anda sendiri :-)
sudodus

Anda tidak perlu dua -execpanggilan. Cukup gunakan grep -Hdan itu akan mencetak nama file (berwarna) serta teks yang cocok.
terdon

0

Hanya untuk menunjukkan bahwa jika kondisi pertanyaan dapat diambil sastra, Anda dapat menggunakan grep langsung:

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

atau

grep 'pattern' abc/def/efg/{1..300}/file.txt
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.