Bash può scrivere sul proprio flusso di input?


39

È possibile in una shell bash interattiva inserire un comando che emette del testo in modo che appaia al prompt dei comandi successivo, come se l'utente avesse digitato quel testo al prompt?

Voglio essere in grado di sourcecreare uno script che generi una riga di comando e lo emetta in modo che appaia quando il prompt ritorna dopo la fine dello script in modo che l'utente possa modificarlo facoltativamente prima di premere enterper eseguirlo.

Questo può essere ottenuto con xdotoolma funziona solo quando il terminale si trova in una finestra X e solo se è installato.

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

Questo può essere fatto usando solo bash?


Sto pensando che questo non dovrebbe essere difficile con Expect, se puoi tollerarlo e far guidare una subshell; ma non me ne ricordo abbastanza per pubblicare una risposta effettiva.
tripla

Risposte:


40

Con zsh, è possibile utilizzare print -zper posizionare del testo nel buffer dell'editor di riga per il prompt successivo:

print -z echo test

innescare l'editor di riga con echo testcui è possibile modificare al prompt successivo.

Non credo che bashabbia una funzione simile, tuttavia su molti sistemi, è possibile adescare il buffer di input del dispositivo terminale con TIOCSTI ioctl():

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

Inserirebbe echo testnel buffer di input del dispositivo terminale, come se fosse ricevuto dal terminale.

