Переменные сбрасываются после цикла чтения, который читает из конвейера. - PullRequest
9 голосов
/ 06 сентября 2011
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 файлов в каталоге. В этом сценарии у меня есть несколько переменных для подсчета итогов (файлы, успешные архивы, успешные проверки целостности), однако, похоже, я столкнулся с проблемой.

Когда у 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, как другие переменные внутри initize.

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

Ответы [ 3 ]

10 голосов
/ 06 сентября 2011

если вы используете bash

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")
8 голосов
/ 07 марта 2015

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

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

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

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

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


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

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

По-видимому, ksh и zsh по умолчанию run любая команда на этапе last конвейера в оболочке current .
Наблюдается в 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 shell

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

#!/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, в данном случае - в самом начале строки, и что после него не должно быть символов.

7 голосов
/ 06 сентября 2011

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

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

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

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

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

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

...