Saindo de um script de shell com loops aninhados

11

Eu tenho um shell script com loops aninhados e descobri que "exit" realmente não sai do script, mas apenas o loop atual. Existe outra maneira de sair completamente do script em uma determinada condição de erro?

Eu não quero usar "set -e", porque há erros aceitáveis e exigiria muita reescrita.

Neste momento, estou usando kill para matar manualmente o processo, mas parece que deve haver uma maneira melhor de fazer isso.

    
por user923487 30.06.2015 / 17:37

4 respostas

18

Seu problema não é loops aninhados, por si só. É que um ou mais de seus loops internos são em execução em uma subshell .

Isso funciona:

#!/bin/bash

for i in $(seq 1 100); do
        echo i $i
        for j in $(seq 1 10) ; do
                echo j $j
                sleep 1
                [[ $j = 3 ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        echo "After the j loop."
done
echo "After all the loops."

saída:

i 1
j 1
j 2
j 3
I've had enough!

Isso apresenta o problema que você descreveu:

#!/bin/bash

for i in $(seq 1 100); do
        echo i $i
        cat /etc/passwd | while read line; do
                echo LINE $line
                sleep 1
                [[ "$line" = "daemon:x:2:2:daemon:/sbin:/sbin/nologin" ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        echo "After the j loop."
done    
echo "After all the loops."

saída:

i 1
LINE root:x:0:0:root:/root:/bin/bash
LINE bin:x:1:1:bin:/bin:/sbin/nologin
LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!
After the j loop.
i 2
LINE root:x:0:0:root:/root:/bin/bash
LINE bin:x:1:1:bin:/bin:/sbin/nologin
LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!
After the j loop.
i 3
LINE root:x:0:0:root:/root:/bin/bash
(...etc...)

Aqui está a solução; você tem que testar o valor de retorno dos loops internos que são executados em subshells:

#!/bin/bash

for i in $(seq 1 100); do
        echo i $i
        cat /etc/passwd | while read line; do
                echo LINE $line
                sleep 1
                [[ "$line" = "daemon:x:2:2:daemon:/sbin:/sbin/nologin" ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        [[ $? != 0 ]] && exit $?
        echo "After the j loop."
done
echo "After all the loops."

Observe o teste: [[ $? != 0 ]] && exit $?

saída:

i 1
LINE root:x:0:0:root:/root:/bin/bash
LINE bin:x:1:1:bin:/bin:/sbin/nologin
LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!

Editar: Para verificar em que sub-linha você está, modifique o script "answer" para informar qual é o ID do processo do seu shell atual. NOTA: Isso só funciona no bash 4:

#!/bin/bash

for i in $(seq 1 100); do
        echo pid $BASHPID i $i
        cat /etc/passwd | while read line; do
                echo pid $BASHPID LINE $line
                sleep 1
                [[ "$line" = "daemon:x:2:2:daemon:/sbin:/sbin/nologin" ]] && { echo "I've had enough!" 1>&2; exit 1; }
        done
        [[ $? != 0 ]] && echo pid $BASHPID && exit $?
        echo "After the j loop."
done
echo "After all the loops."

saída:

pid 31793 i 1
pid 31796 LINE root:x:0:0:root:/root:/bin/bash
pid 31796 LINE bin:x:1:1:bin:/bin:/sbin/nologin
pid 31796 LINE daemon:x:2:2:daemon:/sbin:/sbin/nologin
I've had enough!
pid 31793

As variáveis "i" e "j" são oferecidas a você como cortesia do Fortran. Tenha um bom dia. : -)

    
por 30.06.2015 / 18:06
1

Você pode usar break .

De help break :

Exit a FOR, WHILE or UNTIL loop.  If N is specified, break N enclosing loops.

Portanto, para sair de três loops envolventes, ou seja, se você tiver dois loops aninhados dentro do primeiro, use-o para sair de todos eles:

break 3
    
por 30.06.2015 / 17:43
1

Uma resposta anterior sugere o uso de [[ $? != 0 ]] && exit $? , mas isso não funcionará como esperado, porque o [[ $? != 0 ]] test redefinirá $? de volta para zero, o que significa que, embora o script seja saída antecipada como esperado, sempre sairá com o código 0 (como não esperado). Além disso, seria melhor usar o teste de comparação numérica -ne , em vez do teste de comparação% string!=. Portanto, uma solução melhor para IMHO é usar:

err=$?; [[ $err -ne 0 ]] && exit $err

pois isso garante que o código de saída real esteja definido corretamente.

    
por 10.04.2017 / 02:36
0

exit termina o shell inteiro ou o sub-shell atual:

$ bash -c 'for i in 1 2 3; do for j in 4 5 6; do echo $i; exit 1; echo $j; done; done'
1
$ echo $?
1
    
por 30.06.2015 / 18:07