Для рекурсивного поиска по файлам в директории удобно использовать grep. Многие с ним, как и с его аналогами (ack, rg, ag, pt, git grep…), хорошо знакомы. Вывод grep можно хорошо сопрячь с открытием нужного файла в текстовом редакторе — сразу с найденной позицией. Например, Vim поддерживает открытие файла на нужной строчке c аргументом +ln
, где ln — это номер строки; KWrite имеет опцию --line ln
; в других редакторах почти наверняка есть соответствующие опции.
Этот текст был прислан на конкурс авторов, который мы запустили весной. Мы разобрались с большим количеством пришедших материалов, подвели итоги и наградили победителей. Автор этой заметки получил приз — трехмесячную подписку на «Хакер». Поздравляем!
Запуская grep в режиме рекурсивного поиска, мы указываем ему опцию вывода номера строки -n
. Пример поиска на коде ядра:
$ grep -nre 'struct task_struct' scsi/scsi_host.h:562: struct task_struct * ehandler; scsi/libfc.h:458: struct task_struct *resp_task; scsi/libfcoe.h:335: struct task_struct *kthread; asm-generic/switch_to.h:23: struct task_struct *); asm-generic/mmu_context.h:11:struct task_struct; ...
В выводе у нас будет все, что нужно для запуска редактора. Если добавить следующий код в свой .bashrc
, мы получим команду, которая будет запускать редактор на каждой найденной строчке:
kgrep() { IFS=$'n'; for i in $(grep -nrPe $1 ${2:-.}) do kwrite $(echo "$i" | cut -d ':' -f 1) --line $(echo "$i" | cut -d ':' -f 2) done } open_vim() { vim $(echo "$1" | cut -d ':' -f 1) +$(echo "$1" | cut -d ':' -f 2) } vgrep() { IFS=$'n'; for i in $(grep -nrPe $1 ${2:-.}) do open_vim "$i" done }
Пример запуска:
$ kgrep 'struct task_struct {' $ vgrep 'struct inode {'
Неудобство состоит в том, что редактор будет запускаться последовательно для всех найденных строчках всех файлов. Иногда бывает уместнее сначала выбрать какой-то результат поиска, а уже потом открывать его в редакторе. Это очень легко делается с помощью консольной программы dialog
. Для этого необходимо в свой .bashrc
добавить следующую функцию:
vvgrep() { declare -a args=() while read do tag=$(echo "$REPLY" | cut -d ':' -f 1-2) item=$(echo "$REPLY" | cut -d ':' -f 3-) args+=("$tag" "$item") done < <( grep --color=no -nrPe $1 ${2:-.} ) exec 3>&1 result=$(dialog --menu "Please select the file" 0 0 0 "${args[@]}" 2>&1 1>&3) exitcode=$? [[ $exitcode -eq 0 ]] && open_vim "$result" while [[ $exitcode -eq 0 ]] do result=$(dialog --default-item "$result" --menu "Please select the file" 0 0 0 "${args[@]}" 2>&1 1>&3) exitcode=$? [[ $exitcode -eq 0 ]] && open_vim "$result" done exec 3>&- clear }
Теперь после поиска у нас будет выводиться диалоговое меню с результатами в виде отдельных строк. Можно выбрать одну из них и открыть в редакторе. Последний пример приведен для редактора Vim, для остальных делается по аналогии.
Grep поддерживает регулярные выражения Perl, а это значит, что можно писать продвинутые запросы для рекурсивного поиска. Например, поиск определения структуры:
$ grep -nrPe 'structs+task_structs*{' linux/sched.h:483:struct task_struct {
Или макроса:
$ grep -nrPe '#s*defines+PAGE_SIZE' asm-generic/page.h:17:#define PAGE_SIZE (1 << PAGE_SHIFT) asm-generic/page.h:19:#define PAGE_SIZE (1UL << PAGE_SHIFT) uapi/linux/a.out.h:128:#define PAGE_SIZE 0x400 linux/raid/pq.h:49:# define PAGE_SIZE 4096
Или же вывести определение typedef:
$ grep -Pzore 'typedefs+structs+{[^}]++}s*atomic_ts*;' linux/types.h:typedef struct { int counter; } atomic_t;
Или функции:
$ grep -Pzore 'batomic_incs*([^)]+)s*{[^}]+}' asm-generic/atomic.h:atomic_inc(atomic_t *v) { atomic_add_return(1, v); }
В последних двух случаях используется опция -z
для того, чтобы шаблон сопоставлялся по нескольким строкам. Эта опция объединяет все строки в одну. Также в последних случаях хорошо использовать рекурсивный шаблон сопоставления скобочек, чтобы включать вложенные.
Понятное дело, что подобные запросы не будешь каждый раз набирать в консоли. Поэтому их удобно обернуть в функции bash и добавить в свой .bashrc
.
cstruct() { grep --include='*.[ch]' -nrPe '(?m)structs+'$1's*{' "${2:-.}" } cdefine() { grep --include='*.[ch]' -nrPe '#s*defines+'$1 "${2:-.}" } ctypedef() { grep --include='*.[ch]' -zorPe 'typedefs+((?=struct)?((?s*w+)?s*(?{(?:(?>[^{}]+)|(?&sbody))*}))|(?:w+s+)+)s*'$1's*;' "${2:-.}" } cfunc() { grep --include='*.[ch]' -Pzore 'w[ws*]+s*b'$1's*(?((?:(?>[^()]+)|(?&fargs))+))s*(?{(?:(?>[^{}]+)|(?&sbody))*})' "${2:-.}" | tr '' 'n' }
В таком случае весь запрос сводится к вызову соответствующей команды, где первый аргумент — шаблон, а последующие — файлы и папки для поиска.
Мы решили продлить конкурс и превратить его в постоянную акцию. Прислав нам описание хака, полезный совет или описание клевой неизвестной проги, ты по-прежнему можешь получить подписку на месяц, три месяца или, если постараешься, на год. Следуй рекомендациям и присылай свой текст!
Читайте также
Последние новости