Ловушка с внешним вызовом в подстановке команд ломает родительскую оболочку Bash - PullRequest
3 голосов
/ 15 апреля 2020

У меня есть текстовый скрипт пользовательского интерфейса, который позволяет мне просматривать каталоги и выбирать файл. Графика выводится на stderr, путь выбранного файла отправляется на stdout. Это позволяет получить выбранный файл следующим образом:

file="$(./script)"

Это очень удобно, поскольку подстановка команд только захватывает stdout.

Но мне нужен мой скрипт для обработки сигналов, чтобы когда сценарий прерывается, он может сбросить отображение. Я установил ловушку, которая обрабатывает сигнал INT. Чтобы смоделировать, что он делает, рассмотрим следующий скрипт:

catch() { 
    echo "caught"
    ps # Calling an external command
    exit
}

trap catch INT

while read -sN1; do # Reading from the keyboard
    echo $REPLY >&2
done

Затем скрипт вызывается с var="$(./script)". Теперь, если вы отправите сигнал INT, нажав ^C, родительская оболочка сломается: все, что вы напечатаете (включая управляющие символы), будет напечатано до тех пор, пока вы не нажмете return, тогда ни один из ваших входных данных не будет показан.

Удаление внешнего вызова команды в функции catch, кажется, решает проблему (тем не менее, echo, кажется, не работает), но я не понимаю почему, и я не могу обойтись без него в моем последнем сценарии.

Я что-то упускаю? Почему это ломает родительскую оболочку?

Ответы [ 2 ]

2 голосов
/ 16 апреля 2020

Моя непроверенная, но лучшая теория состоит в том, что это вызвано гонкой между Родителем, читающим настройки терминала, и Ребенком, восстанавливающим их.

При прерывании интерактивная оболочка прекращает попытки чтения из канала и тщательно проверяет текущие настройки терминала, чтобы не допустить их залипания позже. Если ребенок еще не восстановил их, родитель прочитает неверные настройки и предположит, что именно таким должен быть терминал.

Это объясняет, почему вы можете набрать одну строку, прежде чем она начнет портиться: 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
0 голосов
/ 20 апреля 2020

Поскольку другие пользователи, похоже, согласились с тем, что это ошибка, я подал отчет об ошибке. Я получил следующий ответ:

Это условие гонки - родительская оболочка обрабатывает SIGINT раньше, чем должна. Это будет исправлено в следующей ветке разработки pu sh.

Так что лучше всего здесь следить за Bash 'git.

В качестве «исправления» мне пришлось реорганизовать сценарий для получения (. script.sh), чтобы он мог взаимодействовать с вызывающей стороной без привлечения временных файлов, поскольку подстановка процесса приводила к точно такой же поведение, чем подстановка команд.

...