У меня проблема в одном из моих сценариев оболочки. Спросил несколько коллег, но все они только покачали головами (немного почесавшись), поэтому я пришел сюда за ответом.
Насколько я понимаю, следующий сценарий оболочки должен печатать «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" )
Длинное объяснение:
CNT
в вашем скрипте значение 0;|
к while read
;$CNT
переменная экспортируется в SubShell со значением 0;CNT
значение до 5;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