Czy bash może pisać we własnym strumieniu wejściowym?


39

Czy w interaktywnej powłoce bash można wprowadzić polecenie, które wyświetla tekst, tak aby pojawił się w następnym wierszu polecenia, tak jakby użytkownik wpisał ten tekst w tym wierszu?

Chcę mieć możliwość sourceskryptu, który wygeneruje wiersz polecenia i wyśle ​​go tak, aby pojawił się, gdy monit powróci po zakończeniu skryptu, aby użytkownik mógł opcjonalnie edytować go przed naciśnięciem go, enteraby go uruchomić.

Można to osiągnąć, xdotoolale działa to tylko wtedy, gdy terminal znajduje się w oknie X i tylko wtedy, gdy jest zainstalowany.

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

Czy można to zrobić tylko za pomocą bash?


Myślę, że nie powinno to być trudne w przypadku Oczekiwania, jeśli możesz to tolerować i sprawić, by prowadził podpowłokę; ale nie pamiętam wystarczająco dużo, aby opublikować prawdziwą odpowiedź.
tripleee

Odpowiedzi:


40

Za pomocą zshmożna print -zwstawić tekst do bufora edytora linii dla następnego monitu:

print -z echo test

wypełniłby edytor linii, za pomocą echo testktórego możesz edytować w następnym monicie.

Nie sądzę, że bashma podobną funkcję, jednak w wielu systemach można przygotować bufor wejściowy urządzenia końcowego za pomocą TIOCSTI ioctl():

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

Wstawia echo testsię do bufora wejściowego urządzenia końcowego, tak jakby został odebrany z terminala.

Bardziej przenośną odmianą podejścia @ mike,Terminology która nie poświęca bezpieczeństwa, byłoby wysłanie emulatorowi terminala dość standardowej query status reportsekwencji ucieczki: <ESC>[5nktóre terminale niezmiennie odpowiadają (tak jak dane wejściowe) jako <ESC>[0ni wiążą to z łańcuchem, który chcesz wstawić:

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

Jeśli screenjesteś w GNU , możesz także:

screen -X stuff 'echo test'

Teraz, z wyjątkiem metody ioctl TIOCSTI, prosimy emulator terminala, aby przesłał nam ciąg znaków, jakby był wpisany. Jeśli to ciąg jest przed readline( bash„s edytora linii) ma wyłączoną terminala lokalnego echa, to ciąg będzie wyświetlany nie w skorupkach szybkiego, brudząc się wyświetlacz lekko.

Aby obejść ten problem, możesz opóźnić wysyłanie żądania do terminala, aby upewnić się, że odpowiedź dotrze, gdy echo zostanie wyłączone przez readline.

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

(tutaj przy założeniu, że sleepobsługuje rozdzielczość poniżej sekundy).

Idealnie byłoby zrobić coś takiego:

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

Jednak bash(w przeciwieństwie do zsh) nie ma wsparcia dla takich, wait-until-the-response-arrivesktóre nie czytają odpowiedzi.

Ma jednak has-the-response-arrived-yetfunkcję 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"

Dalsza lektura

Zobacz odpowiedzi @ starfry, które rozszerzają się na dwa rozwiązania podane przez @mikeserv i mnie z kilkoma bardziej szczegółowymi informacjami.


Myślę, że bind '"\e[0n": "echo test"'; printf '\e[5n'chyba jedyna odpowiedź, której szukam. Mi to pasuje. Jednak ^[[0ndrukuję się również przed monitem. Odkryłem, że jest to spowodowane, gdy $PS1zawiera podpowłokę. Możesz go odtworzyć, wykonując PS1='$(:)'przed poleceniem bind. Dlaczego tak się dzieje i czy można coś z tym zrobić?
starfry

Chociaż wszystko w tej odpowiedzi jest poprawne, pytanie dotyczyło bash, a nie zsh. Czasami nie mamy wyboru, jakiej powłoki użyć.
Falsenames,

@Falsenames tylko pierwszy akapit dotyczy zsh. Reszta jest zależna od powłoki lub specyficzna dla bash. Pytania i odpowiedzi nie muszą być przydatne tylko do bashowania użytkowników.
Stéphane Chazelas,

1
@ starfry wydaje się, że możesz po prostu postawić \return na czele $PS1? To powinno zadziałać, jeśli $PS1jest wystarczająco długie. Jeśli nie, to umieść ^[[Mtam.
mikeserv

@mikeserv - załatwia sprawę r. To oczywiście nie przeszkadza w wyjściu, jest po prostu nadpisywane, zanim oko go zobaczy. ^[[MWydaje mi się, że usuwa wiersz, aby usunąć wstrzyknięty tekst, jeśli jest on dłuższy niż monit. Czy to prawda (nie mogłem tego znaleźć na liście zmian ANSI, którą mam)?
starfry

24

Ta odpowiedź ma na celu wyjaśnienie mojego rozumienia i jest inspirowana przez @ StéphaneChazelas i @mikeserv przede mną.

TL; DR

  • nie jest to możliwe bashbez pomocy zewnętrznej;
  • poprawnym sposobem na to jest wejście terminala wysyłania, ioctl ale
  • najłatwiejsze w użyciu bashrozwiązanie bind.

Proste rozwiązanie

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

Bash ma wbudowaną powłokę o nazwie, bindktóra pozwala na wykonanie polecenia powłoki po otrzymaniu sekwencji klawiszy. Zasadniczo dane wyjściowe polecenia powłoki są zapisywane w buforze wejściowym powłoki.

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

Sekwencja klawiszy \e[0n( <ESC>[0n) jest kodem ucieczki terminala ANSI, który terminal wysyła w celu wskazania, że ​​działa normalnie. Wysyła to w odpowiedzi na żądanie raportu o stanie urządzenia, które jest wysyłane jako <ESC>[5n.

Wiążąc odpowiedź na wartość echowyjściową tekstu do wstrzyknięcia, możemy wstrzyknąć ten tekst w dowolnym momencie, żądając statusu urządzenia, a to odbywa się poprzez wysłanie <ESC>[5nsekwencji specjalnej.

printf '\e[5n'

To działa i prawdopodobnie wystarczy, aby odpowiedzieć na pierwotne pytanie, ponieważ nie są w to zaangażowane żadne inne narzędzia. Jest czysty, bashale opiera się na dobrze zachowującym się terminalu (praktycznie wszyscy są).

Pozostawia echem tekst w wierszu poleceń gotowy do użycia, tak jakby został wpisany. Można go dodawać, edytować i naciskać, co ENTERpowoduje jego wykonanie.

Dodaj \ndo powiązanego polecenia, aby było ono wykonywane automatycznie.

Jednak to rozwiązanie działa tylko w bieżącym terminalu (co wchodzi w zakres pierwotnego pytania). Działa z interaktywnego monitu lub ze skryptu źródłowego, ale powoduje błąd, jeśli jest używany z podpowłoki:

bind: warning: line editing not enabled

Prawidłowe rozwiązanie opisane poniżej jest bardziej elastyczne, ale opiera się na zewnętrznych poleceniach.

Prawidłowe rozwiązanie

Właściwy sposób wstrzykiwania danych wejściowych wykorzystuje tty_ioctl , systemowe wywołanie systemu Unix dla I / O Control, które ma TIOCSTIpolecenie, którego można użyć do wprowadzenia danych wejściowych.

TIOC z " T erminal MKOl tl " i STI z " S końca T erminal I nPrzenieś ".

Nie ma w bashtym wbudowanego polecenia ; wykonanie tego wymaga zewnętrznego polecenia. W typowej dystrybucji GNU / Linux nie ma takiego polecenia, ale nie jest to trudne do wykonania przy odrobinie programowania. Oto funkcja powłoki, która używa perl:

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

Oto 0x5412kod TIOCSTIpolecenia.

TIOCSTIjest stałą zdefiniowaną w standardowych plikach nagłówka C z wartością 0x5412. Spróbuj grep -r TIOCSTI /usr/includelub zajrzyj do środka /usr/include/asm-generic/ioctls.h; jest zawarty w programach C pośrednio przez #include <sys/ioctl.h>.

Następnie możesz:

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

Implementacje w niektórych innych językach pokazano poniżej (zapisz w pliku, a następnie chmod +x):

Perl inject.pl

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

Możesz wygenerować, sys/ioctl.phktóry definiuje TIOCSTIzamiast używać wartości liczbowej. Zobacz tutaj

Pyton 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)

Rubin inject.rb

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

do inject.c

Połącz z 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;
}

**! ** Istnieją dalsze przykłady tutaj .

Używanie ioctldo tego działa w podpowłokach. Może również wstrzykiwać do innych zacisków, jak wyjaśniono poniżej.

Dalszy rozwój (sterowanie innymi terminalami)

To wykracza poza zakres pierwotnego pytania, ale możliwe jest wstrzykiwanie znaków do innego terminala, pod warunkiem posiadania odpowiednich uprawnień. Zwykle oznacza to bycie root, ale zobacz poniżej inne sposoby.

Rozszerzenie programu C podanego powyżej, aby zaakceptować argument wiersza poleceń określający tty innego terminala, umożliwia wstrzyknięcie do tego terminala:

#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;
}  

Domyślnie wysyła również nowy wiersz, ale podobnie jak echoumożliwia -nopcję jego zniesienia. Opcja --tlub --ttywymaga argumentu - ttyterminalu, który ma zostać wstrzyknięty. Wartość tego można uzyskać w tym terminalu:

$ tty
/dev/pts/20

Skompiluj to z gcc -o inject inject.c. Prefiks tekstu do wstrzyknięcia, --jeśli zawiera on łączniki, aby zapobiec błędnej interpretacji parsera argumentów opcji wiersza polecenia. Zobaczyć ./inject --help. Użyj tego w ten sposób:

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

Lub tylko

$ inject  -- ls -lrt

wstrzyknąć aktualny terminal.

Wstrzyknięcie do innego terminalu wymaga uprawnień administracyjnych, które można uzyskać poprzez:

  • wydanie polecenia jako root,
  • za pomocą sudo,
  • posiadający CAP_SYS_ADMINzdolność lub
  • ustawianie pliku wykonywalnego setuid

Aby przypisać CAP_SYS_ADMIN:

$  sudo setcap cap_sys_admin+ep inject

Aby przypisać setuid:

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

Czysta wydajność

Wstrzyknięty tekst pojawia się przed monitem, tak jakby został wpisany przed wyświetleniem monitu (który w rzeczywistości był), ale potem pojawia się ponownie po monicie.

Jednym ze sposobów ukrycia tekstu wyświetlanego przed pytaniem jest dodanie znaku zachęty ze znakiem powrotu karetki ( \rbez podawania wiersza) i wyczyszczenie bieżącej linii ( <ESC>[M):

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

Spowoduje to jednak wyczyszczenie tylko linii, w której pojawia się monit. Jeśli wstrzyknięty tekst zawiera znaki nowej linii, nie będzie to działać zgodnie z przeznaczeniem.

Inne rozwiązanie wyłącza echo wstrzykiwanych znaków. Opakowanie używa sttydo tego celu:

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"

gdzie injectjest jedno z opisanych powyżej rozwiązań lub zastąpione przez printf '\e[5n'.

Alternatywne podejścia

Jeśli twoje środowisko spełnia pewne wymagania wstępne, możesz mieć inne dostępne metody, których możesz użyć do wstrzyknięcia danych wejściowych. Jeśli pracujesz w środowisku komputerowym, xdotool to narzędzie X.Org , które symuluje działanie myszy i klawiatury, ale twoja dystrybucja może nie zawierać go domyślnie. Możesz spróbować:

$ xdotool type ls

Jeśli używasz multipleksera terminalowego tmux , możesz to zrobić:

$ tmux send-key -t session:pane ls

gdzie -twybiera sesję i panel do wstrzyknięcia. GNU Screen ma podobne możliwości ze swoim stuffpoleceniem:

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

Jeśli twoja dystrybucja zawiera pakiet narzędzi konsolowych , możesz mieć writevtkomendę, która używa ioctlpodobnie jak nasze przykłady. Jednak większość dystrybucji zdezaktualizowała ten pakiet na rzecz kbd, który nie ma tej funkcji.

Zaktualizowaną kopię pliku writevt.c można skompilować za pomocą gcc -o writevt writevt.c.

Inne opcje, które mogą lepiej pasować do niektórych przypadków użycia, obejmują oczekiwanie i puste, które zostały zaprojektowane w celu umożliwienia skryptowania interaktywnych narzędzi.

Możesz także użyć powłoki, która obsługuje iniekcję terminalną, na przykład zshco może zrobić print -z ls.

Odpowiedź „Wow, to sprytne ...”

Metoda opisana tutaj jest także omawiane tutaj i opiera się na metodzie omówione tutaj .

Przekierowanie powłoki z /dev/ptmxdostaje nowy pseudo-terminal:

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

Małe narzędzie napisane w C, które odblokowuje pseudoterminal master (ptm) i wysyła nazwę pseudoterminalu slave (pts) na standardowe wyjście.

#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;
}

(zapisz jako pts.ci skompiluj z gcc -o pts pts.c)

Kiedy program jest wywoływany ze standardowym wejściem ustawionym na ptm, odblokowuje odpowiednie punkty i wypisuje swoją nazwę na standardowe wyjście.

$ ./pts </dev/ptmx
/dev/pts/20
  • Funkcja unlockpt () odblokowuje pseudoterminal podrzędny odpowiadający pseudoterminalowi głównemu, do którego odwołuje się dany deskryptor pliku. Program przyjmuje to jako zero, co jest standardowym wejściem programu .

  • Funkcja ptsname () zwraca nazwę urządzenia pseudoterminalnego slave odpowiadającego urządzeniu nadrzędnemu określonemu przez dany deskryptor pliku, ponownie przekazując zero dla standardowego wejścia programu.

Proces można podłączyć do pkt. Najpierw pobierz ptm (tutaj jest przypisany do deskryptora pliku 3, otwarty odczyt-zapis przez <>przekierowanie).

 exec 3<>/dev/ptmx

Następnie rozpocznij proces:

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

Procesy powstałe w tym wierszu poleceń najlepiej ilustrują pstree:

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

Dane wyjściowe odnoszą się do bieżącej powłoki ( $$), a PID ( -p) i PGID ( -g) każdego procesu są pokazane w nawiasach (PID,PGID).

Na czele drzewa znajduje bash(5203,5203)się interaktywna powłoka, w której wpisujemy polecenia, a jej deskryptory plików łączą ją z aplikacją terminalową, z której korzystamy do interakcji ( xtermlub podobnej).

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

Ponownie patrząc na komendę, pierwszy zestaw nawiasów uruchomił podpowłokę bash(6524,6524)), a deskryptor pliku 0 ( standardowe wejście ) jest przypisany do pts (który jest otwierany do odczytu i zapisu <>), jak zwrócony przez inną podpowłokę wykonaną w ./pts <&3celu odblokowania pts skojarzone z deskryptorem pliku 3 (utworzonym w poprzednim kroku, exec 3<>/dev/ptmx).

Deskryptor pliku podpowłoki 3 jest zamknięty ( 3>&-), więc ptm nie jest dla niego dostępny. Jego standardowe wejście (fd 0), czyli pts, które zostały otwarte do odczytu / zapisu, jest przekierowywane (tak naprawdę fd jest kopiowane - >&0) na standardowe wyjście (fd 1).

To tworzy podpowłokę ze standardowym wejściem i wyjściem podłączonym do pts. Można go przesłać, pisząc do ptm, a jego wynik można zobaczyć, czytając z ptm:

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

Podshell wykonuje następujące polecenie:

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

Działa bash(6527,6527)w -itrybie interaktywnym ( ) w nowej sesji ( setsid -czauważ, że PID i PGID są takie same). Standardowy błąd jest przekierowywany do standardowego wyjścia ( 2>&1) i przesyłany potokowo, tee(6528,6524)dzięki czemu jest zapisywany zarówno w logpliku, jak i w pts. To daje inny sposób, aby zobaczyć dane wyjściowe podpowłoki:

$ tail -f log

Ponieważ podpowłoka działa bashinteraktywnie, można jej wysyłać polecenia do wykonania, tak jak w tym przykładzie, który wyświetla deskryptory plików podpowłoki:

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

Czytanie wyników ( tail -f loglub cat <&3) podpowłoki ujawnia:

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

Standardowe wejście (fd 0) jest podłączone do pts, a oba standardowe wyjście (fd 1) i błąd (fd 2) są podłączone do tej samej rury, tej, która łączy się z 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]

I spojrzenie na deskryptory plików 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

Standardowe wyjście (fd 1) to pts: wszystko, co „tee” zapisuje na standardowe wyjście, jest wysyłane z powrotem do ptm. Błąd standardowy (fd 2) to punkty należące do terminala sterującego.

Podsumowując

Poniższy skrypt używa techniki opisanej powyżej. Konfiguruje bashsesję interaktywną, którą można wstrzyknąć, pisząc do deskryptora pliku. Jest dostępny tutaj i udokumentowany objaśnieniami.

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

W najprostszym bind '"\e[0n": "ls -l"'; printf '\e[5n'rozwiązaniu, po tym wszystkim, wyjście ls -lrównież ^[[0nzostanie wyprowadzone na terminalu, gdy tylko naciśniesz klawisz Enter, a następnie uruchom ls -l. Jakieś pomysły, jak to „ukryć”, proszę? Dziękuję Ci.
Ali

1
Przedstawiłem jedno rozwiązanie, które daje efekt, którego szukasz - w sekcji czystego wyniku mojej odpowiedzi sugeruję dodanie powrotu do monitu, aby ukryć zbędny tekst. Próbowałem PS1="\r\e[M$PS1"wcześniej, bind '"\e[0n": "ls -l"'; printf '\e[5n'a to dało efekt, który opisujesz.
starfry

Dziękuję Ci! Całkowicie mi tego brakowało.
Ali

20

To zależy co masz na myśli bashtylko . Jeśli masz na myśli pojedynczą, interaktywną bashsesję, odpowiedź jest prawie na pewno nie . Dzieje się tak, ponieważ nawet po wprowadzeniu polecenia, takiego jak ls -lw wierszu polecenia na dowolnym kanonicznym terminalu, bashnie jest on jeszcze nawet tego świadomy - i bashnawet nie jest w tym momencie zaangażowany.

Do tego momentu doszło do tego, że dyscyplina liniowa tty jądra zbuforowała i stty echod wkład użytkownika tylko do ekranu. To wypłukuje że wejście do swojego czytnika - bash, w przykładowym przypadku - linia po linii - i na ogół przekłada \returns do \newlines na systemach Unix, jak również - a tak bashnie jest - a więc nie może być Twój source skrypt - uświadomić istnieje wprowadzać w ogóle, dopóki użytkownik nie naciśnie ENTERklawisza.

Teraz jest kilka obejść. Najbardziej niezawodny wcale nie jest obejście problemu i wymaga użycia wielu procesów lub specjalnie napisanych programów do sekwencyjnego wprowadzania danych, ukrywania dyscypliny liniowej -echoprzed użytkownikiem i zapisywania na ekranie tylko tego, co zostanie uznane za właściwe podczas interpretacji danych wejściowych szczególnie w razie potrzeby. Może to być trudne do zrobienia, ponieważ oznacza pisanie reguł interpretacji, które mogą obsłużyć dowolny znak wejściowy char przez znak wejściowy i wypisać go jednocześnie bez pomyłki, aby zasymulować to, czego oczekiwałby przeciętny użytkownik w tym scenariuszu. Z tego powodu prawdopodobnie interaktywne terminale we / wy są tak rzadko dobrze rozumiane - perspektywa tak trudna nie jest tą, która w większości przypadków wymaga dalszych badań.

Inne obejście może obejmować emulator terminala. Mówisz, że problemem dla ciebie jest zależność od X i dalej xdotool. W takim przypadku takie obejście, które zamierzam zaoferować, może mieć podobne problemy, ale pójdę dalej z tym samym.

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

To zadziała w xtermzestawie allowwindowOpszasobów. Najpierw zapisuje nazwy ikon / okien na stosie, następnie ustawia ciąg ikon terminala, ^Umy commanda następnie żąda, aby terminal wstrzyknął tę nazwę do kolejki wejściowej, a ostatnio resetuje ją do zapisanych wartości. Powinien działać niewidocznie dla interaktywnych bashpowłok działających w xterm / w odpowiedniej konfiguracji - ale to prawdopodobnie zły pomysł. Zobacz komentarze Stéphane poniżej.

Tutaj jednak zrobiłem zdjęcie mojego terminala po uruchomieniu printfbitów z inną sekwencją ucieczki na moim komputerze. Dla każdej nowej linii w printfpoleceniu wpisałem CTRL+Vwtedy, CTRL+Ja następnie nacisnąłem ENTERklawisz. Później nic nie wpisałem, ale, jak widać, terminal wstrzyknął my commandmi kolejkę wprowadzania linii:

term_inject

Prawdziwym sposobem na to jest zagnieżdżenie pty. To jak screeni tmuxpodobna praca - które, nawiasem mówiąc, mogą ci to umożliwić. xtermw rzeczywistości jest wyposażony w mały program o nazwie, luitktóry może to umożliwić. Nie jest to jednak łatwe.

Oto jeden ze sposobów:

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

Nie jest to bynajmniej przenośne, ale powinno działać na większości systemów Linux, mając odpowiednie uprawnienia do otwierania /dev/ptmx. Mój użytkownik jest w ttygrupie, która wystarczy w moim systemie. Będziesz także potrzebował ...

<<\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

... który, działając na systemie GNU (lub dowolnym innym ze standardowym kompilatorem C, który może również czytać ze standardowego wejścia) , wypisze mały plik wykonywalny o nazwie pts, który uruchomi unlockpt()funkcję na swoim standardowym interfejsie i zapisze na standardowym nazwa urządzenia pty, które właśnie odblokowało. Napisałem to, pracując nad ... Jak uzyskać ten pty i co mogę z tym zrobić? .

W każdym razie powyższy fragment kodu uruchamia bashpowłokę w warstwie pod bieżącym tty. bashmówi się, aby zapisywał wszystkie dane wyjściowe na slave pty, a bieżący tty jest skonfigurowany nie zarówno do -echowejścia, jak i do buforowania, ale zamiast tego przekazuje (głównie) raw do cat, do którego kopiuje bash. I przez cały czas inne w tle catkopiują wszystkie dane wyjściowe slave do bieżącego tty.

W przeważającej części powyższa konfiguracja byłaby całkowicie bezużyteczna - po prostu redundantna, w zasadzie - poza tym, że uruchamiamy ją bashz kopią własnego pty master fd <>9. Oznacza to, że bashmożna swobodnie zapisywać do własnego strumienia wejściowego za pomocą prostego przekierowania. Wszystko, bashco musisz zrobić, to:

echo echo hey >&9

... rozmawiać z samym sobą.

Oto kolejne zdjęcie:

wprowadź opis zdjęcia tutaj


2
W jakich terminalach udało ci się to uruchomić? Tego rodzaju rzeczy były nadużywane w dawnych czasach i obecnie powinny być domyślnie wyłączone. Za pomocą xtermmożna nadal sprawdzać tytuł ikony za pomocą, \e[20tale tylko jeśli skonfigurowano za pomocą allowWindowOps: true.
Stéphane Chazelas


@ StéphaneChazelas, który działa w terminologii, ale jestem prawie pewien, że działa również w terminalu gnome, w terminalu kde (zapomniałem jego nazwy i myślę, że jest inna ucieczka) i, jak mówisz, w / xtermw / właściwy config. Jednak z właściwym Xtermem możesz czytać i pisać bufor kopiuj / wklej, więc myślę, że staje się to prostsze. Xterm ma również sekwencje specjalne do zmiany / wpływania na sam opis terminu.
mikeserv

Nie mogę sprawić, żeby działało to tylko w terminologii (która btw ma kilka innych podobnych luk). To, że CVE ma ponad 12 lat i jest stosunkowo dobrze znane, zdziwiłbym się, gdyby którykolwiek z głównych emulatorów terminali miał tę samą lukę. Zauważ, że z \e[20t\e]1;?\a
Xterm


8

Chociaż ioctl(,TIOCSTI,) odpowiedź Stéphane'a Chazelasa jest oczywiście poprawna, niektórzy ludzie mogą być zadowoleni z tej częściowej, ale trywialnej odpowiedzi: po prostu wepchnij polecenie na stos historii, a następnie użytkownik może przesunąć 1 linię w górę historii, aby znaleźć dowództwo.

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

Może to stać się prostym skryptem, który ma własną historię 1 linii:

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

read -eumożliwia edycję danych w trybie readline, -pjest monitem.


Działa to tylko w funkcjach powłoki lub jeśli skrypt został . foo.shpobrany ( lub `source foo.sh, zamiast uruchamiać się w podpowłoce). Ciekawe podejście. Podobny włamanie, które wymaga zmodyfikowania kontekstu powłoki wywołującej, polegałoby na skonfigurowaniu niestandardowego zakończenia, które rozszerzyło pustą linię do czegoś, a następnie przywróciło stary moduł obsługi zakończenia.
Peter Cordes,

@PeterCordes masz rację. Zbyt dosłownie podjąłem to pytanie. Ale dodałem przykład prostego skryptu, który mógłby działać.
Meuh

@ mikeserv Hej, to tylko proste rozwiązanie, które może być przydatne dla niektórych osób. Możesz nawet usunąć, evaljeśli masz proste polecenia do edycji, bez potoków i przekierowań itp.
meuh

1

O, moje słowo, przegapiliśmy proste rozwiązanie wbudowane w bash : readpolecenie ma opcję -i ..., która w połączeniu z -ewypycha tekst do bufora wejściowego. Ze strony podręcznika:

-i tekst

Jeśli do odczytu linii używany jest readline, tekst jest umieszczany w buforze edycji przed rozpoczęciem edycji.

Utwórz więc małą funkcję bash lub skrypt powłoki, który pobiera polecenie do użytkownika i uruchamia lub ocenia ich odpowiedź:

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

Bez wątpienia korzysta z ioctl (, TIOCSTI), który istnieje już od ponad 32 lat, ponieważ istniał już w wersji 2.9BSD ioctl.h .


1
Interesujący z podobnym efektem, ale nie wstrzykuje do monitu.
starfry

przy 2. myśli masz rację. bash nie potrzebuje TIOCSTI, ponieważ robi to samo we / wy.
Meuh
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.