Для рекурсивного поиска по файлам в директории удобно использовать 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'
}

В таком случае весь запрос сводится к вызову соответствующей команды, где первый аргумент — шаблон, а последующие — файлы и папки для поиска.

 

Конкурс продолжается

Мы решили продлить конкурс и превратить его в постоянную акцию. Прислав нам описание хака, полезный совет или описание клевой неизвестной проги, ты по-прежнему можешь получить подписку на месяц, три месяца или, если постараешься, на год. Следуй рекомендациям и присылай свой текст!

3 комментария

  1. vlalexon

    19.09.2017 at 12:50

    А чем же vimgrep не устраивает?

Оставить мнение

Check Also

Алмазный фонд «Хакера». Самые крутые материалы по реверсингу и malware за три года

Иногда мы, редакторы и авторы «Хакера», сами читаем «Хакер». Нет, ну то есть мы постоянно …