Область видимости переменной Bash


104

Пожалуйста, объясните мне, почему самое последнее echoутверждение пустое? Я ожидаю, что XCODEэто увеличивается в цикле while до значения 1:

#!/bin/bash
OUTPUT="name1 ip ip status" # normally output of another command with multi line output

if [ -z "$OUTPUT" ]
then
        echo "Status WARN: No messages from SMcli"
        exit $STATE_WARNING
else
        echo "$OUTPUT"|while read NAME IP1 IP2 STATUS
        do
                if [ "$STATUS" != "Optimal" ]
                then
                        echo "CRIT: $NAME - $STATUS"
                        echo $((++XCODE))
                else
                        echo "OK: $NAME - $STATUS"
                fi
        done
fi

echo $XCODE

Я пробовал использовать следующий оператор вместо ++XCODEметода

XCODE=`expr $XCODE + 1`

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


Где вы инициализируете XCODE для чего-то, что можно увеличивать?
Пол Томблин,

Я попытался добавить «XCODE = 0» вверху кода, вне оператора while
Мэтт П.

Без хлама у меня работает. #! / bin / bash для i in 1 2 3 4 5; do echo $ ((++ XCODE)) done echo "fin:" $ XCODE Я думаю, ваша проблема не имеет ничего общего с областью видимости переменных и не имеет ничего общего с тем, что происходит в данный момент.
Пол Томблин,

Согласен .. похоже это связано с циклом "пока читается"?
Мэтт П.

Ответы:


117

Поскольку вы подключаетесь к циклу while, создается суб-оболочка для запуска цикла while.

Теперь у этого дочернего процесса есть собственная копия среды, и он не может передавать никакие переменные обратно своему родительскому процессу (как и в любом процессе Unix).

Поэтому вам нужно будет реструктурировать, чтобы вы не попадали в петлю. В качестве альтернативы вы можете запустить функцию, например, и echoзначение, которое вы хотите вернуть из подпроцесса.

http://tldp.org/LDP/abs/html/subshells.html#SUBSHELL


2
это просто ответило на многие из, казалось бы, случайных проблем, с которыми я столкнулся при написании сценариев bash.
Даниэль Аганс

Этот идеальный ответ меня очень расстраивает и объясняет действительно странное поведение нашей системы CI.
KayCee

108

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

Ваш конкретный пример можно решить, переписав канал на

while ... do ... done <<< "$OUTPUT"

или возможно

while ... do ... done < <(echo "$OUTPUT")

32
Для тех, кто смотрит на это, не понимая, что такое весь синтаксис <() (как и я), он называется «Замена процесса», а конкретное использование, описанное выше, можно увидеть здесь: mywiki.wooledge.org/ProcessSubstitution
Росс Эйкен

2
Замена процесса - это то, что каждый должен регулярно использовать! Это очень полезно. Я делаю что-то подобное vimdiff <(grep WARN log.1 | sort | uniq) <(grep WARN log.2 | sort | uniq)каждый день. Учтите, что вы можете использовать несколько одновременно и обращаться с ними как с файлами ... ВОЗМОЖНОСТИ!
Бруно Броноски

8

Это тоже должно работать (потому что echo и while находятся в одной подоболочке):

#!/bin/bash
cat /tmp/randomFile | (while read line
do
    LINE="$LINE $line"
done && echo $LINE )

3

Еще один вариант:

#!/bin/bash
cat /some/file | while read line
do
  var="abc"
  echo $var | xsel -i -p  # redirect stdin to the X primary selection
done
var=$(xsel -o -p)  # redirect back to stdout
echo $var

РЕДАКТИРОВАТЬ: Здесь xsel является требованием (установите его). В качестве альтернативы вы можете использовать xclip: xclip -i -selection clipboard вместо xsel -i -p


Я получаю сообщение об ошибке: ./scraper.sh: line 111: xsel: command not found ./scraper.sh: line 114: xsel: command not found
3kstc

@ 3kstc очевидно, установите xsel. Кроме того, вы можете использовать xclip, но его использование немного отличается. Главное здесь: 1-й вы помещаете вывод в буфер обмена (3 из них в linux), 2-й - вы берете его оттуда и отправляете на стандартный вывод.
Rammix

1
 #!/bin/bash
 OUTPUT="name1 ip ip status"
+export XCODE=0;
 if [ -z "$OUTPUT" ]
----

                     echo "CRIT: $NAME - $STATUS"
-                    echo $((++XCODE))
+                    export XCODE=$(( $XCODE + 1 ))
             else

echo $XCODE

Посмотрим, помогут ли эти изменения


При этом я теперь получаю «0» для печати последнего оператора эха. однако я ожидаю, что значение будет равно 1, а не нулю. Кроме того, зачем использовать экспорт? Я предполагаю, что это заставляет его попасть в окружающую среду?
Мэтт П,

0

Другой вариант - вывести результаты в файл из подоболочки, а затем прочитать его в родительской оболочке. что-то вроде

#!/bin/bash
EXPORTFILE=/tmp/exportfile${RANDOM}
cat /tmp/randomFile | while read line
do
    LINE="$LINE $line"
    echo $LINE > $EXPORTFILE
done
LINE=$(cat $EXPORTFILE)

0

Я обошел это, когда делал свой собственный маленький ду:

ls -l | sed '/total/d ; s/  */\t/g' | cut -f 5 | 
( SUM=0; while read SIZE; do SUM=$(($SUM+$SIZE)); done; echo "$(($SUM/1024/1024/1024))GB" )

Дело в том, что я делаю подоболочку с (), содержащую мою переменную SUM и while, но я передаю целую () вместо самого while, что позволяет избежать ошибок.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.