我正在为自己编写 Bash 脚本来学习脚本。在某些时候,如果脚本被终止,我需要添加陷阱以清除不需要的目录和文件。但是,由于某种原因我不明白,trap 调用清理函数——clean_a()当脚本被杀死但$LINENO指向清理函数本身中的一行,而不是函数本身archieve_it()时——当脚本被杀死时。
预期行为:
clean_a()函数clean_a()函数回显行号,Ctrl+C被按下。让它成为第 10 行archieve_it()。实际发生的情况:
clean_a()函数clean_a()回显一个不相关的行号。比如说,clean_a()函数中的第 25 行。这是作为我脚本一部分的示例:
archieve_it () {
trap 'clean_a $LINENO $BASH_COMMAND'\
SIGHUP SIGINT SIGTERM SIGQUIT
for src in ${sources}; do
mkdir -p "${dest}${today}${src}"
if [[ "$?" -ne 0 ]] ; then
error "Something!"
fi
rsync "${options}" \
--stats -i \
--log-file="${dest}${rsync_log}" \
"${excludes}" "${src}" "${dest}${today}${src}"
done
}
clean_a () {
error "something!
line: $LINENO
command: $BASH_COMMAND
removing ${dest}${today}..."
cd "${dest}"
rm -rdf "${today}"
exit "$1"
}
Run Code Online (Sandbox Code Playgroud)
PS:原始脚本可以在这里看到。定义和变量名称使用土耳其语。如果需要,我可以将任何内容翻译成英文。
编辑:根据@mikeserv 的解释,我尽可能地更改脚本,如下所示:
#!/bin/bash
PS4='DEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
trap 'clean_a $LASTNO $LINENO $BASH_COMMAND'\
SIGHUP SIGINT SIGTERM SIGQUIT
..
}
clean_a () {
error " ...
line: $LINENO $LASTNO
..."
}
Run Code Online (Sandbox Code Playgroud)
现在,如果我set -x用Ctrl+运行脚本并用+终止它C,它会打印正确的行号,如下所示:
DDEBUG: 1 : clean_a 1 336 rsync '"${options}"' ...
Run Code Online (Sandbox Code Playgroud)
但是,在clean_a()函数中,值的$LASTNO打印为 1。
line: 462 1
Run Code Online (Sandbox Code Playgroud)
它与@Arkadiusz Drabczyk 显示的错误有关吗?
EDIT2:我按照@mikesrv 推荐给我的方式更改了脚本。但是 $LASTNO 在脚本终止时返回 1 作为该行的值(它应该是 337)。
#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
SIGHUP SIGINT SIGTERM SIGQUIT
...
} 2>/dev/null
clean_a () {
error " ...
line: $LASTNO $LINENO
..."
} 2>&1
Run Code Online (Sandbox Code Playgroud)
如果我运行脚本并在 rsync 运行时用Ctrl+终止它C,我会得到以下输出:
^^MDEBUG: 1 : clean_a '337 1 rsync "${options}" --delete-during ...
...
line: 1 465
Run Code Online (Sandbox Code Playgroud)
如您所见,$LASTNO 的值为 1。
当我试图找出问题所在时,我编写了另一个函数testing——使用参数替换格式${parameter:-default}。所以脚本变成了这样:
#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
trap 'testing "$LASTNO $LINENO $BASH_COMMAND"'\
SIGHUP SIGINT SIGTERM SIGQUIT
...
} 2>/dev/null
testing() {
echo -e "${1:-Unknown error!}"
exit 1
} 2>&1
Run Code Online (Sandbox Code Playgroud)
现在,如果我运行脚本并按Ctrl+ C,我会得到以下输出:
^^MDEBUG: 1 : testing '337 1 rsync "${options}" --delete-during ...
337 1 rsync "${options}" --delete-during ...
Run Code Online (Sandbox Code Playgroud)
当我按下Ctrl+时 337 指出该行C,而 rsync 正在运行。
对于另一个测试,我尝试clear_a像这样编写函数:
clear_a () {
echo -e " $LASTNO $LINENO"
}
Run Code Online (Sandbox Code Playgroud)
并且 $LASTNO 仍然返回 1。
那么,这意味着如果我们使用参数替换,我们可以在脚本终止时获得正确的行号?
EDIT3似乎我在 EDIT2 中错误地应用了 @mikeserv 的解释。我纠正了我的错误。位置参数"$1应替换为函数中的 $LASTNO clear_a。
这是我希望它如何工作的脚本:
#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
SIGHUP SIGINT SIGTERM SIGQUIT
...
} 2>/dev/null
clean_a () {
error " ...
line: $1
..."
} 2>&1
Run Code Online (Sandbox Code Playgroud)
当脚本终止时,trap评估$LASTNO- 第一个参数 -,$LINENO- 第二个参数 - 和 - 第三个$BASH_COMMAND参数 -,然后将它们的值传递给clear_a函数。最后,我们打印 $LASTNO$1作为脚本终止的行号。
mikeserv 的解决方案很好,但他说在执行陷阱时fn传递了trap线路是不正确的$LINENO。在前面插入一行trap ...,您将看到fn实际上总是通过的1,无论在哪里声明陷阱。
PS4='DEBUG: $LINENO : ' \
bash -x <<\EOF
echo Foo
trap 'fn "$LINENO"' EXIT
fn() { printf %s\\n "$LINENO" "$1"; }
echo "$LINENO"
exit
EOF
Run Code Online (Sandbox Code Playgroud)
DEBUG: 1 : echo Foo
Foo
DEBUG: 2 : trap 'fn "$LINENO"' EXIT
DEBUG: 4 : echo 4
4
DEBUG: 5 : exit
DEBUG: 1 : fn 1
DEBUG: 3 : printf '%s\n' 3 1
3
1
Run Code Online (Sandbox Code Playgroud)
由于 trap 的第一个参数fn "$LINENO", 被放在单引号内,当且仅当EXIT 触发时,它才会$LINENO被扩展,因此应该扩展为。那为什么不呢?事实上它确实如此,直到 bash-4.0 故意更改它以便在触发陷阱时将 $LINENO 重置为 1,因此扩展为. [来源]然而,ERR 陷阱仍然保持原始行为,可能是因为使用类似的频率。fn 5fn 1trap 'echo "Error at line $LINENO"' ERR
#!/bin/bash
trap 'echo "exit at line $LINENO"' EXIT
trap 'echo "error at line $LINENO"' ERR
false
exit 0
Run Code Online (Sandbox Code Playgroud)
error at line 5
exit at line 1
Run Code Online (Sandbox Code Playgroud)
使用 GNU bash 测试,版本 4.3.42(1)-release (x86_64-pc-linux-gnu)
我认为问题是你期望"$LINENO"给你最后一个命令的执行行,这可能几乎可以工作,但clean_a() 也有它自己的$LINENO,你应该这样做:
error "something!
line: $1
...
Run Code Online (Sandbox Code Playgroud)
但即使这样也可能行不通,因为我希望它只会打印您设置trap.
这是一个小演示:
PS4='DEBUG: $LINENO : ' \
bash -x <<\CMD
trap 'fn "$LINENO"' EXIT
fn() { printf %s\\n "$LINENO" "$1"; }
echo "$LINENO"
CMD
Run Code Online (Sandbox Code Playgroud)
DEBUG: 1 : trap 'fn "$LINENO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1
DEBUG: 2 : printf '%s\n' 2 1
2
1
Run Code Online (Sandbox Code Playgroud)
因此trap,获取设置,然后fn()定义,然后echo执行。当 shell 完成执行其输入时,EXIT陷阱将运行并被fn调用。它传递一个参数 - 这是该trap行的$LINENO. fn首先打印它自己的内容$LINENO,然后打印它的第一个参数。
我可以想到一种方法,你可能会得到你期望的行为,但它有点搞砸了 shell 的stderr:
PS4='DEBUG: $((LASTNO=$LINENO)) : ' \
bash -x <<\CMD
trap 'fn "$LINENO" "$LASTNO"' EXIT
fn() { printf %s\\n "$LINENO" "$LASTNO" "$@"; }
echo "$LINENO"
CMD
Run Code Online (Sandbox Code Playgroud)
DEBUG: 1 : trap 'fn "$LINENO" "$LASTNO"' EXIT
DEBUG: 3 : echo 3
3
DEBUG: 1 : fn 1 3
DEBUG: 2 : printf '%s\n' 2 1 1 3
2
1
1
3
Run Code Online (Sandbox Code Playgroud)
它使用 shell 的$PS4调试提示符来定义$LASTNO执行的每一行。它是当前的 shell 变量,您可以在脚本中的任何位置访问它。这意味着无论当前正在访问哪一行,您都可以引用 中运行的脚本的最新行$LASTNO。当然,正如您所看到的,它带有调试输出。您可以将其推到2>/dev/null脚本执行的大部分时间,然后再2>&1执行clean_a()其他操作。
1您进入的原因$LASTNO是因为这是最后设置的值,$LASTNO因为这是最后一个$LINENO值。你已经trap在archieve_it()函数中得到了你的,所以它有自己的$LINENO,如下面的规范中所述。尽管看起来bash无论如何都没有做正确的事情,所以也可能是因为必须根据信号trap重新执行 shell ,因此被重置。在这种情况下,我对此有点模糊——显然,就是这样。INT$LINENObash
我认为你不想$LASTNO在 中进行评估clean_a()。更好的方法是在 中对其进行评估,并将接收到的trap值作为参数传递给 to 。也许是这样的:trap$LASTNOclean_a()
#!/bin/bash
PS4='^MDEBUG: $((LASTNO=$LINENO)) : '; set -x
archieve_it () {
trap 'clean_a $LASTNO $LINENO "$BASH_COMMAND"' \
SIGHUP SIGINT SIGTERM SIGQUIT
while :; do sleep 1; done
} 2>/dev/null
clean_a () { : "$@" ; } 2>&1
Run Code Online (Sandbox Code Playgroud)
尝试一下 - 我认为它应该可以满足你的要求。哦 - 请注意,其中PS4=^M是^M字面返回 - 就像 CTRL+V ENTER 一样。
在执行每个命令之前,由 shell 设置为一个十进制数字,表示脚本或函数中当前的连续行号(编号从 1 开始)。如果用户取消设置或重置
LINENO,该变量可能会失去其在 shell 生命周期中的特殊含义。如果 shell 当前未执行脚本或函数,则 的值LINENO未指定。本卷 IEEE Std 1003.1-2001 仅指定了变量对支持“用户可移植性实用程序”选项的系统的影响。