Пошук з командного рядка
Останнє оновлення 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:
ВИХІД
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’:
ВИХІД
Is not the true Tao, until
"My Thesis" not found
Today it is not working
У цьому випадку not — це шаблон для пошуку. Команда
grep шукає у файлі збіги із заданим шаблоном. Щоб
скористатися нею, введіть grep, далі шаблон для пошуку, а
потім назву файлу (або файлів), у якому (у яких) ми шукаємо.
У вихідний файл виводяться три рядки, які містять літери ‘not’.
За замовчуванням grep шукає шаблон з урахуванням
регістру. Також обраний нами шаблон пошуку не обов’язково повинен бути
повним словом, як показано в наступному прикладі.
Відшукаймо тепер шаблон ‘The’.
ВИХІД
The Tao that is seen
"My Thesis" not found.
Цього разу буде виведено два рядки з літерами ‘The’, і один із них містить наш шаблон пошуку всередині довшого слова ‘Thesis’.
Щоб обмежити збіги до рядків, що містять слово ‘The’ окремо, а не як
частинку іншого слова, ми використаємо grep з опцією
-w. Це обмежить збіги лише межами повних слів.
Пізніше у цьому уроці ми також побачимо, як можна змінити поведінку
пошуку grep стосовно чутливості до регістру.
ВИХІД
The Tao that is seen
Зауважте, що ‘межа слова’ включає початок і кінець рядка, а не лише
літери, оточені пробілами. Іноді ми хочемо шукати не окреме слово, а
фразу. Це також легко зробити за допомогою grep, взявши
фразу в лапки.
ВИХІД
Today it is not working
Ми вже бачили, що не обов’язково брати в лапки окремі слова, але лапки варто використовувати під час пошуку кількох слів. Це також допомагає легше відрізнити пошуковий термін або фразу від файлу, в якому відбувається пошук. У наступних прикладах ми будемо використовувати лапки.
Ще одна корисна опція - це -n, яка додає до виводу
номери знайдених рядків:
ВИХІД
5:With searching comes loss
9:Yesterday it worked
10:Today it is not working
Ми бачимо, що рядки 5, 9 і 10 містять літери ‘it’.
Ми можемо комбінувати опції (тобто прапорці) так само як і в інших
командах Unix. Наприклад, знайдемо рядки, які містять слово ‘the’. Ми
можемо комбінувати опцію -w для пошуку рядків зі словом
‘the’, та опцію -n для нумерації рядків із
результатами:
ВИХІД
2:Is not the true Tao, until
6:and the presence of absence:
Тепер ми хочемо використати опцію -i, щоб зробити наш
пошук нечутливим до регістру:
ВИХІД
1:The Tao that is seen
2:Is not the true Tao, until
6:and the presence of absence:
Тепер використаймо опцію -v для зворотного пошуку, тобто
виводу рядків, які не містять слова ‘the’.
ВИХІД
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:
ВИХІД
./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 має багато інших опцій. Щоб переглянути їх, ми
можемо ввести:
ВИХІД
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:
grep "of" haiku.txtgrep -E "of" haiku.txtgrep -w "of" haiku.txtgrep -i "of" haiku.txt
Правильна відповідь 3, тому що опція -w шукає збіги лише
між цілими словами. Інші варіанти також шукатимуть збіги зі словом ‘of’,
якщо воно є частиною іншого слова.
Символи підстановки
Проте справжня сила grep полягає не у його опціях, а у
тому, що шаблони можуть містити символи підстановки. (Технічний термін
для них - регулярні вирази (regular expressions) - саме
це має на увазі ‘re’ у слові ‘grep’). Регулярні вирази є водночас
складними й потужними; якщо ви хочете виконувати розширені пошуки,
перегляньте цей
урок на нашому сайті. Як короткий приклад, ми можемо знайти рядки, у
яких літера ‘o’ знаходиться на другій позиції, ось так:
ВИХІД
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
Нижче кожен рядок містить окрему команду або канал. Розташуйте їх у правильному порядку в одній команді, щоб допомогти Леї досягти її мети:
Підказка: перегляньте 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 і перегляньте вивід після
кожного етапу, щоб зрозуміти, чому це відбувається.
Ось як слід запускати наведений вище скрипт:
“Маленькі жінки”
Ви з другом щойно закінчили читати “Маленькі жінки” Луїзи Мей Елкотт
і дискутуєте. З чотирьох сестер у книзі — Джо, Мег, Бет і Емі — ваш друг
вважає, що Джо згадувалася найчастіше. Ви, однак, впевнені, що це Емі.
На щастя, у вас є файл 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).
ВИХІД
.
./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 виведе імена
п’яти каталогів (включно з .):
ВИХІД
.
./writing
./creatures
./animal-counts
./alkanes
Зверніть увагу, що об’єкти, які знаходить find, не
відсортовані. Якщо ми змінимо -type d на
-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
Тепер спробуємо пошук за іменем:
ВИХІД
./numbers.txt
Ми очікували, що будуть знайдені усі текстові файли, але було
виведено лише ./numbers.txt. Проблема полягає у тому, що
оболонка розкриває символи підстановки, такі як *, ще
до виконання команд. Оскільки *.txt у поточному
каталозі розширюється до ./numbers.txt, то команда, яку ми
виконали, була такою:
Команда find зробила те, що ми просили; ми просто
попросили не те, що слід.
Щоб досягти потрібного результату, слід зробити так само, як і з
grep: візьмемо *.txt у лапки, щоб оболонка не
змогла розгорнути шаблон *. Таким чином, find
фактично отримає шаблон *.txt, а не ім’я файлу
numbers.txt:
ВИХІД
./writing/LittleWomen.txt
./writing/haiku.txt
./numbers.txt
Порівняння ls та
find
Обидві команди ls та find можна налаштувати
для виконання подібних завдань за допомогою відповідних опцій, але
зазвичай ls перелічує всі доступні елементи, тоді як, тоді
як find шукає обʼєкти з певними властивостями.
Як ми вже зазначали, потужність командного рядка полягає в об’єднанні
різних інструментів. Ми бачили, як цього досягти за допомогою каналів;
тепер розглянемо іншу методику. Як ми щойно бачили, команда
find . -name "*.txt" повертає список усіх текстових файлів
у поточному каталозі та його підкаталогах. Як ми можемо поєднати це з
wc -l, щоб порахувати кількість рядків в усіх цих
файлах?
Найпростіший спосіб - помістити команду find всередину
$():
ВИХІД
21022 ./writing/LittleWomen.txt
11 ./writing/haiku.txt
5 ./numbers.txt
21038 total
Коли термінал виконуватиме цю команду, він спочатку виконує все, що
знаходиться у виразі $(). Потім він замінить вираз
$() на результат виконання цієї команди. Оскільки
результатом команди find є три файли
./writing/LittleWomen.txt, ./writing/haiku.txt
та ./numbers.txt, термінал створює таку команду:
що є саме тим, що нам було потрібно. Це розширення працює так само,
як обробка шаблонів * та ? в оболонці, але
дозволяє нам використовувати будь-яку команду як власний “шаблон”.
Дуже поширено використовувати find та grep
разом. Перша команда знаходить файли, які відповідають заданому шаблону;
тоді як друга шукає в цих файлах рядки, що відповідають іншому шаблону.
Наприклад, ми можемо знайти txt-файли, які містять слово “searching”
шляхом пошуку рядка ‘searching’ у всіх файлах .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.
find creatures -name "*.dat" | grep -v unicornfind creatures -name *.dat | grep -v unicorngrep -v "unicorn" $(find creatures -name "*.dat")- Жоден із наведених вище варіантів.
Варіант 1 правильний. Взяття виразу шаблону у лапки запобігає
розгортанню його у терміналі та гарантує передачу безпосередньо команді
find.
Варіант 2 також працює у цьому випадку, оскільки термінал намагається
розгорнути *.dat, але у поточному каталозі немає файлів
*.dat, тому вираз із символами підстановки буде передано до
find. Вперше ми зіткнулися з цим у епізоді 3.
Варіант 3 є хибним, оскільки він переглядає вміст файлів у пошуках рядків, що не містять слово ‘unicorn’, замість фільтрації за іменами файлів.
Бінарні файли
Ми зосереджувалися виключно на пошуку шаблонів у текстових файлах. Але що робити, якщо ваші дані зберігаються у вигляді зображень, баз даних або в іншому форматі?
Існує декілька інструментів, які розширюють можливості
grep для роботи з деякими нетекстовими форматами. Проте
більш гнучкий підхід полягає в перетворенні даних у текст або вилучення
текстових елементів з даних. З одного боку, це полегшує виконання
простих завдань. З іншого боку, складні завдання зазвичай неможливо
виконати. Наприклад, досить легко написати програму, яка знаходить
розміри X і Y з файлів зображень для роботи з grep, але як
ви напишете щось для пошуку значень в електронній таблиці, клітинки якої
містять формули?
Останній варіант - усвідомити обмеження оболонки та обробки тексту і скористатися іншою мовою програмування. Коли прийде час це зробити, не будьте надто суворими до термінала. Багато сучасних мов програмування запозичили з нього багато ідей, а наслідування вважається найщирішою формою похвали.
Термінал Unix був створений ще до того, як народилась більшість його користувачів. Він проіснував так довго, тому що це одне з найпродуктивніших середовищ для програмування, які коли-небудь були створені - можливо, навіть саме найпродуктивніше. Хоча його синтаксис може здаватися незрозумілим, ті, хто його опанував, можуть експериментувати з різними командами в інтерактивному режимі, а потім використовувати набуті знання для автоматизації своїх завдань. Графічні інтерфейси користувача можуть бути простішими у використанні спочатку, але після опанування терміналу, продуктивність роботи в ньому стає неперевершеною. І, як писав Альфред Норт Уайтхед у 1911 році: ‘Цивілізація розвивається шляхом збільшення кількості важливих операцій, які ми можемо виконувати, не думаючи про них свідомо’.
Рекурсивно знаходить всі файли з розширенням
.datу поточному каталозіРахує кількість рядків у кожному з цих файлів
Сортує вивід з пункту 2. за числовим значенням
-
findшукає файли з певними властивостями, які відповідають шаблонам. -
grepфільтрує та повертає рядки з файлів, які відповідають заданим шаблонам. - Опція
--helpпідтримується багатьма командами bash та програмами, які можна виконати у bash, для отримання довідки щодо їх використання. -
man [команда]показує сторінку довідки для заданої команди. -
$([команда])виконує команду та заміняє вираз$()на результат її виконання.