Bash versucht zwei Shell-Eingabeaufforderungen zu schreiben?

11

Ich betrachte die Strace-Ausgabe eines laufenden Bash-Prozesses, der mit einem Terminal verbunden ist, zu Bildungszwecken.

Mein Bash-Prozess hat PID 2883.

Ich tippe

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

In ein Terminal. Ich gehe dann in meinen Bash-Prozess und habe die folgende Interaktion:

[OP@localhost ~]$ ls

Wenn ich mir die Ausgabe anschaue, sehe ich

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

Ich bin in den letzten beiden Zeilen verwirrt. Es scheint, dass bash versucht, zwei Shell-Eingabeaufforderungen zu schreiben? Was ist denn hier los?

extremeaxe5
quelle

Antworten:

24

Die <ESC>]0;Sequenz (wie \33]0;durch Strace dargestellt) ist die Escape-Sequenz zum Festlegen des Titels des Terminalfensters. Es wird mit dem BEL-Zeichen ( \7) abgeschlossen, sodass der erste writeden Fenstertitel festlegt. Die zweite druckt die eigentliche Eingabeaufforderung. Beachten Sie, dass sie auch abgesehen von der Escape-Sequenz nicht genau gleich sind. Die Eingabeaufforderung hat eine Umgebung, [..]während der Fenstertitel dies nicht tut.

Wir können auch sehen, dass der erste Schreibvorgang an stdout (fd 1, das erste Argument an write()) und der zweite an stderr geht. Bash druckt die Eingabeaufforderung an stderr, sodass der erste Schreibvorgang von einem anderen Ort stammt. Das ist wahrscheinlich irgendwo so PROMPT_COMMAND, wie in Debians Standard-Startskripten für Bash. Da drin ist so etwas:

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

Es legt fest, dass, PROMPT_COMMANDwenn ausgeführt wird, xtermoder rxvt, was diese Escape-Sequenz unterstützen soll.

ilkkachu
quelle
Wissen Sie, warum Bash Dinge Zeichen für Zeichen zu lesen scheint, anstatt jeweils in einer Zeile zu lesen? Warum schreibt bash "l" und "s" in stdout? Wenn ich eine ähnliche Strace mit mache cat, gibt es zwei Unterschiede: Es liest die Eingabe Zeile für Zeile, und während es seine Eingabe an stdout zurücksendet, sehe ich die Eingabe zweimal (einmal, wenn ich tippe, und einmal, wenn cat sie wiedergibt).
Extremeaxe5
@ extremeaxe5, das liegt im Grunde daran, dass Bash (oder besser gesagt die Readline-Bibliothek) die gesamte Befehlszeilenverarbeitung selbst abwickelt, anstatt sich auf die eher eingeschränkte Verarbeitung durch das Terminal zu verlassen. Die Eingabe muss sofort erfolgen, um zu entscheiden, was zu tun ist, wenn z. B. ein TAB-Zeichen oder ^A(Strg-A) oder die verschiedenen Sonderzeichen gedrückt werden. Außerdem wird das Echo des Terminals ausgeschaltet, sodass entschieden werden kann, was für jedes bestimmte Eingabezeichen ausgegeben werden soll (TAB gibt normalerweise kein TAB aus.) cat. Wenn Sie es dashtun , versuchen Sie es auszuführen , was keine Befehlszeilenbehandlung übernimmt.
Ilkkachu
Der Grund, warum Bash aufruft, read()um jeweils nur ein Byte zu lesen, ist, dass es nicht über eine neue Zeile hinaus lesen kann. Der Zeilenumbruch kann dazu führen, dass ein externes Programm ausgeführt wird, das möglicherweise auch von derselben Eingabe liest. (Und dieses Programm sollte in der Lage sein, alle Zeichen nach dem Zeilenumbruch zu lesen.) Wenn es sich nicht darum kümmern müsste, könnte es read()mit einem größeren Limit aufrufen , und wenn sich das Terminal im Raw-Modus befindet, würde es normalerweise immer noch die Eingabe erhalten jeweils ein Zeichen. (Es würde davon
abhängen
Ihr zweiter Kommentar scheint nur wahr zu sein, da Bash die Befehlszeilenbehandlung selbst durchführt.
Extremeaxe5
@ extremeaxe5, na ja, das habe ich angenommen, da es sowieso der übliche Fall ist. Aber selbst wenn sich die Shell auf die Zeilenbearbeitung des Terminals stützte, könnte das Timing dennoch ein Problem sein. Wenn zwei Zeilen schnell hintereinander gesendet wurden (denken Sie an das Einfügen von Daten) und das System so geladen war, dass die Shell nicht sofort geplant wurde (oder schlimmer noch, die Shell wurde gestoppt), gibt ein read()mit größerem Puffer möglicherweise immer noch beide Zeilen zurück der gleiche Anruf. Ich glaube nicht, dass es eine Garantie gibt, read()die im gekochten Modus immer nur eine Zeile zurückgibt.
Ilkkachu