bash: переменная теряет значение в конце цикла чтения
У меня проблема в одном из моих скриптов оболочки. Спросил несколько коллег, но они все только качают головами (после некоторого расчесывания), поэтому я пришел сюда за ответом.
Согласно моему пониманию следующий скрипт оболочки должен вывести "Count is 5" в качестве последней строки. За исключением того, что это не так. Это печатает "Количество равно 0". Если заменить "while read" на любой другой вид цикла, он работает просто отлично. Вот сценарий:
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 | пока читаешь; делать пусть CNT++; echo "Подсчет в $CNT" сделанный echo "Count is $CNT"
Почему это происходит и как я могу предотвратить это? Я пробовал это в Debian Lenny и Squeeze, тот же результат (то есть bash 3.2.39 и bash 4.1.5. Я полностью признаю, что не являюсь мастером сценариев оболочки, поэтому любые указатели приветствуются.
4 ответа
См. Аргумент @ Bash FAQ #24: "Я устанавливаю переменные в цикле. Почему они внезапно исчезают после завершения цикла? Или, почему я не могу передать данные для чтения?" (последнее архивировано здесь).
Резюме: это поддерживается только в bash 4.2 и выше. Вы должны использовать различные способы, такие как подстановка команд вместо канала, если вы используете bash.
Это своего рода "распространенная" ошибка. Трубы создают SubShells, поэтому while read
работает в другой оболочке, чем ваш скрипт, что делает ваш CNT
переменная никогда не изменяется (только та, которая находится внутри подоболочки трубы).
Группировать последний echo
с подоболочки while
исправить это (есть много других способов исправить это, это один. Ответы Iain и Ignacio имеют другие.)
CNT=0
cat input.data | ( while read
do
let CNT++;
echo "Counting to $CNT"
done
echo "Count is $CNT" )
Длинное объяснение:
- Вы заявляете
CNT
в вашем скрипте должно быть значение 0; - SubShell запускается на
|
вwhile read
; - Ваш
$CNT
переменная экспортируется в SubShell со значением 0; - SubShell считает и увеличивает
CNT
значение до 5; - SubShell заканчивается, переменные и значения уничтожаются (они не возвращаются в вызывающий процесс / скрипт).
- Вы
echo
ваш оригиналCNT
значение 0.
Это работает
CNT=0
while read ;
do
let CNT++;
echo "Counting to $CNT"
done <input.data
echo "Count is $CNT"
Попробуйте вместо этого передать данные в под-оболочку, как будто это файл перед циклом while. Это похоже на решение La in, но предполагает, что вам не нужен прерывистый файл:
total=0
while read var
do
echo "variable: $var"
((total+=var))
done < <(echo 45) #output from a command, script, or function
echo "total: $total"
Другое решение - просто добавить
shopt -s lastpipe
перед циклом while.
Я говорю, что если проблема возникает из-за того, что время находится в последнем сегменте конвейера, а в Bash все команды в конвейере выполняются в подоболочке в отдельном процессе, тогда, используя
lastpipe
выполнит последнюю команду в конвейере на переднем плане.
Например:
CNT=0
shopt -s lastpipe
cat input.data | while read ;
...
И почти все осталось прежним.
Я нашел способ использовать файл 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