Назад | Перейти на главную страницу

bash: переменная теряет значение в конце цикла чтения

У меня проблема в одном из моих сценариев оболочки. Спросил несколько коллег, но все они только покачали головами (немного почесавшись), поэтому я пришел сюда за ответом.

Насколько я понимаю, следующий сценарий оболочки должен печатать «Count is 5» в качестве последней строки. Но это не так. Он печатает «Count is 0». Если заменить «пока читается» на любой другой цикл, он работает нормально. Вот сценарий:

echo "1">input.data
echo "2">>input.data
echo "3">>input.data
echo "4">>input.data
echo "5">>input.data

CNT=0 

cat input.data | while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT"

Почему это происходит и как это предотвратить? Я пробовал это в Debian Lenny и Squeeze, результат тот же (т.е. bash 3.2.39 и bash 4.1.5. Я полностью признаю, что не являюсь мастером сценариев оболочки, поэтому любые указатели будут оценены.

Это своего рода «распространенная» ошибка. Трубы создают субоболочки, поэтому while read работает в оболочке, отличной от вашего скрипта, что делает ваш CNT переменная никогда не изменяется (только та, которая находится внутри подоболочки трубы).

Сгруппируйте последние echo с подоболочкой while чтобы исправить это (есть много других способов исправить это, это один. У Иэна и Игнасио есть другие.)

CNT=0

 cat input.data | ( while read 
do
  let CNT++;
  echo "Counting to $CNT"
done 
echo "Count is $CNT" )

Длинное объяснение:

  1. Вы заявляете CNT в вашем скрипте значение 0;
  2. SubShell запускается на | к while read;
  3. Ваш $CNT переменная экспортируется в SubShell со значением 0;
  4. SubShell считает и увеличивает CNT значение до 5;
  5. SubShell заканчивается, переменные и значения уничтожаются (они не возвращаются в вызывающий процесс / скрипт).
  6. Вы echo ваш оригинал CNT значение 0.

См. Аргумент @ Запись № 24 в часто задаваемых вопросах Bash: «Я устанавливаю переменные в цикле. Почему они внезапно исчезают после завершения цикла? Или почему я не могу передать данные для чтения?» (последний раз заархивирован Вот).

Резюме: это поддерживается только начиная с bash 4.2 и выше. Если вы используете bash, вам нужно использовать разные способы, например, подстановку команд, а не канал.

Это работает

CNT=0 

while read ;
do
  let CNT++;
  echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"

Вместо этого попробуйте передать данные во вспомогательной оболочке, например, в файле перед циклом while. Это похоже на решение lain, но предполагает, что вам не нужен какой-то прерывистый файл:

total=0
while read var
do
  echo "variable: $var"
  ((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"

Я нашел способ использовать файл stderr для хранения значения var i.

# reading lines of content from 2 files concatenated
# inside loop: write value of var i to stderr (before iteration)
# outside: read var i from stderr, has last iterative value
f=/tmp/file1
g=/tmp/file2

i=1
cat $f $g | \
while read -r s;
do
  echo $s > /dev/null;  # some work
  echo $i > 2
  let i++
done;
read -r i < 2
echo $i