Моя непроверенная, но лучшая теория состоит в том, что это вызвано гонкой между Родителем, читающим настройки терминала, и Ребенком, восстанавливающим их.
При прерывании интерактивная оболочка прекращает попытки чтения из канала и тщательно проверяет текущие настройки терминала, чтобы не допустить их залипания позже. Если ребенок еще не восстановил их, родитель прочитает неверные настройки и предположит, что именно таким должен быть терминал.
Это объясняет, почему вы можете набрать одну строку, прежде чем она начнет портиться: child восстановил хорошие настройки в буферизованном каноническом режиме, поэтому вы можете ввести полную строку. После того, как вы нажмете Enter, bash получит команду, и как часть ее запроса восстанавливает неверные настройки, которые, как он думал, должен был иметь терминал.
Чтобы обойти это, вы можете использовать родительский дескриптор SIGINT для продолжительность захвата. Не имеет значения, что делает обработчик, потому что единственное, что нужно, - это заставить Bash ждать, пока текущие команды не закончатся sh, чтобы он мог вызвать обработчик.
Вот пример:
#!/bin/bash
catch() {
sleep 1 # Make sure to lose the race
echo "caught"
ps
exit
}
trap catch INT
while read -sN1; do # Reading from the keyboard
echo $REPLY >&2
done
и вот интерактивная оболочка после ввода x
и нажатия Ctrl- C:
bash-5.0$ trap 'true' INT; var=$(./script)
x
bash-5.0$ echo "The prompt works fine"
The prompt works fine
bash-5.0$ declare -p var
declare -- var="caught
PID TTY TIME CMD
650388 pts/3 00:00:00 bash
650859 pts/3 00:00:00 script
650862 pts/3 00:00:00 ps"
bash-5.0$
Здесь она без ловушки в родительском элементе, демонстрирующая, как работает только первая команда пока первый ввод не сработает, а остальная часть ввода будет скрыта:
bash-5.0$ trap - INT; var=$(./script)
x
bash-5.0$ echo "I can see this first line"
I can see this first line
bash-5.0$ bash: fasdfasdfasdfasdfa: command not found