Разделенные IFS элементы в цикле

Разделенные IFS элементы в цикле

Рекомендуется использовать IFS для перебора списка, разделенного запятыми, например:здесь. Но я вижу, что

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

производит

1 
2 Hello World
1
2 Question

Вместо

1 Hello World
2 Hello World
1 Question 
2 Question

желательно. WASA?

решение1

Основная проблема здесь в том, что вторая строка, IFS=, sentences1=($sentences), устанавливает IFS в "," навсегда; это означает, что вы получаете странное и неожиданное поведение парсинга во всем остальном скрипте. Обратите внимание, что с чем-то вроде IFS=, read ..., этого не произойдет, потому что назначение IFS рассматривается как применяемое только к readкоманде; но в IFS=, sentences1=($sentences), нет настоящей команды, только назначения, поэтому назначения рассматриваются как применяемые ко всей оболочке.

Итак, давайте посмотрим, что происходит в оставшейся части скрипта. sentences2=$sentencesпросто копирует sentencesв sentences2, поэтому for sentence in ${sentences2[@]}это немного странно. sentences2не является массивом (просто простая строка), поэтому этот [@]бит ничего не делает, но поскольку IFS по-прежнему равен ",", он "разделяется по словам" на "Hello World" и "Questions" — т. е. вы получаете правильный результат, но по неправильной причине.

(Примечание: если бы это был настоящий массив, вам бы пришлось заключить его в двойные кавычки, чтобы for sentence in "${sentences2[@]}"предотвратить разбиение элементов на слова.)

Следующая команда, for i in $(seq 1 2), — вот где начинаются настоящие проблемы. seq 1 2печатает "1[newline]2[newline]", конструкция $( )обрезает завершающую новую строку, а затем "1[newline]2" разделяется на слова — но поскольку там нет запятой, оно рассматривается как одно слово (которое случайно содержит новую строку). Таким образом, внутренний цикл выполняется только один раз с iустановкой "1[newline]2".

Следующая строка, echo $i $sentence, выводит "1[newline]2", за которым следует пробел, а затем содержимое sentence. На первой итерации, с sentenceустановкой "Hello World", это выводит:

1 
2 Hello World

...из-за символа новой строки посередине создается впечатление, что печатаются два объекта, но на самом деле это просто один echoобъект, содержащий символ новой строки.

Итак, две большие рекомендации: во-первых, когда вы меняете IFS, меняйте его обратно потом. Во-вторых, всегда заключайте ссылки на переменные в двойные кавычки, чтобы избежать неожиданного разделения слов (и расширения подстановочных знаков). Вот что я получаю для скрипта:

#!/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

решение2

Я не знаю, в чем причина, но irc://freenode/bash предлагает решение.

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"

который печатает правильно.

Связанный контент