Пошук з командного рядка

Останнє оновлення 2025-11-27 | Редагувати цю сторінку

Приблизний час: 45 хвилин

Огляд

Питання

  • Як я можу знайти потрібні файли?
  • Як знайти щось у файлах?

Цілі

  • Використати grep для пошуку у текстових файлах рядків, які відповідають простим шаблонам.
  • Використати find для пошуку файлів і каталогів, назви яких відповідають простим шаблонам.
  • Використати вихідні дані однієї команди як аргумент(и) командного рядка для іншої команди.
  • Пояснити, що мається на увазі під ‘текстовими’ та ‘бінарними’ файлами, і чому багато поширених інструментів погано працюють з останніми.

Так само, як багато хто з нас зараз використовує ‘Google’ як дієслово, що означає ‘шукати’, Unix-програмісти часто використовують слово ‘grep’. ‘grep’ - це скорочення від ‘global/regular expression/print’ (з англ. ‘глобальний/регулярний вираз/друк’), поширена послідовність операцій у ранніх текстових редакторах Unix. Це також назва дуже корисної програми командного рядка.

grep шукає і виводить рядки у файлах, які відповідають шаблону. У нашому прикладі ми використаємо файл, який містить три хайку, взяті з конкурсу 1998 року в журналі Salon (авторство належить Біллу Торкасо (Bill Torcaso), Говарду Кордеру (Howard Korder) та Маргарет Сігал (Margaret Segall), відповідно. Див. Haiku Error Messages в архіві [Сторінка 1] (https://web.archive.org/web/20000310061355/http://www.salon.com/21st/chal/1998/02/10chal2.html) та Сторінка 2 .). Для цього набору прикладів ми будемо працювати у підкаталозі writing:

BASH

$ cd
$ cd Desktop/shell-lesson-data/exercise-data/writing
$ cat haiku.txt

ВИХІД

The Tao that is seen
Is not the true Tao, until
You bring fresh toner.

With searching comes loss
and the presence of absence:
"My Thesis" not found.

Yesterday it worked
Today it is not working
Software is like that.

Знайдемо рядки, які містять слово ‘not’:

BASH

$ grep not haiku.txt

ВИХІД

Is not the true Tao, until
"My Thesis" not found
Today it is not working

У цьому випадку not — це шаблон для пошуку. Команда grep шукає у файлі збіги із заданим шаблоном. Щоб скористатися нею, введіть grep, далі шаблон для пошуку, а потім назву файлу (або файлів), у якому (у яких) ми шукаємо.

У вихідний файл виводяться три рядки, які містять літери ‘not’.

За замовчуванням grep шукає шаблон з урахуванням регістру. Також обраний нами шаблон пошуку не обов’язково повинен бути повним словом, як показано в наступному прикладі.

Відшукаймо тепер шаблон ‘The’.

BASH

$ grep The haiku.txt

ВИХІД

The Tao that is seen
"My Thesis" not found.

Цього разу буде виведено два рядки з літерами ‘The’, і один із них містить наш шаблон пошуку всередині довшого слова ‘Thesis’.

Щоб обмежити збіги до рядків, що містять слово ‘The’ окремо, а не як частинку іншого слова, ми використаємо grep з опцією -w. Це обмежить збіги лише межами повних слів.

Пізніше у цьому уроці ми також побачимо, як можна змінити поведінку пошуку grep стосовно чутливості до регістру.

BASH

$ grep -w The haiku.txt

ВИХІД

The Tao that is seen

Зауважте, що ‘межа слова’ включає початок і кінець рядка, а не лише літери, оточені пробілами. Іноді ми хочемо шукати не окреме слово, а фразу. Це також легко зробити за допомогою grep, взявши фразу в лапки.

BASH

$ grep -w "is not" haiku.txt

ВИХІД

Today it is not working

Ми вже бачили, що не обов’язково брати в лапки окремі слова, але лапки варто використовувати під час пошуку кількох слів. Це також допомагає легше відрізнити пошуковий термін або фразу від файлу, в якому відбувається пошук. У наступних прикладах ми будемо використовувати лапки.

Ще одна корисна опція - це -n, яка додає до виводу номери знайдених рядків:

BASH

$ grep -n "it" haiku.txt

ВИХІД

5:With searching comes loss
9:Yesterday it worked
10:Today it is not working

Ми бачимо, що рядки 5, 9 і 10 містять літери ‘it’.

Ми можемо комбінувати опції (тобто прапорці) так само як і в інших командах Unix. Наприклад, знайдемо рядки, які містять слово ‘the’. Ми можемо комбінувати опцію -w для пошуку рядків зі словом ‘the’, та опцію -n для нумерації рядків із результатами:

BASH

$ grep -n -w "the" haiku.txt

ВИХІД

2:Is not the true Tao, until
6:and the presence of absence:

Тепер ми хочемо використати опцію -i, щоб зробити наш пошук нечутливим до регістру:

BASH

$ grep -n -w -i "the" haiku.txt

ВИХІД

1:The Tao that is seen
2:Is not the true Tao, until
6:and the presence of absence:

Тепер використаймо опцію -v для зворотного пошуку, тобто виводу рядків, які не містять слова ‘the’.

BASH

$ grep -n -w -v "the" haiku.txt

ВИХІД

1:The Tao that is seen
3:You bring fresh toner.
4:
5:With searching comes loss
7:"My Thesis" not found.
8:
9:Yesterday it worked
10:Today it is not working
11:Software is like that.

Якщо ми використовуємо опцію -r (recursive, з англ. - рекурсивний), grep може шукати шаблон рекурсивно у підкаталогах.

Виконаймо рекурсивний пошук слова Yesterday у каталозі shell-lesson-data/exercise-data/writing:

BASH

$ grep -r Yesterday .

ВИХІД

./LittleWomen.txt:"Yesterday, when Aunt was asleep and I was trying to be as still as a
./LittleWomen.txt:Yesterday at dinner, when an Austrian officer stared at us and then
./LittleWomen.txt:Yesterday was a quiet day spent in teaching, sewing, and writing in my
./haiku.txt:Yesterday it worked

grep має багато інших опцій. Щоб переглянути їх, ми можемо ввести:

BASH

$ grep --help

ВИХІД

Usage: grep [OPTION]... PATTERN [FILE]...
Search for PATTERN in each FILE or standard input.
PATTERN is, by default, a basic regular expression (BRE).
Example: grep -i 'hello world' menu.h main.c

Regexp selection and interpretation:
  -E, --extended-regexp     PATTERN is an extended regular expression (ERE)
  -F, --fixed-strings       PATTERN is a set of newline-separated fixed strings
  -G, --basic-regexp        PATTERN is a basic regular expression (BRE)
  -P, --perl-regexp         PATTERN is a Perl regular expression
  -e, --regexp=PATTERN      use PATTERN for matching
  -f, --file=FILE           obtain PATTERN from FILE
  -i, --ignore-case         ignore case distinctions
  -w, --word-regexp         force PATTERN to match only whole words
  -x, --line-regexp         force PATTERN to match only whole lines
  -z, --null-data           a data line ends in 0 byte, not newline

Miscellaneous:
...        ...        ...
Вправа

Використання grep

Яка команда призведе до наступного результату:

ВИХІД

and the presence of absence:
  1. grep "of" haiku.txt
  2. grep -E "of" haiku.txt
  3. grep -w "of" haiku.txt
  4. grep -i "of" haiku.txt

Правильна відповідь 3, тому що опція -w шукає збіги лише між цілими словами. Інші варіанти також шукатимуть збіги зі словом ‘of’, якщо воно є частиною іншого слова.

Виноска

Символи підстановки

Проте справжня сила grep полягає не у його опціях, а у тому, що шаблони можуть містити символи підстановки. (Технічний термін для них - регулярні вирази (regular expressions) - саме це має на увазі ‘re’ у слові ‘grep’). Регулярні вирази є водночас складними й потужними; якщо ви хочете виконувати розширені пошуки, перегляньте цей урок на нашому сайті. Як короткий приклад, ми можемо знайти рядки, у яких літера ‘o’ знаходиться на другій позиції, ось так:

BASH

$ grep -E "^.o" haiku.txt

ВИХІД

You bring fresh toner.
Today it is not working
Software is like that.

Ми використовуємо опцію -E і беремо шаблон у лапки, щоб оболонка не намагалася його інтерпретувати іншим чином. (Наприклад, якщо шаблон містить *, то оболонка спробує розгорнути його перед виконанням grep.) Символ ^ у шаблоні вимагає, щоб збіг був на початку рядка. Символ . відповідає одному символу (подібно до ? у командному рядку), тоді як o відповідає справжній літері ‘o’.

Вправа

Відстеження видів диких тварин

Лея має кілька сотень файлів даних, збережених в одному каталозі, кожен з яких відформатовано таким чином:

2012-11-05,deer,5
2012-11-05,rabbit,22
2012-11-05,raccoon,7
2012-11-06,rabbit,19
2012-11-06,deer,2
2012-11-06,fox,4
2012-11-07,rabbit,16
2012-11-07,bear,1

Вона хоче створити командний скрипт, який використовує вид тварини як перший аргумент командного рядка, а каталог — як другий. Скрипт повинен повернути один файл з назвою <species>.txt, який містить список дат і кількість особин цього виду, які були помічені для кожної дати. Наприклад, використовуючи дані, показані вище, rabbit.txt буде містити:

2012-11-05,22
2012-11-06,19
2012-11-07,16

Нижче кожен рядок містить окрему команду або канал. Розташуйте їх у правильному порядку в одній команді, щоб допомогти Леї досягти її мети:

BASH

cut -d : -f 2
>
|
grep -w $1 -r $2
|
$1.txt
cut -d , -f 1,3

Підказка: перегляньте man grep для інформації про рекурсивний пошук у каталогах і man cut для виділення декількох полів у рядку.

Приклад файлу такого типу наведено у shell-lesson-data/exercise-data/animal-counts/animals.сsv.

grep -w $1 -r $2 | cut -d : -f 2 | cut -d , -f 1,3 > $1.txt

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

Ось як слід запускати наведений вище скрипт:

BASH

$ bash count-species.sh bear .
Вправа

“Маленькі жінки”

Ви з другом щойно закінчили читати “Маленькі жінки” Луїзи Мей Елкотт і дискутуєте. З чотирьох сестер у книзі — Джо, Мег, Бет і Емі — ваш друг вважає, що Джо згадувалася найчастіше. Ви, однак, впевнені, що це Емі. На щастя, у вас є файл LittleWomen.txt, який містить повний текст роману (shell-lesson-data/exercise-data/writing/LittleWomen.txt). Використовуючи цикл for, як можна вивести звіт про те, скільки разів згадується кожна з чотирьох сестер?

Підказка: один варіант відповіді може використовувати команди grep, wc та | разом, а інший може використовувати опції команди grep. Зазвичай існує кілька способів розв’язання задачі програмування, вибір рішення залежить від комбінації отримання правильного результату, елегантності, читабельності та швидкості.

for sis in Jo Meg Beth Amy
do
    echo $sis:
    grep -ow $sis LittleWomen.txt | wc -l
done

Альтернативне, трохи гірше рішення:

for sis in Jo Meg Beth Amy
do
    echo $sis:
    grep -ocw $sis LittleWomen.txt
done

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

Уважні спостерігачі могли помітити, що імена персонажів іноді пишуться великими літерами у назвах розділів (наприклад, “MEG GOES TO VANITY FAIR”). Якщо ви хочете врахувати й ці випадки, можна додати опцію -i для нечутливості до регістру (хоча в цьому випадку це не впливає на відповідь, яка сестра згадується найчастіше).

Поки grep знаходить рядки у файлах, команда find знаходить самі файли. Знову ж таки, у неї є багато опцій; щоб продемонструвати, як працюють найпростіші з них, ми скористаємося структурою каталогів shell-lesson-data/exercise-data, наведеною нижче.

ВИХІД

.
├── animal-counts/
│   └── animals.csv
├── creatures/
│   ├── basilisk.dat
│   ├── minotaur.dat
│   └── unicorn.dat
├── numbers.txt
├── alkanes/
│   ├── cubane.pdb
│   ├── ethane.pdb
│   ├── methane.pdb
│   ├── octane.pdb
│   ├── pentane.pdb
│   └── propane.pdb
└── writing/
    ├── haiku.txt
    └── LittleWomen.txt

Каталог exercise-data містить один файл numbers.txt та чотири підкаталоги: animal-counts, creatures, proteins і writing, кожен з яких містить різні файли.

Для початку виконаймо find . (не забудьте запустити цю команду з каталогу shell-lesson-data/exercise-data).

BASH

$ find .

ВИХІД

.
./writing
./writing/LittleWomen.txt
./writing/haiku.txt
./creatures
./creatures/basilisk.dat
./creatures/unicorn.dat
./creatures/minotaur.dat
./animal-counts
./animal-counts/animals.csv
./numbers.txt
./alkanes
./alkanes/ethane.pdb
./alkanes/propane.pdb
./alkanes/octane.pdb
./alkanes/pentane.pdb
./alkanes/methane.pdb
./alkanes/cubane.pdb

Як завжди, символ . сам по собі позначає поточний робочий каталог, звідки починається наш пошук. Результатом виконання find буде перелік імен усіх файлів та каталогів у поточному робочому каталозі. Спочатку це може виглядати безглуздо, але find має багато можливостей для фільтрації результатів, і у цьому уроці ми розглянемо деякі з них.

Наприклад, опція -type d означає ‘обʼєкти, які є каталогами’. Як і очікувалося, команда find виведе імена п’яти каталогів (включно з .):

BASH

$ find . -type d

ВИХІД

.
./writing
./creatures
./animal-counts
./alkanes

Зверніть увагу, що об’єкти, які знаходить find, не відсортовані. Якщо ми змінимо -type d на -type f, натомість ми отримаємо список усіх файлів:

BASH

$ find . -type f

ВИХІД

./writing/LittleWomen.txt
./writing/haiku.txt
./creatures/basilisk.dat
./creatures/unicorn.dat
./creatures/minotaur.dat
./animal-counts/animals.csv
./numbers.txt
./alkanes/ethane.pdb
./alkanes/propane.pdb
./alkanes/octane.pdb
./alkanes/pentane.pdb
./alkanes/methane.pdb
./alkanes/cubane.pdb

Тепер спробуємо пошук за іменем:

BASH

$ find . -name *.txt

ВИХІД

./numbers.txt

Ми очікували, що будуть знайдені усі текстові файли, але було виведено лише ./numbers.txt. Проблема полягає у тому, що оболонка розкриває символи підстановки, такі як *, ще до виконання команд. Оскільки *.txt у поточному каталозі розширюється до ./numbers.txt, то команда, яку ми виконали, була такою:

BASH

$ find . -name numbers.txt

Команда find зробила те, що ми просили; ми просто попросили не те, що слід.

Щоб досягти потрібного результату, слід зробити так само, як і з grep: візьмемо *.txt у лапки, щоб оболонка не змогла розгорнути шаблон *. Таким чином, find фактично отримає шаблон *.txt, а не ім’я файлу numbers.txt:

BASH

$ find . -name "*.txt"

ВИХІД

./writing/LittleWomen.txt
./writing/haiku.txt
./numbers.txt
Виноска

Порівняння ls та find

Обидві команди ls та find можна налаштувати для виконання подібних завдань за допомогою відповідних опцій, але зазвичай ls перелічує всі доступні елементи, тоді як, тоді як find шукає обʼєкти з певними властивостями.

Як ми вже зазначали, потужність командного рядка полягає в об’єднанні різних інструментів. Ми бачили, як цього досягти за допомогою каналів; тепер розглянемо іншу методику. Як ми щойно бачили, команда find . -name "*.txt" повертає список усіх текстових файлів у поточному каталозі та його підкаталогах. Як ми можемо поєднати це з wc -l, щоб порахувати кількість рядків в усіх цих файлах?

Найпростіший спосіб - помістити команду find всередину $():

BASH

$ wc -l $(find . -name "*.txt")

ВИХІД

  21022 ./writing/LittleWomen.txt
     11 ./writing/haiku.txt
      5 ./numbers.txt
  21038 total

Коли термінал виконуватиме цю команду, він спочатку виконує все, що знаходиться у виразі $(). Потім він замінить вираз $() на результат виконання цієї команди. Оскільки результатом команди find є три файли ./writing/LittleWomen.txt, ./writing/haiku.txt та ./numbers.txt, термінал створює таку команду:

BASH

$ wc -l ./writing/LittleWomen.txt ./writing/haiku.txt ./numbers.txt

що є саме тим, що нам було потрібно. Це розширення працює так само, як обробка шаблонів * та ? в оболонці, але дозволяє нам використовувати будь-яку команду як власний “шаблон”.

Дуже поширено використовувати find та grep разом. Перша команда знаходить файли, які відповідають заданому шаблону; тоді як друга шукає в цих файлах рядки, що відповідають іншому шаблону. Наприклад, ми можемо знайти txt-файли, які містять слово “searching” шляхом пошуку рядка ‘searching’ у всіх файлах .txt поточного каталогу:

BASH

$ grep "searching" $(find . -name "*.txt")

ВИХІД

./writing/LittleWomen.txt:sitting on the top step, affected to be searching for her book, but was
./writing/haiku.txt:With searching comes loss
Вправа

Порівняння та віднімання

Параметр -v із командою grep змінює логіку зіставлення на протилежну, тому виводяться лише рядки, які не відповідають шаблону. Враховуючи це, яка з наведених нижче команд знайде всі файли .dat у каталозі creatures окрім файлу unicorn.dat? Після того, як ви обміркуєте свою відповідь, ви можете протестувати команди у каталогу shell-lesson-data/exercise-data.

  1. find creatures -name "*.dat" | grep -v unicorn
  2. find creatures -name *.dat | grep -v unicorn
  3. grep -v "unicorn" $(find creatures -name "*.dat")
  4. Жоден із наведених вище варіантів.

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

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

Варіант 3 є хибним, оскільки він переглядає вміст файлів у пошуках рядків, що не містять слово ‘unicorn’, замість фільтрації за іменами файлів.

Виноска

Бінарні файли

Ми зосереджувалися виключно на пошуку шаблонів у текстових файлах. Але що робити, якщо ваші дані зберігаються у вигляді зображень, баз даних або в іншому форматі?

Існує декілька інструментів, які розширюють можливості grep для роботи з деякими нетекстовими форматами. Проте більш гнучкий підхід полягає в перетворенні даних у текст або вилучення текстових елементів з даних. З одного боку, це полегшує виконання простих завдань. З іншого боку, складні завдання зазвичай неможливо виконати. Наприклад, досить легко написати програму, яка знаходить розміри X і Y з файлів зображень для роботи з grep, але як ви напишете щось для пошуку значень в електронній таблиці, клітинки якої містять формули?

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

Термінал Unix був створений ще до того, як народилась більшість його користувачів. Він проіснував так довго, тому що це одне з найпродуктивніших середовищ для програмування, які коли-небудь були створені - можливо, навіть саме найпродуктивніше. Хоча його синтаксис може здаватися незрозумілим, ті, хто його опанував, можуть експериментувати з різними командами в інтерактивному режимі, а потім використовувати набуті знання для автоматизації своїх завдань. Графічні інтерфейси користувача можуть бути простішими у використанні спочатку, але після опанування терміналу, продуктивність роботи в ньому стає неперевершеною. І, як писав Альфред Норт Уайтхед у 1911 році: ‘Цивілізація розвивається шляхом збільшення кількості важливих операцій, які ми можемо виконувати, не думаючи про них свідомо’.

Вправа

Розуміння використання find у конвеєрі

Напишіть короткий пояснювальний коментар до наступного скрипту термінала:

BASH

wc -l $(find . -name "*.dat") | sort -n
  1. Рекурсивно знаходить всі файли з розширенням .dat у поточному каталозі

  2. Рахує кількість рядків у кожному з цих файлів

  3. Сортує вивід з пункту 2. за числовим значенням

Ключові моменти
  • find шукає файли з певними властивостями, які відповідають шаблонам.
  • grep фільтрує та повертає рядки з файлів, які відповідають заданим шаблонам.
  • Опція --help підтримується багатьма командами bash та програмами, які можна виконати у bash, для отримання довідки щодо їх використання.
  • man [команда] показує сторінку довідки для заданої команди.
  • $([команда]) виконує команду та заміняє вираз $() на результат її виконання.