
Eu tenho um script bash que aceita vários argumentos de linha de comando. O único que importa neste contexto é o primeiro, $1, que é um arquivo de texto.
O cabeçalho é muito longo, abaixo estão exemplos de alguns dos campos.
COL0___LINE_NUMBER
COL1_AFF_ID
COL2_FULL_NAME
COL3_ADDRESS
BDID
BEST_STATE
COL48_LATITUDE
COL49_LONGITUDE
Preciso alterar a linha do cabeçalho, o que posso fazer usando o código abaixo. Isso realiza o que eu quero, no entanto, quaisquer alterações estilísticas, etc., que preservem a variável como na saída abaixo são bem-vindas, considerando que esta é minha primeira vez em scripts bash.
columns=`cat $1 | head -1 |sed 's/-/_/g' | sed 's/ /_/g' |
sed 's/COL[0-9]\+_BDID/DROP_BDID/g' | sed 's/COL[0-9]\+_//g' |
tr '\t' '\n' | tr "[:lower:]" "[:upper:]"`
Nota: As guias para novas linhas são formatadas puramente como uma tentativa de estética quando o cabeçalho das colunas é repetido. Isso é tanto para facilitar a leitura para mim quanto para os usuários do script para os quais a instrução vertica create table é repetida.
De qualquer forma, agora quero tornar a variável de colunas a linha do cabeçalho do meu arquivo de texto para poder trabalhar com a nova versão dentro do script. Então, eu gostaria do arquivo de texto original completosemé a linha de cabeçalho original e com aquela que criei para que o seguinte, por exemplo, se refira à versão editada do meu arquivo,
col_arr=($columns)
cut_cols = ""
for i in ${!col_arr[@]}; do
if [[ "${col_arr[$i]}" =~ ^(__LINE_NUMBER|CONFIDENCE|DROP_BDID|LINE_NUMBER|ZIP9|ZIP9|ZIP9MATCH)$ ]]; then
echo "$i"
#haven't written yet, but this will add to cut_cols so that
#I can remove the above listed columns in the text file
#based on their index.
fi
done
/opt/vertica/bin/vsql -U ${4} -w ${5} -h ${database} \
-c "copy $schema.$table from STDIN delimiter E'\t' direct no escape;"
Responder1
Podemos combinar todos os comandos do columns=
pipeline do shell original em um sed
script. Este sed
script modifica apenas a primeira linha da entrada e depois sai. O seguinte fazexatamentea mesma coisa que columns=
na pergunta original:
columns=$(
sed '
1 { # execute block on line 1
s/-/_/g
s/ /_/g
s/COL[0-9]\+_BDID/DROP_BDID/g
s/COL[0-9]\+_//g
s/\t/\n/g
y/abcdefghijklmnopqrstuv/ABCDEFGHIJKLMNOPQRSTUV/
q # quit after line 1
}
' "$1"
)
# . . .
Eu prefiro o formato multilinha também para facilitar a leitura. Embora a declaração original estivesse em uma linha, era muito menos eficiente e, na minha opinião, mais difícil de ler. sim
Agora você tem os cabeçalhos do arquivo de entrada (arg 1), armazenados na variável columns
separados por novas linhas. Você pode iterar sobre as strings $columns
com um for
loop, isso separará os nomes das colunas cut_cols
por novas linhas:
cut_cols="$(
for col in $columns
do
case $col in
(*__LINE_NUMBER*|*CONFIDENCE*|*DROP_BDID*|*LINE_NUMBER*|*ZIP9*|*ZIP9MATCH*)
echo "$col"
;;
esac
done
)"
Dependendo de suas preferências, isso faz a mesma coisa:
cut_cols=
for col in $columns
do
case $col in
(*__LINE_NUMBER*|*CONFIDENCE*|*DROP_BDID*|*LINE_NUMBER*|*ZIP9*|*ZIP9MATCH*)
cut_cols="$cut_cols $col"
;;
esac
done
cut_cols=$(echo "$cut_cols" | sed 's/^ *//; s/ /\n/g')
Não testei seu loop de array cut_cols
porque não uso arrays de shell. O método de iteração acima $columns
é o método mais universal e tradicional. Array
s são uma extensão, não disponível em todos os shells.
Depois de atribuir a cut_cols
, você pode iterá-lo da mesma forma que $columns
.
Para enviar um novo cabeçalho com os dados do arquivo original, imprima o novo cabeçalho e, em seguida, imprima tudo, exceto a primeira linha do arquivo original. Faça isso em um grupo de comandos (entre {
e }
) para poder redirecionar a saída de ambos os comandos juntos, como se fossem um programa.
O seguinte produz o arquivo de texto original completo sem a linha de cabeçalho original e com aquela que você criou e o envia para o stdin
of vsql
:
# . . .
{ # start command group
echo "$columns" | tr '\n' '\t'; # print with tabs instead of newlines
echo # add newline record separator
sed 1d "$1" # print all but 1st line of "$1"
} | # pipe as one file to vsql
/opt/vertica/bin/vsql -U ${4} -w ${5} -h ${database} \
-c "copy $schema.$table from STDIN delimiter E'\t' direct no escape;"
Responder2
Eu realmente não entendo muito dessa pergunta(especialmente a causa da edição apenas da linha do cabeçalho da coluna em um arquivo - o que acontece com todas as linhas usadas para identificar posteriormente?), mas esta parte faz sentido:
#haven't written yet, but this will add to cut_cols so that
#I can remove the above listed columns in the text file
#based on their index.
Isso eu entendo. Aqui estão alguns sed
truques para extrair campos específicos de um arquivo:
printf 'one two three' |
sed 's|[^ ]*||5'
one three
Isso parece estranho, certo? Aqui sed
remove o 5ºpossívelsequência de caracteres não espaciais, que funciona para contar qualquer sequência de comprimento de caracteres não espaciais como um único campo - para incluir uma sequência de comprimento zero. E assimumé o primeiro campo, o próximo é a string nula entre o espaço seguinte e o espaço que o segue, e o mesmo vale para os campos 3 e quatro, e o quinto campo tem 4 espaços.
printf 'one two three' |
sed 's|[^ ][^ ]*||2'
one three
Lá eu incluo umdefinidocorresponde a pelo menos um caractere não espacial por campo e, portanto, sed
se comporta mais como alguns outros programas. O que é útil sobre expressões regulares, porém, e especialmente quando aplicadas a edições, é que você pode personalizar muito especificamente o comportamento de sua saída, e lidar com strings nulas é apenas uma parte disso.
Responder3
Ok, então eu descobri isso. A questão, que confundiu alguns, foi como faço para pegar minha linha de cabeçalho, editar algumas excentricidades nos nomes dos campos e anexá-la novamente ao arquivo.
O que acabei fazendo:
- Edite a linha do cabeçalho e atribua à variável.
- Mantenha a linha do cabeçalho e o arquivo de texto restante separados o tempo todo.
Esta solução se deve em grande parte à natureza do script como uma ferramenta de carregamento para uma tabela Vertica. Contanto que os mesmos campos sejam cortados da linha de cabeçalho e do arquivo, não importa se eles serão um arquivo novamente. Eu queria principalmente reunir o cabeçalho editado com seu conteúdo original para poder salvar um arquivo de texto com a linha de cabeçalho correta em meu diretório e para não precisar cortar a linha de cabeçalho e o conteúdo separadamente. No entanto, acabei cortando-os separadamente assim,
col_arr=($columns)
cut_cols=""
for i in ${!col_arr[@]}; do
if ! [[ "${col_arr[$i]}" =~ ^(__LINE_NUMBER|CONFIDENCE|DROP_BDID|LINE_NUMBER|ZIP9|ZIP9|ZIP9MATCH)$ ]]; then
ind=$(($i+1))
cut_cols="$cut_cols,$ind"
fi
done
cut_cols=$(echo $cut_cols | sed s/^,//g)
columns=$(echo "$columns" | cut -f "$cut_cols")
cut -f ${cut_cols} ${1}>member_temp.txt
sed -i 1d member_temp.txt
Minha decisão de manter uma variável para colunas vem do uso deste script como carregador. A criação de uma tabela no Vertica requer uma instrução que identifique cada campo e seu tipo de dados. Faço isso executando a variável de colunas (linha de cabeçalho) por meio de algumas instruções if que preenchem uma variável com campos e tipos de dados em uma string a ser usada na sintaxe de uma instrução create.
Em seguida, carreguei member_temp.txt na tabela criada anteriormente. Não importa que não haja linha de cabeçalho porque eu simplesmente a removeria de qualquer maneira, pois não quero que ela seja armazenada em minha tabela.
cat member_temp.txt | /opt/vertica/bin/vsql -U ${4} -w ${5} -h ${database} \
-c "copy $schema.$table from STDIN delimiter E'\t' direct no escape;"