Как разбить строки в текстовом файле на две последовательные строки в новом файле?
У меня есть find -exec grep
пара команд, которая группирует путь / имя файла.ext:ln#:содержимое строки в одной строке. Я хочу разделить строку на две последовательные строки во втором файле. Последовательные строки:
path/filename/ext:ln#
contents of the line itself
Я мог написать программу для этого, но мне было интересно, есть ли еще команда, которая сделает это?
2 ответа
Ваш вопрос и мое понимание этого
В вашем вопросе в настоящее время отсутствуют конкретные примеры ввода и желаемого результата, поэтому я постараюсь ответить на ваш ответ, насколько я понимаю, и отредактировать, когда вы предоставите больше информации.
Насколько я понимаю ваш вопрос прямо сейчас, так это то, что вы выполняете что-то вроде следующего:
find /path/to/directory -exec grep -H -n 'SomeString' {} \;
Который дает результат, который выглядит примерно так:
$ find /home/$USER/fortesting -type f -exec grep -H -n 'HelloWorld' {} \;
/home/serg/fortesting/file3:1:HelloWorld
/home/serg/fortesting/file1:4:HelloWorld
Или вообще /path/to/file:lineNumber:String
Возможные решения
Соответственно, это работа для awk
: у вас есть 3 поля, разделенные двоеточием (разделитель полей), который переводится в код awk awk -F":" '{printf $1 FS $2 FS "\n" $3 "\n" }'
Таким образом, мы можем сделать следующее:
$ find /home/$USER/fortesting -type f -exec grep -H -n 'HelloWorld' {} \; | awk -F ":" '{printf $1 FS $2 FS "\n" $3 "\n" }'
/home/xieerqi/fortesting/file3:1:
HelloWorld
/home/xieerqi/fortesting/file1:4:
HelloWorld
Сейчас, awk
это универсальный инструмент; мы можем имитировать вывод find -exec grep
с `find -exec awk '(код awk здесь)', который уже будет обработан и сэкономит на конвейере.
Рассмотрим код ниже:
$ find $PWD -type f -exec awk '/HelloWorld/ {print FILENAME":"FNR"\n"$0 }' {} \;
/home/xieerqi/fortesting/file3:1
HelloWorld
/home/xieerqi/fortesting/file1:4
HelloWorld
Меньше трубопроводов и содержимого обрабатываются по мере их обнаружения. Кроме того, если файл имеет двоеточие в своем имени, этот код все равно будет обрабатывать его правильно, так как мы не зависим от разделителей полей, а скорее печатаем переменную FILENAME с последующим двоеточием, затем FNR (номер входной записи в текущем входном файле) и найденная строка, разделенная новой строкой.
КПД
Теперь давайте рассмотрим эффективность, так как количество файлов увеличивается. Сначала я создаю файлы file1
в file1000
, а затем мы используем /usr/bin/time
проверить каждую версию команды.
$ echo 'HelloWorld' | tee file{$(seq -s',' 1 1000)}
$ /usr/bin/time find /home/$USER/fortesting -type f -exec grep -H -n 'HelloWorld' {} \; | awk -F ":" '{printf $1 FS $2 FS "\n" $3 "\n" }' > /dev/null
0.04user 0.34system 0:03.09elapsed 12%CPU (0avgtext+0avgdata 2420maxresident)k
0inputs+0outputs (0major+113358minor)pagefaults 0swaps
$ /usr/bin/time find $PWD -type f -exec awk '/HelloWorld/ {print FILENAME":"FNR"\n"$0 }' {} \; > /dev/null
0.82user 2.03system 0:04.25elapsed 67%CPU (0avgtext+0avgdata 2856maxresident)k
0inputs+0outputs (0major+145292minor)pagefaults 0swaps
Таким образом, длинная версия кажется более эффективной, требует меньше времени и процента загрузки процессора.
Теперь вот компромисс - изменить \;
в +
:
/usr/bin/time find $PWD -type f -exec awk '/HelloWorld/ {print FILENAME":"NR"\n"$0 }' {} +
Что это +
оператор делает? Большая разница в том, что +
говорит exec перечислить столько файлов, сколько awk
команда как можно, в то время как \;
марки awk
вызываться каждый раз для каждого найденного файла.
$ /usr/bin/time find $PWD -type f -exec awk '/HelloWorld/ {print FILENAME":"FNR"\n"$0 }' {} + > /dev/null
0.00user 0.02system 0:00.02elapsed 74%CPU (0avgtext+0avgdata 3036maxresident)k
0inputs+0outputs (0major+398minor)pagefaults 0swaps
Эй, намного быстрее, верно? Хотя все еще тяжело на процессоре.
Вывод в другой файл
Что касается вывода в другой файл, добавьте использование >
оператор перенаправления
sed с готовностью делает это:
$ echo 'path/filename.ext:ln#:line contents' | sed -r 's/([^:]*:[^:]*):/\1\n/'
path/filename.ext:ln#
line contents
Регулярное выражение ([^:]*:[^:]*):
ищет первые два поля, разделенных двоеточиями, и сохраняет их в группе 1. Текст замены, \1\n
, ставит новую строку после этих двух полей.
улучшение
Если само имя файла содержит двоеточие, это, конечно, даст неверные результаты. Как предполагает Steeldriver, этого можно избежать, используя -Z
возможность grep
который поместит символ NUL, \x00
вместо двоеточия после имени файла. Например:
grep -ZHn 'regex' * | sed -r 's/\x00([^:]*):/:\1\n/'
Или, если возможности find
являются обязательными:
find . -type f -exec grep -ZHn 'regex' {} + | sed -r 's/\x00([^:]*):/:\1\n/'
Это будет работать, даже если в имени файла появятся двоеточия, или совпадет строка, или и то, и другое.