awk tomando nomes de arquivos em vez de arquivos em loop

1

Ok, preciso extrair uma certa coluna com awk de um determinado arquivo, colocá-la em uma matriz e depois classificá-la, e depois eu precisaria procurar alguns valores dentro dessas colunas ordenadas extraídas com o awk também, mas agora eu tenho alguns problemas com o meu loop:

for var in $1 $2
do
myarr=($(awk -v row=$3 -F';' '$row!="" {print $row}' $var))
sorted_array=( $( printf "%s\n" "${myarr[@]}" | sort -n ) )
echo "${sorted_array[@]} $var"
done

A saída é:

 dbdump.csv
 dbdump2.csv

quais são os nomes dos dois arquivos csv que eu quero extrair a coluna de . Se alguém pudesse fornecer algum tipo de solução, seria muito apreciado, porque eu preciso deste script para pesquisar coisas. Além disso, se você puder sugerir uma abordagem mais rápida do ponto de vista algorítmico, por favor, foi apenas eu aprendendo alguns scripts de bash e tentando montar algum código.

Os arquivos de entrada contêm registros como este, e eu tenho dois desses arquivos que não possuem valores correspondentes na coluna 3 (foi o que meu gerente disse):

1101590479;Frank Haemers;;20060310;1;RESI;;01;06;0007;0000000000;;CRM000;
1101590473;Van KetsmJan;;20060310;2;PROF;;01;08;;0000000000;75;CRM000;0686143950

Os dois arquivos têm cerca de 5 milhões desses registros. Eu tenho outro arquivo com uma certa quantidade de padrões que devem ser procurados esses dois enormes arquivos CSV, e se um desses padrões corresponde em um dos arquivos que eu preciso para saída em outro arquivo algo como:

echo "$pattern has been found in $file"

Eu preciso fazer isso para todos os padrões encontrados no arquivo de texto de meus padrões

    
por Cristian Baciu 06.12.2016 / 10:52

2 respostas

2

Ao escrever um script de shell, é melhor especificar primeiro as variáveis verificadas e os nomes dos arquivos para que você possa variar o número de arquivos especificados. No seu caso, você tem o número da coluna, um arquivo com os padrões nele e dois (ou talvez mais) nomes de arquivo para trabalhar. Então, comece seu script Bash com

#!/bin/bash
if [ $# -lt 2 ] || [ "$1" = "-h" ] || [ "$1" = "--help" ]; then
    echo ""
    echo "Usage: $0 [ -h | --help ]"
    echo "       $0 COLUMN PATTERNFILE [ FILE(s) ... ]"
    echo ""
    exit 0
fi

A cláusula if acima usa a formatação de shell POSIX do estilo antigo e funcionará em dash (e outros shells POSIX), bem como na maioria dos shells sh do estilo antigo também. A intenção é que, se o usuário não especificar nenhum argumento de linha de comando, ou apenas um -h ou --help , o script apenas imprima um pequeno texto de ajuda.

Você deve expandir o texto de ajuda, a propósito, porque torna muito mais fácil descobrir o que ele faz em dois ou três meses, depois que você esqueceu que o escreveu. (Acontece comigo o tempo todo, e eu tenho lotes de tais scriptlets, então eu achei esta prática valer o pequeno esforço.)

Em seguida, extraia os parâmetros necessários (apenas um acima) e shift deles para que possamos usar "$@" para nos referirmos a todos os nomes de arquivo especificados na linha de comando:

column=$1
patternfile="$2"
shift 2

Note que eu gosto de colocar aspas duplas em torno do material que eu quero expandir no shell, mesmo quando não explicitamente necessário. Isso ocorre porque a maioria dos problemas da vida real que encontro com os shell scripts deve-se ao esquecimento de uma expansão, quando isso seria necessário. Essa prática é fácil de lembrar, e além de ter algum comentário de que todos sabem que "você realmente não precisa dessas aspas duplas lá" em um tom nasal irritante, elas não causam danos.

Vamos então usar awk para processar os arquivos de entrada:

awk -v column=$column \
  'BEGIN {
       RS = "[\t\v\f ]*(\r\n|\n\r|\r|\n|)[\t\v\f ]*"
       FS = "[\t\v\f ]*;[\t\v\f ]*"
   }

A barra invertida no final da primeira linha acima apenas informa ao shell que o comando continua na próxima linha. Observe também que não há cotação única de fechamento ' , então as linhas abaixo são realmente continuação para o parâmetro de string de linha de comando que estamos fornecendo para awk .

A regra BEGIN no awk é executada antes dos arquivos serem processados. O RS acima define o separador de registro para qualquer convenção de nova linha e inclui espaços em branco à esquerda ou à direita em cada linha. Da mesma forma, o separador de campo é um ponto-e-vírgula, mas inclui qualquer espaço em branco ao seu redor. Assim, a ; b tem dois campos, sendo o primeiro a e segundo b , nem tendo nenhum espaço em branco.

Eu uso o seguinte idioma para saber qual arquivo de entrada está sendo processado:

    FNR==1 { ++filenum }

Se apenas significar que, para o primeiro registro em cada arquivo de entrada que processamos, incrementamos a variável filenum . Incrementar uma variável não inicializada é o mesmo que incrementar um zero, portanto, obtemos 1 para o primeiro arquivo de entrada e assim por diante.

