退出带有嵌套循环的 shell 脚本

退出带有嵌套循环的 shell 脚本

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

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

现在,我正在使用kill来手动终止进程,但似乎应该有更好的方法来做到这一点。

答案1

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

这有效:

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

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

#!/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,请修改“answer”脚本以告诉您当前 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 提供。祝你今天过得愉快。 :-)

答案2

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

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

因为这将确保实际的退出代码设置正确。

答案3

您可以使用break

help break

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

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

break 3

答案4

测试管道的退出状态代码(如接受的答案中所示)的替代方法是使用进程替换。

所以代替这个:

#!/usr/bin/env bash


seq 3 | while read line_1; do
    seq 3 | while read line_2; do
        if [ "$line_2" -eq 3 ]; then
            echo 'Stop!'
            exit 1
        fi

        echo "$line_1 $line_2"
    done
done

做这个:

#!/usr/bin/env bash


while read line_1; do
    while read line_2; do
        if [ "$line_2" -eq 3 ]; then
            echo 'Stop!'
            exit 1
        fi

        echo "$line_1 $line_2"
    done < <(seq 3)
done < <(seq 3)

我找到了这个解决方案这里。该答案还提到了一些重要的警告:

请注意,该<( )构造(“进程替换”)并非在所有 shell 中都可用,甚至在处于 sh 兼容模式的 bash 中(即当它作为shor调用时/bin/sh)也不可用。因此,请务必在脚本中使用显式 bash shebang(例如#!/bin/bash#!/usr/bin/env bash),并且不要通过使用命令运行脚本来覆盖它sh

相关内容