Как передать входные данные в цикл Bash while и сохранить переменные после завершения цикла

87

Bash позволяет использовать: cat <(echo "$FILECONTENT")

Bash также позволяет использовать: while read i; do echo $i; done </etc/passwd

чтобы объединить предыдущие два, это можно использовать: echo $FILECONTENT | while read i; do echo $i; done

Проблема с последним состоит в том, что он создает суб-оболочку, и после завершения цикла while iдоступ к переменной больше невозможен.

У меня вопрос:

Как добиться чего-то вроде этого: while read i; do echo $i; done <(echo "$FILECONTENT")или, другими словами: как я могу быть уверен, что iцикл while выживает?

Обратите внимание, что я знаю о включении оператора while, {}но это не решает проблему (представьте, что вы хотите использовать цикл while в функции и возвращаемой iпеременной)

Вакан Танка
источник
Связанный: stackoverflow.com/questions/37229058/… . Объясняет все варианты, включая указанную ниже замену процесса, lastpipeа также их плюсы и минусы.
ivan_pozdeev

Ответы:

115

Правильная запись для подстановки процесса :

while read i; do echo $i; done < <(echo "$FILECONTENT")

Последнее значение, iприсвоенное в цикле, становится доступным, когда цикл завершается. Альтернатива:

echo $FILECONTENT | 
{
while read i; do echo $i; done
...do other things using $i here...
}

Фигурные скобки представляют собой операцию группирования ввода-вывода и сами по себе не создают подоболочку. В этом контексте они являются частью конвейера и поэтому выполняются как подоболочка, но это из-за того |, что , а не из-за { ... }. Вы упомянули об этом в вопросе. AFAIK, вы можете сделать возврат из них внутри функции.


Bash также предоставляет shoptвстроенную функцию, и один из многих ее вариантов:

lastpipe

Если установлено, а управление заданиями неактивно, оболочка выполняет последнюю команду конвейера, которая не выполняется в фоновом режиме в текущей среде оболочки.

Таким образом, использование в скрипте чего-то вроде этого делает модифицируемый sumдоступным после цикла:

FILECONTENT="12 Name
13 Number
14 Information"
shopt -s lastpipe   # Comment this out to see the alternative behaviour
sum=0
echo "$FILECONTENT" |
while read number name; do ((sum+=$number)); done
echo $sum

Выполнение этого в командной строке обычно приводит к фолу «управление заданиями неактивно» (то есть в командной строке активно управление заданиями). Проверить это без использования сценария не удалось.

Кроме того, как отметил Гарет Рис в своем ответе , иногда вы можете использовать здесь строку :

while read i; do echo $i; done <<< "$FILECONTENT"

Это не требует shopt; вы можете сохранить процесс, используя его.

Джонатан Леффлер
источник
Простите мое незнание. Я знаю, что это правильное решение, и пометил его как ответ, так что у меня оно сработало. Но теперь, когда я запускаю, while read i; do echo $i; done < <(cat /etc/passwd); echo $iон не возвращал последнюю строку два раза. Что я делаю не так?
Вакан Танка,
@WakanTanka: Мне пришлось немного поэкспериментировать ... Я считаю, что ответ заключается в том, что неудачное чтение сбрасывается iдо пустого, поэтому эхо после цикла повторяет пустую строку.
Джонатан Леффлер,
1
Престижность за ссылку на замену процесса. Я не знал об этом.
Атколд,
@Wakan Tanka: результат такой же, как у вас, я использую while read i; do x=$i; done < <(cat /etc/passwd); echo i=$i; echo x=$xсработало, // может быть, в те дни поведение bash изменилось?
yurenchen
Ты спасатель! Долго искал подобное решение. Ваш единственный, что сработало.
Пэт
0

Эта функция создает дубликаты файлов jpg в $ NUM раз (bash)

function makeDups() {
NUM=$1
echo "Making $1 duplicates for $(ls -1 *.jpg|wc -l) files"
ls -1 *.jpg|sort|while read f
do
  COUNT=0
  while [ "$COUNT" -le "$NUM" ]
  do
    cp $f ${f//sm/${COUNT}sm}
    ((COUNT++))
  done
done
}
Стив
источник