Bash 스크립트에서 서스펜스(ctrl-z)를 지우는 방법은 무엇입니까?

Bash 스크립트에서 서스펜스(ctrl-z)를 지우는 방법은 무엇입니까?

다음 스크립트가 있습니다.

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...종료됩니다.

Ctrl-Z그런데 을 누르면 ^Z문자가 화면에 나타났다가 멈춥니다.

내가 어떻게 할 수있는:

  • 거기에 정리 코드를 실행하고 Ctrl-Z, 어쩌면 무언가를 에코할 수도 있습니다.
  • 그 후에도 계속 일시중지하시겠습니까?

glibc 문서를 무작위로 읽다가 다음을 발견했습니다.

SUSP 문자의 일반적인 해석을 비활성화하는 응용 프로그램은 사용자에게 작업을 중지할 수 있는 다른 메커니즘을 제공해야 합니다. 사용자가 이 메커니즘을 호출하면 프로그램은 프로세스 자체뿐만 아니라 프로세스의 프로세스 그룹에 SIGTSTP 신호를 보내야 합니다.

그러나 이것이 여기에 적용되는지 확실하지 않으며 어쨌든 작동하지 않는 것 같습니다.

문맥:Vim/Neovim에서 지원하는 모든 서스펜스 관련 기능을 지원하는 대화형 셸 스크립트를 만들려고 합니다. 지금 바로:

  1. 프로그래밍 방식으로 일시 중단하는 기능( :suspend사용자가 Ctrl-z를 누르는 대신 Ctrl-z 사용)
  2. 일시 중단되기 전에 작업을 수행하는 기능(Vim에 자동 저장)
  3. 재개하기 전에 작업을 수행하는 기능(NeoVim의 VimResume)

편집하다:sleep 600로 변경해 도 for x in {1..100}; do sleep 6; done작동하지 않습니다.

편집 2:sleep 600로 교체하면 작동합니다 sleep 600 & wait. 나는 그것이 왜, 어떻게 작동하는지, 또는 이와 같은 것의 한계가 무엇인지 전혀 확신하지 못합니다.

답변1

Linux 및 기타 UNIX 계열 시스템에서의 신호 처리는 커널 터미널 드라이버, 상위->하위 관계, 프로세스 그룹, 터미널 제어, 작업 제어를 위한 셸 신호 처리 활성화/비활성화, 각 프로세스의 신호 처리기 등 많은 플레이어가 관련된 매우 복잡한 주제입니다. , 등.

첫째, Control- C, Control-key 바인딩은 Z셸이 아니라 커널에 의해 처리됩니다. 다음 명령을 사용하여 기본 정의를 볼 수 있습니다 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을 사용하여 커널에서 이 정보를 가져옵니다. 시스템 호출:

$ 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-and-를 사용하여 Control생성된 신호가 Z전경으로 전송됩니다.프로세스 그룹새로 실행되는 스크립트가 리더인 상위 대화형 셸에 의해 생성됩니다. 스크립트와 그어린이들그룹에 속해 있습니다.

따라서 사용자 의견에서 올바르게 지적했듯이카밀 마코로프스키, 보낼 때 Control- Z스크립트를 시작한 후 스크립트가 SIGTSTP 신호를 수신합니다.그리고 sleep 신호가 그룹에 전송되면 그룹의 모든 프로세스가 신호를 수신하기 때문입니다. 코드에서 트랩을 제거하면 다음과 같이 표시되는지 쉽게 확인할 수 있습니다(그런데 항상 https://en.wikipedia.org/wiki/shebang_(유닉스), 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:

쉘이 유틸리티의 포그라운드 명령 실행이 완료되기를 기다리는 동안 트랩 세트 신호가 수신되면 해당 신호와 연관된 트랩은 포그라운드 명령이 완료될 때까지 실행되지 않습니다.

(오픈 소스 세계에서 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

그 이유는 쉘이 백그라운드에서 하위 프로세스를 실행할 때 기본 SIGINT 핸들러를 비활성화하여 그 안에 있는 프로세스를 종료하기 때문입니다(signal(7))](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html):

쉘이 비동기 목록을 실행할 때 작업 제어가 비활성화된 경우(set -m 설명 참조) 목록의 명령은 쉘의 SIGINT 및 SIGQUIT 신호에 대한 무시(SIG_IGN) 신호 작업을 상속합니다. 다른 모든 경우에, 쉘에 의해 실행되는 명령은 트랩 특수 내장 함수에 의해 신호 작동이 수정되지 않는 한 부모로부터 쉘에 의해 상속된 것과 동일한 신호 작동을 상속해야 합니다(트랩 참조).

일부 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 매뉴얼 설명하다:

주어진 신호가 이전에 무시하도록 설정된 경우 이 코드는 해당 설정을 변경하지 않습니다. 이는 비작업 제어 쉘이 하위 프로세스를 시작할 때 특정 신호를 무시하는 경우가 많기 때문에 하위 프로세스가 이를 존중하는 것이 중요합니다.

하지만 우리가 스스로 그것을 죽이지 않는다면 왜 sleep을 죽이지 않고 차단만 해야 하는 SIGTSTP를 보낸 후에 sleep이 죽는 걸까요? 고아 프로세스 그룹에 속하는 모든 중지된 프로세스커널에서 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

이제 suspense_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

답변2

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

답변3

man 7 signal, 또는 를 읽으면 https://www.man7.org/linux/man-pages/man7/signal.7.html다음이 표시됩니다.

SIGKILL 및 SIGSTOP 신호는 포착, 차단 또는 무시될 수 없습니다.

그래서, 당신은 할 수 없습니다.

관련 정보