Kann bash in einen eigenen Eingabestream schreiben?


39

Ist es in einer interaktiven Bash- Shell möglich, einen Befehl einzugeben, der Text ausgibt, so dass er bei der nächsten Eingabeaufforderung angezeigt wird, als hätte der Benutzer diesen Text bei dieser Eingabeaufforderung eingegeben?

Ich möchte in der Lage sein, sourceein Skript zu erstellen , das eine Befehlszeile generiert und so ausgibt, dass es angezeigt wird, wenn die Eingabeaufforderung nach dem Ende des Skripts zurückkehrt, sodass der Benutzer es optional bearbeiten kann, bevor er auf drückt enter, um es auszuführen.

Dies kann jedoch nur erreicht werden xdotool, wenn sich das Terminal in einem X-Fenster befindet und wenn es installiert ist.

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

Kann das nur mit bash gemacht werden?


Ich denke, dass dies mit Expect nicht schwer sein sollte, wenn Sie das tolerieren können und es eine Subshell fahren lassen; aber ich erinnere mich nicht genug daran, um eine tatsächliche Antwort zu posten.
Tripleee

Antworten:


40

Mit zshkönnen Sie print -zText für die nächste Eingabeaufforderung in den Zeileneditorpuffer einfügen:

print -z echo test

würde den Zeileneditor vorbereiten, mit echo testdem Sie bei der nächsten Eingabeaufforderung bearbeiten können.

Ich glaube nicht, dass bashes eine ähnliche Funktion gibt. Auf vielen Systemen können Sie den Eingabepuffer des Endgeräts jedoch mit den TIOCSTI ioctl()folgenden Befehlen füllen :

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

Würde echo testin den Eingabepuffer des Endgeräts einfügen , als ob er vom Endgerät empfangen würde.

