Вопрос или проблема
Например, если я хочу запустить из оболочки что-то на Python, я бы сделал следующее:
echo 'print("hello world")' | python2
Теперь представьте, что я хочу запустить что-то интерактивное, то есть, что-то, что требует моего ввода, например:
name = raw_input("Введите ваше имя:")
Когда я делаю:
echo 'name = raw_input("Введите ваше имя:")' | python2
Я получаю ошибку. Итак, каков правильный способ запустить что-то из bash, с чем я мог бы взаимодействовать?
PS – это просто пример. Мне нужно запустить набор скриптов в другой внешней программе (не на Python), и некоторые из этих скриптов требуют ввода от пользователя. Все работает гладко до момента, когда требуется ввод, после чего текущий скрипт завершается и переходит к следующему.
Спасибо 🙂
Когда вы передаете программу как строку интерпретатору команд, интерпретатор считывает её из стандартного ввода, который, таким образом, связан с концом чтения трубы: команды, которые пытаются читать интерактивно предоставленную информацию, как правило, получают EOF.
Например:
$ echo 'read x; echo "$x"' | bash
# Просто выводит пустую строку, не ждет ввода
Чтобы это работало, вам нужно либо:
-
переписать скрипт, чтобы он читал интерактивно предоставленный ввод из другого дескриптора файла, созданного как дубликат стандартного ввода процесса, который выполняет всю цепочку:
{ echo '0<&3 read x; echo "$x"' | bash; } 3<&0
В случае Python (только для иллюстрации, я не программист на Python):
{ echo 'import sys; \ sys.stdin = open("/dev/fd/3"); \ name = raw_input("Введите ваше имя:")' | python2 } 3<&0
Или
-
заставить интерпретатор читать из другого дескриптора файла, связанного с концом чтения трубы, и немного поменять дескрипторы файлов, чтобы стандартный ввод от процесса, который выполняет всю цепочку, был ему доступен. Предполагая, что интерпретатор принимает скрипт в качестве аргумента и ваша платформа поддерживает способ доступа к дескрипторам файлов
/dev/fd/n
:{ echo 'read x; echo "$x"' | bash /dev/fd/3 3<&0 0<&4; } 4<&0
В случае вашего примера скрипта на Python:
{ echo 'name = raw_input("Введите ваше имя:")' | { python2 /dev/fd/3 } 3<&0 0<&4 } 4<&0
Или
-
если ваша внешняя программа не принимает скрипт в качестве аргумента (ни как строку, как в
bash -c 'echo "foo"'
, ни как имя файла, как вbash /dev/fd/3
) и ей нужно читать как скрипт, так и любой интерактивно предоставленный ввод из стандартного ввода, вы можете использовать что-то вроде:{ echo 'echo start; echo "foo? "; read x; echo "$x"; echo end' | bash -c '0<&3 cat - while IFS= read -r x do printf "%s\n" "$x" done' 3<&0 0<&4 | bash } 4<&0
Простой код (надеюсь, вы поймете):
read i && echo $i|python3
Передайте скрипт как аргумент вместо stdin:
python2 -c 'name = raw_input("Введите ваше имя:")'
Имейте в виду, что ничего не мешает вам разместить этот скрипт на нескольких строках, как:
python2 -c '
# мой шикарный встроенный скрипт
name = raw_input("Введите ваше имя:")
'
(но имейте в виду, что для того, чтобы код Python содержал '
, вам нужно будет ввести его как '\''
).
Синтаксис интерпретатор -c <код>
общепринят для нескольких интерпретаторов. Например, именно так оболочки запускаются большую часть времени (system("некоторый код оболочки")
на самом деле выполняет sh -c "некоторый код оболочки"
). Некоторые другие интерпретаторы (sed
/ perl
/ gawk
) используют -e
(для выражения) вместо -c
(для кода / команды); php
использует -r
(для выполнения).
(это ограничено короткими скриптами (не более 128КиБ на Linux).
Или через другой дескриптор файла и здесь-документ:
python2 /dev/fd/3 3<< 'EOF'
name = raw_input("Введите ваше имя:")
EOF
Ответ или решение
Как выполнить внешнюю программу из bash в интерактивном режиме
При попытке запустить интерактивную программу (например, на Python), некоторые команды могут не работать так, как ожидается, когда стандартный ввод перенаправляется через конвейер. Ваша программа может завершаться ошибкой, поскольку она не получает необходимый ввод от пользователя. Давайте рассмотрим несколько способов, как это можно обойти.
1. Использование -c
для выполнения скрипта
Если возможно, можно использовать флаг -c
, который позволяет интерпретатору выполнять скрипт, указанный в строковом виде. Это часто используется и поддерживается многими интерпретаторами, в том числе Python.
python2 -c 'name = raw_input("Введите ваше имя: ")'
Примечание: Помните, что многострочные скрипты можно указать также, но для символа '
внутри скрипта нужно будет экранировать его.
python2 -c '
name = raw_input("Введите ваше имя: ")
'
2. Использование перенаправления с описателями файлов
Если ваша программа требует ввод как часть стандартного ввода, и вам необходимо передать несколько команд, вы можете воспользоваться перенаправлением с помощью файловых дескрипторов.
Вот пример, как можно сделать это с помощью bash:
{ echo 'import sys; \
sys.stdin = open("/dev/fd/3"); \
name = raw_input("Введите ваше имя: ")' | python2; } 3<&0
3. Использование HERE-документов
Вы также можете использовать HERE-документы, чтобы предоставлять ввод непосредственно при запуске программы:
python2 /dev/fd/3 << 'EOF'
name = raw_input("Введите ваше имя: ")
EOF
4. Перенаправление с помощью командного подстановки
Если требуется использовать дополнительную обработку, вы можете использовать команды внутри подстановки:
{ echo 'echo "Введите ваше имя:"; read name; echo "Привет, $name!"'; } | bash
Общие примечания
- Учтите, что разные интерпретаторы могут поддерживать свои собственные варианты выполнения скриптов.
- Использование
-c
или--file
часто является наиболее простым методом для выполнения скрипта в интерактивном режиме. - Если ваша программа не может быть вызвана с помощью обычного перенаправления, возможно, стоит рассмотреть возможность её переписывания, чтобы имеющиеся возможности ввода передавались непосредственно через стандартный ввод.
Надеюсь, этот ответ поможет вам успешно запускать внешние программы из bash в интерактивном режиме без ошибок!