Tenho alguma experiência com terminais Inix em estágios científicos dos quais participei, principalmente usando alguns utilitários como grep
, awk
, e sed
mas há uma coisa que venho tentando descobrir há algum tempo que realmente me tornaria muito mais eficiente com a análise de números que tenho que fazer.
Eu tenho um script run.awk
que realiza algumas manipulações em uma grande coleção de arquivos de texto enormes. Como está, ele pegará o arquivo chloride.out
, extrairá os dados dele e gravará chloride.cm
.
Existe alguma maneira de fazer com que esse script receba *.out
e grave *.cm
arquivos com base na frase curinga inicial no shell?
A quantidade de scripts que escrevi para processar grandes quantidades de dados e dos quais tive que fazer mais de cem iterações é simplesmente irritante.
Idealmente, gostaria de saber se existe uma maneira de fazer isso para todos os meus scripts com algo por meio do shell. Se não puder ser automatizado no shell ou equivalente, posso pelo menos automatizar meus awk
scripts de maneira semelhante à descrita?
Responder1
Você certamente pode fazer com que o awk lide com vários arquivos por meio de curingas. Uma sugestão seria deixar o run.awk
como uma "função" genérica que recebe um único arquivo e produz um único arquivo de saída, e então chamá-lo de outro script que poderia então cuidar da assimilação dos arquivos de entrada e saída.
Exemplo
Este seria um script Bash, podemos chamá-lo de awk_runner.bash
.
#!/bin/bash
for ifname in *.out; do
ofname=${ifname/.out/.cm}
printf "IN: %s, OUT: %s\n" $ifname $ofname
printf "running run.awk with %s & %s\n\n" $ifname $ofname
run.awk $ifname $ofname
done
Execução de amostra
Criei um diretório de exemplo com alguns arquivos de teste.
$ touch file{1..4}.out
Isso resultou na criação de 4 arquivos:
$ ls -1
file1.out
file2.out
file3.out
file4.out
Agora executamos nosso script:
$ ./awk_runner.bash
IN: file1.out, OUT: file1.cm
running run.awk with file1.out & file1.cm
IN: file2.out, OUT: file2.cm
running run.awk with file2.out & file2.cm
IN: file3.out, OUT: file3.cm
running run.awk with file3.out & file3.cm
IN: file4.out, OUT: file4.cm
running run.awk with file4.out & file4.cm
Após cada linha que começa com "running..." nosso script pode ser executado a partir daqui.
Arquivos em uma lista
Digamos que em vez de usar o curinga, *.out
tínhamos um arquivo com uma lista de nomes de arquivos, digamos:
$ cat filelist.txt
file1.out
file2.out
file3.out
file4.out
Poderíamos usar esta versão modificada do nosso script, que usaria um while
loop em vez de um for
loop. Agora vamos chamar essa variante do script awk_file_runner.bash
de:
#!/bin/bash
while read ifname; do
ofname=${ifname/.out/.cm}
printf "IN: %s, OUT: %s\n" $ifname $ofname
printf "running run.awk with %s & %s\n\n" $ifname $ofname
run.awk $ifname $ofname
done < filelist.txt
Esta versão do script lê a entrada do arquivo filelist.txt
:
done < filelist.txt
Então, para cada volta do while
loop, estamos usando o read
comando para ler uma linha do arquivo de entrada.
while read ifname; do
Em seguida, ele executa tudo da mesma maneira que o primeiro script, onde executará o awk
script run.awk
à medida que percorre cada linha do arquivo.
Responder2
Em vez de escrever um wrapper de shell e gerar uma nova instância do awk para cada arquivo processado, você pode fazer isso diretamente no awk. Se você já possui um script awk, pode acessar o arquivo atual usando a variável FILENAME. Portanto, se você executar awk 'some commands' file1 file2
, poderá saber se está trabalhando com arquivo1 ou arquivo2 usando FILENAME. Você também pode usar >
on print
/ printf
in awk. Então, se você tiver um script awk como
/pattern/{ print $1,$3 }
você poderia facilmente fazer
/pattern/{ print $1,$3 > FILENAME".processed" }
ou use FNR=1
para saber quando você está em um novo arquivo e crie uma variável para fazer uma manipulação mais complexa no nome do arquivo. Como substituir uma .in
extensão por .out
, como em
sauer@humpy:/tmp$ grep . file*.in
file1.in:a
file1.in:b
file2.in:c
sauer@humpy:/tmp$ awk 'FNR=1{out=FILENAME;sub("\.in$",".out",out)} {print "processed"$0 > out}' file*.in
sauer@humpy:/tmp$ grep . file*.out
file1.out:processeda
file1.out:processedb
file2.out:processedc
Estou usando grep .
para mostrar o nome do arquivo e o conteúdo de vários arquivos aqui, o que também é um truque divertido. Mas o importante é definir o valor da out
variável para uma versão modificada de FILENAME
quando FNR
muda para 1 (então estamos na linha 1 do arquivo), e então redirecionar todos os prints para out
. Observe que isso é um pouco perigoso, pois a falha na correspondência da extensão resultará em nenhuma substituição, levando à substituição dos arquivos de entrada. Portanto, seria bom adicionar uma verificação à prova de falhas para ter certeza disso out != FILENAME
ou algo parecido também. Isso fica como exercício para o leitor. ;)
Se você precisar de um arquivo contendo uma lista de nomes de arquivos, é mais fácil executá-lo como
awkscript $(< /path/to/filename_list_file )
Que pega o conteúdo do filename_list_file
e o coloca na linha de comando.