Bash пытается написать две подсказки оболочки?

11

Я смотрю на результаты работы запущенного процесса bash, подключенного к терминалу, в образовательных целях.

Мой процесс bash имеет PID 2883.

Я печатаю

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

В терминал. Затем я вхожу в процесс bash и получаю следующее взаимодействие:

[OP@localhost ~]$ ls

Глядя на вывод, я вижу

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

Я запутался в последних двух строках. Похоже, что bash пытается написать две подсказки оболочки? Что тут происходит?

extremeaxe5
источник

Ответы:

24

<ESC>]0;Последовательность (показано \33]0;на Трассировании) является последовательностью выхода , чтобы установить заголовок окна терминала. Он заканчивается символом BEL ( \7), поэтому первый writeустанавливает заголовок окна. Вторая печатает фактическую подсказку. Обратите внимание, что даже за исключением escape-последовательности, они не совсем одинаковы. Приглашение имеет окружение, [..]а заголовок окна - нет.

Мы также видим, что первая запись идет в stdout (fd 1, первый аргумент to write()), а вторая в stderr. Bash выводит подсказку в stderr, поэтому первая запись происходит откуда-то еще. Вероятно PROMPT_COMMAND, это где-то похоже на сценарии запуска Debian по умолчанию для Bash. Там что-то вроде этого:

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

Он устанавливает это, PROMPT_COMMANDесли работает xtermили rxvt, который должен поддерживать эту escape-последовательность.

ilkkachu
источник
Знаете ли вы, почему bash читает вещи символ за символом, а не читает по очереди? Кроме того, почему bash пишет "l" и "s" в стандартный вывод? Если я выполняю аналогичную страйс с cat, есть два различия: он читает входные данные построчно, и хотя он выводит свой ввод обратно на стандартный вывод, я вижу ввод дважды (один раз, когда я печатаю, и один раз, когда кошка повторяет его).
extremeaxe5
@ extremeaxe5, это в основном потому, что Bash (или, скорее, библиотека readline) обрабатывает всю обработку командной строки, а не полагается на довольно ограниченную обработку, выполняемую терминалом. Он должен немедленно получить ввод, чтобы решить, что делать, например, при ^Aнажатии клавиши TAB или (Ctrl-A) или различных специальных символов. Кроме того, он отключает эхо-сигнал терминала, так что он может решить, что выводить для каждого конкретного входного символа (опять же, TAB обычно не выводит TAB.) Не catделает ничего из этого. Если это так, попробуйте запустить dash, что не делает никакой обработки командной строки.
ilkkachu
Фактически, причина, по которой Bash вызывает read()чтение только одного байта за раз, заключается в том, что он не может читать после новой строки. Новая строка может привести к запуску внешней программы, которая также может читать из того же ввода. (И эта программа должна быть способна читать любые символы после новой строки.) Если ей не нужно об этом заботиться, она может вызывать read()с большим лимитом, а с терминалом в режиме raw, она все равно обычно получает ввод по одному персонажу за раз. (Это будет зависеть от того, как быстро будут поступать вводимые символы и как планировался процесс.)
ilkkachu
Ваш второй комментарий кажется верным только потому, что Bash сам обрабатывает командную строку.
extremeaxe5
@ extremeaxe5, ну да, я так и предполагал, так как в любом случае это частый случай. Но, даже если оболочка полагается на редактирование строки терминала, время может все еще быть проблемой. Если две строки были отправлены в быстрой последовательности (подумайте о вставке данных), и система была загружена достаточно, чтобы оболочка не была назначена немедленно (или, что еще хуже, оболочка была остановлена), тогда read()буфер с большим объемом может по-прежнему возвращать обе строки в тот же звонок. Я не думаю, что есть гарантия, read()которая всегда вернула бы только одну строку в готовом режиме.
ilkkachu