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.