Переменные сбрасываются после цикла чтения while, который считывается из конвейера

initiate () {
read -p "Location(s) to look for .bsp files in? " loc
find $loc -name "*.bsp" | while read
do
    if [ -f "$loc.bz2" ]
    then
        continue
    else
        filcount=$[$filcount+1]
        bzip $loc
    fi
    if [ "$scan" == "1" ]; then bzipint $loc
    fi
    echo $filcount    #Correct counting
    echo $zipcount    #Correct counting
    echo $scacount    #Correct counting
    echo $valid       #Equal to 1
done

echo $filcount    #Reset to 0
echo $zipcount    #Reset to 0
echo $scacount    #Reset to 0
echo $valid       #Still equal to 1
}

Я пишу сценарий оболочки bash, чтобы использовать bzip2 для архивирования всех .bsp файлов внутри каталога. В этом сценарии у меня есть несколько переменных для подсчета итогов (файлы, успешные zip-файлы, успешные проверки целостности), однако, похоже, я столкнулся с проблемой.

Когда у find $loc -name "*.bsp" заканчиваются файлы для выхода while read и while read, он обнуляет $filcount, $zipcount и $scacount (все они изменяются (увеличиваются) внутри initiate (), bzip () (который вызывается во время initiate ()) или bzipint () (который также вызывается в initiate ()).

Чтобы проверить, связано ли это с изменением переменных внутри initiate () или с другими функциями, доступными из него, я использовал echo $valid, который определен вне initiate () (например, $filcount, $zipcount и т. д.), но не изменяется из-за другой функции внутри initiate () или внутри самого initiate ().

Интересно, что $valid не сбрасывается в 0, как другие переменные внутри Initial.

Может ли кто-нибудь сказать мне, почему мои переменные волшебным образом сбрасываются при завершении чтения?


person Community    schedule 05.09.2011    source источник


Ответы (3)


Я столкнулся с этой проблемой вчера.

Проблема в том, что вы делаете find $loc -name "*.bsp" | while read. Поскольку здесь используется канал, цикл while read не может выполняться в том же процессе bash, что и остальная часть вашего скрипта; bash должен создать подпроцесс, чтобы он мог подключить стандартный вывод find к стандартному выводу цикла while.

Все это очень умно, но это означает, что любые переменные, установленные в цикле, не будут видны после цикла, что полностью сводит на нет всю цель цикла while, который я писал.

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

var="$(producer | while read line; do
    ...
    echo "${something}"
done)"

Что заставило меня настроить var на все вещи, которые были отражены в цикле. Вероятно, я перепутал синтаксис этого примера; У меня нет кода, который я написал под рукой в ​​данный момент.

person Ben    schedule 05.09.2011
comment
Отличный ответ, спасибо за разъяснение. Да, это определенно вызовет некоторые сложности... Возможно, мне придется использовать временный файл или два, чтобы обойти это, как это сделали вы. :П - person ; 06.09.2011

если вы используете баш

while read
do
    if [ -f "$REPLY.bz2" ]
    then
        continue
    else
        filcount=$[$filcount+1]
        bzip $REPLY
    fi
    if [ "$scan" == "1" ]; then bzipint $REPLY
    fi
    echo $filcount    #Correct counting
    echo $zipcount    #Correct counting
    echo $scacount    #Correct counting
    echo $valid       #Equal to 1
done < <(find $loc -name "*.bsp")
person ghostdog74    schedule 06.09.2011
comment
+1 намного чище, чем передача переменных в качестве вывода; это делает то же самое, что и конвейерная версия, но с циклом while в основном процессе. - person Gordon Davisson; 06.09.2011

Чтобы подвести итоги для использования read в конце [концептуального эквивалента] конвейера в POSIX-подобных оболочках:

Напомним: в bash по умолчанию и всегда в оболочках, строго совместимых с POSIX, все команды в конвейере выполняются в вложенной оболочке, поэтому переменные, которые они создают или изменяют, выигрывают. не будет виден текущей оболочке (не будет существовать после завершения конвейера).

Далее рассматриваются bash, ksh, zsh и sh ([в основном] оболочки только с функциями POSIX, такие как dash) и показаны способы избежать создания подоболочки, чтобы сохранить переменные, созданные/измененные read.

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