Queremos apenas lembrar o conteúdo de cada linha no primeiro arquivo de entrada, nosso arquivo padrão:

    filenum==1 { pattern[$0] }

Os arrays awk são associativos, portanto, podemos usar apenas um array associativo para manter os padrões conhecidos. Acima, usamos um recurso awk engraçado para nossa vantagem: se você tentar acessar uma entrada de matriz associativa que ainda não existe, o awk a cria!

Para o resto dos arquivos, nós apenas verificamos se o campo $column (fornecido ao awk scriptlet na variável awk column ) corresponde (exatamente) a qualquer um dos padrões vistos no primeiro arquivo, e se sim, nós imprimimos o registro inteiro:

    filenum > 1 && ($column in pattern) { printf "%s\n", $0 }

Acima, $column tem um significado diferente comparado a um script de shell. Aqui, column é uma variável e $column expande para o valor do campo column th no registro atual (no entanto, a coluna zeroth é o registro inteiro). A sintaxe foo in array é awkism para verificar se array contém uma chave foo . Portanto, no geral, para o segundo e mais arquivos de entrada, se o valor do campo column th for listado no primeiro arquivo de entrada, o registro será impresso. para saída padrão.

Ainda estamos na sequência de parâmetros da linha de comando awk e precisamos fechar a sequência de aspas simples. Também queremos fornecer os nomes dos arquivos:

    ' "$patternfile" "$@"

que conclui este scriptk awk.

    
por 06.12.2016 / 12:05
0

Se você quiser apenas obter uma lista de padrões e um conjunto de arquivos e imprimir os nomes de todos os arquivos que correspondem a cada padrão em uma coluna específica, tudo o que você precisa é o GNU awk (padrão no Linux):

awk -F';' '{
                if(NR==FNR){ 
                    p[$0]++; 
                    next
                } 
                if($3 in p){
                    printf "%s found in %s\n", $3,FILENAME; 
                    nextfile
                }
            }' patterns file1.csv file2.csv fileN.csv

Explicação

  • awk -F';' : defina o separador de campos como ; .
  • if(NR==FNR){ p[$0]++;next} : NR é o número da linha de entrada atual e FNR é o número da linha do arquivo atual. Os dois são iguais apenas enquanto o primeiro arquivo está sendo processado. Isso salvará, portanto, cada linha do arquivo de padrões (o primeiro arquivo) na matriz p e irá para a linha next . Ele só será executado para o arquivo de padrões.
  • if($3 in p){printf "%s found in %s\n", $3,FILENAME; nextfile : Agora estamos vendo os arquivos csv. Se o 3º campo for um dos elementos na matriz p (se estiver no arquivo de padrões), imprima o 3º campo (o padrão) e o nome do arquivo em que foi encontrado. Em seguida, pule para o próximo arquivo. A variável FILENAME contém o caminho do arquivo currentloy sendo processado. O nextfile é um recurso do gawk e faz o que diz na lata: ele pula para o próximo arquivo a ser processado.

Por exemplo, considerando esses arquivos:

$ cat patterns 
foo
bar
baz

$ cat file1.csv 
blah;blah;foo;blah
blah;blah;foo;blah
blah;blah;foo;blah

$ cat file2.csv 
blah;blah;bar;blah

$ cat file3.csv 
blah;blah;baz;blah

Você receberá esta saída:

$ awk -F';' '{if(NR==FNR){p[$0]++; next} if($3 in p){printf "%s found in %s\n", $3,FILENAME; nextfile}}' patterns file*csv 
foo found in file1.csv
bar found in file2.csv
baz found in file3.csv

Se você puder ter cada padrão presente em vários arquivos, poderá usar uma abordagem ligeiramente diferente:

awk -F';' '{
            if(NR==FNR){ 
                p[$0]++; 
                next
            } 
            if($3 in p && !seen[FILENAME][$3]){
                printf "%s found in %s\n", $3,FILENAME; 
                seen[FILENAME][$3]++
            }
        }' patterns file1.csv file2.csv fileN.csv

Desta vez, não há nextfile , pois precisamos processar o arquivo inteiro e há um contador que é incrementado toda vez que um padrão é encontrado em um determinado arquivo, portanto, não relatamos o mesmo padrão várias vezes.

Então, alterando o file1.csv acima para:

$ cat file1.csv 
blah;blah;foo;blah
blah;blah;baz;blah
blah;blah;bar;blah
blah;blah;foo;blah

Recebemos:

$ awk -F';' '{if(NR==FNR){p[$0]++; next} if($3 in p && !seen[FILENAME][$3]){printf "%s found in %s\n", $3,FILENAME; seen[FILENAME][$3]++}}' patterns file*csv 
foo found in file1.csv
baz found in file1.csv
bar found in file1.csv
bar found in file2.csv
baz found in file3.csv

Se isso for muito lento, como pode ser para arquivos grandes, você pode modificá-lo para que pare de ler um arquivo se todos os padrões já tiverem sido encontrados nele:

awk -F';' '{
            if(NR==FNR){ 
                p[$0]++; 
                next
            } 
            if($3 in p && !seen[FILENAME][$3]){
                printf "%s found in %s\n", $3,FILENAME; 
                seen[FILENAME][$3]++
            }
            if( length(seen[FILENAME]) == length(p) ){
                nextfile
            }
           }' patterns file1.csv file2.csv fileN.csv
    
por 06.12.2016 / 13:04