Bash tenta escrever dois prompts de shell?

11

Eu estou olhando para a saída strace de um processo bash em execução conectado a um terminal, para fins educacionais.

Meu processo de bash tem 2883 PID.

Eu digito

[OP@localhost ~]$ strace -e trace=openat,read,write,fork,vfork,clone,execve -p 2883 2> bash.strace

Em um terminal. Entro no meu processo do bash e tenho a seguinte interação:

[OP@localhost ~]$ ls

Olhando para a saída, vejo

strace: Process 2883 attached
read(0, "l", 1)                         = 1
write(2, "l", 1)                        = 1
read(0, "s", 1)                         = 1
write(2, "s", 1)                        = 1
read(0, "\r", 1)                        = 1
write(2, "\n", 1)                       = 1
clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7fec6b1d8e50) = 3917
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=3917, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
write(1, "\33]0;OP@localhost:~\7", 23) = 23
write(2, "[OP@localhost ~]$ ", 22)  = 22
...

Estou confuso nas duas últimas linhas. Parece que o bash está tentando escrever dois prompts de shell? O que está acontecendo aqui?

extremeaxe5
fonte

Respostas:

24

A <ESC>]0;sequência (mostrada como \33]0;strace) é a sequência de escape para definir o título da janela do terminal. É finalizado com o caractere BEL ( \7), então o primeiro writedefine o título da janela. O segundo imprime o prompt real. Observe que, além da sequência de escape, eles não são exatamente iguais. O prompt tem um ambiente, [..]enquanto o título da janela não.

Também podemos ver que a primeira gravação vai para stdout (fd 1, o primeiro argumento para write()) e a segunda para stderr. O Bash imprime o prompt para stderr, para que a primeira gravação venha de outro lugar. Provavelmente PROMPT_COMMAND, isso está em algum lugar , como o dos scripts de inicialização padrão do Debian para o Bash. Há algo assim aqui:

case "$TERM" in
xterm*|rxvt*)
    PROMPT_COMMAND='echo -ne "\033]0;${USER}@${HOSTNAME}: ${PWD}\007"'
    ;;
*)
    ;;
esac

Ele define que, PROMPT_COMMANDse estiver executando xtermou rxvt, o que deve suportar essa sequência de escape.

ilkkachu
fonte
Você sabe por que o bash parece ler coisas caractere por caractere, em vez de ler em uma linha de cada vez? Além disso, por que o bash grava "l" e "s" no stdout? Se eu fizer um rastreio semelhante cat, há duas diferenças: ele lê a entrada linha por linha e, embora faça eco de sua entrada de volta para stdout, vejo a entrada duas vezes (uma vez quando digito e uma vez quando gato faz eco).
extremeaxe5
@ extremeaxe5, isso é basicamente porque o Bash (ou melhor, a biblioteca readline) lida com todo o processamento da linha de comando, em vez de confiar no processamento bastante limitado feito pelo terminal. Ele deve receber a entrada imediatamente para decidir o que fazer quando, por exemplo, um caractere TAB ou ^A(Ctrl-A) ou os vários caracteres especiais forem pressionados. Além disso, desativa o eco do terminal, para que ele possa decidir o que produzir para cada caractere de entrada específico (novamente, o TAB normalmente não gera um TAB.) catNão faz nada disso. Se você, tente executar dash, o que não faz nenhum tratamento de linha de comando.
ilkkachu
Na verdade, o motivo pelo qual o Bash chama read()apenas para ler um byte de cada vez é que ele não consegue ler além de uma nova linha. A nova linha pode fazer com que execute um programa externo, que também pode ler da mesma entrada. (E esse programa deve ser capaz de ler todos os caracteres após a nova linha.) Se ele não precisa se preocupar com isso, ele poderia chamar read()com um limite maior, e com o terminal no modo raw, ainda seria normalmente obter a entrada um caractere de cada vez. (Seria até quão rápido os caracteres de entrada chegaria e como o processo foi agendada.)
ilkkachu
Seu segundo comentário parece verdadeiro apenas porque o Bash faz o tratamento de linha de comando.
extremeaxe5
@ extremeaxe5, bem, sim, eu estava assumindo isso, já que é o caso comum de qualquer maneira. Mas, mesmo que o shell dependesse da edição da linha do terminal, o tempo ainda poderia ser um problema. Se duas linhas foram enviadas em rápida sucessão (pense em colar dados) e o sistema foi carregado o suficiente para que o shell não fosse agendado imediatamente (ou pior, o shell foi interrompido), um read()buffer com maior ainda poderia retornar as duas linhas em a mesma ligação. Eu não acho que há uma garantia de que read()seria sempre retornar apenas uma linha no modo cozidos.
Ilkkachu