Возможно ли в интерактивной оболочке bash ввести команду, которая выводит некоторый текст, чтобы он отображался в следующей командной строке, как если бы пользователь вводил этот текст в этой строке?
Я хочу иметь возможность source
сценария, который будет генерировать командную строку и выводить ее так, чтобы она появлялась, когда приглашение возвращается после завершения сценария, чтобы пользователь мог по желанию редактировать его перед нажатием enter
для его выполнения.
Это может быть достигнуто с помощью, xdotool
но это работает только тогда, когда терминал находится в окне X и только если он установлен.
[me@mybox] 100 $ xdotool type "ls -l"
[me@mybox] 101 $ ls -l <--- cursor appears here!
Это можно сделать только с помощью bash?
Ответы:
С помощью
zsh
вы можетеprint -z
поместить текст в буфер редактора строк для следующего приглашения:запустил бы редактор строк, с помощью
echo test
которого вы можете редактировать в следующем приглашении.Я не думаю, что
bash
имеет аналогичную функцию, однако во многих системах вы можете заполнить буфер ввода терминального устройства с помощьюTIOCSTI
ioctl()
:Вставил бы
echo test
во входной буфер устройства терминала, как если бы получил от терминала.Более переносимым вариантом подхода @ mike,
Terminology
который не жертвует безопасностью, было бы посылать эмулятору терминала довольно стандартнуюquery status report
escape-последовательность:<ESC>[5n
какие терминалы неизменно отвечают (как ввод)<ESC>[0n
и связывают это со строкой, которую вы хотите вставить:Если в GNU
screen
, вы также можете сделать:Теперь, за исключением подхода TIOCSTI ioctl, мы просим эмулятор терминала отправить нам некоторую строку, как если бы она была напечатана. Если эта строка появляется раньше
readline
(bash
редактор строк) отключил локальное эхо-сигнал терминала, то эта строка будет отображаться не в приглашении оболочки, что немного портит отображение.Чтобы обойти это, вы можете либо немного задержать отправку запроса на терминал, чтобы убедиться, что ответ приходит, когда эхо-сигнал отключен с помощью readline.
(здесь предполагается, что ваша
sleep
поддержка поддерживает субсекундное разрешение).В идеале вы хотели бы сделать что-то вроде:
Однако
bash
(вопрекиzsh
) нет поддержки такогоwait-until-the-response-arrives
, который не читает ответ.Однако он имеет
has-the-response-arrived-yet
функцию сread -t0
:дальнейшее чтение
См . Ответ @ starfry, который расширяет два решения, предоставленные @mikeserv и мной, с более подробной информацией.
источник
bind '"\e[0n": "echo test"'; printf '\e[5n'
вероятно, ответ только для bash, который я ищу. Меня устраивает. Тем не менее, я получаю^[[0n
печать до моего приглашения. Я обнаружил, что это вызвано, когда$PS1
содержит подоболочку. Вы можете воспроизвести его, выполнивPS1='$(:)'
перед командой bind. Почему это случилось, и можно ли что-нибудь сделать с этим?\r
Этерн во главе$PS1
? Это должно работать, если$PS1
достаточно долго. Если нет, то положи^[[M
туда.r
делает трюк. Это, конечно, не мешает выводу, оно просто перезаписывается до того, как его увидит глаз. Я думаю,^[[M
стирает строку, чтобы очистить введенный текст, если он длиннее, чем подсказка. Правильно ли это (я не смог найти его в списке для сброса ANSI, который у меня есть)?Этот ответ является разъяснением моего собственного понимания и вдохновлен @ StéphaneChazelas и @mikeserv до меня.
TL; DR
bash
без посторонней помощи;ioctl
ноbash
решенийbind
.Простое решение
Bash имеет встроенную оболочку,
bind
которая позволяет выполнять команду оболочки при получении последовательности клавиш. По сути, выходные данные команды оболочки записываются во входной буфер оболочки.Последовательность клавиш
\e[0n
(<ESC>[0n
) - это управляющий код терминала ANSI, который терминал отправляет, чтобы указать, что он функционирует нормально. Это отправляется в ответ на запрос отчета о состоянии устройства, который отправляется как<ESC>[5n
.Связав ответ с
echo
выводом текста для вставки, мы можем внедрить этот текст, когда захотим, запросив состояние устройства, и это делается путем отправки<ESC>[5n
escape-последовательности.Это работает, и, вероятно, достаточно, чтобы ответить на первоначальный вопрос, потому что никакие другие инструменты не участвуют. Это чисто,
bash
но полагается на хорошо ведущий себя терминал (практически все).Он оставляет отображаемый текст в командной строке готовым для использования, как если бы он был напечатан. Это может быть добавлено, отредактировано, и нажатие
ENTER
заставляет это быть выполненным.Добавьте
\n
к связанной команде, чтобы она выполнялась автоматически.Однако это решение работает только в текущем терминале (что находится в рамках исходного вопроса). Он работает из интерактивного приглашения или из сценария с источником, но вызывает ошибку при использовании из подоболочки:
Правильное решение, описанное ниже, является более гибким, но оно опирается на внешние команды.
Правильное решение
Правильный способ ввода ввода использует tty_ioctl , системный вызов unix для управления вводом / выводом, в котором есть
TIOCSTI
команда, которую можно использовать для ввода ввода.TIOC из « Т erminal МОК ТЛ » и ИППП от « S конца T erminal я Nput ».
Для этого нет встроенной команды
bash
; для этого требуется внешняя команда. В типичном дистрибутиве GNU / Linux такой команды нет, но это не сложно сделать с помощью небольшого программирования. Вот функция оболочки, которая используетperl
:Вот
0x5412
код дляTIOCSTI
команды.TIOCSTI
это константа , определенная в стандартных заголовочных файлов С со значением0x5412
. Попробуйgrep -r TIOCSTI /usr/include
или посмотри/usr/include/asm-generic/ioctls.h
; он включен в программы на C косвенным путем#include <sys/ioctl.h>
.Затем вы можете сделать:
Реализации на некоторых других языках показаны ниже (сохраните в файле, а затем в
chmod +x
нем):Perl
inject.pl
Вы можете создать,
sys/ioctl.ph
который определяетTIOCSTI
вместо использования числового значения. Смотри здесьпитон
inject.py
Рубин
inject.rb
С
inject.c
компилировать с
gcc -o inject inject.c
**! ** Есть другие примеры здесь .
Использование
ioctl
для этого работает в подоболочках. Он также может вводить в другие терминалы, как объяснено далее.Идем дальше (контролируем другие терминалы)
Это выходит за рамки оригинального вопроса, но можно вводить символы в другой терминал при условии наличия соответствующих разрешений. Обычно это означает быть
root
, но о других путях смотрите ниже.Расширение приведенной выше программы на C для приема аргумента командной строки, указывающего tty другого терминала, позволяет ввести этот терминал:
Он также отправляет новую строку по умолчанию, но, аналогично
echo
, он предоставляет-n
опцию для ее подавления. Опция--t
or--tty
требует аргумент -tty
терминала, который будет введен. Значение для этого можно получить в этом терминале:Скомпилируйте это с
gcc -o inject inject.c
. Добавьте к тексту префикс,--
если он содержит дефисы, чтобы синтаксический анализатор не мог правильно интерпретировать параметры командной строки. См./inject --help
. Используйте это так:или просто
ввести текущий терминал.
Внедрение в другой терминал требует административных прав, которые могут быть получены:
root
,sudo
,CAP_SYS_ADMIN
возможность илиsetuid
Назначить
CAP_SYS_ADMIN
:Назначить
setuid
:Чистый вывод
Введенный текст появляется перед подсказкой, как если бы он был напечатан до появления подсказки (что, по сути, так и было), но затем снова появляется после подсказки.
Один из способов скрыть текст, который появляется перед приглашением, состоит в том, чтобы добавить к приглашению возврат каретки (
\r
не перевод строки) и очистить текущую строку (<ESC>[M
):Однако это только очистит строку, на которой появляется подсказка. Если введенный текст содержит символы новой строки, это не будет работать так, как задумано.
Другое решение отключает отображение введенных символов. Для этого используется оболочка
stty
:где
inject
одно из решений, описанных выше, или заменено наprintf '\e[5n'
.Альтернативные подходы
Если ваша среда отвечает определенным предварительным условиям, у вас могут быть другие доступные методы, которые вы можете использовать для ввода данных. Если вы работаете в среде рабочего стола, то xdotool - это утилита X.Org, которая имитирует работу мыши и клавиатуры, но ваш дистрибутив может не включать ее по умолчанию. Можешь попробовать:
Если вы используете tmux , терминальный мультиплексор, то вы можете сделать это:
где
-t
выбирает, какой сеанс и панель для вставки. GNU Screen имеет аналогичную возможность с егоstuff
командой:Если ваш дистрибутив включает в себя пакет console-tools , то у вас может быть
writevt
команда, которая используетioctl
как наши примеры. Однако большинство дистрибутивов не одобряют этот пакет в пользу kbd, в котором отсутствует эта функция.Обновленная копия writevt.c может быть скомпилирована с использованием
gcc -o writevt writevt.c
.Другие варианты, которые могут лучше подходить для некоторых сценариев использования, включают ожидаемый и пустой, которые предназначены для сценариев интерактивных инструментов.
Вы также можете использовать оболочку, которая поддерживает инъекцию терминала, например,
zsh
которая может это сделатьprint -z ls
.Ответ "Ого, это умно ..."
Описанный здесь метод также обсуждается здесь и основывается на методе, обсуждаемом здесь .
Перенаправление оболочки от
/dev/ptmx
получает новый псевдо-терминал:Небольшой инструмент, написанный на C, который разблокирует мастер псевдотерминала (ptm) и выводит имя ведомого псевдотерминала (pts) на его стандартный вывод.
(сохранить как
pts.c
и скомпилировать сgcc -o pts pts.c
)Когда программа вызывается со стандартным входом, установленным в ptm, она разблокирует соответствующие точки и выводит свое имя в стандартный вывод.
Функция unlockpt () разблокирует подчиненное псевдотерминальное устройство, соответствующее главному псевдотерминалу, на который ссылается данный файловый дескриптор. Программа передает это как ноль, который является стандартным вводом программы .
Функция ptsname () возвращает имя подчиненного псевдотерминального устройства, соответствующего мастеру, указанному в данном дескрипторе файла, снова передавая ноль для стандартного ввода программы.
Процесс может быть связан с очками. Сначала получите ptm (здесь он назначен файловому дескриптору 3, открытому для чтения-записи
<>
перенаправлением).Затем запустите процесс:
Процессы, порожденные этой командной строкой, лучше всего иллюстрируются с помощью
pstree
:Выходные данные относятся к текущей оболочке (
$$
), а PID (-p
) и PGID (-g
) каждого процесса показаны в скобках(PID,PGID)
.Во главе дерева находится
bash(5203,5203)
интерактивная оболочка, в которую мы вводим команды, и ее файловые дескрипторы связывают ее с терминальным приложением, которое мы используем для взаимодействия с ней (xterm
или аналогичным образом).Посмотрев на команду еще раз, первый набор скобок запустил подоболочку,
bash(6524,6524)
) с ее файловым дескриптором 0 (его стандартный ввод ), назначенным для pts (который открыт для чтения-записи<>
), как это было возвращено другой подоболочкой, которая была выполнена./pts <&3
для разблокировки pts, связанные с файловым дескриптором 3 (созданным на предыдущем шагеexec 3<>/dev/ptmx
).Файловый дескриптор
3>&-
subshell 3 закрыт ( ), поэтому ptm для него недоступен. Его стандартный ввод (fd 0), который является pts, который был открыт для чтения / записи, перенаправляется (фактически, fd копируется ->&0
) на стандартный вывод (fd 1).Это создает подоболочку со стандартным вводом и выводом, соединенным с очками. Он может отправить входные данные, написав в ptm, а его вывод можно увидеть, прочитав из ptm:
Subshell выполняет эту команду:
Он запускается
bash(6527,6527)
в интерактивном-i
режиме ( ) в новом сеансе (setsid -c
обратите внимание, что PID и PGID одинаковы). Его стандартная ошибка перенаправляется в стандартный вывод (2>&1
) и передается по каналу,tee(6528,6524)
поэтому записывается вlog
файл, а также в pts. Это дает еще один способ увидеть результат подоболочки:Поскольку подоболочка работает в
bash
интерактивном режиме, ей можно отправлять команды для выполнения, как в этом примере, который отображает дескрипторы файлов подоболочки:Чтение вывода subshell (
tail -f log
илиcat <&3
) показывает:Стандартный вход (fd 0) подключен к точкам, и оба стандартных выхода (fd 1) и ошибка (fd 2) подключены к одной и той же трубе, которая подключается к
tee
:И посмотрите на файловые дескрипторы
tee
Стандартный вывод (fd 1) - это число: все, что 'tee' записывает в свой стандартный вывод, отправляется обратно в ptm. Стандартная ошибка (fd 2) - это точки, принадлежащие управляющему терминалу.
Завершение
Следующий скрипт использует технику, описанную выше. Он устанавливает интерактивный
bash
сеанс, который может быть введен путем записи в файловый дескриптор. Это доступно здесь и задокументировано с объяснениями.источник
bind '"\e[0n": "ls -l"'; printf '\e[5n'
решением, после того, как все выходные данные будутls -l
также^[[0n
выведены на Терминал, как только я нажму клавишу ввода, запуститеls -l
. Есть идеи как это "спрятать" пожалуйста? Спасибо.PS1="\r\e[M$PS1"
прежде чем делать,bind '"\e[0n": "ls -l"'; printf '\e[5n'
и это дало эффект, который вы описываете.Это зависит от того, что вы имеете в виду
bash
только . Если вы имеете в виду один интерактивныйbash
сеанс, то ответ почти наверняка нет . И это потому, что даже когда вы вводите команду, какls -l
в командной строке на любом каноническом терминале, онаbash
еще даже не осознает ее - иbash
даже не участвует в этом моменте.Скорее, то, что произошло до этого момента, заключается в том, что tty line-дисциплина ядра буферизовала и
stty echo
делала ввод пользователя только на экран. Он сбрасывает этот ввод своему читателю -bash
в вашем примере - строка за строкой - и, как правило, также переводит\r
оборотные стороны в электронные\n
строки в системах Unix - и этоbash
не так - и поэтому ваш исходный скрипт также не может быть осведомлен, что есть вводить вообще, пока пользователь не нажметENTER
клавишу.Теперь есть некоторые обходные пути. На самом деле, наиболее надежный способ не является обходным путем и включает в себя использование нескольких процессов или специально написанных программ для последовательного ввода, скрытия линейных дисциплин
-echo
от пользователя и вывода на экран только того, что будет сочтено целесообразным при интерпретации ввода. особенно когда это необходимо. Это может быть трудно сделать хорошо, потому что это означает написание правил интерпретации, которые могут обрабатывать произвольный ввод символов char по мере их поступления, и выписывать их одновременно без ошибок, чтобы имитировать то, что средний пользователь ожидает в этом сценарии. По этой причине, вероятно, интерактивный терминальный ввод / вывод так редко понимается - трудная перспектива не та, которая поддается дальнейшему исследованию для большинства.Другой обходной путь может включать эмулятор терминала. Вы говорите, что проблема для вас - это зависимость от X и от
xdotool
. В таком случае такой обходной путь, который я собираюсь предложить, может иметь аналогичные проблемы, но я продолжу в том же духе.Это будет работать в
xterm
ж / кallowwindowOps
набору ресурсов. Сначала он сохраняет имена значков / окон в стеке, затем устанавливает строку значков терминала, а^Umy command
затем запрашивает, чтобы терминал ввел это имя во входную очередь, и, наконец, сбрасывает его в сохраненные значения. Он должен работать незаметно для интерактивныхbash
оболочек, запускаемых вxterm
правильной конфигурации, но, вероятно, это плохая идея. Пожалуйста, смотрите комментарии Стефана ниже.Здесь, однако, представлена фотография моего терминала терминологии после запуска
printf
бита с другой escape-последовательностью на моей машине. Для каждой новой строки вprintf
команде я набиралCTRL+V
тогдаCTRL+J
и потом нажималENTER
клавишу. После этого я ничего не печатал, но, как вы можете видеть, терминал ввелmy command
для меня очередь ввода линейной дисциплины:Реальный способ сделать это с вложенным pty. Это как
screen
иtmux
аналогичная работа - и то, и другое, кстати, может сделать это возможным для вас.xterm
на самом деле поставляется с небольшой программой под названием,luit
которая также может сделать это возможным. Это не легко, хотя.Вот один из способов, которым вы могли бы:
Это ни в коем случае не переносимо, но должно работать на большинстве систем Linux с соответствующими разрешениями на открытие
/dev/ptmx
. Мой пользователь находится вtty
группе, которой достаточно в моей системе. Вам также понадобится ...... который при запуске в системе GNU (или любой другой со стандартным компилятором C, который также может читать из stdin) , запишет небольшой исполняемый файл с именем
pts
, который запуститunlockpt()
функцию на своем stdin, и запишет в ее стандартный вывод имя pty устройства, которое оно только что разблокировало. Я написал это при работе над ... Как я могу получить этот pty и что я могу с этим сделать? ,В любом случае, то, что делает приведенный выше фрагмент кода, запускает
bash
оболочку в pty-слое под текущим tty.bash
сказано записать весь вывод в ведомый pty, и текущий tty настроен не на-echo
его вход и не на его буферизацию, а вместо этого передает его (главным образом)raw
вcat
, который копирует егоbash
. И все время другое, фоновоеcat
копирование копирует весь ведомый вывод в текущий tty.По большей части вышеуказанная конфигурация была бы совершенно бесполезной - просто избыточной, в основном - за исключением того, что мы запускаем
bash
с копией своего собственного pty master fd<>9
. Это означает, чтоbash
можно свободно записывать в свой собственный входной поток с помощью простого перенаправления. Все, чтоbash
нужно сделать, это:... поговорить с самим собой.
Вот еще одна картина:
источник
xterm
вы все равно можете запросить заголовок значка с помощью,\e[20t
но только если настроено сallowWindowOps: true
.xterm
w / правильный конфигурации. Однако, если использовать xterm, вы можете читать и записывать буфер копирования / вставки, и, кроме того, я думаю, что он становится еще проще. Xterm также имеет escape-последовательности для изменения / воздействия на сам термин описания.\e[20t
(не\e]1;?\a
)?
Для запроса только для шрифта и цвета там , а не титулыХотя
ioctl(,TIOCSTI,)
ответ Стефана Шазеласа является, конечно, правильным ответом, некоторые люди могут быть достаточно довольны этим частичным, но тривиальным ответом: просто вставьте команду в стек истории, тогда пользователь может переместиться на 1 строку вверх по истории, чтобы найти команда.Это может стать простым скриптом, который имеет собственную историю в 1 строку:
read -e
разрешает редактирование ввода с помощью readline,-p
является подсказкой.источник
. foo.sh
или `source foo.sh вместо запуска в подоболочке). Хотя интересный подход. Подобным хаком, который требует изменения контекста вызывающей оболочки, было бы установить пользовательское завершение, которое расширило пустую строку до чего-то, а затем восстановило старый обработчик завершения.eval
если у вас есть простые команды для редактирования, без каналов, перенаправления и т. Д.Боже мой, мы пропустили простое решение, встроенное в bash :
read
команда имеет опцию-i ...
, которая при использовании с-e
выталкивает текст во входной буфер. Со страницы руководства:Поэтому создайте небольшую функцию bash или сценарий оболочки, который принимает команду для представления пользователю и запускает или оценивает их ответ:
Это, без сомнения, использует ioctl (, TIOCSTI,), который существует уже более 32 лет, как он уже существовал в 2.9BSD ioctl.h .
источник