Una variante più portatile dell'approccio di @mikeTerminology e che non sacrifica la sicurezza sarebbe quella di inviare all'emulatore di terminale una query status reportsequenza di escape abbastanza standard : <ESC>[5nquali terminali invariabilmente rispondono (quindi come input) come <ESC>[0ne lo legano alla stringa che si desidera inserire:

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

Se all'interno di GNU screen, puoi anche fare:

screen -X stuff 'echo test'

Ora, ad eccezione dell'approccio ioctl di TIOCSTI, chiediamo all'emulatore di terminale di inviarci una stringa come se fosse digitata. Se la stringa viene prima readline( bash's line) ha disabilitato terminale echo locale, allora quella stringa verrà visualizzato non in corrispondenza dello scafo richiesta, rovinare il display leggermente.

Per ovviare a ciò, è possibile ritardare leggermente l'invio della richiesta al terminale per assicurarsi che la risposta arrivi quando l'eco è stata disabilitata da readline.

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

(qui supponendo che sleepsupporti la risoluzione sub-secondo).

Idealmente, vorresti fare qualcosa del tipo:

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

Tuttavia bash(contrariamente a zsh) non ha il supporto per un tale wait-until-the-response-arrivesche non legge la risposta.

Tuttavia ha una has-the-response-arrived-yetfunzione con 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"

Ulteriori letture

Vedi la risposta di @ starfry che si espande sulle due soluzioni fornite da @mikeserv e da me con alcune informazioni più dettagliate.


Penso che bind '"\e[0n": "echo test"'; printf '\e[5n'probabilmente l'unica risposta bash che sto cercando. Per me funziona. Tuttavia, vengo ^[[0nstampato anche prima del mio prompt. Ho scoperto che questo è causato quando $PS1contiene una subshell. Puoi riprodurlo facendo PS1='$(:)'prima del comando bind. Perché dovrebbe succedere e si può fare qualcosa al riguardo?
Starfry il

Sebbene tutto in questa risposta sia corretto, la domanda era per bash, non per zsh. A volte non abbiamo scelta di quale shell usare.
Falsenames,

@Falsenames solo il primo paragrafo è per zsh. Il resto è o shell agnostico o bash specifico. Le domande e risposte non devono essere utili solo per colpire gli utenti.
Stéphane Chazelas,

1
@starfry sembra che potresti semplicemente mettere un \return alla testa di $PS1? Dovrebbe funzionare se $PS1è abbastanza lungo. Altrimenti, mettici ^[[Mlì.
Mikeserv,

@mikeserv - rfa il trucco. Questo ovviamente non impedisce l'output, è solo sovrascritto prima che l'occhio lo veda. Immagino che ^[[Mcancella la riga per cancellare il testo iniettato nel caso sia più lungo del prompt. Esatto (non sono riuscito a trovarlo nell'elenco di escape ANSI che ho)?
Starfry,

25

Questa risposta è fornita come chiarimento della mia comprensione ed è ispirata da @ StéphaneChazelas e @mikeserv prima di me.

TL; DR

  • non è possibile farlo bashsenza un aiuto esterno;
  • il modo corretto per farlo è con un input del terminale di invio ioctl ma
  • la bashsoluzione praticabile più semplice utilizza bind.

La soluzione semplice

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

Bash ha un builtin shell chiamato bindche permette di eseguire un comando shell quando viene ricevuta una sequenza di tasti. In sostanza, l'output del comando shell viene scritto nel buffer di input della shell.

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

La sequenza di tasti \e[0n( <ESC>[0n) è un codice di escape del terminale ANSI che un terminale invia per indicare che funziona normalmente. Invia questo in risposta a una richiesta di rapporto sullo stato del dispositivo che viene inviata come <ESC>[5n.

Associando la risposta a un messaggio echoche genera il testo da iniettare, possiamo iniettare quel testo ogni volta che lo desideriamo, richiedendo lo stato del dispositivo e facendo ciò inviando una <ESC>[5nsequenza di escape.

printf '\e[5n'

Funziona ed è probabilmente sufficiente per rispondere alla domanda originale perché non sono coinvolti altri strumenti. È puro bashma si basa su un terminale ben educato (praticamente tutti lo sono).

Lascia il testo echo sulla riga di comando pronto per essere utilizzato come se fosse stato digitato. Può essere aggiunto, modificato e premendo ENTERprovoca l'esecuzione.

Aggiungi \nal comando associato per eseguirlo automaticamente.

Tuttavia, questa soluzione funziona solo nel terminale corrente (che rientra nell'ambito della domanda originale). Funziona da un prompt interattivo o da uno script di origine ma genera un errore se utilizzato da una subshell:

bind: warning: line editing not enabled

La soluzione corretta descritta di seguito è più flessibile ma si basa su comandi esterni.

La soluzione corretta

Il modo corretto di iniettare l'input utilizza tty_ioctl , una chiamata di sistema unix per il controllo I / O che ha un TIOCSTIcomando che può essere utilizzato per iniettare l'input.

TIOC da " T erminal IOC tl " e STI da " S end T erminal I nput ".

Non esiste un comando incorporato bashper questo; farlo richiede un comando esterno. Non esiste un comando simile nella tipica distribuzione GNU / Linux, ma non è difficile da ottenere con un po 'di programmazione. Ecco una funzione shell che utilizza perl:

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

Ecco 0x5412il codice per il TIOCSTIcomando.

TIOCSTIè una costante definita nei file di intestazione C standard con il valore 0x5412. Prova grep -r TIOCSTI /usr/includeo guarda dentro /usr/include/asm-generic/ioctls.h; è incluso nei programmi C indirettamente #include <sys/ioctl.h>.

È quindi possibile fare:

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

Le implementazioni in alcune altre lingue sono mostrate di seguito (salvare in un file e quindi chmod +x):

Perl inject.pl

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

È possibile generare sys/ioctl.phquale definisce TIOCSTIinvece di utilizzare il valore numerico. Vedi qui

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

Rubino inject.rb

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

C inject.c

compilare con 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;
}

**! ** Ci sono altri esempi qui .

Usare ioctlper fare questo funziona in subshells. Può anche essere iniettato in altri terminali come spiegato di seguito.

Portarlo oltre (controllare altri terminali)

È al di là dell'ambito della domanda originale ma è possibile iniettare caratteri in un altro terminale, a condizione che dispongano delle autorizzazioni appropriate. Normalmente questo significa essere root, ma vedi sotto per altri modi.

L'estensione del programma C sopra indicato per accettare un argomento della riga di comando che specifica il tty di un altro terminale consente l'iniezione a quel terminale:

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

Invia anche una nuova riga per impostazione predefinita ma, analogamente a echo, fornisce -nun'opzione per eliminarla. L' opzione --to --ttyrichiede un argomento: l' ttyiniezione del terminale. Il valore per questo può essere ottenuto in quel terminale:

$ tty
/dev/pts/20

Compilalo con gcc -o inject inject.c. Prefisso il testo da iniettare --se contiene trattini per impedire all'analizzatore di argomenti di interpretare erroneamente le opzioni della riga di comando. Vedere ./inject --help. Usalo in questo modo:

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

o solo

$ inject  -- ls -lrt

per iniettare il terminale corrente.

L'iniezione in un altro terminale richiede diritti amministrativi che possono essere ottenuti da:

  • impartendo il comando come root,
  • usando sudo,
  • avere la CAP_SYS_ADMINcapacità o
  • impostazione dell'eseguibile setuid

Per assegnare CAP_SYS_ADMIN:

$  sudo setcap cap_sys_admin+ep inject

Per assegnare setuid:

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

Uscita pulita

Il testo iniettato appare prima del prompt come se fosse stato digitato prima che apparisse (che, in effetti, lo era) ma poi riapparirà dopo il prompt.

Un modo per nascondere il testo che appare davanti al prompt è anteporre il prompt con un ritorno a capo ( \rnon avanzamento riga) e cancellare la riga corrente ( <ESC>[M):

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

Tuttavia, questo cancellerà solo la linea su cui appare il prompt. Se il testo iniettato include nuove righe, questo non funzionerà come previsto.

Un'altra soluzione disabilita l'eco dei caratteri iniettati. Un wrapper usa sttyper fare questo:

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"

dove injectè una delle soluzioni sopra descritte o sostituita da printf '\e[5n'.

Approcci alternativi

Se l'ambiente soddisfa determinati prerequisiti, è possibile che siano disponibili altri metodi che è possibile utilizzare per iniettare l'input. Se ti trovi in ​​un ambiente desktop, xdotool è un'utilità X.Org che simula l'attività del mouse e della tastiera, ma la distribuzione potrebbe non includerla per impostazione predefinita. Puoi provare:

$ xdotool type ls

Se usi tmux , il multiplexer terminale, puoi farlo:

$ tmux send-key -t session:pane ls

dove -tseleziona la sessione e il riquadro da iniettare. Lo schermo GNU ha una capacità simile con il suo stuffcomando:

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

Se la tua distribuzione include il pacchetto console-tools , potresti avere un writevtcomando che usa ioctlcome i nostri esempi. La maggior parte delle distro, tuttavia, ha deprecato questo pacchetto a favore di kbd che manca di questa funzionalità.

È possibile compilare una copia aggiornata di writevt.c utilizzando gcc -o writevt writevt.c.

Altre opzioni che potrebbero adattarsi meglio ad alcuni casi d'uso includono prevedono e svuota, progettate per consentire lo script di strumenti interattivi.

È inoltre possibile utilizzare una shell che supporti l'iniezione terminale come ciò zshche può fare print -z ls.

La risposta "Wow, che intelligente ..."

Il metodo qui descritto è anche discusso qui e si basa sul metodo discusso qui .

Un reindirizzamento della shell da /dev/ptmxottiene un nuovo pseudo-terminale:

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

Un piccolo strumento scritto in C che sblocca il master pseudoterminal (ptm) e restituisce il nome dello slave pseudoterminal (pts) alla sua uscita standard.

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

(salva come pts.ce compila con gcc -o pts pts.c)

Quando il programma viene chiamato con il suo input standard impostato su un ptm sblocca i punti corrispondenti e invia il suo nome all'output standard.

$ ./pts </dev/ptmx
/dev/pts/20
  • La funzione unlockpt () sblocca il dispositivo pseudoterminale slave corrispondente allo pseudoterminale principale a cui fa riferimento il descrittore di file specificato. Il programma passa questo come zero, che è l' input standard del programma .

  • La funzione ptsname () restituisce il nome del dispositivo pseudoterminale slave corrispondente al master a cui fa riferimento il descrittore di file specificato, passando di nuovo zero per l'input standard del programma.

Un processo può essere collegato ai punti. Prima ottieni un ptm (qui è assegnato al descrittore di file 3, aperto in lettura-scrittura dal <>reindirizzamento).

 exec 3<>/dev/ptmx

Quindi avviare il processo:

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

I processi generati da questa riga di comando sono meglio illustrati con pstree:

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

L'output è relativo all'attuale shell ( $$) e il PID ( -p) e PGID ( -g) di ciascun processo sono mostrati tra parentesi (PID,PGID).

Alla testa dell'albero c'è bash(5203,5203)la shell interattiva in cui stiamo digitando i comandi e i suoi descrittori di file lo collegano all'applicazione terminale che stiamo usando per interagire con esso ( xtermo simili).

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

Guardando di nuovo il comando, il primo set di parentesi ha avviato una subshell bash(6524,6524)) , con il suo descrittore di file 0 (il suo input standard ) assegnato ai pts (che è aperto in lettura-scrittura <>) , come restituito da un'altra subshell eseguita ./pts <&3per sbloccare il pts associati al descrittore di file 3 (creato nel passaggio precedente, exec 3<>/dev/ptmx).

Il descrittore di file 3 della subshell è chiuso ( 3>&-) in modo che il ptm non sia accessibile ad esso. Il suo input standard (fd 0), che è il pts che è stato aperto in lettura / scrittura, viene reindirizzato (in realtà il fd viene copiato - >&0) al suo output standard (fd 1).

Questo crea una subshell con i suoi input e output standard collegati ai punti. Può essere inviato input scrivendo al ptm e il suo output può essere visto leggendo dal ptm:

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

La subshell esegue questo comando:

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

Funziona bash(6527,6527)in -imodalità interattiva ( ) in una nuova sessione ( setsid -cnotare che PID e PGID sono uguali). Il suo errore standard viene reindirizzato al suo output standard ( 2>&1) e reindirizzato via in tee(6528,6524)modo che sia scritto su un logfile e sui pts. Questo offre un altro modo per vedere l'output della subshell:

$ tail -f log

Poiché la subshell è in esecuzione in modo bashinterattivo, è possibile inviare comandi da eseguire, come in questo esempio che mostra i descrittori di file della subshell:

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

La lettura dell'output ( tail -f logo cat <&3) della subshell rivela:

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

L'ingresso standard (fd 0) è collegato ai punti e sia l'uscita standard (fd 1) sia l'errore (fd 2) sono collegati alla stessa pipe, quella che si collega a 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]

E uno sguardo ai descrittori di file di 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

L'output standard (fd 1) è il pts: tutto ciò che "tee" scrive nel suo output standard viene rinviato al ptm. Errore standard (fd 2) sono i punti appartenenti al terminale di controllo.

Avvolgendolo

Il seguente script utilizza la tecnica sopra descritta. Imposta una bashsessione interattiva che può essere iniettata scrivendo in un descrittore di file. È disponibile qui e documentato con spiegazioni.

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

Con la bind '"\e[0n": "ls -l"'; printf '\e[5n'soluzione più semplice , dopo tutto l'output dell'output verrà ls -lanche ^[[0nemesso sul Terminale una volta che premo il tasto Invio, quindi eseguo ls -l. Qualche idea su come "nasconderlo" per favore? Grazie.
Ali,

1
Ho presentato una soluzione che dà l'effetto che stai cercando : nella sezione di output pulito della mia risposta suggerisco di aggiungere un ritorno al prompt per nascondere il testo in eccesso. Ho provato PS1="\r\e[M$PS1"prima di farlo bind '"\e[0n": "ls -l"'; printf '\e[5n'e questo ha dato l'effetto che descrivi.
Starfry

Grazie! Ho completamente perso quel punto.
Ali,

20

Dipende da cosa intendi bashsolo . Se intendi una singola bashsessione interattiva , la risposta è quasi sicuramente no . E questo perché anche quando si immette un comando come ls -lda riga di comando su qualsiasi terminale canonico, allora bashnon ne è nemmeno consapevole - e bashnon è nemmeno coinvolto a quel punto.

Piuttosto, ciò che è accaduto fino a quel momento è che la disciplina della linea tty del kernel ha bufferizzato e stty echod l'input dell'utente solo sullo schermo. Svuota quell'input per il suo lettore - bash, nel tuo caso esempio - riga per riga - e generalmente traduce \returns in \newline anche su sistemi Unix - e quindi bashnon lo è - e quindi nemmeno lo script di provenienza può essere reso consapevole che c'è qualche inserire fino a quando l'utente preme il ENTERtasto.

Ora, ci sono alcune soluzioni alternative. Il più robusto non è affatto una soluzione, in realtà, e prevede l'utilizzo di più processi o programmi appositamente scritti per sequenziare l'input, nascondere -echoall'utente la disciplina di linea e scrivere sullo schermo solo ciò che viene giudicato appropriato nell'interpretazione dell'input specialmente quando necessario. Questo può essere difficile da fare bene perché significa scrivere regole di interpretazione in grado di gestire caratteri di input arbitrari tramite char quando arriva e scriverlo simultaneamente senza errori per simulare ciò che l'utente medio si aspetterebbe in quello scenario. È per questo motivo, probabilmente, che l'I / O terminale interattivo è così raramente ben compreso - una prospettiva così difficile non è quella che si presta a ulteriori indagini per la maggior parte.

Un'altra soluzione potrebbe coinvolgere l'emulatore di terminale. Dici che un problema per te è una dipendenza da X in poi xdotool. In tal caso, una soluzione simile a quella che sto per offrire potrebbe avere problemi simili, ma andrò avanti lo stesso.

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

Che funzionerà in un xtermw / l' allowwindowOpsinsieme delle risorse. Prima salva i nomi delle icone / finestre su uno stack, quindi imposta la stringa di icone del terminale in modo da ^Umy commandrichiedere che il terminale inietti quel nome nella coda di input e lo reimposta per ultimo sui valori salvati. Dovrebbe funzionare in modo invisibile per le bashshell interattive eseguite in una xterm configurazione corretta, ma probabilmente è una cattiva idea. Di seguito, vedi i commenti di Stéphane.

Qui, però, c'è una foto che ho fatto del mio terminale Terminology dopo aver eseguito il printfbit con una diversa sequenza di escape sulla mia macchina. Per ogni nuova riga nel printfcomando ho digitato CTRL+Vquindi CTRL+Je successivamente premuto il ENTERtasto. Dopo non ho scritto nulla, ma, come puoi vedere, il terminale my commandha inserito la coda di input della disciplina di linea per me:

term_inject

Il vero modo per farlo è con un pty nidificato. È come screene tmuxsimili lavori - entrambi i quali, tra l'altro, possono renderlo possibile per te. xtermin realtà viene fornito con un piccolo programma chiamato luitche può anche renderlo possibile. Non è facile, però.

Ecco un modo in cui potresti:

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

Questo non è affatto portatile, ma dovrebbe funzionare sulla maggior parte dei sistemi Linux con le autorizzazioni appropriate per l'apertura /dev/ptmx. Il mio utente è nel ttygruppo che è abbastanza sul mio sistema. Avrai anche bisogno di ...

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

... che, quando eseguito su un sistema GNU (o qualsiasi altro con un compilatore C standard che può anche leggere da stdin) , scriverà un piccolo binario eseguibile chiamato ptsche eseguirà la unlockpt()funzione sul suo stdin e scriverà sul suo stdout il nome del dispositivo pty appena sbloccato. L'ho scritto quando ho lavorato su ... Come posso ottenere questo pty e cosa posso farci? .

Comunque, ciò che fa il bit di codice sopra è eseguire una bashshell in un pty un livello sotto l'attuale tty. bashviene detto di scrivere tutto l'output sullo slave pty e l'attuale tty non è configurato né nel -echosuo input né nel buffer, ma invece per passarlo (principalmente) raw a cat, su cui lo copia bash. E nel frattempo un altro, in background, catcopia tutti gli output degli slave nel tty corrente.

Per la maggior parte la configurazione di cui sopra sarebbe del tutto inutile - semplicemente ridondante, fondamentalmente - tranne per il fatto che lanciamo bashcon una copia del suo master pty fd on <>9. Ciò significa che bashpuò scrivere liberamente sul proprio flusso di input con un semplice reindirizzamento. Tutto quello che bashdeve fare è:

echo echo hey >&9

... per parlare a se stesso.

Ecco un'altra foto:

inserisci qui la descrizione dell'immagine


2
In quali terminali sei riuscito a farlo funzionare? Quel genere di cose veniva abusato ai vecchi tempi e dovrebbe essere disabilitato di default al giorno d'oggi. Con xterm, è ancora possibile eseguire una query sul titolo dell'icona con, \e[20tma solo se configurato con allowWindowOps: true.
Stéphane Chazelas,


@ StéphaneChazelas che funziona in Terminologia, ma sono abbastanza sicuro che funzioni anche nel terminale GNOME, nel terminale KDE (ho dimenticato il suo nome e penso che ci sia una via di fuga diversa) , e come dici tu, w / xtermw / il giusto config. Con un xterm corretto, tuttavia, puoi leggere e scrivere il buffer di copia / incolla e quindi diventa più semplice, penso. Xterm ha anche sequenze di escape per cambiare / influenzare il termine stesso descrizione.
Mikeserv,

Non riesco a farlo funzionare se non nella terminologia (che tra l'altro ha diverse altre vulnerabilità simili). Che CVE abbia più di 12 anni e sia relativamente noto, sarei sorpreso se uno dei principali emulatori terminali avesse la stessa vulnerabilità. Nota che con xterm, questo è \e[20t(non \e]1;?\a)
Stéphane Chazelas il


8

Sebbene la ioctl(,TIOCSTI,) risposta di Stéphane Chazelas sia, ovviamente, la risposta giusta, alcune persone potrebbero essere abbastanza felici con questa risposta parziale ma banale: basta semplicemente inserire il comando nello stack della cronologia, quindi l'utente può spostare 1 riga nella cronologia per trovare il comando.

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

Questo può diventare un semplice script, che ha una cronologia a 1 riga:

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

read -eabilita la modifica readline dell'input, -pè un prompt.


Funzionerà solo con le funzioni della shell, o se lo script è stato fornito ( . foo.sho `source foo.sh, invece di essere eseguito in una subshell.) Approccio interessante, comunque. Un trucco simile che richiede la modifica del contesto della shell chiamante sarebbe quello di impostare un completamento personalizzato che espandesse la linea vuota a qualcosa e quindi ripristinasse il vecchio gestore di completamento.
Peter Cordes,

@PeterCordes hai ragione. Stavo prendendo la domanda troppo alla lettera. Ma ho aggiunto un esempio di un semplice script che potrebbe funzionare.
Meuh

@mikeserv Ehi, è solo una soluzione semplice che può essere utile per alcune persone. Puoi anche rimuovere il evalcomando se hai semplici comandi da modificare, senza pipe, reindirizzamenti, ecc.
Meuh

1

Oh mia parola, abbiamo perso una semplice soluzione integrata per bash : il readcomando ha un'opzione -i ..., che se usato con -e, inserisce il testo nel buffer di input. Dalla pagina man:

-i testo

Se readline viene utilizzato per leggere la riga, il testo viene inserito nel buffer di modifica prima dell'inizio della modifica.

Quindi crea una piccola funzione bash o uno script di shell che prende il comando per presentare all'utente ed esegue o valuta la loro risposta:

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

Questo senza dubbio utilizza lo ioctl (, TIOCSTI), che esiste da oltre 32 anni, poiché esisteva già in 2.9BSD ioctl.h .


1
Interessante con un effetto simile, ma non si inietta al prompt.
Starfry,

al 2 ° pensiero hai ragione. bash non ha bisogno di TIOCSTI poiché sta eseguendo tutto l'I / o stesso.
Meuh
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.