Por que o awk não ignora “espaço” como delimitador?

Por que o awk não ignora “espaço” como delimitador?

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".

captura de tela de depuração

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 $2se 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.

informação relacionada