Filtre o texto de cada arquivo e transforme-o em uma lista de valores separados por vírgula

3

Estou tentando extrair algumas informações de vários arquivos e criar um arquivo do tipo csv. Até agora eu tenho a extração e a escrita para arquivar parte do trabalho, mas não sei como eu poderia adicionar uma vírgula entre cada saída ou tirar a nova linha no final.

#!/bin/bash
for file in folder/*.txt do
  grep 'sometext:' $file | sed '/^.*:\s*//' >> list.txt
  #doing simliar stuff with other lines in the current file
done

Eu tentei usar echo -n para remover a nova linha, mas isso não retornou nada útil.

O que o código deve fazer:
Para cada arquivo na pasta, localize as linhas que começam com alguns padrões (ex. sometext: , someothertext: etc) e anexe o restante da linha e um , a uma única linha, correspondente a esse arquivo em list.txt .

Exemplo de conteúdo do arquivo na pasta:

randomtext: ...
sometext: Hello
randomtext: ...
someothertext: World
somedifferenttext: !
randomtext:

resultaria em uma única linha no arquivo de saída Hello,World,!,

    
por Pit 03.05.2016 / 13:01

2 respostas

4

OK, antes de tudo, não use um loop for ! Isso é muito ineficiente. Apenas dê grep todos os nomes de arquivos de uma só vez:

grep 'sometext:' folder/*.txt

Nesse caso, no entanto, usaria awk em vez de grep . Eu fiz 10 cópias do seu arquivo de entrada para testar:

$ awk '{
        if($1~/sometext|someothertext|somedifferenttext/){
            printf "%s,",$2
        }
        if(FNR==1 && NR>1){
            print ""
        }
    }
    END{ print "" }' folder/*txt 
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,

Explicação

awk é uma linguagem de script que lê sua linha de entrada por linha e divide cada linha no espaço em branco (por padrão, você pode alterar isso com -F ) em campos. O primeiro campo será $1 , o segundo $2 etc.

  • if($1~/sometext|someothertext|somedifferenttext/){ : se o primeiro campo corresponder a sometext ou someothertext ou somedifferenttext . Observe que isso também corresponderá a foosometext . Se você quiser limitar as correspondências exatas, altere para:

    if($1=="sometext:" || $1=="someothertext:" || $1=="somedifferenttext:"){
    
  • printf "%s,",$2 : se a condição acima for atendida, imprima o 2º campo seguido por uma vírgula.

  • if(FNR==1 && NR>1){ print "" } : NR é o número da linha de entrada atual e FNR é o número da linha do arquivo atual. Portanto, imprima uma nova linha (awk print call adiciona uma nova linha por padrão, portanto, imprimir nada é como imprimir uma nova linha) sempre que o número da linha do arquivo for 1, mas não se o número total de linhas processadas também for um. Em outras palavras, imprima uma nova linha toda vez que começarmos a ler um novo arquivo.

  • END{ print "" }' : imprima também uma nova linha depois de processar todos os arquivos.

Observe que isso pressupõe que você tenha apenas 2 campos por linha. Se você precisar imprimir toda a linha, poderá usar (usando a versão que imprime apenas correspondências exatas para ilustrar):

awk '{
    if($1=="sometext:" || 
       $1=="someothertext:" || 
       $1=="somedifferenttext:"){
        $1=""; 
        printf "%s,",$0
    }
    if(FNR==1 && NR>1){print ""}
    }END{print ""}' folder/*txt | sed 's/^ //'

A diferença é que usamos $0 (a linha completa) em vez de $2 e definimos $1 na cadeia vazia antes da impressão. Isso resulta em um espaço extra impresso no início (porque o $1 vazio ainda é considerado um campo), então passamos por sed para removê-lo.

Alternativamente, você também pode fazer tudo em Perl:

 $ perl -lane '
    if($F[0]=~/(sometext|someothertext|somedifferenttext):/){
        push @k,@F[1..$#F]
    } 
    if(eof){
        print join ",", @k; @k=();
    }' folder/file*
Hello,World,!
Hello,World,!
Hello,World,!
Hello,World,!
Hello,World,!
Hello,World,!
Hello,World,!
Hello,World,!
Hello,World,!
Hello,World,!
Hello,World,!

Ou também para ter o , :

 $ perl -lane '
    if($F[0]=~/^(sometext|someothertext|somedifferenttext):$/){
        push @k,@F[1..$#F]
    } 
    if(eof){
        print join ",", @k , ""; @k=();
    }' folder/file*
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,

Explicação

A ideia básica aqui é a mesma. A opção -a do Perl faz com que ela se comporte como awk , dividindo cada linha de entrada na matriz @F . Então, se o primeiro elemento da matriz for uma das strings desejadas, o restante dos campos ( @F[1..$#F] ) serão adicionados à matriz @k . Se chegarmos ao final de um arquivo ( if(eof) ), juntamos o conteúdo do array @k com vírgulas e imprimimos a string resultante.

Finalmente, aqui está uma maneira de fazer isso da maneira que você estava tentando (assumindo o GNU grep ):

$ for f in folder/*; do 
    grep -hoP '^(sometext|someothertext|somedifferenttext): \K.*' "$f" | 
        perl -pe 's/\n/,/; END{print "\n"}'; 
  done
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
Hello,World,!,
    
por 03.05.2016 / 13:47
2

com gnu sed :

sed -Es '/pattern1|pattern2|pattern3/{
s/.*:[[:blank:]]*//;H}
$!d;x;/^\n$/d;s/\n(.*)/,/;s/\n/,/g' folder/*.txt > list.txt

onde list.txt content será algo como:

file1match1,file1match2,
file2match1,
file4match1,file4match2,file4match3,

então file3 está faltando na saída, pois não havia linha correspondente a pattern * .
Como funciona: ele processa cada arquivo -s eparately, removendo (via s/.*:[[:blank:]]*// ) a parte desnecessária nas linhas que correspondem a padrão * e anexando o resultado ao buffer H old. Exclui cada linha, exceto a $ t quando e x altera os buffers. Se houver apenas uma \n ewline no espaço de padrão, isso significa que nenhuma linha nesse arquivo correspondeu a padrão * , portanto, exclui o espaço de padrão. Além disso, remove a entrelinha \n , substitui as restantes por vírgulas e adiciona a vírgula final.

Com outros sed s você terá que fazer um loop:

for file in folder/*.txt do
sed '/pattern1\|pattern2\|pattern3/{
s/.*:[[:blank:]]*//
H
}
$!d
x
/^\n$/d
s/\n\(.*\)/,/
s/\n/,/g' "$file"
done > list.txt
    
por 03.05.2016 / 15:04