Bash próbuje napisać dwa monity powłoki?

11

Patrzę na dane wyjściowe strace uruchomionego procesu bash podłączonego do terminala, do celów edukacyjnych.

Mój proces bash ma PID 2883.

Piszę

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

Do terminala. Następnie przechodzę do procesu bash i mam następującą interakcję:

[OP@localhost ~]$ ls

Widzę, patrząc na wynik

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

Jestem zmieszany na dwóch ostatnich liniach. Wygląda na to, że bash próbuje napisać dwa monity powłoki? Co tu się dzieje?

extremeaxe5
źródło

Odpowiedzi:

24

<ESC>]0;Sekwencja (pokazany \33]0;przez strace) jest sekwencja ucieczki ustawienia terminala tytuł okna. Jest zakończony znakiem BEL ( \7), więc pierwszy writeustawia tytuł okna. Drugi drukuje rzeczywisty monit. Zauważ, że nawet poza sekwencją ucieczki, nie są dokładnie takie same. Monit ma otoczenie, [..]a tytuł okna nie.

Widzimy również, że pierwszy zapis przechodzi do stdout (fd 1, pierwszy argument do write()), a drugi do stderr. Bash wypisuje monit do stderr, więc pierwszy zapis pochodzi z innego miejsca. Prawdopodobnie jest PROMPT_COMMANDtak, jak w domyślnych skryptach startowych Debiana dla Bash. Jest tam coś takiego:

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

Ustawia to, PROMPT_COMMANDjeśli działa xtermlub rxvt, co powinno obsługiwać tę sekwencję zmiany znaczenia.

ilkkachu
źródło
Czy wiesz, dlaczego bash wydaje się czytać rzeczy znak po znaku, zamiast czytać wiersz po linii? Ponadto, dlaczego bash zapisuje „l” i „s” na standardowe wyjście? Jeśli wykonam podobną ścieżkę cat, istnieją dwie różnice: odczytuje dane wejściowe linia po linii i chociaż echo wprowadza dane z powrotem na standardowe wyjście, widzę dane wejściowe dwa razy (raz, gdy piszę, a raz, gdy kot je echa).
extremeaxe5
@ extremeaxe5, to w zasadzie dlatego, że Bash (a raczej biblioteka readline) obsługuje całe przetwarzanie wiersza poleceń, zamiast polegać na dość ograniczonym przetwarzaniu wykonywanym przez terminal. Musi natychmiast uzyskać dane wejściowe, aby zdecydować, co zrobić po ^Anaciśnięciu np. Znaku TAB lub (Ctrl-A) lub różnych znaków specjalnych. Ponadto wyłącza echo terminala, aby mógł zdecydować, co wyprowadzić dla każdego konkretnego znaku wejściowego (ponownie, TAB zwykle nie wypisuje TAB.) catNic z tego nie robi. Jeśli tak, spróbuj uruchomić dash, który nie obsługuje żadnej linii poleceń.
ilkkachu
W rzeczywistości powodem, dla którego Bash wzywa read()tylko do odczytu jednego bajtu na raz, jest to, że nie może on odczytać nowego wiersza. Nowy wiersz może powodować uruchomienie zewnętrznego programu, który może również czytać z tego samego wejścia. (I ten program powinien być w stanie odczytać dowolne znaki po nowej linii.) Gdyby nie musiał się tym przejmować, mógłby wywoływać read()z większym limitem, a przy terminalu w trybie surowym nadal zwykle pobierałby dane wejściowe jedna postać na raz. (To zależy od tego, jak szybko pojawią się znaki wejściowe i jak zaplanowano proces.)
ilkkachu
Twój drugi komentarz wydaje się być prawdą tylko dlatego, że Bash sam obsługuje wiersz poleceń.
extremeaxe5
@ extremeaxe5, cóż, tak, zakładałem, że i tak jest to powszechny przypadek. Ale nawet jeśli powłoka polegała na edycji linii terminala, czas może nadal stanowić problem. Jeśli dwa wiersze zostały wysłane szybko po sobie (pomyśl o wklejeniu danych), a system został załadowany na tyle, aby powłoka nie została natychmiast zaplanowana (lub, co gorsza, powłoka została zatrzymana), wówczas read()większy bufor może nadal zwrócić obie linie w to samo połączenie. Nie sądzę, aby istniała gwarancja, read()która zawsze zwróciłaby tylko jedną linię w trybie gotowania.
ilkkachu