Обратите внимание, что в качестве [совместимой с POSIX] альтернативы приведенным ниже решениям вы всегда можете записать вывод команды в [временный] файл, а затем передать его read как < file, что также позволяет избежать подоболочки.


ksh и zsh: обходной путь/изменение конфигурации не требуется вообще:

Встроенная команда read по умолчанию запускается в текущей оболочке при использовании в качестве последней команды в конвейере.

По-видимому, ksh и zsh по умолчанию запускают любую команду на последнем этапе конвейера в текущем shell.
Наблюдается в ksh 93u+ и zsh 5.0.5.
Если вы знаете, в какой именно версии была введена эта функция, сообщите мне.

#!/usr/bin/env ksh
#!/usr/bin/env zsh

out= # initialize output variable

# Pipe multiple lines to the `while` loop and collect the values in the output variable.
printf '%s\n' one two three | 
 while read -r var; do
   out+="$var/"
 done

echo "$out" # -> 'one/two/three/'

bash 4.2+: используйте опцию оболочки lastpipe

В bash версии 4.2 или выше включение параметра оболочки lastpipe приводит к тому, что последний сегмент конвейера запускается в текущей оболочке, что позволяет read создавать переменные, видимые для текущей оболочки.

#!/usr/bin/env bash

shopt -s lastpipe # bash 4.2+: make the last pipeline command run in *current* shell

out=
printf '%s\n' one two three | 
 while read -r var; do
   out+="$var/"
 done

echo "$out" # -> 'one/two/three/'

bash, ksh, zsh: используйте подстановку процессов

Грубо говоря, подстановка процесса — это способ заставить выходные данные команды вести себя как временный файл.

out=
while read -r var; do
  out+="$var/"
done < <(printf '%s\n' one two three) # <(...) is the process substitution

echo "$out" # -> 'one/two/three'

bash, ksh, zsh: используйте здесь-строку с подстановка команд

out=
while read -r var; do
  out+="$var/"
done <<< "$(printf '%s\n' one two three)" # <<< is the here-string operator

echo "$out" # -> 'one/two/three'

Обратите внимание на необходимость заключать подстановку команды в двойные кавычки, чтобы защитить ее вывод от расширения оболочки.


POSIX-совместимое решение (sh): используйте здесь-документ с подстановка команд

#!/bin/sh

out=
while read -r var; do
  out="$out$var/"
done <<EOF # <<EOF ... EOF is the here-doc
$(printf '%s\n' one two three)
EOF

echo "$out" # -> 'one/two/three'

Обратите внимание, что по умолчанию конечный разделитель - EOF нужно ставить в самом начале строки, и за ним не должно следовать никаких символов.

person mklement0    schedule 07.03.2015
comment
Таким образом, только встроенные оболочки могут влиять на среду оболочки, и при этом только встроенные оболочки, работающие в текущей оболочке, а оболочки исторически запускали встроенные функции только непосредственно из первого этапа конвейера. Таким образом, какая-то гипотетическая чудесная оболочка может решить, что если есть один этап, на котором работает встроенная функция, то это этап, на котором она должна работать напрямую, но пока вы либо запускаете встроенную функцию на первом этапе, либо используете параметр bash lastpipe, чтобы он использовал последний вместо этого. . Прохладный. Спасибо! Я как бы знал это раньше, но только отчасти. - person jthill; 07.03.2015
comment
@jthill: Оказывается, у ksh и zsh уже есть встроенная магия для последнего сегмента конвейера — см. мое обновление. Первый этап конвейера всегда выполняется в вложенной оболочке, даже в ksh и zsh. - person mklement0; 07.03.2015
comment
@jthill: обратите внимание, что типичным вариантом использования является желание, чтобы последний этап находился в текущей оболочке: вы хотите зафиксировать результаты конвейера в переменных, видимых для текущего оболочка. Можете ли вы сказать мне, что вы имеете в виду, говоря, что встроенная функция выполняется на одном этапе? - person mklement0; 07.03.2015
comment
Чтобы подключиться к середине конвейера, произнесите sort data | while read; do whatever here;: $((subtotal+=someresult)); echo $((someresult)); done | work on the munged stuff; done - person jthill; 07.03.2015
comment
@jthill: Понятно - это было бы удобно, хотя я подозреваю, что на последнем этапе обычно вы снова входите в мир оболочки, концептуально говоря. - person mklement0; 08.03.2015