Может ли bash писать в свой собственный поток ввода?

39

Возможно ли в интерактивной оболочке bash ввести команду, которая выводит некоторый текст, чтобы он отображался в следующей командной строке, как если бы пользователь вводил этот текст в этой строке?

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

Это может быть достигнуто с помощью, xdotoolно это работает только тогда, когда терминал находится в окне X и только если он установлен.

[me@mybox] 100 $ xdotool type "ls -l"
[me@mybox] 101 $ ls -l  <--- cursor appears here!

Это можно сделать только с помощью bash?

starfry
источник
Я думаю, что это не должно быть сложно с Expect, если вы можете это терпеть, и заставить его двигаться в дураках; но я не помню достаточно, чтобы опубликовать фактический ответ.
tripleee

Ответы:

40

С помощью zshвы можете print -zпоместить текст в буфер редактора строк для следующего приглашения:

print -z echo test

запустил бы редактор строк, с помощью echo testкоторого вы можете редактировать в следующем приглашении.

Я не думаю, что bashимеет аналогичную функцию, однако во многих системах вы можете заполнить буфер ввода терминального устройства с помощью TIOCSTI ioctl():

perl -e 'require "sys/ioctl.ph"; ioctl(STDIN, &TIOCSTI, $_)
  for split "", join " ", @ARGV' echo test

Вставил бы echo testво входной буфер устройства терминала, как если бы получил от терминала.

