我有一个像这样的脚本(/bin/sh
在 OpenBSD 上编写),它首先使用 更新一些 CVS 存储库的本地副本rsync
,然后更新我的计算机上这些的签出版本。该脚本通过 OpenBSD 以 root 身份运行doas
(sudo
OpenBSD 上的“等效项”):
#!/bin/sh
int_handler () {
echo 'Wait...' >&2
quit=1
}
quit=0
trap int_handler INT
for r in CVSROOT src xenocara ports; do
su cvsuser -c 'rsync --archive --itemize-changes --delete --omit-dir-times \
"rsync://anoncvs.eu.openbsd.org/OpenBSD-cvs/$r/" \
"/extra/cvs/$r/"'
done
trap - INT
[ "$quit" -eq 1 ] && exit
# rest of script updates checked-out CVS repositories from local copy
我的想法是,我可以在循环运行Ctrl+C时按rsync
,以便稍后跳过脚本的第二部分(我可能并不总是想运行,具体取决于存储库中更新的内容),而不中断循环及其su
进程rsync
。
这不起作用,rsync
进程接收信号并终止(并且for
循环的下一次迭代继续)。 rsync
说(当Ctrl+C多次按下直到脚本终止时):
^Crsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(642) [generator=3.1.3]
rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at io.c(504) [receiver=3.1.3]
Wait...
rsync: [receiver] write error: Broken pipe (32)
rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at io.c(1633) [sender=3.1.3]
^Crsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(642) [generator=3.1.3]rsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at io.c(504) [receiver=3.1.3
]
rsync: [receiver] write error: Broken pipe (32)
Wait...
^Crsync error: received SIGINT, SIGTERM, or SIGHUP (code 20) at rsync.c(642) [generator=3.1.3]
rsync error: received SIGUSR1 (code 19) at main.c(1440) [receiver=3.1.3]
Wait...
根据问题的回答“如何让 ctrl+c /not/ 中断 while 循环?“,至少在跑步时bash
,一个人应该能够忽略信号INT
,这将使子进程也忽略该信号。这并不理想,但我很乐意尝试。
因此,鉴于此,第二次尝试:
#!/usr/local/bin/bash
trap '' INT
for r in CVSROOT src xenocara ports; do
su cvsuser -c 'rsync --archive --itemize-changes --delete --omit-dir-times \
"rsync://anoncvs.eu.openbsd.org/OpenBSD-cvs/$r/" \
"/extra/cvs/$r/"'
done
trap - INT
read -p 'Press enter or interrupt...' junk
# rest of script updates checked-out CVS repositories from local copy
这同样允许rsync
进程被 中断Ctrl+C。
好吧,也许是su
重置信号掩码的原因?第三次尝试:
#!/usr/local/bin/bash
trap '' INT
for r in CVSROOT src xenocara ports; do
su -s /usr/local/bin/bash cvsuser -c \
'trap "" INT; rsync --archive --itemize-changes --delete --omit-dir-times \
"rsync://anoncvs.eu.openbsd.org/OpenBSD-cvs/$r/" \
"/extra/cvs/$r/"'
done
trap - INT
read -p 'Press enter or interrupt...' junk
# rest of script updates checked-out CVS repositories from local copy
不,没有快乐。过程rsync
是仍然以同样的方式可中断。
有没有办法让脚本按照我(最初)预期的方式工作?
bash
我的机器上的版本是 4.4.23。这/bin/sh
是pdksh
在 POSIX 模式下运行的 OpenBSD 的本机变体。
答案1
那么,既然您并不真正想要中断,那么如何在终端级别禁用它呢stty intr ""
?这将使 出现^C
在输入缓冲区中作为任何普通字符,我们可以从那里读取它。
这对我在 Linux 上有效:
#!/bin/bash
t=$(stty -g)
stty intr ""
echo please hold
sleep 5
stty "$t"
# short timeout, just check if there was any input earlier
read -n1 -t0.1 a
[[ "$a" = $'\003' ]] && echo "you hit ^C"
(我无法在 OpenBSD 上进行测试,但我认为终端设置内容是标准的。)
答案2
rsync
即使 SIGINT 在启动时被忽略,也会在 SIGINT 上安装处理程序。因此,如果您不想^C
停止 rsync(我自己也讨厌这样,因为我讨厌紧急按钮不起作用),则必须确保 SIGINT 不会传递到rsync
.
因此,要么告诉终端驱动程序不要向前台进程组发送 SIGINT,^C
如@ikkachu 的回答,或将运行该命令的进程rsync
置于前台进程组之外。
与与信号处理相关的所有内容一样,其是否有效因 shell 不同(有时还因版本不同而异)。
您最好的选择是坚持使用您知道有效的已知实现,并希望几年后它仍然可以在新版本中工作。就像zsh
:
#! /usr/bin/env zsh
# enable job control (run pipelines in different process groups)
set -o monitor
interrupted() false
trap 'echo Wait...; interrupted() true' INT
# run in background so it won't get the SIGINT upon ^C
# as it won't be in the foreground process group. Note that with
# version 5.1.1 at least, running it without & will also run in
# in a new process group, and not make it the foreground process
# group of the terminal as the shell is not interactive. However
# the trap would be run asynchronously.
rsync... &
while (($#jobstates)) {wait}
interrupted && exit
echo rest of the script
答案3
在 Linux 上,您可以使用该命令setsid
在单独的会话中运行 rsync。尽管存在系统调用,但 OpenBSD 似乎没有将此作为命令。您可以编写自己的 C 版本的命令,或者尝试一下 perl(如果有的话):
#!/usr/bin/perl
use POSIX qw(setsid);
POSIX::setsid();
exec @ARGV;
如果您命名该脚本,mysetsid
则可以将其用作命令的前缀su
,例如:./mysetsid su cvsuser ...
在脚本的第一个版本中。请注意,只有当前 rsync 命令完成后,处理程序函数才会看到中断。
答案4
bash 实现
从实验中可以清楚地看出,它的rsync
行为与其他工具(例如ping
dodx)一样,不会从调用 Bash 父级的信号继承。
因此,您必须对此发挥一点创意,并执行以下操作:
$ cat rsync.bash
#!/bin/sh
set -m
trap '' SIGINT SIGTERM EXIT
rsync -avz LargeTestFile.500M [email protected]:/tmp/. &
wait
echo FIN
现在当我运行它时:
$ ./rsync.bash
X11 forwarding request failed
building file list ... done
LargeTestFile.500M
^C^C^C^C^C^C^C^C^C^C
sent 509984 bytes received 42 bytes 92732.00 bytes/sec
total size is 524288000 speedup is 1027.96
FIN
我们可以看到文件已完全传输:
$ ll -h | grep Large
-rw-------. 1 501 games 500M Jul 9 21:44 LargeTestFile.500M
怎么运行的
这里的技巧是我们告诉 Bash viaset -m
禁用其中任何后台作业的作业控制。然后,我们将后台rsync
运行一个wait
命令,该命令将等待最后一个运行命令,rsync
直到完成。
然后我们用trap '' SIGINT SIGTERM EXIT
.