AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • Início
  • system&network
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • Início
  • system&network
    • Recentes
    • Highest score
    • tags
  • Ubuntu
    • Recentes
    • Highest score
    • tags
  • Unix
    • Recentes
    • tags
  • DBA
    • Recentes
    • tags
  • Computer
    • Recentes
    • tags
  • Coding
    • Recentes
    • tags
Início / unix / Perguntas / 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

Como limpar o suspense (ctrl-z) em um script Bash?

  • 772

Eu tenho o seguinte roteiro:

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

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

trap 'suspense_cleanup' SIGTSTP 
trap 'int_cleanup' SIGINT

sleep 600

Se eu executá-lo e pressionar Ctrl-C, Int clean up...show e ele sai.

Mas se eu apertar Ctrl-Z, os ^Zcaracteres são exibidos na tela e depois trava.

Como posso:

  • Execute algum código de limpeza em Ctrl-Z, talvez até ecoando algo, e
  • prosseguir com a suspensão depois?

Lendo aleatoriamente a documentação do glibc, encontrei isto:

Os aplicativos que desativam a interpretação normal do caractere SUSP devem fornecer algum outro mecanismo para o usuário interromper o trabalho. Quando o usuário invoca esse mecanismo, o programa deve enviar um sinal SIGTSTP para o grupo de processos do processo, não apenas para o próprio processo.

Mas não tenho certeza se isso se aplica aqui e, de qualquer forma, não parece funcionar.

Contexto: Estou tentando fazer um shell script interativo que suporte todos os recursos relacionados ao suspense que o Vim/Neovim suporta. Nomeadamente:

  1. Capacidade de suspender programaticamente (com :suspend, em vez de apenas permitir que o usuário pressione Ctrl-z)
  2. Capacidade de executar uma ação antes de suspender (salvar automaticamente no Vim)
  3. Capacidade de executar uma ação antes de retomar (VimResume no NeoVim)

Edit: Mudar sleep 600para for x in {1..100}; do sleep 6; donetambém não funciona.

Editar 2: funciona ao substituir sleep 600por sleep 600 & wait. Não tenho certeza de por que ou como isso funciona, ou quais são as limitações de algo assim.

bash
  • 3 3 respostas
  • 153 Views