Eine tragbarere Variante des @ mike- TerminologyAnsatzes , die die Sicherheit nicht einschränkt , besteht darin, dem Terminal-Emulator eine recht standardmäßige query status reportEscape-Sequenz zu senden : <ESC>[5nWelche Terminals antworten (also als Eingabe) <ESC>[0nund binden diese an die Zeichenfolge, die Sie einfügen möchten:

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

Wenn Sie sich in GNU befinden screen, können Sie auch Folgendes tun:

screen -X stuff 'echo test'

Mit Ausnahme des TIOCSTI ioctl-Ansatzes bitten wir den Terminal-Emulator, uns eine Zeichenfolge wie eingegeben zu senden. Wenn diese Zeichenfolge kommt, bevor readline( bashder Zeileneditor) das lokale Echo des Terminals deaktiviert hat, wird diese Zeichenfolge nicht an der Shell-Eingabeaufforderung angezeigt, wodurch die Anzeige leicht durcheinander gebracht wird.

Um dies zu umgehen, können Sie entweder das Senden der Anforderung an das Terminal etwas verzögern, um sicherzustellen, dass die Antwort eintrifft, wenn das Echo durch Readline deaktiviert wurde.

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

(hier unter der Annahme, dass Sie sleepeine Auflösung von weniger als einer Sekunde unterstützen).

Idealerweise möchten Sie Folgendes tun:

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

Allerdings bash(im Gegensatz zu zsh) hat keine Unterstützung für solche wait-until-the-response-arrives, die die Antwort nicht lesen.

Es hat jedoch eine has-the-response-arrived-yetFunktion mit 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"

Weitere Lektüre

Siehe @ starfry Antwort ist , dass baut auf den beiden gegebenen Lösungen von @mikeserv und mich mit ein paar ausführlichere Informationen.


Ich denke, bind '"\e[0n": "echo test"'; printf '\e[5n'wahrscheinlich die einzige Antwort, die ich suche. Für mich geht das. Ich werde jedoch auch ^[[0nvor meiner Aufforderung gedruckt. Ich habe festgestellt, dass dies verursacht wird, wenn $PS1eine Subshell enthalten ist. Sie können es reproduzieren, indem Sie PS1='$(:)'vor dem Bindebefehl ausführen. Warum sollte das passieren und kann etwas dagegen unternommen werden?
Starfry

Obwohl alles in dieser Antwort richtig ist, war die Frage für Bash, nicht zsh. Manchmal haben wir keine Wahl, welche Shell wir verwenden möchten.
Falsenames

@ Falsenames nur der erste Absatz ist für zsh. Der Rest ist entweder Shell-Agnostiker oder bash-spezifisch. Die Fragen und Antworten müssen nicht nur nützlich sein, um Benutzer zu verprügeln.
Stéphane Chazelas

1
@starfry scheint, als \rkönntest du vielleicht einfach eine Wende machen $PS1? Das sollte funktionieren, wenn $PS1es lang genug ist. Wenn nicht, dann dort setzen ^[[M.
mikeserv

@mikeserv - rmacht den Trick. Dies verhindert natürlich nicht die Ausgabe, sie wird nur überschrieben, bevor das Auge sie sieht. Ich denke, ^[[Mdie Zeile wird gelöscht, um den eingefügten Text zu löschen, falls er länger als die Eingabeaufforderung ist. Ist das richtig (ich konnte es nicht in der ANSI-Escape-Liste finden, die ich habe)?
Starfry

24

Diese Antwort dient der Klarstellung meines eigenen Verständnisses und wurde von @ StéphaneChazelas und @mikeserv vor mir inspiriert.

TL; DR

  • bashohne fremde hilfe ist dies nicht möglich ;
  • der richtige weg dies zu tun ist mit einem sendeterminal eingang ioctl aber
  • die am einfachsten umsetzbare bashLösung verwendet bind.

Die einfache Lösung

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

Bash hat eine eingebaute Shell bind, die es ermöglicht, einen Shell-Befehl auszuführen , wenn eine Tastensequenz empfangen wird. Im Wesentlichen wird die Ausgabe des Shell-Befehls in den Eingabepuffer der Shell geschrieben.

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

Die Tastenfolge \e[0n( <ESC>[0n) ist ein ANSI-Terminal-Escape-Code , den ein Terminal sendet, um anzuzeigen, dass es normal funktioniert. Es sendet dies als Antwort auf eine Gerätestatusberichtsanforderung, die als gesendet wird <ESC>[5n.

Indem echowir die Antwort an eine binden, die den einzufügenden Text ausgibt, können wir diesen Text jederzeit einfügen, indem wir den Gerätestatus abfragen. Dazu wird eine <ESC>[5nEscape-Sequenz gesendet.

printf '\e[5n'

Dies funktioniert und reicht wahrscheinlich aus, um die ursprüngliche Frage zu beantworten, da keine anderen Tools beteiligt sind. Es ist pur bash, setzt aber auf ein gut funktionierendes Terminal (praktisch alle).

Der wiedergegebene Text bleibt in der Befehlszeile zur Verwendung bereit, als ob er eingegeben worden wäre. Es kann angehängt, bearbeitet und durch Drücken ENTERvon ausgeführt werden.

Fügen Sie \nden gebundenen Befehl hinzu, damit er automatisch ausgeführt wird.

Diese Lösung funktioniert jedoch nur im aktuellen Terminal (das im Rahmen der ursprünglichen Frage liegt). Es funktioniert über eine interaktive Eingabeaufforderung oder über ein bereitgestelltes Skript, löst jedoch einen Fehler aus, wenn es in einer Subshell verwendet wird:

bind: warning: line editing not enabled

Die richtige Lösung, die als nächstes beschrieben wird, ist flexibler, stützt sich jedoch auf externe Befehle.

Die richtige Lösung

Die richtige Methode zum Einfügen von Eingaben verwendet tty_ioctl , einen Unix-Systemaufruf für die E / A-Steuerung , der einen TIOCSTIBefehl zum Einfügen von Eingaben enthält.

TIOC von " T erminal IOC tl " und STI von " S end T erminal I nput ".

Dafür ist kein Befehl eingebaut bash. Dies erfordert einen externen Befehl. Es gibt keinen solchen Befehl in der typischen GNU / Linux-Distribution, aber es ist nicht schwierig, ihn mit ein wenig Programmierung zu erreichen. Hier ist eine Shell-Funktion, die Folgendes verwendet perl:

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

Hier 0x5412ist der Code für den TIOCSTIBefehl.

TIOCSTIist eine Konstante, die in den Standard-C-Header-Dateien mit dem Wert definiert ist 0x5412. Versuchen Sie es grep -r TIOCSTI /usr/includeoder schauen Sie rein /usr/include/asm-generic/ioctls.h; es ist indirekt in C-Programmen von enthalten #include <sys/ioctl.h>.

Sie können dann Folgendes tun:

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

Implementierungen in einigen anderen Sprachen werden unten gezeigt (in einer Datei speichern und dann chmod +x):

Perl inject.pl

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

Sie können festlegen, sys/ioctl.phwelche Definitionen TIOCSTIanstelle des numerischen Werts verwendet werden sollen. Sehen Sie hier

Python 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) }

C inject.c

kompilieren mit 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;
}

**! ** Es gibt weitere Beispiele hier .

Unter Verwendung ioctldieses Werk in Subshells zu tun. Es kann auch in andere Terminals eingespeist werden, wie im Folgenden erläutert wird.

Weiterentwickeln (andere Terminals steuern)

Dies würde den Rahmen der ursprünglichen Frage sprengen, es ist jedoch möglich, Zeichen in ein anderes Terminal einzufügen, sofern die entsprechenden Berechtigungen vorliegen. Normalerweise bedeutet dies sein root, aber siehe unten für andere Möglichkeiten.

Wenn Sie das oben angegebene C-Programm erweitern, um ein Befehlszeilenargument zu akzeptieren, das die tty eines anderen Terminals angibt, können Sie Folgendes in dieses Terminal einfügen:

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

Es sendet auch eine neue Zeile standardmäßig aber ähnlich echo, es bietet eine -nMöglichkeit , es zu unterdrücken. Die Option --toder --ttyerfordert ein Argument - das ttydes zu injizierenden Terminals. Der Wert hierfür kann in diesem Terminal abgerufen werden:

$ tty
/dev/pts/20

Kompiliere es mit gcc -o inject inject.c. Stellen Sie dem einzufügenden Text ein Präfix voran, --wenn er Bindestriche enthält, um zu verhindern, dass der Argument-Parser Befehlszeilenoptionen falsch interpretiert. Sehen ./inject --help. Benutze es so:

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

oder nur

$ inject  -- ls -lrt

den Stromanschluss zu injizieren.

Für das Injizieren in ein anderes Terminal sind Administratorrechte erforderlich, die erhalten werden können durch:

  • Ausgabe des Befehls als root,
  • mit sudo,
  • mit der CAP_SYS_ADMINFähigkeit oder
  • Einstellen der ausführbaren Datei setuid

Zuweisen CAP_SYS_ADMIN:

$  sudo setcap cap_sys_admin+ep inject

Zuweisen setuid:

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

Ausgabe reinigen

Eingefügter Text wird vor der Eingabeaufforderung angezeigt, als wäre er vor der Eingabeaufforderung eingegeben worden (was tatsächlich der Fall war), wird jedoch nach der Eingabeaufforderung erneut angezeigt.

Eine Möglichkeit, den Text vor der Eingabeaufforderung auszublenden, besteht darin, der Eingabeaufforderung einen \rZeilenumbruch ( kein Zeilenvorschub) voranzustellen und die aktuelle Zeile zu löschen ( <ESC>[M):

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

Dadurch wird jedoch nur die Zeile gelöscht, in der die Eingabeaufforderung angezeigt wird. Wenn der eingefügte Text Zeilenumbrüche enthält, funktioniert dies nicht wie beabsichtigt.

Eine andere Lösung deaktiviert das Echo von eingefügten Zeichen. Ein Wrapper verwendet sttydazu:

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"

wo injectist eine der oben beschriebenen lösungen oder ersetzt durch printf '\e[5n'.

Alternative Ansätze

Wenn Ihre Umgebung bestimmte Voraussetzungen erfüllt, stehen Ihnen möglicherweise andere Methoden zur Verfügung, mit denen Sie Eingaben einfügen können. Wenn Sie sich in einer Desktop-Umgebung befinden, ist xdotool ein X.Org- Dienstprogramm, das Maus- und Tastaturaktivitäten simuliert, das Ihre Distribution jedoch möglicherweise nicht standardmäßig enthält. Du kannst es versuchen:

$ xdotool type ls

Wenn Sie den Terminal-Multiplexer tmux verwenden , können Sie dies tun:

$ tmux send-key -t session:pane ls

wo -twählt welche Sitzung und Bereich zu injizieren. GNU Screen hat eine ähnliche Fähigkeit mit seinem stuffBefehl:

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

Wenn Ihre Distribution das Paket console-tools enthält , haben Sie möglicherweise einen writevtBefehl, der ioctlwie in unseren Beispielen verwendet wird. Die meisten Distributionen haben dieses Paket jedoch zugunsten von kbd abgelehnt , dem diese Funktion fehlt.

Eine aktualisierte Kopie von writevt.c kann mit kompiliert werden gcc -o writevt writevt.c.

Andere Optionen, die für einige Anwendungsfälle besser geeignet sind, sind Expect und Empty, mit denen interaktive Tools skriptgesteuert werden können.

Sie können auch eine Shell verwenden, die die Terminalinjektion unterstützt, wie zshdies möglich ist print -z ls.

Die "Wow, das ist schlau ..." Antwort

Die hier beschriebene Methode wird auch hier besprochen und baut auf der hier besprochenen Methode auf .

Eine Shell-Umleitung von /dev/ptmxerhält ein neues Pseudo-Terminal:

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

Ein kleines in C geschriebenes Tool, das den Pseudoterminal-Master (ptm) entsperrt und den Namen des Pseudoterminal-Slaves (pts) an seine Standardausgabe ausgibt.

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

(speichern unter pts.cund kompilieren mit gcc -o pts pts.c)

Wenn das Programm mit einer Standardeingabe von ptm aufgerufen wird, entsperrt es die entsprechenden Punkte und gibt seinen Namen an die Standardausgabe aus.

$ ./pts </dev/ptmx
/dev/pts/20
  • Die Funktion unlockpt () entsperrt das Slave-Pseudoterminal, das dem Master-Pseudoterminal entspricht, auf das der angegebene Dateideskriptor verweist. Das Programm übergibt dies als Null, was die Standardeingabe des Programms ist .

  • Die Funktion ptsname () gibt den Namen des Slave-Pseudoterminals zurück, das dem Master entspricht, auf den der angegebene Dateideskriptor verweist, und übergibt für die Standardeingabe des Programms erneut Null.

Ein Prozess kann mit dem Punkt verbunden werden. Holen Sie sich zuerst ein ptm (hier ist es dem Dateideskriptor 3 zugeordnet, der vom <>Redirect mit Lese- und Schreibzugriff geöffnet wird ).

 exec 3<>/dev/ptmx

Dann starte den Prozess:

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

Die von dieser Befehlszeile ausgehenden Prozesse lassen sich am besten wie folgt veranschaulichen pstree:

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

Die Ausgabe ist relativ zur aktuellen Shell ( $$) und die PID ( -p) und PGID ( -g) jedes Prozesses werden in Klammern angezeigt (PID,PGID).

Am Anfang des Baums befindet bash(5203,5203)sich die interaktive Shell, in die wir Befehle eingeben, und ihre Dateideskriptoren verbinden sie mit der Terminalanwendung, die wir für die Interaktion mit ihr verwenden ( xtermoder ähnlichem).

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

Bei nochmaliger Betrachtung des Befehls starteten die ersten Klammern eine Subshell, bash(6524,6524)wobei der Dateideskriptor 0 (die Standardeingabe ) den PTS (die mit Lese- und Schreibzugriff geöffnet werden) zugewiesen wurde, die <>von einer anderen Subshell zurückgegeben wurden, die ./pts <&3zum Entsperren der PTS ausgeführt wurde Punkte, die dem Dateideskriptor 3 zugeordnet sind (der im vorherigen Schritt erstellt wurde exec 3<>/dev/ptmx).

Der Dateideskriptor 3 3>&-der Subshell ist geschlossen ( ), so dass der ptm nicht darauf zugreifen kann. Seine Standardeingabe (fd 0), die die Punkte darstellt, die beim Lesen / Schreiben geöffnet wurden, wird >&0auf seine Standardausgabe (fd 1) umgeleitet (tatsächlich wird die fd - kopiert ).

Dadurch wird eine Subshell erstellt, deren Standardeingabe und -ausgabe mit den pts verbunden ist. Es kann eine Eingabe gesendet werden, indem in das ptm geschrieben wird, und die Ausgabe kann durch Lesen des ptm angezeigt werden:

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

Die Subshell führt diesen Befehl aus:

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

Es wird in einer neuen Sitzung bash(6527,6527)im interaktiven ( -i) Modus ausgeführt ( setsid -cbeachten Sie, dass PID und PGID identisch sind). Der Standardfehler wird auf die Standardausgabe ( 2>&1) umgeleitet und über die Pipeline weitergeleitet, tee(6528,6524)sodass er sowohl in eine logDatei als auch in die Punkte geschrieben wird. Dies gibt eine andere Möglichkeit, die Ausgabe der Subshell zu sehen:

$ tail -f log

Da die Subshell bashinteraktiv ausgeführt wird, können Befehle zur Ausführung gesendet werden, wie in diesem Beispiel, in dem die Dateideskriptoren der Subshell angezeigt werden:

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

Das Lesen der Ausgabe ( tail -f logoder cat <&3) der Subshell zeigt:

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

Der Standardeingang (fd 0) ist mit dem Punkt verbunden, und sowohl der Standardausgang (fd 1) als auch der Fehler (fd 2) sind mit derselben Pipe verbunden, die mit Folgendem verbunden ist 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]

Und ein Blick auf die Dateideskriptoren von 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

Standardausgabe (fd 1) ist der Punkt: Alles, was 'tee' in seine Standardausgabe schreibt, wird an den Punkt zurückgesendet. Standardfehler (fd 2) sind die Punkte, die zum steuernden Terminal gehören.

Verpacken

Das folgende Skript verwendet die oben beschriebene Technik. Es wird eine interaktive bashSitzung eingerichtet, die durch Schreiben in einen Dateideskriptor eingefügt werden kann. Es ist hier verfügbar und mit Erläuterungen dokumentiert.

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

Mit der einfachsten bind '"\e[0n": "ls -l"'; printf '\e[5n'Lösung, wird doch die Ausgabe der ls -lauch ^[[0nauf dem Terminal ausgegeben, sobald ich die Eingabetaste drücke und damit laufe ls -l. Irgendwelche Ideen, wie man es bitte "versteckt"? Vielen Dank.
Ali

1
Ich habe eine Lösung vorgestellt, die den Effekt hervorruft, nach dem Sie suchen - im Abschnitt " Saubere Ausgabe " meiner Antwort schlage ich vor, der Aufforderung, überflüssigen Text auszublenden, eine Rückkehr hinzuzufügen. Ich habe es PS1="\r\e[M$PS1"vorher versucht bind '"\e[0n": "ls -l"'; printf '\e[5n'und das hat den von Ihnen beschriebenen Effekt gebracht.
Uhr

Danke! Ich habe diesen Punkt total verpasst.
Ali vor

20

Es hängt davon ab , was Sie bedeuten bashnur . Wenn Sie eine einzelne, interaktive bashSitzung meinen, lautet die Antwort fast definitiv nein . Und das liegt daran, dass Sie selbst dann, wenn Sie einen Befehl wie ls -lin der Befehlszeile eines beliebigen kanonischen Terminals eingeben, bashnoch nicht einmal darüber informiert sind - und bashzu diesem Zeitpunkt noch nicht einmal daran beteiligt sind.

Vielmehr ist bis zu diesem Zeitpunkt passiert, dass die tty-Zeilendisziplin des Kernels gepuffert und stty echodie Benutzereingaben nur auf dem Bildschirm angezeigt wurden. Es spült diesen Eingang seine Leser - bash, im Beispielfall - Zeile für Zeile - und übersetzt im Allgemeinen \returns zu \newlines auf Unix - Systemen als auch - und so bashist nicht - und so kann weder Ihre sourced Skript sein - darauf aufmerksam gemacht, es irgendeine Eingabe, bis der Benutzer die ENTERTaste drückt .

Nun gibt es einige Workarounds. Die robusteste Methode ist überhaupt keine Umgehungsmöglichkeit und umfasst die Verwendung mehrerer Prozesse oder speziell geschriebener Programme, um Eingaben zu sequenzieren, die Zeilendisziplin -echovor dem Benutzer zu verbergen und nur auf den Bildschirm zu schreiben, was bei der Interpretation von Eingaben als angemessen erachtet wird besonders wenn nötig. Dies kann schwierig zu bewerkstelligen sein, da es bedeutet, Interpretationsregeln zu schreiben, die willkürlich eingegebene Zeichen für Zeichen verarbeiten und gleichzeitig ohne Fehler ausschreiben können, um zu simulieren, was der durchschnittliche Benutzer in diesem Szenario erwarten würde. Aus diesem Grund wird das interaktive Terminal-I / O wahrscheinlich so selten gut verstanden - eine so schwierige Perspektive, die sich für die meisten nicht für weitere Untersuchungen eignet.

Eine andere Problemumgehung könnte den Terminalemulator betreffen. Sie sagen, dass ein Problem für Sie eine Abhängigkeit von X und von ist xdotool. In diesem Fall könnte ein Workaround, den ich anbieten werde, ähnliche Probleme haben, aber ich werde trotzdem weitermachen.

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

Das wird in einem xtermw / the allowwindowOpsResource Set funktionieren . Es speichert zuerst die Symbol- / Fensternamen auf einem Stapel, setzt dann die Symbolzeichenfolge des Terminals auf die ^Umy commandAnforderung, dass das Terminal diesen Namen in die Eingabewarteschlange einfügt, und setzt ihn zuletzt auf die gespeicherten Werte zurück. Es sollte unsichtbar für interaktive bashShells xterm funktionieren, die mit der richtigen Konfiguration ausgeführt werden - aber es ist wahrscheinlich eine schlechte Idee. Bitte beachten Sie die Kommentare von Stéphane unten.

Hier ist jedoch ein Bild, das ich von meinem Terminologieterminal aufgenommen habe, nachdem ich das printfBit mit einer anderen Escape-Sequenz auf meinem Computer ausgeführt habe. Für jede neue Zeile im printfBefehl habe ich CTRL+Vdann getippt CTRL+Jund anschließend die ENTERTaste gedrückt . Ich habe danach nichts mehr getippt, aber, wie Sie sehen, das Terminal my commandfür mich in die Eingabewarteschlange der Leitung eingespeist:

term_inject

Der wirkliche Weg, dies zu tun, ist mit einer verschachtelten Pty. So screenund so tmux- beides kann Ihnen das übrigens ermöglichen. xtermkommt eigentlich mit einem kleinen programm das aufgerufen wird luitdas kann man auch machen. Es ist jedoch nicht einfach.

Hier ist eine Möglichkeit, wie Sie:

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

Das ist keineswegs portabel, sollte aber auf den meisten Linux-Systemen funktionieren, wenn die entsprechenden Öffnungsberechtigungen vorliegen /dev/ptmx. Mein Benutzer ist in der ttyGruppe, die auf meinem System ausreicht. Du brauchst auch ...

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

... das, wenn es auf einem GNU-System (oder einem anderen mit einem Standard-C-Compiler, der auch von stdin lesen kann) ausgeführt wird, eine kleine ausführbare Binärdatei namens ptsausschreibt, die die unlockpt()Funktion auf ihrem stdin ausführt und in ihr stdout schreibt Name des Pty-Geräts, das gerade entsperrt wurde. Ich habe es geschrieben, als ich daran gearbeitet habe ... Wie komme ich zu dieser Pty und was kann ich damit machen? .

Wie auch immer, das obige bashCodebit führt eine Shell in einer Schicht unterhalb der aktuellen Schicht aus. bashwird angewiesen, alle Ausgaben in das Slave-Pty zu schreiben, und das aktuelle Tty wird sowohl nicht für -echoseinen Eingang als auch für das Puffern konfiguriert , sondern um es (meistens) raw zu übergeben cat, woraufhin es kopiert wird bash. Und währenddessen catkopiert ein weiterer Hintergrund alle Slave-Ausgaben auf das aktuelle Tty.

Zum größten Teil wäre die obige Konfiguration völlig nutzlos - im Grunde genommen nur redundant - mit der Ausnahme, dass wir bashmit einer Kopie des eigenen Pty-Masters starten <>9. Dies bedeutet, dass bashmit einer einfachen Umleitung frei in einen eigenen Eingabestream geschrieben werden kann. Alles was bashzu tun ist:

echo echo hey >&9

... um mit sich selbst zu reden.

Hier ist ein weiteres Bild:

Bildbeschreibung hier eingeben


2
In welchen Terminals haben Sie das geschafft? So etwas wurde in früheren Zeiten missbraucht und sollte heutzutage standardmäßig deaktiviert sein. Mit xtermkönnen Sie weiterhin den Symboltitel abfragen, \e[20tjedoch nur, wenn dies mit konfiguriert wurde allowWindowOps: true.
Stéphane Chazelas


@ StéphaneChazelas, das in der Terminologie funktioniert, aber ich bin mir ziemlich sicher, dass es auch im Gnome-Terminal funktioniert, im KDE-Terminal (ich vergesse seinen Namen, und ich denke, es gibt eine andere Fluchtmöglichkeit) , und wie Sie sagen, w / xtermw / the proper config. W / a Xterm eigentliche, können Sie lesen und schreiben Sie den Copy / Paste-Puffer und so wird es außerdem einfacher, denke ich. Xterm verfügt auch über Escape-Sequenzen zum Ändern / Beeinflussen der Begriffsbeschreibung.
mikeserv

Ich kann das nur in der Terminologie zum Laufen bringen (die übrigens mehrere ähnliche Schwachstellen hat). Daß CVE über 12 Jahre alt und relativ bekannt ist, würde mich wundern, wenn einer der Haupt-Terminal-Emulatoren die gleiche Schwachstelle hätte. Beachten Sie, dass mit xterm, das ist \e[20t(nicht \e]1;?\a)
Stéphane Chazelas


8

Obwohl die ioctl(,TIOCSTI,) Antwort von Stéphane Chazelas natürlich die richtige ist, können einige Leute mit dieser teilweisen, aber trivialen Antwort zufrieden sein: Schieben Sie einfach den Befehl auf den Verlaufsstapel, und der Benutzer kann 1 Zeile nach oben in den Verlauf springen, um den zu finden Befehl.

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

Dies kann ein einfaches Skript werden, das einen eigenen Verlauf mit einer Zeile hat:

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

read -eermöglicht das readline editieren der eingabe, -pist eine aufforderung.


Das funktioniert nur in Shell-Funktionen, oder wenn das Skript aus einer . foo.shQuelle ( oder `source foo.sh) stammt, anstatt in einer Subshell ausgeführt zu werden. Interessanter Ansatz. Ein ähnlicher Hack, bei dem der Kontext der aufrufenden Shell geändert werden muss, besteht darin, eine benutzerdefinierte Vervollständigung einzurichten, die die leere Zeile zu etwas erweitert und dann den alten Vervollständigungshandler wiederherstellt.
Peter Cordes

@PeterCordes du hast recht. Ich habe die Frage zu wörtlich genommen. Aber ich habe ein Beispiel für ein einfaches Skript hinzugefügt, das funktionieren könnte.
Meuh

@mikeserv Hey, es ist nur eine einfache Lösung, die für manche Leute nützlich sein kann. Sie können sogar die entfernen, evalwenn Sie einfache Befehle zum Bearbeiten haben, ohne Pipes und Umleitung usw.
meuh

1

Oh mein Wort, wir haben eine einfache integrierte Lösung für Bash verpasst : Der readBefehl hat eine Option -i ..., die bei Verwendung mit -eText in den Eingabepuffer schiebt. Von der Manpage:

-i Text

Wenn readline zum Lesen der Zeile verwendet wird, wird Text in den Bearbeitungspuffer eingefügt, bevor die Bearbeitung beginnt.

Erstellen Sie also eine kleine Bash-Funktion oder ein Shell-Skript, das den Befehl verwendet, um ihn dem Benutzer zu präsentieren, und dessen Antwort ausführt oder auswertet:

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

Dies verwendet zweifellos das ioctl (, TIOCSTI,), das es schon seit über 32 Jahren gibt, da es bereits in 2.9BSD ioctl.h existiert .


1
Interessant mit einem ähnlichen Effekt, der jedoch nicht in die Eingabeaufforderung eingeht.
Starfry

Bei 2 Gedanken hast du recht. Bash muss nicht zu TIOCSTI, da es alle I / O selbst tut.
Meuh
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.