Поиск слова в файле с помощью grep
Пожалуй, самый простой пример использования команды grep – это поиск подстроки в файле:
grep "Безухов" voyna-i-mir-tom-1.txt
– выведет строки, в которых grep нашёл точное совпадение. Однако, в этом списке строк сложно ориентироваться. Предлагаю немного апгрейдить нашу команду, чтобы и номера строк выводила, да ещё и контекст добавляла:
grep -in -C 3 "Безухов" voyna-i-mir-tom-1.txt
Пробежимся по ключам:
-i
– игнорирование регистра – grep будет находить, даже если строчные буквы в подстроке оказались заглавными и наоборот. В случае с "Безуховым" это не так критично, но если бы мы искали "Граф", то в начале предложения оно таким и было бы, а вот внутри могло быть и с маленькой буквы.-n
– вывод номера строки, где было найдено вхождение нашей подстроки. Согласитесь, так приятнее и искать, да и представлять, на сколько подстрока разбросана по файлу, легче.-C 3
– выводим по 3 строки до найденой строки и после – полезно понимать, в каком контексте была произнесена та или иная фраза. Для удобства каждая находка отделяется от других строкой из "--".
В итоге мы получим нечто следующее:
--
848-
849-Но дамы невольно смеялись и сами.
850-
851:— Насилу спасли этого несчастного, — продолжала гостья. — И это сын графа Кирилла Владимировича Безухова так умно забавляется! — прибавила она. — А говорили, что так хорошо воспитан и умен. Вот всё воспитание заграничное куда довело. Надеюсь, что здесь его никто не примет, несмотря на его богатство. Мне хотели его представить. Я решительно отказалась: у меня дочери.
852-
853-— Отчего вы говорите, что этот молодой человек так богат? — спросила графиня, нагибаясь от девиц, которые тотчас же сделали вид, что не слушают. — Ведь у него только незаконные дети. Кажется… и Пьер незаконный.
854-
--
Рекурсивный поиск текста в директории с grep
Искать в файле – это хорошо, но что делать, если ты не помнишь, в каком
файле было то, что тебе нужно. К примеру, программируя на Си под MacOS я часто
не могу понять, куда положили чудесные функции из сторонних библиотек. Тогда
мне надо найти в директории /usr/local/include
файл для подключения, имея
только название функции:
$ grep -rl SDL_GetError /usr/local/include
/usr/local/include/SDL/SDL_error.h
/usr/local/include/SDL/SDL_ttf.h
/usr/local/include/SDL/SDL_mixer.h
/usr/local/include/SDL/SDL_image.h
/usr/local/include/SDL2/SDL_error.h
/usr/local/include/SDL2/SDL_main.h
/usr/local/include/SDL2/SDL_ttf.h
/usr/local/include/SDL2/SDL_mixer.h
/usr/local/include/SDL2/SDL_image.h
– значит SDL_GetError
использовалась в перечисленных файлах – уже проще.
Более того, по названию файлов я догадаюсь, что функция таки описана в
/usr/local/include/SDL/SDL_error.h
... Просто так привык. Давайте расскажу, что
означают эти флаги, после расскажу, как поискать уже привычным нам образом:
с выводом строк + контекстом, например, в 3 строки сверху и снизу. Кстати,
вы уже можете задуматься сами – какие ключи/флаги надо установить grep. Итак,
-r
– выполнять рекурсивно. А это значит, что мы, добавляя этот ключ, скармливаем grep не просто файл, а целую директорию (папку), в которой он будет искать по файлам.-l
– вывести список файлов. Просто, без дополнительной информации. Если вы хотите узнать только имена файлов, содержащих подстроку – вот ваш выбор. Также полезно для написания консольных утилит на bash.
Если же мы хотим спокойно и комфортно просмотреть все найденные вхождения, то нам скорее подойдёт:
$ grep -rin -C 3 SDL_GetError /usr/local/include
/usr/local/include/SDL/SDL_error.h-41- */
/usr/local/include/SDL/SDL_error.h-42-/*@{*/
/usr/local/include/SDL/SDL_error.h-43-extern DECLSPEC void SDLCALL SDL_SetError(const char *fmt, ...);
/usr/local/include/SDL/SDL_error.h:44:extern DECLSPEC char * SDLCALL SDL_GetError(void);
/usr/local/include/SDL/SDL_error.h-45-extern DECLSPEC void SDLCALL SDL_ClearError(void);
/usr/local/include/SDL/SDL_error.h-46-/*@}*/
/usr/local/include/SDL/SDL_error.h-47-
--
...
– все использованные флаги/ключи нам уже изветны. Если вам неочевидно, что тут происходит, перечитайте заметку выше.
Поиск по регулярному выражению в grep
Это всё, конечно, замечательно, но я бы хотел при поиске определения функции искать именно определение функции, без указания файлов, где она только упоминается. То есть мне нужна строка, где будет тип возвращаемого значения (char*), после которого будет идти какое-то количество спец-слов и пробелов, а потом название функции. Нет ничего проще:
grep -r -C 3 -E 'char.*?\*.*?SDL_GetError' /usr/local/include
Почти все ключи нам известны кроме -E
– он включает режим расширенных
регулярных выражений для шаблона поиска. Так в данном регулярном выраженнии
мы написали 'char.?*.?SDL_GetError' то есть:
- в строке должная встретиться подстрока "char",
- сразу за ней должны встретиться любые символы до "*". Тут важно сказать, что
.
в регулярных выражениях отвечает за любой символ,*
– модификатор, указывающий количество: от 0 до бесконечности повторений. Далее идёт?
– ограничитель жадности: да, любое количество повторений, пока не встретишь следующее из регулярного выражения. \*
– мы уже знаем, что*
– спец. символ, означающий любое количество повторений. Поэтому, чтобы указать, что здесь должна быть просто*
, мы используем экранирующий символ –\
. Им же мы можем указать, что нужен символ "точка" или "вопросительный знак", не указывая на их дополнительные символы в рамках данного диалекта регулярных выражений.- снова
.*?
– съедаем все символы, не относящиеся к далее идущим в регулярке. - ну и наша функция "SDL_GetError".
Схематично это можно выразить следующим образом:
| char | .*? | \* | .*? | SDL_GetError |
extern DECLSPEC | char | | * | SDLCALL | SDL_GetError | (void);
Регулярные выражения – отнюдь не очевидная тема. И понять её с ходу вы вряд лм сможете. Я подготовил некоторое объяснение в рамках второй пары по Linux. Но явно нужно более тщательное рассмотрение этой темы.