Itens separados por IFS em loop

Itens separados por IFS em loop

É recomendado usar IFS para percorrer listas separadas por vírgulas, por exemploaqui. Mas, eu vejo isso

sentences="Hello World,Questions"
IFS=, sentences1=($sentences) # if we comment this line then loop is fixed
sentences2=$sentences
for sentence in ${sentences2[@]}; 
do
    for i in $(seq 1 2);
    do
            #This is fine - prints a new word every line
            #echo $sentence

            #This is fine - prints new number every line
            #echo $i

            echo $i $sentence
    done
done

produz

1 
2 Hello World
1
2 Question

Em vez de

1 Hello World
2 Hello World
1 Question 
2 Question

desejado. ERA UM?

Responder1

O principal problema aqui é que a segunda linha, IFS=, sentences1=($sentences), define IFS como "," permanentemente; isso significa que você terá um comportamento de análise estranho e inesperado durante o restante do script. Observe que com algo como IFS=, read ...isso não aconteceria porque a atribuição IFS é tratada como aplicada apenas ao readcomando; mas em IFS=, sentences1=($sentences), não há comando real, apenas atribuições, portanto as atribuições são tratadas como aplicadas a todo o shell.

Então, vamos dar uma olhada no que acontece no restante do script. sentences2=$sentencesapenas copia sentencespara sentences2, então for sentence in ${sentences2[@]}é um pouco estranho. sentences2não é um array (apenas uma string simples), então o [@]bit não faz nada, mas como IFS ainda é ",", ele é "dividido por palavras" em "Hello World" e "Questions" - ou seja, você obter o resultado certo, mas pelo motivo errado.

(Observação: se fosse uma matriz real, você gostaria de colocá-la entre aspas duplas, para for sentence in "${sentences2[@]}"evitar que os elementos fossem divididos por palavras.)

O próximo comando, for i in $(seq 1 2), é onde começa o verdadeiro problema. seq 1 2imprime "1[nova linha]2[nova linha]", a $( )construção corta a nova linha final e então "1[nova linha]2" é dividida em palavras - mas como não há vírgula ali, ela é tratada como uma palavra (isso acontece com contém uma nova linha). Assim, o loop interno é executado apenas uma vez, idefinido como "1[nova linha]2".

A próxima linha, echo $i $sentence, imprime "1[nova linha]2" seguido por um espaço, seguido pelo conteúdo de sentence. Na primeira iteração, sentencedefinida como "Hello World", isso imprime:

1 
2 Hello World

... a nova linha no meio faz com que pareçam duas coisas sendo impressas, mas na verdade é apenas uma echoque contém uma nova linha.

Portanto, duas grandes recomendações: primeiro, quando você alterar o IFS, altere-o novamente depois. Em segundo lugar, sempre coloque aspas duplas nas referências de variáveis ​​para evitar divisão inesperada de palavras (e expansão de curingas). Aqui está o que recebo pelo script:

#!/bin/bash

sentences="Hello World,Questions"

saveIFS="$IFS"
IFS=, sentences1=($sentences)
IFS="$saveIFS"  # Set IFS back to normal!

for sentence in "${sentences1[@]}"; do  # Note double-quotes, and sentences1 is 
an actual array  
    for i in $(seq 1 2); do  # No double-quotes, we *want* seq's output to be spli
t
        echo "$i" "$sentence"  # Double-quotes for safety
    done
done

Responder2

Não sei qual é o motivo, mas irc://freenode/bash fornece a solução

while IFS=, read -ra items; do
    for item in "${items[@]}"; do
        for i in {1..3}; do
            printf '%d %s\n' "$i" "$item"
        done
    done
done <<< "Hello World,Questions,Answers"

que imprime corretamente.

informação relacionada