3 respostas

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

    A manipulação de sinais no Linux e outros sistemas semelhantes ao UNIX é um assunto muito complexo com muitos atores em jogo: driver de terminal do kernel, relação de processo pai -> filho, grupos de processos, terminal de controle, manipulação de shell de sinais com controle de trabalho ativado/desativado, sinal manipuladores em processos individuais e possivelmente mais.

    Primeiro, as combinações de teclas Control- C, Control- Znão são manipuladas pelo shell, mas pelo kernel. Você pode ver as definições padrão com 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
    

    Aqui vemos intr = ^Ce susp = ^Z. stty, por sua vez, obtém essas informações do kernel usando TCGETS ioctl syscall :

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

    As combinações de teclas padrão são definidas no código do kernel do Linux em

    #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 }
    

    As ações padrão também são definidas:

    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;
                    }
    

    O sinal finalmente vai para __kill_pgrp_info() que diz:

    /*
     * __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
     */
    

    Isso é importante para nossa história - o sinal gerado com Control- Ce Control- Zé enviado para o grupo de processos de primeiro plano criado pelo shell interativo pai cujo líder é um script recém-executado. O script e seus filhos pertencem a um grupo.

    Portanto, conforme observado corretamente nos comentários do usuário Kamil Maciorowski , quando você envia Control- Zapós iniciar seu script, o sinal SIGTSTP é recebido pelo script e sleep porque quando um sinal é enviado para um grupo, ele é recebido por todos os processos do grupo. Seria fácil ver se você removeu as armadilhas do seu código para que ficasse assim (BTW, sempre adicione um https://en.wikipedia.org/wiki/shebang_(unix) , não está definido o que deve acontecer se houver não é 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
    

    Execute-o (eu o chamei de sigtstp.sh) e pare:

    $ ./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é o meu nome de usuário, o seu será diferente, os PIDs também serão diferentes, mas o que importa é que ambos os processos estão parados, conforme indicado pela letra 'T'. De man ps:

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

    Isso significa que ambos os processos obtiveram o sinal SIGTSTP. Agora, se ambos os processos, incluindo sigstop.sh, obtêm sinal, por que suspense_cleanup()o manipulador de sinal não é executado? O Bash não o executa até sleep 600terminar. É um requisito imposto pelo POSIX :

    Quando um sinal para o qual um trap foi definido é recebido enquanto o shell está aguardando a conclusão de um utilitário executando um comando de foreground, o trap associado a esse sinal não deve ser executado até que o comando de foreground seja concluído.

    (observe que no mundo do código aberto e no padrão geral de TI é apenas uma coleção de dicas e não há exigência legal para forçar ninguém a segui-las). Não ajudaria se você dormisse menos, digamos 3 segundos, porque sleepo processo seria interrompido de qualquer maneira, para que nunca fosse concluído. Para suspense_cleanup()ser chamado imediatamente, temos que executá-lo em segundo plano e executar waitconforme também explicado no link POSIX acima:

    #!/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
    

    Execute-o e pare-o:

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

    Observe que ambos sleep 600e sigtstp.shagora se foram:

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

    Está claro por que sigtstp.sh se foi - waitfoi interrompido pelo sinal, é a última linha do script, então ele sai. É ainda mais surpreendente quando você percebe que, se enviar SIGINT, o sono ainda será executado mesmo após a morte de 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
    

    Mas, devido à morte de seu pai, ele seria adotado pelo init:

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

    A razão para isso é quando o shell executa um filho em segundo plano, ele desativa o manipulador SIGINT padrão, que deve encerrar o processo (signal(7)) nele]( https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02 .html ):

    Se o controle de tarefas estiver desativado (consulte a descrição de set -m) quando o shell executar uma lista assíncrona, os comandos na lista devem herdar do shell uma ação de sinal ignorada (SIG_IGN) para os sinais SIGINT e SIGQUIT. Em todos os outros casos, os comandos executados pelo shell devem herdar as mesmas ações de sinal herdadas pelo shell de seu pai, a menos que uma ação de sinal seja modificada pelo trap especial integrado (consulte trap)

    Algumas referências 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 . Se você quiser matar todas as crianças após receber o SIGINT, deverá fazê-lo manualmente no manipulador de armadilhas. Observe, no entanto, que SIGINT ainda é entregue a todos os filhos, mas apenas ignorado - se você não usou sleep, mas um comando que instala seu próprio manipulador SIGINT se fosse executado (tente tcpdump, por exemplo)! O manual da Glibc diz:

    Observe que, se um determinado sinal foi definido anteriormente para ser ignorado, esse código evita alterar essa configuração. Isso ocorre porque os shells que não são de controle de trabalho geralmente ignoram certos sinais ao iniciar os filhos, e é importante que os filhos respeitem isso.

    Mas por que o sono está morto depois de enviar o SIGTSTP para ele se não o matamos nós mesmos e o SIGTSTP deve apenas pará-lo, não matá-lo? Todos os processos parados pertencentes ao grupo de processos órfãos obtêm SIGHUP do kernel :

    Se a saída do processo fizer com que um grupo de processos fique órfão e se qualquer membro do grupo de processos recém-órfão for interrompido, um sinal SIGHUP seguido por um sinal SIGCONT deve ser enviado para cada processo no grupo de processos recém-órfão. .

    SIGHUP encerra o processo se nenhum manipulador personalizado para ele foi instalado (signal(7)):

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

    (observe que se você rodar sleep sob strace as coisas ficariam ainda mais complexas...).

    OK, então que tal voltar à sua pergunta original:

    Como posso:

    Executar algum código de limpeza em Ctrl-Z, talvez até ecoando algo, e prosseguir com a suspensão depois?

    A forma que eu faria é:

    #!/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
    

    Now suspense_cleanup()será chamado antes de parar o processo:

    $ ./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
    

    E você pode dormir menos, digamos 10 segundos e ver que o script sairia se o sono terminasse:

    #!/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
    

    Executá-lo:

    $ 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

    Parece que o controle do trabalho Bash interfere nos caracteres especiais tty normais. O SIGTSTP pode ser enviado para o Bash, mas não para o processo que está sendo executado.

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

    Se o sistema operacional no qual o Bash está sendo executado oferece suporte ao controle de tarefas, o Bash contém recursos para usá-lo. Digitar o caractere de suspensão (normalmente '^Z', Control-Z) enquanto um processo está em execução faz com que esse processo seja interrompido e devolve o controle ao Bash.

    Continuar (Ctrl-Q, chamado startem stty) não funciona no controle de trabalho Bash: você precisa fgou bgo processo para destravá-lo.

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

    Ao ler man 7 signal, ou https://www.man7.org/linux/man-pages/man7/signal.7.html, percebe-se que:

    Os sinais SIGKILL e SIGSTOP não podem ser capturados, bloqueados ou ignorados

    Então, você não pode.

    • -2

