Bash intenta escribir dos indicaciones de shell?

11

Estoy mirando la salida de un proceso bash en ejecución conectado a una terminal, con fines educativos.

Mi proceso de bash tiene PID 2883.

yo tecleo

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

En una terminal. Luego entro en mi proceso de bash y tengo la siguiente interacción:

[OP@localhost ~]$ ls

Mirando la salida, veo

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

Estoy confundido en las dos últimas líneas. Parece que bash está intentando escribir dos indicaciones de shell? ¿Que está pasando aqui?

extremeaxe5
fuente

Respuestas:

24

La <ESC>]0;secuencia (que se muestra como \33]0;strace) es la secuencia de escape para establecer el título de la ventana del terminal. Se termina con el carácter BEL ( \7), por lo que el primero writeestablece el título de la ventana. El segundo imprime el mensaje real. Tenga en cuenta que incluso aparte de la secuencia de escape, no son exactamente lo mismo. El indicador tiene un entorno, [..]mientras que el título de la ventana no.

También podemos ver que la primera escritura va a stdout (fd 1, el primer argumento para write()), y la segunda a stderr. Bash imprime la solicitud en stderr, por lo que la primera escritura proviene de otro lugar. Probablemente en algún lugar PROMPT_COMMAND, como el de los scripts de inicio predeterminados de Debian para Bash. Hay algo como esto allí:

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

Establece eso PROMPT_COMMANDsi se está ejecutando xtermo rxvt, lo que debería soportar esa secuencia de escape.

ilkkachu
fuente
¿Sabes por qué bash parece leer las cosas carácter por carácter, en lugar de leer en una línea a la vez? Además, ¿por qué bash escribe "l" y "s" en stdout? Si hago una secuencia similar con cat, hay dos diferencias: lee la entrada línea por línea, y mientras hace eco de su entrada de nuevo a stdout, veo la entrada dos veces (una cuando escribo y una vez cuando cat hace eco).
extremeaxe5
@ extremeaxe5, eso es básicamente porque Bash (o más bien la biblioteca de línea de lectura) maneja todo el procesamiento de la línea de comando en sí mismo, en lugar de confiar en el procesamiento bastante limitado realizado por el terminal. Tiene que obtener la entrada de inmediato para decidir qué hacer cuando, por ejemplo, ^Ase presiona un carácter TAB o (Ctrl-A) o los diversos caracteres especiales. Además, apaga el eco del terminal, para que pueda decidir qué salida para cada carácter de entrada en particular (de nuevo, TAB no suele generar una TAB.) catNo hace nada de eso. Si lo hace, intente ejecutar dash, lo que no hace ningún manejo de línea de comando.
ilkkachu
En realidad, la razón por la que Bash llama read()para leer solo un byte a la vez es que no puede leer más allá de una nueva línea. La nueva línea puede hacer que ejecute un programa externo, que también puede leer desde la misma entrada. (Y que el programa debe ser capaz de leer cualquier carácter después de la nueva línea.) Si no tenía que preocuparse por eso, podría llamar read()con un límite más grande, y con el terminal en modo raw, seguiría siendo por lo general obtener la entrada un personaje a la vez (
Depende de
Su segundo comentario parece ser cierto solo porque Bash sí mismo maneja la línea de comandos.
extremeaxe5
@ extremeaxe5, bueno, sí, estaba asumiendo eso, ya que es el caso común de todos modos. Pero, incluso si el shell dependía de la edición de línea del terminal, el tiempo podría ser un problema. Si se enviaron dos líneas en rápida sucesión (piense en pegar datos), y el sistema se cargó lo suficiente para que el shell no se programara de inmediato (o peor, el shell se detuvo), entonces un read()búfer más grande aún podría devolver ambas líneas en la misma llamada Creo que no hay una garantía de que read()sería siempre volver una sola línea en el modo de cocinado.
ilkkachu