![退出带有嵌套循环的 shell 脚本](https://linux22.com/image/68093/%E9%80%80%E5%87%BA%E5%B8%A6%E6%9C%89%E5%B5%8C%E5%A5%97%E5%BE%AA%E7%8E%AF%E7%9A%84%20shell%20%E8%84%9A%E6%9C%AC.png)
我有一个带有嵌套循环的 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 中(即当它作为sh
or调用时/bin/sh
)也不可用。因此,请务必在脚本中使用显式 bash shebang(例如#!/bin/bash
或#!/usr/bin/env bash
),并且不要通过使用命令运行脚本来覆盖它sh
。