+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'
}
В таком случае весь запрос сводится к вызову соответствующей команды, где первый аргумент — шаблон, а последующие — файлы и папки для поиска.
Конкурс продолжается
Мы решили продлить конкурс и превратить его в постоянную акцию. Прислав нам описание хака, полезный совет или описание клевой неизвестной проги, ты по-прежнему можешь получить подписку на месяц, три месяца или, если постараешься, на год. Следуй рекомендациям и присылай свой текст!