Как разбить строки в текстовом файле на две последовательные строки в новом файле?

У меня есть 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/'

Это будет работать, даже если в имени файла появятся двоеточия, или совпадет строка, или и то, и другое.

Другие вопросы по тегам