Ao processar arquivos de entrada está fazendo cópias / atualizações e renomeando uma abordagem válida?

2

Eu tenho um script que toma como entrada 2 arquivos.
Antes do início do processamento, é feita uma preparação para os arquivos.
Eu tive a idéia de não tocar nos arquivos originais, mas fazer tudo para cópias, imprimir o que é necessário como saída e excluir as cópias. Essa abordagem, porém, tornou o script com muitas variáveis e o tornou propenso a erros.
Exemplo:

#!/bin/bash                                                                                                      
[[ -z $1 ]] && echo 'We need input file a' && exit 1;  
[[ -z $2 ]] && echo 'We need input file b' && exit 1;  

A_CSV=$1;  
B_CSV=$2;  

A_FILE="$A_CSV.tmp";  
B_FILE="$B_CSV.tmp";  

[ -f $A_FILE ]] && rm $A_FILE;  
[[ -f $B_FILE ]] && rm $B_FILE;  

tr -d "\r" < $A_CSV >  $A_FILE;  
tr -d "\r" < $B_CSV > $B_FILE;  

awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' $A_FILE > "$A_FILE.bck";
awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' $B_FILE > "$B_FILE.bck";

rm $A_FILE && mv "$A_FILE.bck" $A_FILE;   
rm $B_FILE && mv "$B_FILE.bck" $B_FILE;   
# extra logic following the same pattern  

Você pode ver como uma cópia é criada para fazer a atualização e renomear várias vezes.

Existe uma maneira de melhorar isso para tornar o script menos propenso a erros?

    
por Jim 03.02.2017 / 21:57

2 respostas

4

Isso é obtido por meio de um pipe ( | ). Existem muitos bons tutoriais, como este .

#!/bin/bash
[[ -z $1 ]] && echo 'We need input file a' && exit 1;
[[ -z $2 ]] && echo 'We need input file b' && exit 1;  

A_CSV=$1;  
B_CSV=$2;  

A_FILE="$A_CSV.tmp";  
B_FILE="$B_CSV.tmp";  

[ -f $A_FILE ]] && rm $A_FILE;
[[ -f $B_FILE ]] && rm $B_FILE;

tr -d "\r" < $A_CSV | awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' > $A_FILE
tr -d "\r" < $B_CSV | awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' > $B_FILE

Eu pessoalmente criaria uma função para lidar com uma única operação, já que você faz a mesma coisa em ambos os arquivos. rm -f $A_FILE $B_FILE também ficaria melhor na minha opinião.

    
por 03.02.2017 / 22:12
3

Deixar os arquivos originais intactos e fazer o processamento nas cópias é uma boa ideia. Você deve ir mais além e não reutilizar os arquivos intermediários. Se você reutilizar os arquivos intermediários e o processo for interrompido, você não terá como saber em que ponto ele foi interrompido.

Você está aplicando a mesma transformação em dois arquivos. Não escreva o código duas vezes! Escreva o código uma vez, usando variáveis conforme necessário, e chame esse trecho de código uma vez para cada arquivo. Em um script de shell, a ferramenta para isso é escrever uma função (ou , se você precisar que esse código seja chamado de mais de um script, crie um script separado).

Todas as ferramentas de processamento de texto que você está usando podem ler da entrada padrão e gravar na saída padrão. Você pode combiná-los colocando um tubo entre a saída de uma ferramenta e a entrada da próxima ferramenta. Dessa forma, você não precisa de tantos arquivos intermediários - na verdade, você não precisa de nenhum arquivo intermediário nesse caso. Pipes são um recurso de design fundamental do Unix.

Uma outra dica de programação de shell: sempre colocam aspas duplas em torno de expansões variáveis , ou seja, $foo .

#!/bin/bash                                                                                                      

preprocess_csv () {
  <"$1" \
  tr -d '\r' |
  awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' >"${1%.csv}.clean"
}

preprocess_csv "$1"
preprocess_csv "$2"

do_stuff_with_preprocessed_file "${1%.csv}.clean" "${2%.csv}.clean" >global_output

Eu usei a expansão de parâmetro para construir ${1%.csv} para transformar por exemplo foo.csv into foo , para que o arquivo de saída dessa transformação seja foo.clean .

Esse script é mais simples do que você, mas ainda pode ser melhorado. Existem ferramentas melhores do que scripts de shell para descrever uma cadeia de comandos de processamento de arquivos: ferramentas de automação de construção como o clássico faça . Consulte Executar uma lista de comandos com pontos de verificação? para uma introdução a ser feita com um caso de uso semelhante. Veja como a transformação que você tem pode ser expressa com o make. Chame este arquivo Makefile . Note que onde as linhas abaixo são recuadas com 8 espaços, você precisa substituir os 8 espaços por um caractere de tabulação, é uma peculiaridade de make.

default: global_output

%.clean: %.csv
        <'$<' tr -d '\r' | awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' >'$@'

global_output: input1.clean input2.clean
        do_stuff_with_preprocessed_files input1.clean input2.clean >$@

$< em um comando representa a dependência (o arquivo à direita do target: dependency acima) e $@ representa o destino. Com o makefile acima, se você executar o comando make global_output (ou apenas make , graças à linha default: no início), ele executará as transformações para produzir os arquivos .clean (os arquivos .csv já deve existir) e depois executará do_stuff_with_preprocessed_files para produzir global_output .

Este makefile é frágil porque deixa arquivos parcialmente processados se interrompidos no meio do caminho. Para corrigir isso, use arquivos temporários em cada regra, conforme explicado em Executar uma lista de comandos com checkpoint? .

default: global_output

%.clean: %.csv
        <'$<' tr -d '\r' | awk '{ if(NR == 1) sub(/^\xef\xbb\xbf/,""); print }' >'[email protected]'
        mv '[email protected]' '$@'

global_output: input1.clean input2.clean
        do_stuff_with_preprocessed_files input1.clean input2.clean >'[email protected]'
        mv '[email protected]' '$@'
    
por 04.02.2017 / 00:02