AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 751897
Accepted
Sebastian Carlos
Sebastian Carlos
Asked: 2023-07-20 00:53:52 +0800 CST2023-07-20 00:53:52 +0800 CST 2023-07-20 00:53:52 +0800 CST

如何在 Bash 脚本中清除悬念 (ctrl-z)?

  • 772

我有以下脚本:

suspense_cleanup () {
  echo "Suspense clean up..."
}

int_cleanup () {
  echo "Int clean up..."
  exit 0
}

trap 'suspense_cleanup' SIGTSTP 
trap 'int_cleanup' SIGINT

sleep 600

如果我运行它并按Ctrl-C, Int clean up...show 并退出。

但是如果我按Ctrl-Z,^Z字符会显示在屏幕上,然后挂起。

我怎么能够:

  • 在 上运行一些清理代码Ctrl-Z,甚至可能回显某些内容,并且
  • 之后继续暂停吗?

随机阅读 glibc 文档,我发现了这一点:

禁用 SUSP 字符正常解释的应用程序应该为用户提供一些其他机制来停止作业。当用户调用此机制时,程序应该向该进程的进程组发送SIGTSTP信号,而不仅仅是向进程本身发送。

但我不确定这是否适用于此,而且无论如何它似乎不起作用。

上下文:我正在尝试制作一个交互式 shell 脚本,它支持 Vim/Neovim 支持的所有与悬念相关的功能。即:

  1. 能够以编程方式挂起(使用:suspend,而不只是让用户按 Ctrl-z)
  2. 能够在挂起之前执行操作(Vim 中自动保存)
  3. 能够在恢复之前执行操作(NeoVim 中的 VimResume)

编辑:更改sleep 600为for x in {1..100}; do sleep 6; done也不起作用。

编辑2:它在替换为 时sleep 600有效sleep 600 & wait。我完全不确定它为什么或如何工作,或者这样的东西有什么局限性。

bash
  • 3 3 个回答
  • 153 Views

