Estou com um problema no meu roteiro.
Prelúdio Em primeiro lugar, tenho uma lista de arquivos de 100 linhas assim:
100;TEST ONE
101;TEST TWO
...
200;TEST HUNDRED
Cada linha possui 2 argumentos. Por exemplo, os argumentos da primeira linha são: "645", "TEST ONE". Portanto, ponto e vírgula é um delimitador.
Preciso colocar os dois argumentos em duas variáveis. Digamos que será $id e $name. Para cada linha, os valores $id e $name serão diferentes. Por exemplo, para a segunda linha $id = "646" e $name = "TEST TWO".
Depois disso, preciso pegar o arquivo de amostra e alterar as palavras-chave predefinidas para os valores $id e $name. O arquivo de amostra fica assim:
xxx is yyy
E como resultado quero ter 100 arquivos com conteúdos diferentes. Cada arquivo deve conter dados $id e $name de cada linha. E deve ser nomeado pelo valor $name.
Aí está o meu script:
#!/bin/bash -x
rm -f output/*
for i in $(cat list)
do
id="$(printf "$i" | awk -F ';' '{print $1}')"
name="$(printf "$i" | awk -F ';' '{print $2}')"
cp sample.xml output/input.tmp
sed -i -e "s/xxx/$id/g" output/input.tmp
sed -i -e "s/yyy/$name/g" output/input.tmp
mv output/input.tmp output/$name.xml
done
Então, eu apenas tento ler meu arquivo de lista linha por linha. Para cada linha, recebo duas variáveis e, em seguida, uso-as para substituir palavras-chave (xxx e yyy) do arquivo de amostra e, em seguida, salvar o resultado.
Mas algo deu errado
Como resultado, tenho apenas 1 arquivo de saída. E a depuração parece ruim.
Aqui está a janela de depuração com apenas 2 linhas no meu arquivo de lista. Eu tenho apenas um arquivo de saída. O nome do arquivo é apenas "TEST" e contém uma string: "101 is TEST".
São esperados dois arquivos: "TEST ONE", "TEST TWO" e deve conter "100 é TEST ONE" e "101 é TEST TWO".
Como você pode ver, a segunda variável possui um espaço ("TEST ONE" por exemplo). Acho que o problema está relacionado ao símbolo especial do espaço, mas não sei por quê. Coloquei o parâmetro -F awk em ";", então o awk deve interpretar apenas ponto e vírgula como separador!
O que eu fiz de errado?
Responder1
Se bem entendi, você pode usar um loop while e expansão de variável
while IFS= read -r line; do
id="${line%;*}"
name="${line#*;}"
cp sample.xml output/input.tmp
sed -i -e "s/xxx/$id/g" output/input.tmp
sed -i -e "s/yyy/$name/g" output/input.tmp
mv output/input.tmp output/"$name".xml
done < file
Conforme proposto por @steeldriver, aqui está uma opção (mais elegante):
while IFS=';' read -r id name; do
cp sample.xml output/input.tmp
sed -i -e "s/xxx/$id/g" output/input.tmp
sed -i -e "s/yyy/$name/g" output/input.tmp
mv output/input.tmp output/"$name".xml
done < file
Responder2
Citando !!. A citação nesta linha está faltando:
mv output/input.tmp output/$name.xml
Deveria ser:
mv output/input.tmp output/"$name".xml
para evitar problemas com nomes de arquivos com espaços.
E a expansão de $(cat list)
está sendo dividida (e glob) pela casca, que também quebra em espaços.
Talvez você possa mudar para este script:
#!/bin/bash -x
rm -f output/*
inputfile=output/input.tmp
while read -r line
do
id=${line%%;*}
name=${line##*;}
cp sample.xml "$inputfile"
sed -i -e "s/xxx/$id/g" "$inputfile"
sed -i -e "s/yyy/$name/g" "$inputfile"
mv "$inputfile" output/"$name".xml; echo
done <list
Responder3
A razão pela qual seu awk não está produzindo os resultados esperados é devido à maneira como você está iterando no arquivo. Ao iterar usando for i in $(cat file)
, você está iterando sobre palavras (divididas por IFS), não sobre linhas. Para ler um arquivo linha por linha, use while read
:
while read -r line; do
...
done < file
Para ler mais, consulte o seguinte FAQ do bash:Como posso ler um arquivo (fluxo de dados, variável) linha por linha (e/ou campo por campo)?
Responder4
Como uma abordagem alternativa,você pode fazer esse trabalho com o awkem 1 processo em vez de 4 para cada linha. É mais provável que isso seja benéfico se houver muitas linhas na lista, mas sample.xml for pequeno.
awk -F';' 'FNR==NR{x=x $0 RS; next}
{t=x; gsub(/xxx/,$1,t); gsub(/yyy/,$2,t); f="output/"$2".xml"; printf "%s",t >f; close(f)}
' sample.xml list
# shown with unnecessary linebreaks for clarity, but you can put it all on one line
Se list tiver finais de linha CRLF (também conhecidos como formato DOS ou Windows), conforme comentado em seu Q, e você não puder (facilmente) ou não quiser removê-los primeiro, o awk também poderá lidar com isso; logo após a segunda {
inserção sub(/\r$/,"",$0);
(ou $2
se preferir).
perl também pode fazer isso (perl pode fazer quase tudo que o awk pode fazer), mas com um pouco mais de detalhes e, embora o perl esteja comumente disponível, não é POSIX como o awk.