relate perguntas

  • exportar variáveis ​​​​env programaticamente, via stdout do comando [duplicado]

  • Problema estranho ao passar variáveis ​​do arquivo de texto

  • Enquanto a linha lê mantendo os espaços de escape?

  • ordem de substituição de processos `te` e `bash`

  • Execute um script muito lento até que seja bem-sucedido

Sidebar

Stats

  • Perguntas 205573
  • respostas 270741
  • best respostas 135370
  • utilizador 68524
  • Highest score
  • respostas
  • Marko Smith

    Possível firmware ausente /lib/firmware/i915/* para o módulo i915

    • 3 respostas
  • Marko Smith

    Falha ao buscar o repositório de backports jessie

    • 4 respostas
  • Marko Smith

    Como exportar uma chave privada GPG e uma chave pública para um arquivo

    • 4 respostas
  • Marko Smith

    Como podemos executar um comando armazenado em uma variável?

    • 5 respostas
  • Marko Smith

    Como configurar o systemd-resolved e o systemd-networkd para usar o servidor DNS local para resolver domínios locais e o servidor DNS remoto para domínios remotos?

    • 3 respostas
  • Marko Smith

    apt-get update error no Kali Linux após a atualização do dist [duplicado]

    • 2 respostas
  • Marko Smith

    Como ver as últimas linhas x do log de serviço systemctl

    • 5 respostas
  • Marko Smith

    Nano - pule para o final do arquivo

    • 8 respostas
  • Marko Smith

    erro grub: você precisa carregar o kernel primeiro

    • 4 respostas
  • Marko Smith

    Como baixar o pacote não instalá-lo com o comando apt-get?

    • 7 respostas
  • Martin Hope
    user12345 Falha ao buscar o repositório de backports jessie 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl Por que a maioria dos exemplos do systemd contém WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky Como exportar uma chave privada GPG e uma chave pública para um arquivo 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll status systemctl mostra: "Estado: degradado" 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim Como podemos executar um comando armazenado em uma variável? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S Por que /dev/null é um arquivo? Por que sua função não é implementada como um programa simples? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 Como ver as últimas linhas x do log de serviço systemctl 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - pule para o final do arquivo 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla Por que verdadeiro e falso são tão grandes? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis Substitua a string em um arquivo de texto enorme (70 GB), uma linha 2017-12-30 06:58:33 +0800 CST

Hot tag

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

Explore

  • Início
  • Perguntas
    • Recentes
    • Highest score
  • tag
  • help

Footer

AskOverflow.Dev

About Us

  • About Us
  • Contact Us

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve