Чи може bash записувати до власного вхідного потоку?


39

Чи можна в інтерактивній оболонці bash ввести команду, яка виводить якийсь текст, щоб він відображався в наступному командному рядку, як ніби користувач набрав цей текст у цьому запиті?

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

Цього можна досягти, xdotoolале це працює лише тоді, коли термінал знаходиться у вікні X і лише якщо він встановлений.

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

Це можна зробити лише за допомогою bash?


Я думаю, що з Expect це не повинно бути важким, якщо ви зможете це терпіти і нехай він запускає нижню частину корпусу; але я не пам’ятаю цього достатньо, щоб опублікувати фактичну відповідь.
трійчатка

Відповіді:


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 reportпослідовністю відходу: <ESC>[5nяка термінали незмінно відповідає (так, як вхід) як <ESC>[0nі прив'язує її до рядка, який потрібно вставити:

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

Якщо в GNU screen, ви також можете:

screen -X stuff 'echo test'

Тепер, за винятком підходу TIOCSTI ioctl, ми просимо емулятор термінала надіслати нам якусь рядок так, ніби введений. Якщо цей рядок вийшов раніше readline( bashредактор рядків) відключив локальне відлуння терміналу, то ця рядок відображатиметься не під запитом оболонки, трохи змішуючи дисплей.

Щоб вирішити це, ви можете або затримати надсилання запиту до терміналу, щоб переконатися, що відповідь надходить, коли відлуння було відключено лінією читання.

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'напевно, лише відповідь, яку я шукаю. Це працює для мене. Однак я ^[[0nнадруковуюсь і перед моїм підказкою. Я виявив, що це викликано, коли $PS1міститься підскладок. Ви можете відтворити його, виконавши PS1='$(:)'перед командою прив'язки. Чому це могло б статися і чи можна з цим щось зробити?
starfry

Хоча в цій відповіді все правильно, питання було за баш, а не зш. Іноді у нас немає вибору, яку оболонку використовувати.
Підроблені імена

@Falsenames лише перший абзац призначений для zsh. Решта - або агностичні, або специфічні для башма. Питання та відповіді не повинні бути корисними лише користувачам, що базуються.
Stéphane Chazelas

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>[5nпослідовності запуску.

printf '\e[5n'

Це працює і, ймовірно, достатньо для відповіді на початкове запитання, оскільки жодних інших інструментів не задіяно. Це чисто, bashале покладається на добре поводиться термінал (практично всі є).

Він залишає відлунне текст у командному рядку готовим до використання так, як ніби він був введений. Він може бути доданий, відредагований і натискання ENTERвикликає його виконання.

Додайте \nдо зв'язаної команди, щоб вона виконувалася автоматично.

Однак це рішення працює лише в поточному терміналі (що знаходиться в межах початкового питання). Він працює з інтерактивного підказки або з скрипту, який використовується з джерела, але він викликає помилку, якщо використовується з додаткової оболонки:

bind: warning: line editing not enabled

Далі описане правильне рішення є більш гнучким, але воно покладається на зовнішні команди.

Правильне рішення

Правильний спосіб введення вводу використовує tty_ioctl , системний виклик unix для управління введення-виводу, який має TIOCSTIкоманду, яку можна використовувати для введення вводу.

TIOC від " T erminal IOC tl " і STI від " S end T erminal I nput ".

Для цього немає вбудованої команди bash; для цього потрібна зовнішня команда. У типовому дистрибутиві GNU / Linux не існує такої команди, але досягти її з невеликим програмуванням не складно. Ось функція оболонки, яка використовує perl:

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

Ось 0x5412код для TIOCSTIкоманди.

TIOCSTI- константа, визначена в стандартних файлах заголовка C зі значенням 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можливість придушити його. Для параметра --tабо --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 має аналогічні можливості зі своєю stuffкомандою:

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

Якщо ваш дистрибутив включає пакет консольних інструментів, то у вас може бути 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 3>&-підкатегорії закритий ( ), щоб ptm був недоступний для нього. Її стандартний вхід (fd 0), який є пунктами, які були відкриті для читання / запису, переспрямовується (насправді fd скопіюється - >&0) на його стандартний вихід (fd 1).

Це створює додаткову оболонку зі стандартним входом і виходом, підключеним до очок. Він може бути надісланий введенням, записавши на ptm, а його вихід можна побачити, прочитавши з ptm:

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

Підрозділ виконує цю команду:

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

Він працює bash(6527,6527)в інтерактивному ( -i) режимі в новому сеансі ( setsid -cзауважте, PID і PGID однакові). Його стандартна помилка переспрямовується на його стандартний вихід ( 2>&1) і передається через tee(6528,6524)так, що вона записується у logфайл, а також у бали. Це дає ще один спосіб бачити вихідний пакет:

$ tail -f log

Оскільки нижня оболонка працює в bashінтерактивному режимі, їй можна надіслати команди для виконання, як цей приклад, який відображає дескриптори файлів підкашлю:

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

Читання результатів ( tail -f logабо cat <&3) підскладок виявляє:

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

Стандартний вхід (fd 0) підключений до pts, і обидва стандартні виходи (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

З найпростішим 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'і це дало ефект, який ви описуєте.
starfry

Дякую! Я повністю пропустив цю точку.
Алі

20

Це залежить від того, що ви маєте на увазі bashлише . Якщо ви маєте на увазі один, інтерактивний bashсеанс, то відповідь майже точно не . І це тому, що навіть коли ви вводите команду, як ls -lу командному рядку на будь-якому канонічному терміналі, про bashце ще не знаєте - і bashнавіть не залучаєтесь до цього моменту.

Швидше за все, що сталося до цього моменту, це те, що чітка дисципліна дисципліни ядра захистила і stty echoвведе користувач лише на екран. Це вмикає цей вклад для його читача - bashу вашому прикладі - рядок за рядком - і, як правило, перекладає \rетюри на \newlines в системах Unix - і bashце не так, і тому не можна також визначити ваш скрипт, що знайдеться введення взагалі, поки користувач не натисне ENTERклавішу.

Зараз є деякі роботи. Найбільш надійний - це взагалі не обхід, який передбачає використання декількох процесів або спеціально написаних програм для введення послідовності, приховування від користувача лінійки дисципліни -echoта записування на екран лише того, що вважають за потрібне під час інтерпретації вводу особливо при необхідності. Це може бути важко зробити, тому що це означає, що написання правил інтерпретації, які можуть обробляти довільну вхідну таблицю за допомогою символу char під час надходження та одночасно виписувати їх без помилок, щоб імітувати те, що очікував середній користувач у такому сценарії. З цієї причини, ймовірно, що інтерактивний термінал вводу / виводу так рідко розуміється - перспектива, що складно не є такою, яка більшості піддається подальшому дослідженню.

Інша обробка може включати емулятор терміналу. Ви кажете, що проблема для вас - це залежність від X і від xdotool. У такому випадку така обробка, яку я буду пропонувати, може мати подібні проблеми, але я продовжую її виконувати так само.

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

Це буде працювати в xtermw / allowwindowOpsнаборі ресурсів. Спершу він зберігає імена піктограм / вікон у стеку, потім встановлює рядок значків термінала, ^Umy commandпісля чого вимагає, щоб термінал вводив це ім'я у чергу вводу, а останній скидає його до збережених значень. Це повинно працювати непомітно для інтерактивних bashоболонок, що працюють у xterm w / правильній конфігурації, але це, мабуть, погана ідея. Будь ласка, дивіться коментарі Стефана нижче.

Однак ось фотографія, яку я зробив із свого термінального терміналу після запуску printfбітової та іншої послідовності втечі на моїй машині. Для кожного нового рядка в 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 та записувати в його stdout ім'я pty пристрою, який він просто розблокував. Я писав це, коли працював над ... Як мені прийти за цим піти і що з ним зробити? .

У будь-якому випадку, те, що наведений вище біт коду, - це запускати bashоболонку в pty-шар під поточним tty. bashсказано записати весь вихід на підлеглий pty, і поточний tty налаштований не на -echoйого вхід, ні на його буфер, а натомість передати його (в основному) raw на те cat, на яке він копіює bash. І весь час інший, фоновий режим catкопіює весь підлеглий вихід до поточного tty.

Здебільшого наведена вище конфігурація була б абсолютно марною - просто зайвою, в основному - за винятком того, що ми запускаємо bashкопію власного pty master fd <>9. Це означає, що bashможна вільно записати у власний потік введення з простим перенаправленням. Все, що bashпотрібно зробити, це:

echo echo hey >&9

... поговорити сам із собою.

Ось ще одна картинка:

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


2
У яких терміналах вам вдалося налагодити роботу? Такі речі піддавалися зловживанням за старих часів, і їх тепер слід відключити за замовчуванням. З xterm, ви все ще можете запитувати заголовок піктограми, \e[20tале лише якщо налаштовано allowWindowOps: true.
Стефан Шазелас


@ StéphaneChazelas, який працює в термінології, але я впевнений, що він також працює в терміналі gnome, в терміналі kde (я забуваю його ім'я, і ​​я думаю, що є інший втечу) , і, як ви кажете, w / xtermw / належне конфігурація. З власним xterm, однак, ви можете читати та писати буфер копіювання / вставки, і, окрім цього, це стає простішим, я думаю. Xterm також має послідовності втечі для зміни / впливу на сам опис терміна.
mikeserv

Я не можу змусити це працювати ні в чому, крім термінології (у btw є кілька інших подібних уразливостей). Цей CVE, який старше 12 років і відносно добре відомий, був би здивований, якщо будь-який з головних емуляторів терміналів мав таку ж вразливість. Зауважте, що з xterm, це \e[20t(ні \e]1;?\a)
Stéphane Chazelas


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дає змогу редагувати вхідні дані, -pє підказкою.


Це буде працювати лише у функціях оболонки, або якщо скрипт був створений ( . foo.shабо `source foo.sh, а не запускається в нижній частині). Хоча цікавий підхід. Аналогічний хак, який потребує зміни контексту оболонки виклику, полягає у встановленні спеціального завершення, яке розширить порожню рядок до чогось, а потім відновить старий обробник завершення.
Пітер Кордес

@PeterCordes ви праві. Я надто буквально сприймав це питання. Але я додав приклад простого сценарію, який міг би працювати.
meuh

@mikeserv Гей, це просто просте рішення, яке може бути корисним для деяких людей. Ви навіть можете видалити , evalякщо у вас є прості команди для редагування, без труб і т.д. перенаправлення
meuh

1

О, моє слово, ми пропустили просте рішення, вбудоване в bash : readкоманда має опцію -i ..., яка при використанні з -e, штовхає текст у вхідний буфер. На чоловіковій сторінці:

-i текст

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

Отже, створіть невелику функцію bash або скрипт оболонки, який приймає команду для представлення користувачеві та виконує або оцінює їх відповідь:

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

Без сумніву, використовується йоктл (, TIOCSTI,), який існує вже більше 32 років, оскільки він вже існував у 2.9BSD ioctl.h .


1
Цікавий той, що має схожий ефект, але він не вводить підказку, хоча.
starfry

по 2-й думках ви праві. bash не потрібно TIOCSTI, оскільки він робить все введення-виведення сам.
meuh
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.