Более переносимым вариантом подхода @ mike,Terminology который не жертвует безопасностью, было бы посылать эмулятору терминала довольно стандартную query status reportescape-последовательность: <ESC>[5nкакие терминалы неизменно отвечают (как ввод) <ESC>[0nи связывают это со строкой, которую вы хотите вставить:

bind '"\e[0n": "echo test"'; printf '\e[5n'

Если в GNU screen, вы также можете сделать:

screen -X stuff 'echo test'

Теперь, за исключением подхода TIOCSTI ioctl, мы просим эмулятор терминала отправить нам некоторую строку, как если бы она была напечатана. Если эта строка появляется раньше readline( bashредактор строк) отключил локальное эхо-сигнал терминала, то эта строка будет отображаться не в приглашении оболочки, что немного портит отображение.

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

bind '"\e[0n": "echo test"'; ((sleep 0.05;  printf '\e[5n') &)

(здесь предполагается, что ваша sleepподдержка поддерживает субсекундное разрешение).

В идеале вы хотели бы сделать что-то вроде:

bind '"\e[0n": "echo test"'
stty -echo
printf '\e[5n'
wait-until-the-response-arrives
stty echo

Однако bash(вопреки zsh) нет поддержки такого wait-until-the-response-arrives, который не читает ответ.

Однако он имеет has-the-response-arrived-yetфункцию с read -t0:

bind '"\e[0n": "echo test"'
saved_settings=$(stty -g)
stty -echo -icanon min 1 time 0
printf '\e[5n'
until read -t0; do
  sleep 0.02
done
stty "$saved_settings"

дальнейшее чтение

См . Ответ @ starfry, который расширяет два решения, предоставленные @mikeserv и мной, с более подробной информацией.

Стефан Шазелас
источник
Я думаю, что, bind '"\e[0n": "echo test"'; printf '\e[5n'вероятно, ответ только для bash, который я ищу. Меня устраивает. Тем не менее, я получаю ^[[0nпечать до моего приглашения. Я обнаружил, что это вызвано, когда $PS1содержит подоболочку. Вы можете воспроизвести его, выполнив PS1='$(:)'перед командой bind. Почему это случилось, и можно ли что-нибудь сделать с этим?
Звездный день
Хотя все в этом ответе правильно, вопрос был для bash, а не для zsh. Иногда у нас нет выбора, какую оболочку использовать.
Falsenames
@Falsenames только первый абзац для zsh. Остальное зависит от оболочки или от bash. Вопросы и ответы не должны быть полезны только для пользователей Bash.
Стефан Шазелас
1
@ Starfry кажется, что вы могли бы просто поставить \rЭтерн во главе $PS1? Это должно работать, если $PS1достаточно долго. Если нет, то положи ^[[Mтуда.
mikeserv
@mikeserv - rделает трюк. Это, конечно, не мешает выводу, оно просто перезаписывается до того, как его увидит глаз. Я думаю, ^[[Mстирает строку, чтобы очистить введенный текст, если он длиннее, чем подсказка. Правильно ли это (я не смог найти его в списке для сброса ANSI, который у меня есть)?
Starfry
25

Этот ответ является разъяснением моего собственного понимания и вдохновлен @ StéphaneChazelas и @mikeserv до меня.

TL; DR

  • это невозможно сделать bashбез посторонней помощи;
  • правильный способ сделать это с помощью входного терминала, ioctl но
  • самое простое из возможных bashрешений bind.

Простое решение

bind '"\e[0n": "ls -l"'; printf '\e[5n'

Bash имеет встроенную оболочку, bindкоторая позволяет выполнять команду оболочки при получении последовательности клавиш. По сути, выходные данные команды оболочки записываются во входной буфер оболочки.

$ bind '"\e[0n": "ls -l"'

Последовательность клавиш \e[0n( <ESC>[0n) - это управляющий код терминала ANSI, который терминал отправляет, чтобы указать, что он функционирует нормально. Это отправляется в ответ на запрос отчета о состоянии устройства, который отправляется как <ESC>[5n.

Связав ответ с echoвыводом текста для вставки, мы можем внедрить этот текст, когда захотим, запросив состояние устройства, и это делается путем отправки <ESC>[5nescape-последовательности.

printf '\e[5n'

Это работает, и, вероятно, достаточно, чтобы ответить на первоначальный вопрос, потому что никакие другие инструменты не участвуют. Это чисто, bashно полагается на хорошо ведущий себя терминал (практически все).

Он оставляет отображаемый текст в командной строке готовым для использования, как если бы он был напечатан. Это может быть добавлено, отредактировано, и нажатие ENTERзаставляет это быть выполненным.

Добавьте \nк связанной команде, чтобы она выполнялась автоматически.

Однако это решение работает только в текущем терминале (что находится в рамках исходного вопроса). Он работает из интерактивного приглашения или из сценария с источником, но вызывает ошибку при использовании из подоболочки:

bind: warning: line editing not enabled

Правильное решение, описанное ниже, является более гибким, но оно опирается на внешние команды.

Правильное решение

Правильный способ ввода ввода использует tty_ioctl , системный вызов unix для управления вводом / выводом, в котором есть TIOCSTIкоманда, которую можно использовать для ввода ввода.

TIOC из « Т erminal МОК ТЛ » и ИППП от « S конца T erminal я Nput ».

Для этого нет встроенной команды bash; для этого требуется внешняя команда. В типичном дистрибутиве GNU / Linux такой команды нет, но это не сложно сделать с помощью небольшого программирования. Вот функция оболочки, которая использует perl:

function inject() {
  perl -e 'ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV' "$@"
}

Вот 0x5412код для TIOCSTIкоманды.

TIOCSTIэто константа , определенная в стандартных заголовочных файлов С со значением 0x5412. Попробуй grep -r TIOCSTI /usr/includeили посмотри /usr/include/asm-generic/ioctls.h; он включен в программы на C косвенным путем #include <sys/ioctl.h>.

Затем вы можете сделать:

$ inject ls -l
ls -l$ ls -l <- cursor here

Реализации на некоторых других языках показаны ниже (сохраните в файле, а затем в chmod +xнем):

Perl inject.pl

#!/usr/bin/perl
ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV

Вы можете создать, sys/ioctl.phкоторый определяет TIOCSTIвместо использования числового значения. Смотри здесь

питон inject.py

#!/usr/bin/python
import fcntl, sys, termios
del sys.argv[0]
for c in ' '.join(sys.argv):
  fcntl.ioctl(sys.stdin, termios.TIOCSTI, c)

Рубин inject.rb

#!/usr/bin/ruby
ARGV.join(' ').split('').each { |c| $stdin.ioctl(0x5412,c) }

С inject.c

компилировать с gcc -o inject inject.c

#include <sys/ioctl.h>
int main(int argc, char *argv[])
{
  int a,c;
  for (a=1, c=0; a< argc; c=0 )
    {
      while (argv[a][c])
        ioctl(0, TIOCSTI, &argv[a][c++]);
      if (++a < argc) ioctl(0, TIOCSTI," ");
    }
  return 0;
}

**! ** Есть другие примеры здесь .

Использование ioctlдля этого работает в подоболочках. Он также может вводить в другие терминалы, как объяснено далее.

Идем дальше (контролируем другие терминалы)

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

Расширение приведенной выше программы на C для приема аргумента командной строки, указывающего tty другого терминала, позволяет ввести этот терминал:

#include <stdlib.h>
#include <argp.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>

const char *argp_program_version ="inject - see https://unix.stackexchange.com/q/213799";
static char doc[] = "inject - write to terminal input stream";
static struct argp_option options[] = {
  { "tty",  't', "TTY", 0, "target tty (defaults to current)"},
  { "nonl", 'n', 0,     0, "do not output the trailing newline"},
  { 0 }
};

struct arguments
{
  int fd, nl, next;
};

static error_t parse_opt(int key, char *arg, struct argp_state *state) {
    struct arguments *arguments = state->input;
    switch (key)
      {
        case 't': arguments->fd = open(arg, O_WRONLY|O_NONBLOCK);
                  if (arguments->fd > 0)
                    break;
                  else
                    return EINVAL;
        case 'n': arguments->nl = 0; break;
        case ARGP_KEY_ARGS: arguments->next = state->next; return 0;
        default: return ARGP_ERR_UNKNOWN;
      }
    return 0;
}

static struct argp argp = { options, parse_opt, 0, doc };
static struct arguments arguments;

static void inject(char c)
{
  ioctl(arguments.fd, TIOCSTI, &c);
}

int main(int argc, char *argv[])
{
  arguments.fd=0;
  arguments.nl='\n';
  if (argp_parse (&argp, argc, argv, 0, 0, &arguments))
    {
      perror("Error");
      exit(errno);
    }

  int a,c;
  for (a=arguments.next, c=0; a< argc; c=0 )
    {
      while (argv[a][c])
        inject (argv[a][c++]);
      if (++a < argc) inject(' ');
    }
  if (arguments.nl) inject(arguments.nl);

  return 0;
}  

Он также отправляет новую строку по умолчанию, но, аналогично echo, он предоставляет -nопцию для ее подавления. Опция --tor --ttyтребует аргумент - ttyтерминала, который будет введен. Значение для этого можно получить в этом терминале:

$ tty
/dev/pts/20

Скомпилируйте это с gcc -o inject inject.c. Добавьте к тексту префикс, --если он содержит дефисы, чтобы синтаксический анализатор не мог правильно интерпретировать параметры командной строки. См ./inject --help. Используйте это так:

$ inject --tty /dev/pts/22 -- ls -lrt

или просто

$ inject  -- ls -lrt

ввести текущий терминал.

Внедрение в другой терминал требует административных прав, которые могут быть получены:

  • выдача команды как root,
  • используя sudo,
  • имея CAP_SYS_ADMINвозможность или
  • установка исполняемого файла setuid

Назначить CAP_SYS_ADMIN:

$  sudo setcap cap_sys_admin+ep inject

Назначить setuid:

$ sudo chown root:root inject
$ sudo chmod u+s inject

Чистый вывод

Введенный текст появляется перед подсказкой, как если бы он был напечатан до появления подсказки (что, по сути, так и было), но затем снова появляется после подсказки.

Один из способов скрыть текст, который появляется перед приглашением, состоит в том, чтобы добавить к приглашению возврат каретки ( \rне перевод строки) и очистить текущую строку ( <ESC>[M):

$ PS1="\r\e[M$PS1"

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

Другое решение отключает отображение введенных символов. Для этого используется оболочка stty:

saved_settings=$(stty -g)
stty -echo -icanon min 1 time 0
inject echo line one
inject echo line two
until read -t0; do
  sleep 0.02
done
stty "$saved_settings"

где injectодно из решений, описанных выше, или заменено на printf '\e[5n'.

Альтернативные подходы

Если ваша среда отвечает определенным предварительным условиям, у вас могут быть другие доступные методы, которые вы можете использовать для ввода данных. Если вы работаете в среде рабочего стола, то xdotool - это утилита X.Org, которая имитирует работу мыши и клавиатуры, но ваш дистрибутив может не включать ее по умолчанию. Можешь попробовать:

$ xdotool type ls

Если вы используете tmux , терминальный мультиплексор, то вы можете сделать это:

$ tmux send-key -t session:pane ls

где -tвыбирает, какой сеанс и панель для вставки. GNU Screen имеет аналогичную возможность с его stuffкомандой:

$ screen -S session -p pane -X stuff ls

Если ваш дистрибутив включает в себя пакет console-tools , то у вас может быть writevtкоманда, которая использует ioctlкак наши примеры. Однако большинство дистрибутивов не одобряют этот пакет в пользу kbd, в котором отсутствует эта функция.

Обновленная копия writevt.c может быть скомпилирована с использованием gcc -o writevt writevt.c.

Другие варианты, которые могут лучше подходить для некоторых сценариев использования, включают ожидаемый и пустой, которые предназначены для сценариев интерактивных инструментов.

Вы также можете использовать оболочку, которая поддерживает инъекцию терминала, например, zshкоторая может это сделать print -z ls.

Ответ "Ого, это умно ..."

Описанный здесь метод также обсуждается здесь и основывается на методе, обсуждаемом здесь .

Перенаправление оболочки от /dev/ptmxполучает новый псевдо-терминал:

$ $ ls /dev/pts; ls /dev/pts </dev/ptmx
0  1  2  ptmx
0  1  2  3  ptmx

Небольшой инструмент, написанный на C, который разблокирует мастер псевдотерминала (ptm) и выводит имя ведомого псевдотерминала (pts) на его стандартный вывод.

#include <stdio.h>
int main(int argc, char *argv[]) {
    if(unlockpt(0)) return 2;
    char *ptsname(int fd);
    printf("%s\n",ptsname(0));
    return argc - 1;
}

(сохранить как pts.cи скомпилировать с gcc -o pts pts.c)

Когда программа вызывается со стандартным входом, установленным в ptm, она разблокирует соответствующие точки и выводит свое имя в стандартный вывод.

$ ./pts </dev/ptmx
/dev/pts/20
  • Функция unlockpt () разблокирует подчиненное псевдотерминальное устройство, соответствующее главному псевдотерминалу, на который ссылается данный файловый дескриптор. Программа передает это как ноль, который является стандартным вводом программы .

  • Функция ptsname () возвращает имя подчиненного псевдотерминального устройства, соответствующего мастеру, указанному в данном дескрипторе файла, снова передавая ноль для стандартного ввода программы.

Процесс может быть связан с очками. Сначала получите ptm (здесь он назначен файловому дескриптору 3, открытому для чтения-записи <>перенаправлением).

 exec 3<>/dev/ptmx

Затем запустите процесс:

$ (setsid -c bash -i 2>&1 | tee log) <>"$(./pts <&3)" 3>&- >&0 &

Процессы, порожденные этой командной строкой, лучше всего иллюстрируются с помощью pstree:

$ pstree -pg -H $(jobs -p %+) $$
bash(5203,5203)─┬─bash(6524,6524)─┬─bash(6527,6527)
                             └─tee(6528,6524)
            └─pstree(6815,6815)

Выходные данные относятся к текущей оболочке ( $$), а PID ( -p) и PGID ( -g) каждого процесса показаны в скобках (PID,PGID).

Во главе дерева находится bash(5203,5203)интерактивная оболочка, в которую мы вводим команды, и ее файловые дескрипторы связывают ее с терминальным приложением, которое мы используем для взаимодействия с ней ( xtermили аналогичным образом).

$ ls -l /dev/fd/
lrwx------ 0 -> /dev/pts/3
lrwx------ 1 -> /dev/pts/3
lrwx------ 2 -> /dev/pts/3

Посмотрев на команду еще раз, первый набор скобок запустил подоболочку, 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:

$ echo 'some input' >&3 # write to subshell
$ cat <&3               # read from subshell

Subshell выполняет эту команду:

setsid -c bash -i 2>&1 | tee log

Он запускается bash(6527,6527)в интерактивном -iрежиме ( ) в новом сеансе ( setsid -cобратите внимание, что PID и PGID одинаковы). Его стандартная ошибка перенаправляется в стандартный вывод ( 2>&1) и передается по каналу, tee(6528,6524)поэтому записывается в logфайл, а также в pts. Это дает еще один способ увидеть результат подоболочки:

$ tail -f log

Поскольку подоболочка работает в bashинтерактивном режиме, ей можно отправлять команды для выполнения, как в этом примере, который отображает дескрипторы файлов подоболочки:

$ echo 'ls -l /dev/fd/' >&3

Чтение вывода subshell ( tail -f logили cat <&3) показывает:

lrwx------ 0 -> /dev/pts/17
l-wx------ 1 -> pipe:[116261]
l-wx------ 2 -> pipe:[116261]

Стандартный вход (fd 0) подключен к точкам, и оба стандартных выхода (fd 1) и ошибка (fd 2) подключены к одной и той же трубе, которая подключается к tee:

$ (find /proc -type l | xargs ls -l | fgrep 'pipe:[116261]') 2>/dev/null
l-wx------ /proc/6527/fd/1 -> pipe:[116261]
l-wx------ /proc/6527/fd/2 -> pipe:[116261]
lr-x------ /proc/6528/fd/0 -> pipe:[116261]

И посмотрите на файловые дескрипторы tee

$ ls -l /proc/6528/fd/
lr-x------ 0 -> pipe:[116261]
lrwx------ 1 -> /dev/pts/17
lrwx------ 2 -> /dev/pts/3
l-wx------ 3 -> /home/myuser/work/log

Стандартный вывод (fd 1) - это число: все, что 'tee' записывает в свой стандартный вывод, отправляется обратно в ptm. Стандартная ошибка (fd 2) - это точки, принадлежащие управляющему терминалу.

Завершение

Следующий скрипт использует технику, описанную выше. Он устанавливает интерактивный bashсеанс, который может быть введен путем записи в файловый дескриптор. Это доступно здесь и задокументировано с объяснениями.

sh -cm 'cat <&9 &cat >&9|(             ### copy to/from host/slave
        trap "  stty $(stty -g         ### save/restore stty settings on exit
                stty -echo raw)        ### host: no echo and raw-mode
                kill -1 0" EXIT        ### send a -HUP to host pgrp on EXIT
        <>"$($pts <&9)" >&0 2>&1\
        setsid -wc -- bash) <&1        ### point bash <0,1,2> at slave and setsid bash
' --    9<>/dev/ptmx 2>/dev/null       ### open pty master on <>9
starfry
источник
С самым простым bind '"\e[0n": "ls -l"'; printf '\e[5n'решением, после того, как все выходные данные будут ls -lтакже ^[[0nвыведены на Терминал, как только я нажму клавишу ввода, запустите ls -l. Есть идеи как это "спрятать" пожалуйста? Спасибо.
Али
1
Я представил одно решение, которое дает желаемый эффект: в разделе « Чистый вывод » моего ответа я предлагаю добавить возврат к приглашению, чтобы скрыть лишний текст. Я пытался, PS1="\r\e[M$PS1"прежде чем делать, bind '"\e[0n": "ls -l"'; printf '\e[5n'и это дало эффект, который вы описываете.
звездный день
Спасибо! Я полностью упустил этот момент.
Али
20

Это зависит от того, что вы имеете в виду bashтолько . Если вы имеете в виду один интерактивный bashсеанс, то ответ почти наверняка нет . И это потому, что даже когда вы вводите команду, как ls -lв командной строке на любом каноническом терминале, она bashеще даже не осознает ее - и bashдаже не участвует в этом моменте.

Скорее, то, что произошло до этого момента, заключается в том, что tty line-дисциплина ядра буферизовала и stty echoделала ввод пользователя только на экран. Он сбрасывает этот ввод своему читателю - bashв вашем примере - строка за строкой - и, как правило, также переводит \rоборотные стороны в электронные \nстроки в системах Unix - и это bashне так - и поэтому ваш исходный скрипт также не может быть осведомлен, что есть вводить вообще, пока пользователь не нажмет ENTERклавишу.

Теперь есть некоторые обходные пути. На самом деле, наиболее надежный способ не является обходным путем и включает в себя использование нескольких процессов или специально написанных программ для последовательного ввода, скрытия линейных дисциплин -echoот пользователя и вывода на экран только того, что будет сочтено целесообразным при интерпретации ввода. особенно когда это необходимо. Это может быть трудно сделать хорошо, потому что это означает написание правил интерпретации, которые могут обрабатывать произвольный ввод символов char по мере их поступления, и выписывать их одновременно без ошибок, чтобы имитировать то, что средний пользователь ожидает в этом сценарии. По этой причине, вероятно, интерактивный терминальный ввод / вывод так редко понимается - трудная перспектива не та, которая поддается дальнейшему исследованию для большинства.

Другой обходной путь может включать эмулятор терминала. Вы говорите, что проблема для вас - это зависимость от X и от xdotool. В таком случае такой обходной путь, который я собираюсь предложить, может иметь аналогичные проблемы, но я продолжу в том же духе.

printf  '\33[22;1t\33]1;%b\33\\\33[20t\33[23;0t' \
        '\025my command'

Это будет работать в xtermж / к allowwindowOpsнабору ресурсов. Сначала он сохраняет имена значков / окон в стеке, затем устанавливает строку значков терминала, а ^Umy commandзатем запрашивает, чтобы терминал ввел это имя во входную очередь, и, наконец, сбрасывает его в сохраненные значения. Он должен работать незаметно для интерактивных bashоболочек, запускаемых в xterm правильной конфигурации, но, вероятно, это плохая идея. Пожалуйста, смотрите комментарии Стефана ниже.

Здесь, однако, представлена ​​фотография моего терминала терминологии после запуска printfбита с другой escape-последовательностью на моей машине. Для каждой новой строки в printfкоманде я набирал CTRL+Vтогда CTRL+Jи потом нажимал ENTERклавишу. После этого я ничего не печатал, но, как вы можете видеть, терминал ввел my commandдля меня очередь ввода линейной дисциплины:

term_inject

Реальный способ сделать это с вложенным pty. Это как screenи tmuxаналогичная работа - и то, и другое, кстати, может сделать это возможным для вас. xtermна самом деле поставляется с небольшой программой под названием, luitкоторая также может сделать это возможным. Это не легко, хотя.

Вот один из способов, которым вы могли бы:

sh -cm 'cat <&9 &cat >&9|(             ### copy to/from host/slave
        trap "  stty $(stty -g         ### save/restore stty settings on exit
                stty -echo raw)        ### host: no echo and raw-mode
                kill -1 0" EXIT        ### send a -HUP to host pgrp on EXIT
        <>"$(pts <&9)" >&0 2>&1\       
        setsid -wc -- bash) <&1        ### point bash <0,1,2> at slave and setsid bash
' --    9<>/dev/ptmx 2>/dev/null       ### open pty master on <>9

Это ни в коем случае не переносимо, но должно работать на большинстве систем Linux с соответствующими разрешениями на открытие /dev/ptmx. Мой пользователь находится в ttyгруппе, которой достаточно в моей системе. Вам также понадобится ...

<<\C cc -xc - -o pts
#include <stdio.h>
int main(int argc, char *argv[]) {
        if(unlockpt(0)) return 2;
        char *ptsname(int fd);
        printf("%s\n",ptsname(0));
        return argc - 1;
}
C

... который при запуске в системе 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нужно сделать, это:

echo echo hey >&9

... поговорить с самим собой.

Вот еще одна картина:

введите описание изображения здесь

mikeserv
источник
2
На каких терминалах вам удалось это сделать? Подобными вещами злоупотребляли в старину, и в настоящее время их следует отключить по умолчанию. С помощью xtermвы все равно можете запросить заголовок значка с помощью, \e[20tно только если настроено с allowWindowOps: true.
Стефан Шазелас
Это CVE-2003-0063
Стефан
@ StéphaneChazelas, который работает в терминологии, но я уверен, что он также работает в терминале gnome, в терминале kde (я забыл его имя, и я думаю, что есть другой выход) , и, как вы говорите, w / xtermw / правильный конфигурации. Однако, если использовать xterm, вы можете читать и записывать буфер копирования / вставки, и, кроме того, я думаю, что он становится еще проще. Xterm также имеет escape-последовательности для изменения / воздействия на сам термин описания.
mikeserv
Я не могу заставить это работать ни в чем, кроме терминологии (которая, кстати, имеет несколько других подобных уязвимостей). Этот CVE, которому более 12 лет и относительно известный, я был бы удивлен, если бы какой-либо из эмуляторов основного терминала имел такую ​​же уязвимость. Обратите внимание, что с xterm это \e[20t(не \e]1;?\a)
Стефан Шазелас
8

Хотя ioctl(,TIOCSTI,) ответ Стефана Шазеласа является, конечно, правильным ответом, некоторые люди могут быть достаточно довольны этим частичным, но тривиальным ответом: просто вставьте команду в стек истории, тогда пользователь может переместиться на 1 строку вверх по истории, чтобы найти команда.

$ history -s "ls -l"
$ echo "move up 1 line in history to get command to run"

Это может стать простым скриптом, который имеет собственную историю в 1 строку:

#!/bin/bash
history -s "ls -l"
read -e -p "move up 1 line: "
eval "$REPLY"

read -eразрешает редактирование ввода с помощью readline, -pявляется подсказкой.

meuh
источник
Это будет работать только в функциях оболочки, или если скрипт был получен ( . foo.shили `source foo.sh вместо запуска в подоболочке). Хотя интересный подход. Подобным хаком, который требует изменения контекста вызывающей оболочки, было бы установить пользовательское завершение, которое расширило пустую строку до чего-то, а затем восстановило старый обработчик завершения.
Питер Кордес
@PeterCordes ты прав. Я воспринимал вопрос слишком буквально. Но я добавил пример простого скрипта, который может работать.
meuh
@mikeserv Эй, это просто простое решение, которое может быть полезно для некоторых людей. Вы даже можете удалить, evalесли у вас есть простые команды для редактирования, без каналов, перенаправления и т. Д.
meuh
1

Боже мой, мы пропустили простое решение, встроенное в bash : readкоманда имеет опцию -i ..., которая при использовании с -eвыталкивает текст во входной буфер. Со страницы руководства:

текст

Если для чтения строки используется readline, текст помещается в буфер редактирования перед началом редактирования.

Поэтому создайте небольшую функцию bash или сценарий оболочки, который принимает команду для представления пользователю и запускает или оценивает их ответ:

domycmd(){ read -e -i "$*"; eval "$REPLY"; }

Это, без сомнения, использует ioctl (, TIOCSTI,), который существует уже более 32 лет, как он уже существовал в 2.9BSD ioctl.h .

meuh
источник
1
Интересный с подобным эффектом, но это не вводит к подсказке все же.
Звездный день
на 2-й мысли ты прав. Bash не нуждается в TIOCSTI, поскольку он выполняет все операции ввода-вывода.
meuh