Escopo da variável Bash

104

Explique por que a última echodeclaração está em branco. Espero que XCODEseja incrementado no loop while para um valor de 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

Tentei usar a seguinte instrução em vez do ++XCODEmétodo

XCODE=`expr $XCODE + 1`

e também não imprimirá fora da instrução while. Acho que estou perdendo algo sobre escopo de variável aqui, mas a velha página do manual não está mostrando para mim.

Matt P
fonte
Onde você inicializa XCODE para algo que pode ser incrementado?
Paul Tomblin
Tentei lançar um "XCODE = 0" no topo do código, fora da instrução while
Matt P
Sem o cruft, funciona para mim. #! / bin / bash para i em 1 2 3 4 5; do echo $ ((++ XCODE)) done echo "fin:" $ XCODE Acho que o seu problema não tem nada a ver com escopo de variável e tudo a ver com o que está acontecendo no momento.
Paul Tomblin
Concordo .. parece que tem a ver com o loop "durante a leitura"?
Matt P
Há um FAQ do Bash sobre isso: mywiki.wooledge.org/BashFAQ/024
Fabio diz Reintegrar Monica em

Respostas:

117

Como você está canalizando para o loop while, um sub-shell é criado para executar o loop while.

Agora, este processo filho tem sua própria cópia do ambiente e não pode passar nenhuma variável de volta para seu pai (como em qualquer processo Unix).

Portanto, você precisará reestruturar para não entrar no circuito. Como alternativa, você pode executar em uma função, por exemplo, e echoo valor que deseja retornar do subprocesso.

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

pixelbeat
fonte
2
isso apenas respondeu a muitos dos problemas aparentemente aleatórios que eu estava enfrentando com o script bash.
Daniel Agans
Esta resposta perfeita me perturba muito e explica um comportamento realmente estranho em nosso sistema de CI.
KayCee
108

O problema é que os processos reunidos com um pipe são executados em subshells (e, portanto, têm seu próprio ambiente). O que quer que aconteça dentro do whilenão afeta nada fora do tubo.

Seu exemplo específico pode ser resolvido reescrevendo o tubo para

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

ou talvez

while ... do ... done < <(echo "$OUTPUT")
Mweerden
fonte
32
Para aqueles que estão olhando para isso confusos sobre o que é toda a sintaxe <() (como eu), é chamada de "Substituição de Processo", e o uso específico detalhado acima pode ser visto aqui: mywiki.wooledge.org/ProcessSubstitution
Ross Aiken
2
Substituição de processo é algo que todos deveriam usar regularmente! É super útil. Eu faço algo como vimdiff <(grep WARN log.1 | sort | uniq) <(grep WARN log.2 | sort | uniq)todos os dias. Considere que você pode usar vários de uma vez e tratá-los como arquivos ... POSSIBILIDADES!
Bruno Bronosky
8

Isso também deve funcionar (porque echo e while estão no mesmo subshell):

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

Mais uma opção:

#!/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

EDITAR: Aqui, xsel é um requisito (instale-o). Alternativamente, você pode usar xclip: em xclip -i -selection clipboard vez de xsel -i -p

Rammix
fonte
Recebo um erro: ./scraper.sh: linha 111: xsel: comando não encontrado ./scraper.sh: linha 114: xsel: comando não encontrado
3kstc
@ 3kstc obviamente, instale o xsel. Além disso, você pode usar o xclip, mas seu uso é um pouco diferente. O ponto principal aqui: primeiro você coloca a saída em uma área de transferência (3 delas no linux), segundo - você pega de lá e envia para o stdout.
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

veja se essas mudanças ajudam

Kent Fredric
fonte
Ao fazer isso, agora obtenho um "0" para imprimir a última instrução de eco. entretanto, espero que o valor seja 1 e não zero. Além disso, por que usar a exportação? Presumo que isso o force ao meio ambiente?
Matt P
0

Outra opção é gerar os resultados em um arquivo do subshell e depois lê-lo no shell pai. algo como

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

Eu contornei isso quando estava fazendo meu próprio du:

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" )

O ponto é que eu faço um subshell com () contendo minha variável SUM e o while, mas eu canalizo para o todo () em vez de para o próprio while, o que evita o problema.

Adrian May
fonte