使用嵌套循环退出shell脚本


11

我有一个带有嵌套循环的shell脚本,只是发现“退出”并没有真正退出脚本,而只是退出了当前循环。还有另一种方法可以在某些错误情况下完全退出脚本?

我不想使用“ set -e”,因为存在可接受的错误,并且需要进行大量重写。

现在,我正在使用kill手动终止进程,但是似乎应该有更好的方法来执行此操作。


1
您是什么意思,“退出”实际上并没有退出脚本?可以,只需尝试bash -c 'for x in y z; do exit; done; echo "This never gets printed"'
克里斯·

没错,它通常应该退出嵌套循环,但是当我使用exit时,脚本会继续执行外部循环。我无法发布脚本。
user923487 2015年

2
您为什么不能编写一个显示问题的脚本并将其发布在这里?这对我来说听起来不太可能。
Toby Speight 2015年

1
内部循环是否发生在代码的子外壳中?
Toby Speight 2015年

@Toby大部分脚本位于子外壳中,以进行记录,但两个循环和其余代码均位于同一子外壳中。
user923487

Answers:


19

您的问题不是嵌套循环本身。这是您的一个或多个内部循环在subshel​​l中运行

这有效:

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

输出:

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

这提出了您描述的问题:

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

输出:

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

这是解决方案;您必须测试在子外壳程序中运行的内部循环的返回值:

#!/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
        err=$?; [[ $err != 0 ]] && exit $err
        echo "After the j loop."
done
echo "After all the loops."

注意测试: [[ $? != 0 ]] && exit $?

输出:

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!

编辑:要验证您所在的子Shell,请修改“答案”脚本以告诉您当前Shell的进程ID是什么。注意:这仅适用于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
        err=$?; [[ $err != 0 ]] && echo pid $BASHPID && exit $err
        echo "After the j loop."
done
echo "After all the loops."

输出:

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

变量“ i”和“ j”由Fortran提供。祝你今天愉快。:-)


内部循环和外部循环都在同一个子外壳中运行,因此退出内部循环应该同时退出两者吗?我自己也很难重现问题。当我删除大多数程序逻辑后,问题就消失了。无论如何,我现在将其标记为答案,因为它可能必须对此做一些事情。
user923487

阅读您提供的链接后,我认为我找到了问题。在外循环中,我正在做“猫文件|同时读取行”。管道将创建一个子外壳。我不知道
user923487

@ user923487-请参阅我的最新答案。如果您使用的是bash 4,则可以回显(或者,如果是现代版本,则为printf)子shell pid,并验证您是否在子shell中。要查看您的bash版本,请bash --version在命令行上键入。
Mike S

很有帮助。谢谢!尽管我不得不说“ killall scriptname.sh”似乎是解决此问题的最直接方法。
user923487

2

较早的答案建议使用[[ $? != 0 ]] && exit $?但是这不会按预期方式工作,因为[[ $? != 0 ]]测试将重新$?回零,这意味着虽然该脚本将如预期早退出,它会永远与代码0退出(不预期) 。另外,最好使用-ne数字比较测试,而不是!=字符串比较测试。因此,恕我直言,更好的解决方案是使用:

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

这样可以确保正确设置实际的退出代码。


良好的反馈。我已经更正了代码。
Mike S

1

您可以使用break

来自help break

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

因此,要退出三个封闭循环,即如果在主循环中有两个嵌套循环,请使用此循环从所有循环中退出:

break 3

循环后还有更多代码,因此仅凭这些代码是不够的。
user923487 2015年

1
太棒了!示例for((i=0;i<3;i++));do echo A;for((j=0;j<2;j++));do echo B;break 2;done;done
Aquarius Power

0

exit 确实终止了整个外壳程序或当前子外壳程序:

$ 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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.