Bash tente d'écrire deux invites shell?

11

Je regarde la sortie strace d'un processus bash en cours d'exécution connecté à un terminal, à des fins éducatives.

Mon processus bash a le PID 2883.

J'écris

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

Dans un terminal. J'entre alors dans mon processus bash, et j'ai l'interaction suivante:

[OP@localhost ~]$ ls

En regardant la sortie, je vois

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

Je suis confus aux deux dernières lignes. Il semble que bash tente d'écrire deux invites shell? Que se passe t-il ici?

extremeaxe5
la source

Réponses:

24

La <ESC>]0;séquence (représentée \33]0;par strace) est la séquence d'échappement pour définir le titre de la fenêtre du terminal. Il se termine par le caractère BEL ( \7), donc le premier writedéfinit le titre de la fenêtre. La seconde imprime l'invite réelle. Notez que même en dehors de la séquence d'échappement, ils ne sont pas exactement les mêmes. L'invite est entourée [..]alors que le titre de la fenêtre ne l'est pas.

Nous pouvons également voir que la première écriture va à stdout (fd 1, le premier argument à write()) et la seconde à stderr. Bash imprime l'invite sur stderr, donc la première écriture vient d'ailleurs. C'est quelque part probablement PROMPT_COMMAND, comme celui des scripts de démarrage par défaut de Debian pour Bash. Il y a quelque chose comme ça là-dedans:

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

Il définit cela PROMPT_COMMANDsi exécutant xtermou rxvt, qui devrait prendre en charge cette séquence d'échappement.

ilkkachu
la source
Savez-vous pourquoi bash semble lire les choses caractère par caractère, plutôt que de lire en ligne à la fois? Aussi, pourquoi bash écrit-il "l" et "s" sur stdout? Si je fais une séquence similaire avec cat, il y a deux différences: il lit l'entrée ligne par ligne, et bien qu'il répète son entrée à stdout, je vois l'entrée deux fois (une fois lorsque je tape et une fois lorsque cat la fait écho).
extremeaxe5
@ extremeaxe5, c'est essentiellement parce que Bash (ou plutôt la bibliothèque readline) gère tout le traitement en ligne de commande lui-même, au lieu de s'appuyer sur le traitement plutôt limité effectué par le terminal. Il doit obtenir l'entrée immédiatement pour décider quoi faire lorsque, par exemple, un caractère TAB ou ^A(Ctrl-A) ou les divers caractères spéciaux sont pressés. En outre, il désactive l'écho du terminal, afin qu'il puisse décider quoi produire pour chaque caractère d'entrée particulier (encore une fois, TAB ne génère généralement pas de TAB.) Ne catfait rien de tout cela. Si vous le faites, essayez de lancer dash, qui ne fait aucune manipulation en ligne de commande.
ilkkachu
En fait, la raison pour laquelle Bash appelle read()à ne lire qu'un octet à la fois est qu'il ne peut pas lire au-delà d'une nouvelle ligne. La nouvelle ligne peut provoquer l'exécution d'un programme externe, qui peut également lire à partir de la même entrée. (Et ce programme devrait être capable de lire tous les caractères après la nouvelle ligne.) S'il n'avait pas à s'en soucier, il pourrait appeler read()avec une limite plus grande, et avec le terminal en mode brut, il obtiendrait généralement l'entrée un personnage à la fois. (Cela
dépendra
Votre deuxième commentaire ne semble être vrai que parce que Bash gère lui-même la ligne de commande.
extremeaxe5
@ extremeaxe5, eh bien, oui, je supposais que, puisque c'est le cas commun de toute façon. Mais, même si le shell s'appuyait sur l'édition de ligne du terminal, le timing pourrait toujours être un problème. Si deux lignes ont été envoyées en succession rapide (pensez à coller des données) et que le système a été suffisamment chargé pour que le shell ne soit pas immédiatement planifié (ou pire, le shell a été arrêté), un read()avec un tampon plus important peut toujours renvoyer les deux lignes dans le même appel. Je ne pense pas qu'il y ait une garantie qui read()serait toujours revenir une seule ligne en mode cuit.
ilkkachu