Рекурсивно шукати шаблон / текст лише у вказаному імені файлу каталогу?


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того, що у випадках, коли до нього пройдений лише один шлях, цей шлях все-таки надрукується (а не просто відповідні рядки з файлу).
Елія Каган

24

Ви також можете використовувати globstar.

Створення grepкоманд за допомогою find, як у відповіді Занні , є надзвичайно надійним, універсальним і портативним способом зробити це (див. Також відповідь sudodus ). І Муру опублікував відмінний підхід до використання варіанту grep's--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використовуючи цей метод. Саме оболонка рекурсує каталоги в розширенні шаблону, що містить **, а неgrep .

Ці інструкції стосуються оболонки Bash. Bash - це оболонка користувача за замовчуванням в Ubuntu (і більшості інших операційних систем GNU / Linux), тому якщо ви перебуваєте на Ubuntu і не знаєте, що таке оболонка, Bash майже напевно. Хоча популярні оболонки зазвичай підтримують глобуси, що **пересувають каталоги , вони не завжди працюють однаково. Для отримання додаткової інформації див Stéphane Chazelas «s відмінний відповідь на Результат логінсервера *, ** Ls і Ls *** на Unix.SE .

Як це працює

Включення globstar Баш опції оболонки робить **шляху відповідності , що містять роздільник каталогів ( /). Таким чином, це глобальний каталог, що повторюється. Зокрема, як man bashпояснюється:

Коли параметр оболонки globstar увімкнено, а * використовується в контексті розширення імені шляху, два суміжні * s, використовувані як єдиний шаблон, будуть відповідати всім файлам і нульовим або більше каталогів і підкаталогів. Якщо слідує a /, два сусідні * s будуть відповідати лише каталогів і підкаталогів.

Вам слід бути обережними з цим, оскільки ви можете запускати команди, які змінюють або видаляють набагато більше файлів, ніж ви планували, особливо якщо ви пишете, **коли ви мали намір писати *. (Це безпечно в цій команді, яка не змінює жодного кроку.) shopt -u globstarВимикає варіант оболонки globstar.

Існує декілька практичних відмінностей між globstar і find.

findнабагато більш універсальний, ніж globstar. Все, що ви можете зробити з globstar, ви можете зробити і з findкомандою. Мені подобається globstar, і іноді це зручніше, але globstar не є загальною альтернативою find.

Наведений вище метод не шукає всередині каталогів, імена яких починаються з а .. Іноді ви не хочете повторювати такі папки, але іноді це робите.

Як і у звичайному глобулі, оболонка будує список усіх відповідних шляхів і передає їх як аргументи вашій команді ( grep) замість самого глобуса. Якщо у вас так багато файлів, що викликаються, file.txtщо отримана команда буде занадто довгою для виконання системи, то спосіб, описаний вище, не вдасться. На практиці вам знадобиться (принаймні) тисячі таких файлів, але це може статися.

Методи, які використовують find, не підпадають під цю обмеження, оскільки:

  • Занна спосіб будує та виконує grepкоманду з потенційно багатьма аргументами шляху. Але якщо більше файлів знайдені , ніж може бути перераховані в одному шляху, то +-завершённое -execдію запускає команду з деякими з шляхів, а потім запускає його знову ще кілька шляхів, і так далі. У разі greping для рядка в декількох файлах це призводить до правильної поведінки.

    Як і метод globstar, який розглядається тут, і цей друкує всі відповідні рядки з попередньою до кожного шляху.

  • Шлях судодуса проходить grepокремо для кожного file.txtзнайденого. Якщо файлів багато, це може бути повільніше, ніж деякі інші методи, але це працює.

    Цей метод знаходить файли та друкує їх шляхи з подальшим узгодженням рядків, якщо такі є. Це інший формат висновок з формату створюваного мого методу, Зано - х і Мури - х .

Отримання кольору за допомогою 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 Спасибі - я уточнив, що не всі оболонки мають цю особливість. Хоча багато оболонок (а не лише баш) підтримують **глобуси, що переміщуються каталогів , ваша основна критика є правильною: виклад **цього відповіді є специфічним для bash, при цьому shopt є лише bash, а термін "globstar" є (я думаю) bash і ткш тільки. Я спочатку оглянув це через ті складності, але ви праві, що це дещо заплутано. Замість того, щоб детально обговорити це у цій відповіді, я посилався на інший (досить ретельний) пост, який займається важким підйомом.
Ілля Каган

@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результатами.
Ілля Каган

@sudodus Так grepсамо не буде робити те, що ти робиш. Деякі інші критики теж помилялися. grep -Hзапускається -execне буде розфарбовуватися без --color(або GREP_COLOR). IEEE 1003.1-2008 не гарантує {}розширення ##### {}:, але Ubuntu має 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).

Приємно - це здається найкращим способом. Простий та ефективний. Я хотів би, щоб я знав про (або думав перевірити сторінку сторінки) цього методу. Спасибі!
Елія Каган

@EliahKagan Я більше здивований, коли Занна не опублікувала це - я показала приклад цього варіанту для іншої відповіді деякий час тому. :)
муру

2
повільний вчитель, на жаль, але я потрапляю туди, врешті-решт, ваші вчення на мене не тратяться повністю;)
Zanna

Це дуже просто і легко запам'ятовується. Дякую.
Раджеш Келадімат

Я згоден, що це найкраща відповідь. Чи слід видалити свою відповідь, щоб зменшити плутанину, або дозволити їй залишитися, щоб показати, що є альтернативи, і що можна зробитиfind?
sudodus

8

Даний метод в відповідь Муру в , працювати 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шукає файли з правильним іменем та пропускає всі інші файли.

Ubuntu використовує пошук GNU , який завжди розширюється, {}навіть коли він з'являється у більшій рядку , наприклад ##### {}:. Якщо вам потрібна ваша команда для роботи findв системах, які можуть не підтримувати це , або ви віддаєте перевагу використовувати -execдію лише при крайній необхідності, ви можете використовувати:

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

Щоб полегшити читання результатів , ви можете використовувати послідовності пошуку ANSI, щоб отримати кольорові імена файлів. Завдяки цьому заголовок шляху кожного файлу краще виділяється з відповідних рядків, які друкуються під ним:

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

Це призводить до того, що ваша оболонка перетворить код втечі для зеленого в фактичну послідовність втечі, яка видає зелений в терміналі, і зробити те ж саме з кодом евакуації для звичайного кольору. Ці шляхові передачі передаються тому 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" {} \;

я збирався зробити "для циклу" з масивом, і я не замислювався про 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 каже: "Відновіть Моніку"

@ G-man, 1) Оскільки я використовував колір ANSI спочатку: find . -name "file.txt" -type f -exec echo -e "\0033[32m{}:\0033[0m" \; -exec grep -i "pattern" {} \;2) Ви можете мати рацію, але поки що це працює для мене. 3) -принт та -принт також є альтернативами. 4) Це вже є в основній відповіді. - У будь-якому випадку, вас вітають власною відповіддю :-)
sudodus

Вам не потрібні два -execдзвінки. Просто використовуйте, grep -Hі це буде надрукувати ім'я файлу (кольоровим), а також відповідний текст.
тердон

0

Тільки зазначимо, що якщо умови питання можна визнати літературними, ви можете скористатися прямим грепом:

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

або

grep 'pattern' abc/def/efg/{1..300}/file.txt
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.