Como dividir as linhas em um arquivo de texto em duas linhas consecutivas em um novo arquivo?

3

Eu tenho um par de comandos find -exec grep que agrupa caminho / arquivo.ext: ln #: conteúdo da linha em uma única linha. Eu quero dividir a linha em duas linhas consecutivas em um segundo arquivo. As linhas consecutivas são:

path/filename/ext:ln#
contents of the line itself

Eu poderia escrever um programa para fazer isso, mas imaginei que havia um comando que faria isso?

    
por oldefoxx 03.09.2015 / 04:04

2 respostas

3

Sua pergunta e meu entendimento

Sua pergunta atualmente carece de exemplos concretos de entrada e saída desejada, por isso vou tentar responder a sua resposta como eu a entendo , e editar de acordo quando você fornecer mais informações.

A maneira como eu entendi sua pergunta agora é que você está executando algo nas seguintes linhas:

find /path/to/directory -exec grep -H -n 'SomeString' {} \;

Que produz um resultado que é algo assim:

$ find /home/$USER/fortesting -type f -exec grep -H -n 'HelloWorld' {} \;              
/home/serg/fortesting/file3:1:HelloWorld
/home/serg/fortesting/file1:4:HelloWorld

Ou, em geral, /path/to/file:lineNumber:String

Soluções possíveis

Apropriadamente, este é um trabalho para awk : você tem 3 campos separados por dois pontos (separador de campo), o que se traduz em código awk awk -F":" '{printf $1 FS $2 FS "\n" $3 "\n" }' Assim, podemos fazer o seguinte:

$ 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

Agora, awk é uma ferramenta versátil; podemos imitar a saída de find -exec grep com 'find -exec awk' (código awk aqui) ', que será processado e será salva na tubulação.

Considere o código abaixo:

$ find $PWD -type f -exec awk  '/HelloWorld/ {print FILENAME":"FNR"\n"$0 }' {} \;                                                  
/home/xieerqi/fortesting/file3:1
HelloWorld
/home/xieerqi/fortesting/file1:4
HelloWorld

Menos tubulação e o conteúdo estão sendo processados à medida que são encontrados. Além disso, se o arquivo tiver dois pontos em seu nome, esse código ainda processará corretamente, pois não estamos dependendo dos separadores de campo, mas sim da variável FILENAME seguido por dois pontos, seguido por FNR (o número de registro de entrada no arquivo de entrada atual) e a linha encontrada separada por nova linha.

Eficiência

Agora, vamos considerar a eficiência conforme o número de arquivos é grande. Primeiro, crio os arquivos file1 para file1000 e, em seguida, usamos /usr/bin/time para testar cada versão do comando.

$ 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

Assim, a versão longa parece ser mais eficiente, leva menos tempo e porcentagem de CPU.

Agora, aqui está um compromisso - altere \; para + :

/usr/bin/time find $PWD -type f -exec awk '/HelloWorld/ {print FILENAME":"NR"\n"$0 }' {} +

O que faz o operador + ? A grande diferença é que + informa ao exec para listar tantos arquivos quanto a entrada para o comando awk quanto possível, enquanto \; faz com que awk seja chamado toda vez para cada arquivo encontrado.

$ /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

Hey, muito mais rápido, certo? Embora ainda pesado na CPU.

Saída para outro arquivo

Quanto à saída para outro arquivo, adicione o operador > para redirecionamento

    
por Sergiy Kolodyazhnyy 03.09.2015 / 06:56
2

sed prontamente faz isso:

$ echo 'path/filename.ext:ln#:line contents' | sed -r 's/([^:]*:[^:]*):/\n/'
path/filename.ext:ln#
line contents

O regex ([^:]*:[^:]*): procura os dois primeiros campos separados por dois pontos e os salva no grupo 1. O texto de substituição, \n , coloca uma nova linha depois desses dois campos.

Melhoria

Se o próprio nome do arquivo contiver dois pontos, isso, é claro, fornecerá resultados incorretos. Como a steeldriver sugere, isso pode ser evitado usando a opção -Z para grep , que colocará um caractere NUL, \x00 , em vez de dois pontos após o nome do arquivo. Por exemplo:

grep -ZHn 'regex' * | sed -r 's/\x00([^:]*):/:\n/'

Ou, se os recursos de find forem necessários:

find . -type f -exec grep -ZHn 'regex' {} + | sed -r 's/\x00([^:]*):/:\n/'

Isso funcionará mesmo se dois pontos aparecerem no nome do arquivo, na linha correspondente ou em ambos.

    
por John1024 03.09.2015 / 05:05