3 个回答

  • Voted
  1. Best Answer
    Arkadiusz Drabczyk
    2023-07-25T07:01:42+08:002023-07-25T07:01:42+08:00

    Linux 和其他类 UNIX 系统上的信号处理是一个非常复杂的主题,涉及许多参与者:内核终端驱动程序、父进程 -> 子进程关系、进程组、控制终端、启用/禁用作业控制的 shell 信号处理、信号单个进程或更多进程中的处理程序。

    首先,Control- C, Control-Z键绑定不是由 shell 处理的,而是由内核处理的。您可以使用以下命令查看默认定义stty -a:

    $ stty -a
    speed 38400 baud; rows 64; columns 212; line = 0;
    intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
    -parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
    -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel iutf8
    opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
    isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
    

    在这里我们看到intr = ^C和susp = ^Z。stty 又使用 TCGETS ioctl syscall从内核获取此信息:

    $ strace stty -a |& grep TCGETS
    ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
    

    默认的键绑定在 Linux 内核代码中定义

    #define INIT_C_CC {         \
            [VINTR] = 'C'-0x40, \
            [VQUIT] = '\\'-0x40,        \
            [VERASE] = '\177',  \
            [VKILL] = 'U'-0x40, \
            [VEOF] = 'D'-0x40,  \
            [VSTART] = 'Q'-0x40,        \
            [VSTOP] = 'S'-0x40, \
            [VSUSP] = 'Z'-0x40, \
            [VREPRINT] = 'R'-0x40,      \
            [VDISCARD] = 'O'-0x40,      \
            [VWERASE] = 'W'-0x40,       \
            [VLNEXT] = 'V'-0x40,        \
            INIT_C_CC_VDSUSP_EXTRA      \
            [VMIN] = 1 }
    

    还定义了默认操作:

    static void n_tty_receive_char_special(struct tty_struct *tty, unsigned char c,
                                           bool lookahead_done)
    {
            struct n_tty_data *ldata = tty->disc_data;
    
            if (I_IXON(tty) && n_tty_receive_char_flow_ctrl(tty, c, lookahead_done))
                    return;
    
            if (L_ISIG(tty)) {
                    if (c == INTR_CHAR(tty)) {
                            n_tty_receive_signal_char(tty, SIGINT, c);
                            return;
                    } else if (c == QUIT_CHAR(tty)) {
                            n_tty_receive_signal_char(tty, SIGQUIT, c);
                            return;
                    } else if (c == SUSP_CHAR(tty)) {
                            n_tty_receive_signal_char(tty, SIGTSTP, c);
                            return;
                    }
    

    信号最终到达 __kill_pgrp_info() ,其中显示:

    /*
     * __kill_pgrp_info() sends a signal to a process group: this is what the tty
     * control characters do (^C, ^Z etc)
     * - the caller must hold at least a readlock on tasklist_lock
     */
    

    Control这对我们的故事很重要 - 使用-C和Control-生成的信号 Z被发送到由父交互式 shell 创建的前台进程组,其领导者是新运行的脚本。该脚本及其子脚本属于一组。

    因此,正如用户Kamil Maciorowski在评论中正确指出的那样Control,当您在启动脚本后发送ZSIGTSTP 信号时,脚本都会收到信号,并且 sleep 因为当信号发送到组时,该组中的所有进程都会收到该信号。很容易看出您是否从代码中删除了陷阱,使其看起来像这样(顺便说一句,始终添加 https://en.wikipedia.org/wiki/shebang_(unix),它没有定义如果存在应该发生什么不是shebang))

    #!/usr/bin/env bash
    
    # suspense_cleanup () {
    #   echo "Suspense clean up..."
    # }
    
    # int_cleanup () {
    #   echo "Int clean up..."
    #   exit 0
    # }
    
    # trap 'suspense_cleanup' SIGTSTP
    # trap 'int_cleanup' SIGINT
    
    sleep 600
    

    运行它(我将其命名为 sigtstp.sh)并停止它:

    $ ./sigtstp.sh
    ^Z
    [1]+  Stopped                 ./sigtstp.sh
    $ ps aux | grep -e '[s]leep 600' -e '[s]igtstp.sh'
    ja       27062  0.0  0.0   6908  3144 pts/25   T    23:50   0:00 sh ./sigstop.sh
    ja       27063  0.0  0.0   2960  1664 pts/25   T    23:50   0:00 sleep 600
    

    ja是我的用户名,你的用户名会不同,PID 也会不同,但重要的是两个进程都处于停止状态,如字母“T”所示。来自man ps:

    PROCESS STATE CODES
    (...)
    T    stopped by job control signal
    

    这意味着两个进程都收到了 SIGTSTP 信号。现在,如果两个进程(包括 sigstop.sh)都收到信号,为什么 suspense_cleanup()信号处理程序不运行?Bash 直到终止才执行它sleep 600。这是POSIX强加的要求 :

    当 shell 等待实用程序执行前台命令完成时接收到已设置陷阱的信号时,与该信号关联的陷阱只有在前台命令完成后才会执行。

    (请注意,尽管在开源世界中,IT 一般标准只是提示的集合,并且没有法律要求强迫任何人遵循它们)。如果你睡得少一点,比如说 3 秒,那也是没有帮助的,因为sleep无论如何进程都会停止,所以它永远不会完成。为了suspense_cleanup()立即被调用,我们必须在后台运行它并wait按照上面的 POSIX 链接中的说明运行:

    #!/usr/bin/env bash
    
    suspense_cleanup () {
      echo "Suspense clean up..."
    }
    
    int_cleanup () {
      echo "Int clean up..."
      exit 0
    }
    
    trap 'suspense_cleanup' SIGTSTP
    trap 'int_cleanup' SIGINT
    
    sleep 600 &
    wait
    

    运行它并停止它:

    $ ./sigstop.sh
    ^ZSuspense clean up...
    

    请注意, 和sleep 600现在sigtstp.sh都消失了:

    $ ps aux | grep -e '[s]leep 600' -e '[s]igtstp.sh'
    $
    

    很明显为什么 sigtstp.sh 消失了 -wait被信号中断,它是脚本中的最后一行,因此它退出。更令人惊讶的是,当您意识到如果您发送 SIGINT,即使在 sigtstp.sh 死亡后睡眠仍然会运行:

    $ ./sigtstp.sh
    ^CInt clean up...
    $ ps aux | grep -e '[s]leep 600' -e '[s]igtstp.sh'
    ja       32354  0.0  0.0   2960  1632 pts/25   S    00:12   0:00 sleep 600
    

    但是,由于其父级死亡,它将被 init 采用:

    $ grep PPid /proc/32354/status
    PPid:   1
    

    原因是当 shell 在后台运行子进程时,它会禁用默认的 SIGINT 处理程序,该处理程序将终止其中的进程 (signal(7))]( https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02 .html ):

    如果当 shell 执行异步列表时禁用作业控制(请参阅 set -m 的说明),则列表中的命令将从 shell 继承 SIGINT 和 SIGQUIT 信号的忽略 (SIG_IGN) 信号操作。在所有其他情况下,shell 执行的命令应继承与 shell 从其父级继承的信号操作相同的信号操作,除非信号操作被 trap 特殊内置函数修改(请参阅 trap)

    一些SO参考: https://stackoverflow.com/questions/46061694/bash-why-cant-i-set-a-trap-for-sigint-in-a-background-shell/46061734#46061734,https : // stackoverflow.com/questions/45106725/why-do-shells-ignore-sigint-and-sigquit-in-backgrounded-processes。如果您想在收到 SIGINT 后杀死所有子进程,则必须在陷阱处理程序中手动执行此操作。但是请注意,SIGINT 仍然会传递给所有子级,但只是被忽略 - 如果您没有使用 sleep,而是安装了自己的 SIGINT 处理程序的命令(例如,尝试 tcpdump)!Glibc手册 说:

    请注意,如果先前将给定信号设置为忽略,则此代码会避免更改该设置。这是因为非作业控制 shell 在启动子进程时通常会忽略某些信号,因此子进程尊重这一点非常重要。

    但是,如果我们自己不杀死它,那么为什么在向它发送 SIGTSTP 后 sleep 就死掉了,而 SIGTSTP 应该只阻止它,而不是杀死它呢?属于孤立进程组的所有已停止进程都会从内核收到 SIGHUP:

    如果进程的退出导致进程组成为孤立进程组,并且新孤立进程组的任何成员停止,则应向新孤立进程组中的每个进程发送一个 SIGHUP 信号,后跟一个 SIGCONT 信号。

    如果没有安装任何自定义处理程序,SIGHUP 将终止该进程(signal(7)):

    Signal      Standard   Action   Comment
    SIGHUP       P1990      Term    Hangup detected on controlling terminal
                                    or death of controlling process
    

    (请注意,如果您在 strace 下运行 sleep ,事情会变得更加复杂......)。

    好的,那么回到你原来的问题怎么样:

    我怎么能够:

    按 Ctrl-Z 运行一些清理代码,甚至可能回显某些内容,然后继续暂停?

    我会这样做的方式是:

    #!/usr/bin/env bash
    
    suspense_cleanup () {
        echo "Suspense clean up..."
        trap - SIGTSTP
        kill -TSTP $$
        trap 'suspense_cleanup' SIGTSTP
    }
    
    int_cleanup () {
        echo "Int clean up..."
        exit 0
    }
    
    trap 'suspense_cleanup' SIGTSTP
    trap 'int_cleanup' SIGINT
    sleep 600 &
    while true
    do
        if wait
        then
            echo child died, exiting
            exit 0
        fi
    done
    

    Nowsuspense_cleanup()将在停止进程之前调用:

    $ ./sigtstp.sh
    ^ZSuspense clean up...
    
    [1]+  Stopped                 ./sigtstp.sh
    $ ps aux | grep -e '[s]leep 600' -e '[s]igtstp.sh'
    ja        4129  0.0  0.0   6920  3196 pts/25   T    00:29   0:00 bash ./sigtstp.sh
    ja        4130  0.0  0.0   2960  1660 pts/25   T    00:29   0:00 sleep 600
    $ fg
    ./sigtstp.sh
    ^ZSuspense clean up...
    
    [1]+  Stopped                 ./sigtstp.sh
    $  fg
    ./sigtstp.sh
    ^CInt clean up...
    $ ps aux | grep -e '[s]leep 600' -e '[s]igtstp.sh'
    ja        4130  0.0  0.0   2960  1660 pts/25   S    00:29   0:00 sleep 600
    $ grep PPid /proc/4130/status
    PPid:   1
    

    你可以少睡一点,比如说 10 秒,然后看看如果睡眠完成,脚本就会退出:

    #!/usr/bin/env bash
    
    suspense_cleanup () {
        echo "Suspense clean up..."
        trap - SIGTSTP
        kill -TSTP $$
        trap 'suspense_cleanup' SIGTSTP
    }
    
    int_cleanup () {
        echo "Int clean up..."
        exit 0
    }
    
    trap 'suspense_cleanup' SIGTSTP
    trap 'int_cleanup' SIGINT
    # sleep 600 &
    sleep 10 &
    while true
    do
        if wait
        then
            echo child died, exiting
            exit 0
        fi
    done
    

    运行:

    $ time ./sigtstp.sh
    child died, exiting
    
    real    0m10.007s
    user    0m0.003s
    sys     0m0.004s
    
    • 5
  2. Paul_Pedant
    2023-07-20T06:43:26+08:002023-07-20T06:43:26+08:00

    看来 Bash 作业控制会干扰正常的 tty 特殊字符。SIGTSTP 可能会发送到 Bash,但不会发送到正在运行的进程。

    来自https://www.gnu.org/software/bash/manual/bash.html#Job-Control-Basics:

    如果运行 Bash 的操作系统支持作业控制,则 Bash 包含使用它的工具。在进程运行时键入挂起字符(通常为“^Z”、Control-Z)会导致该进程停止并将控制权返回给 Bash。

    继续(Ctrl-Q,start在 中调用stty)在 Bash 作业控制下不起作用:您需要fg或bg进程来取消停止它。

    • -1
  3. waltinator
    2023-07-20T01:51:36+08:002023-07-20T01:51:36+08:00

    在阅读man 7 signal, 或 时 https://www.man7.org/linux/man-pages/man7/signal.7.html,人们会看到:

    信号 SIGKILL 和 SIGSTOP 无法被捕获、阻止或忽略

    所以,你不能。

    • -2

相关问题

  • 通过命令的标准输出以编程方式导出环境变量[重复]

  • 从文本文件传递变量的奇怪问题

  • 虽然行读取保持转义空间?

  • `tee` 和 `bash` 进程替换顺序

  • 运行一个非常慢的脚本直到它成功

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve