Bash tenta di scrivere due prompt della shell?


11

Sto guardando l'output di strace di un processo bash in esecuzione collegato a un terminale, per scopi educativi.

Il mio processo bash ha PID 2883.

io digito

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

In un terminale. Vado quindi nel mio processo bash e ho la seguente interazione:

[OP@localhost ~]$ ls

Guardando l'output, vedo

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

Sono confuso nelle ultime due righe. Sembra che bash stia tentando di scrivere due prompt della shell? Cosa sta succedendo qui?

Risposte:


24

La <ESC>]0;sequenza (mostrata come \33]0;da strace) è la sequenza di escape per impostare il titolo della finestra del terminale. È terminato con il carattere BEL ( \7), quindi il primo writeimposta il titolo della finestra. Il secondo stampa il prompt effettivo. Nota che, a parte la sequenza di escape, non sono esattamente gli stessi. Il prompt ha un contorno [..]mentre il titolo della finestra no.

Possiamo anche vedere che la prima scrittura va su stdout (fd 1, il primo argomento su write()) e la seconda su stderr. Bash stampa il prompt su stderr, quindi la prima scrittura proviene da qualche altra parte. Che da qualche parte è probabilmente PROMPT_COMMAND, come quello degli script di avvio predefiniti di Debian per Bash. C'è qualcosa di simile qui dentro:

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

Imposta che PROMPT_COMMANDse in esecuzione xtermo rxvt, che dovrebbe supportare quella sequenza di escape.


Sai perché Bash sembra leggere le cose carattere per carattere, piuttosto che leggere in una riga alla volta? Inoltre, perché bash scrive "l" e "s" su stdout? Se eseguo una stringa simile con cat, ci sono due differenze: legge l'input riga per riga e mentre fa eco il suo input allo stdout, vedo l'input due volte (una volta quando scrivo e una volta quando cat lo fa eco).
extremeaxe5,

@ extremeaxe5, questo è fondamentalmente perché Bash (o meglio la libreria readline) gestisce tutta l'elaborazione della riga di comando, invece di fare affidamento sull'elaborazione piuttosto limitata eseguita dal terminale. Deve ottenere immediatamente l'input per decidere cosa fare quando, ad esempio, ^Aviene premuto un carattere TAB o (Ctrl-A) o vengono premuti i vari caratteri speciali. Inoltre, disattiva l'eco del terminale, in modo che possa decidere cosa emettere per ogni particolare carattere di input (di nuovo, TAB di solito non genera un TAB.) catNon fa nulla di tutto ciò. In tal caso, prova a eseguire dash, che non esegue alcuna gestione della riga di comando.
ilkkachu,

In realtà, il motivo per cui Bash chiama read()per leggere solo un byte alla volta è che non riesce a leggere oltre una nuova riga. La nuova riga potrebbe causare l'esecuzione di un programma esterno, che potrebbe anche leggere dallo stesso input. (E quel programma dovrebbe essere in grado di leggere tutti i caratteri dopo la nuova riga.) Se non dovesse interessarsene, potrebbe chiamare read()con un limite maggiore e con il terminale in modalità raw, di solito otterrebbe comunque l'input un personaggio alla volta. (
Dipenderebbe

Il tuo secondo commento sembra essere vero solo perché Bash gestisce autonomamente la riga di comando.
extremeaxe5,

@ extremeaxe5, beh, sì, lo stavo supponendo, dato che è comunque il caso comune. Ma, anche se la shell si basava sulla modifica della linea del terminale, i tempi potrebbero essere ancora un problema. Se due linee venissero inviate in rapida successione (pensate a incollare i dati) e il sistema fosse caricato abbastanza in modo che la shell non potesse essere programmata immediatamente (o peggio ancora, la shell fosse fermata), allora un read()buffer con un buffer più grande potrebbe comunque restituire entrambe le righe in la stessa chiamata. Io non credo che ci sia una garanzia che read()sarebbe sempre tornare solo una riga in modalità cotto.
ilkkachu,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.