O bash pode gravar em seu próprio fluxo de entrada?

39

É possível em um shell bash interativo inserir um comando que produza algum texto para que apareça no próximo prompt de comando, como se o usuário tivesse digitado esse texto nesse prompt?

Desejo poder criar sourceum script que gere uma linha de comando e a produza para que apareça quando o prompt retornar após o término do script, para que o usuário possa editá-lo opcionalmente antes de pressionar enterpara executá-lo.

Isso pode ser alcançado com, xdotoolmas isso só funciona quando o terminal está em uma janela X e somente se estiver instalado.

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

Isso pode ser feito usando apenas o bash?

starfry
fonte
Eu estou pensando que isso não deve ser difícil com o Expect, se você pode tolerar isso e fazer com que ele direcione um subshell; mas não me lembro o suficiente para postar uma resposta real.
Tripleee 17/05/19

Respostas:

40

Com zsh, você pode usar print -zpara colocar algum texto no buffer do editor de linha para o próximo prompt:

print -z echo test

prepararia o editor de linha com o echo testqual você pode editar no próximo prompt.

Eu não acho que bashtenha um recurso semelhante, no entanto, em muitos sistemas, você pode preparar o buffer de entrada do dispositivo terminal com o TIOCSTI ioctl():

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

Seria inserido echo testno buffer de entrada do dispositivo do terminal, como se recebido do terminal.

Uma variação mais portátil da Terminologyabordagem de @ mike e que não sacrifica a segurança seria enviar ao emulador de terminal uma query status reportsequência de escape bastante padrão : <ESC>[5nquais terminais respondem invariavelmente (como entrada) <ESC>[0ne vinculam isso à string que você deseja inserir:

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

Se no GNU screen, você também pode:

screen -X stuff 'echo test'

Agora, exceto pela abordagem TIOCSTI ioctl, estamos solicitando ao emulador de terminal que nos envie uma string como se fosse digitada. Se essa seqüência vem antes readline( bash's editor de linha) desativou eco local terminal, em seguida, essa seqüência será exibido não no shell alerta, atrapalhando a visualização um pouco.

Para contornar isso, você pode atrasar levemente o envio da solicitação ao terminal para garantir que a resposta chegue quando o eco for desativado pela linha de leitura.

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

(assumindo aqui que você sleepsuporta resolução de menos de um segundo).

Idealmente, você gostaria de fazer algo como:

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

No entanto bash(ao contrário de zsh) não tem suporte para um wait-until-the-response-arrivesque não leia a resposta.

No entanto, possui um has-the-response-arrived-yetrecurso com 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"

Leitura adicional

Veja a resposta de @ starfry que se expande nas duas soluções fornecidas por mim e @ mikeserv com algumas informações mais detalhadas.

Stéphane Chazelas
fonte
Eu acho que bind '"\e[0n": "echo test"'; printf '\e[5n'provavelmente a resposta apenas para o bash que estou procurando. Funciona para mim. No entanto, também sou ^[[0nimpresso antes da minha solicitação. Eu descobri que isso é causado quando $PS1contém um subshell. Você pode reproduzi-lo executando PS1='$(:)'antes do comando bind. Por que isso aconteceria e algo pode ser feito sobre isso?
starfry
Embora tudo nesta resposta esteja correto, a pergunta era para bash, não para zsh. Às vezes, não temos escolha de qual shell usar.
Falsenames
@Falsenames apenas o primeiro parágrafo é para zsh. O restante é agnóstico ao shell ou específico do bash. As perguntas e respostas não precisam ser úteis apenas para bash usuários.
Stéphane Chazelas
1
@ starfry parece que talvez você possa colocar um \returno na cabeça de $PS1? Isso deve funcionar se $PS1for longo o suficiente. Se não, então coloque ^[[Mlá.
mikeserv
@mikeserv - rfaz o truque. Obviamente, isso não impede a saída, é apenas substituído antes que os olhos a vejam. Acho que ^[[Mapaga a linha para limpar o texto injetado, caso seja maior que o prompt. Está certo (não consegui encontrá-lo na lista de escape ANSI que tenho)?
starfry
25

Esta resposta é fornecida como esclarecimento do meu próprio entendimento e é inspirada em @ StéphaneChazelas e @mikeserv antes de mim.

TL; DR

  • não é possível fazer isso bashsem ajuda externa;
  • a maneira correta de fazer isso é com uma entrada do terminal de envio, ioctl mas
  • a bashsolução viável mais fácil usa bind.

A solução fácil

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

O Bash possui um shell interno chamado bindque permite que um comando do shell seja executado quando uma sequência de teclas é recebida. Em essência, a saída do comando shell é gravada no buffer de entrada do shell.

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

A sequência de teclas \e[0n( <ESC>[0n) é um código de escape do terminal ANSI que um terminal envia para indicar que está funcionando normalmente. Ele envia isso em resposta a uma solicitação de relatório de status do dispositivo que é enviada como <ESC>[5n.

Ao vincular a resposta a uma echoque gera o texto a ser injetado, podemos injetar esse texto sempre que quisermos solicitando o status do dispositivo e isso é feito enviando uma <ESC>[5nsequência de escape.

printf '\e[5n'

Isso funciona e provavelmente é suficiente para responder à pergunta original porque não há outras ferramentas envolvidas. É puro, bashmas depende de um terminal com bom comportamento (praticamente todos são).

Ele deixa o texto ecoado na linha de comando pronto para ser usado como se tivesse sido digitado. Ele pode ser anexado, editado e pressionado ENTERfaz com que seja executado.

Adicione \nao comando ligado para que ele seja executado automaticamente.

No entanto, esta solução funciona apenas no terminal atual (que está dentro do escopo da pergunta original). Ele funciona a partir de um prompt interativo ou de um script de origem, mas gera um erro se usado em um subshell:

bind: warning: line editing not enabled

A solução correta descrita a seguir é mais flexível, mas depende de comandos externos.

A solução correta

A maneira correta de injetar entrada usa tty_ioctl , uma chamada de sistema unix para Controle de E / S que possui um TIOCSTIcomando que pode ser usado para injetar entrada.

TIOC de " T erminal COI tl " e STI de " S final T erminal I nput ".

Não há comando incorporado bashpara isso; fazer isso requer um comando externo. Não existe tal comando na distribuição típica do GNU / Linux, mas não é difícil de obter com um pouco de programação. Aqui está uma função shell que usa perl:

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

Aqui 0x5412está o código para o TIOCSTIcomando.

TIOCSTIé uma constante definida nos arquivos de cabeçalho C padrão com o valor 0x5412. Tente grep -r TIOCSTI /usr/includeou procure /usr/include/asm-generic/ioctls.h; está incluído nos programas C indiretamente por #include <sys/ioctl.h>.

Você pode então fazer:

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

Implementações em alguns outros idiomas são mostradas abaixo (salve em um arquivo e em seguida chmod +x):

Perl inject.pl

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

Você pode gerar sys/ioctl.phquais define, em TIOCSTIvez de usar o valor numérico. Veja aqui

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)

Rubi inject.rb

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

C inject.c

ajuntar com 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;
}

**! ** Existem outros exemplos aqui .

Usar ioctlpara fazer isso funciona em sub-conchas. Também pode injetar em outros terminais, conforme explicado a seguir.

Indo além (controlando outros terminais)

Está além do escopo da pergunta original, mas é possível injetar caracteres em outro terminal, sujeito às permissões apropriadas. Normalmente, isso significa ser root, mas veja abaixo outras maneiras.

Estender o programa C fornecido acima para aceitar um argumento de linha de comando especificando o tty de outro terminal permite injetar nesse terminal:

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

Ele também envia uma nova linha por padrão, mas, semelhante a echo, fornece uma -nopção para suprimi-la. A opção --tou --ttyrequer um argumento - o ttydo terminal a ser injetado. O valor para isso pode ser obtido nesse terminal:

$ tty
/dev/pts/20

Compile com gcc -o inject inject.c. Prefixe o texto a ser injetado --se ele contiver hífens para impedir que o analisador de argumentos interprete mal as opções da linha de comando. Veja ./inject --help. Use-o assim:

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

ou apenas

$ inject  -- ls -lrt

para injetar o terminal atual.

A injeção em outro terminal requer direitos administrativos que podem ser obtidos por:

  • emitindo o comando como root,
  • usando sudo,
  • ter a CAP_SYS_ADMINcapacidade ou
  • configurando o executável setuid

Para atribuir CAP_SYS_ADMIN:

$  sudo setcap cap_sys_admin+ep inject

Para atribuir setuid:

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

Saída limpa

O texto injetado aparece antes do prompt como se tivesse sido digitado antes do prompt aparecer (o que, na verdade, era), mas depois aparece novamente após o prompt.

Uma maneira de ocultar o texto que aparece antes do prompt é anexá-lo com um retorno de carro ( \rsem avanço de linha) e limpar a linha atual ( <ESC>[M):

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

No entanto, isso limpará apenas a linha na qual o prompt aparece. Se o texto injetado incluir novas linhas, isso não funcionará como pretendido.

Outra solução desativa o eco dos caracteres injetados. Um wrapper usa sttypara fazer isso:

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"

onde injecté uma das soluções descritas acima ou substituída por printf '\e[5n'.

Abordagens alternativas

Se o seu ambiente atender a certos pré-requisitos, você poderá ter outros métodos disponíveis que podem ser usados ​​para injetar entrada. Se você estiver em um ambiente de área de trabalho, o xdotool é um utilitário X.Org que simula a atividade do mouse e do teclado, mas sua distribuição não pode incluí-la por padrão. Podes tentar:

$ xdotool type ls

Se você usa tmux , o multiplexador de terminal, pode fazer o seguinte:

$ tmux send-key -t session:pane ls

onde -tseleciona qual sessão e painel injetar. O GNU Screen tem uma capacidade semelhante com seu stuffcomando:

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

Se sua distribuição inclui o pacote console-tools , você pode ter um writevtcomando que usa ioctlcomo nossos exemplos. A maioria das distribuições, no entanto, obsoleta esse pacote em favor do kbd, que não possui esse recurso.

Uma cópia atualizada do writevt.c pode ser compilada usando gcc -o writevt writevt.c.

Outras opções que podem se encaixar melhor em alguns casos de uso incluem expect e empty, projetados para permitir que ferramentas interativas sejam scripts.

Você também pode usar um shell que suporte injeção terminal, como o zshque pode ser feito print -z ls.

A resposta "Uau, isso é inteligente ..."

O método descrito aqui também é discutido aqui e se baseia no método discutido aqui .

Um redirecionamento de shell /dev/ptmxobtém um novo pseudo-terminal:

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

Uma pequena ferramenta escrita em C que desbloqueia o mestre pseudoterminal (ptm) e gera o nome do escravo pseudoterminal (pts) em sua saída padrão.

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

(salve como pts.ce compile com gcc -o pts pts.c)

Quando o programa é chamado com sua entrada padrão definida como ptm, ele desbloqueia os pontos correspondentes e envia seu nome para a saída padrão.

$ ./pts </dev/ptmx
/dev/pts/20
  • A função unlockpt () desbloqueia o dispositivo pseudoterminal escravo correspondente ao pseudoterminal principal referido pelo descritor de arquivo fornecido. O programa passa como zero, que é a entrada padrão do programa .

  • A função ptsname () retorna o nome do dispositivo pseudoterminal escravo correspondente ao mestre referido pelo descritor de arquivo fornecido, passando novamente zero para a entrada padrão do programa.

Um processo pode ser conectado aos pontos. Primeiro, obtenha um ptm (aqui está atribuído ao descritor de arquivo 3, aberto, leitura e gravação pelo <>redirecionamento).

 exec 3<>/dev/ptmx

Então inicie o processo:

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

Os processos gerados por esta linha de comando são melhor ilustrados com pstree:

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

A saída é relativa ao shell atual ( $$) e o PID ( -p) e PGID ( -g) de cada processo são mostrados entre parênteses (PID,PGID).

No topo da árvore está bash(5203,5203)o shell interativo no qual estamos digitando comandos, e seus descritores de arquivos o conectam ao aplicativo de terminal que estamos usando para interagir com ele ( xtermou similar).

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

Observando o comando novamente, o primeiro conjunto de parênteses iniciou um subshell, bash(6524,6524)) com seu descritor de arquivo 0 (sua entrada padrão ) sendo atribuído aos pts (que é aberto para leitura e gravação <>), retornado por outro subshell que foi executado ./pts <&3para desbloquear o pontos associados ao descritor de arquivo 3 (criado na etapa anterior exec 3<>/dev/ptmx).

O descritor de arquivo 3 do subshell é fechado ( 3>&-) para que o ptm não esteja acessível a ele. Sua entrada padrão (fd 0), que é o pts que foi aberto para leitura / gravação, é redirecionada (na verdade o fd é copiado - >&0) para sua saída padrão (fd 1).

Isso cria um subshell com sua entrada e saída padrão conectada aos pts. Ele pode ser enviado de entrada escrevendo no ptm e sua saída pode ser vista lendo no ptm:

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

O subshell executa este comando:

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

É executado bash(6527,6527)no modo interativo ( -i) em uma nova sessão ( setsid -cobserve que o PID e o PGID são os mesmos). Seu erro padrão é redirecionado para sua saída padrão ( 2>&1) e transmitido por meio de tee(6528,6524)modo que seja gravado em um logarquivo e nos pts. Isso fornece outra maneira de ver a saída do subshell:

$ tail -f log

Como o subshell está em execução bashinterativamente, ele pode receber comandos para executar, como este exemplo, que exibe os descritores de arquivo do subshell:

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

A leitura da saída do subshell ( tail -f logou cat <&3) revela:

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

A entrada padrão (fd 0) é conectada aos pontos e a saída padrão (fd 1) e o erro (fd 2) são conectados ao mesmo tubo, aquele que se conecta 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 uma olhada nos descritores de arquivo de 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

Saída padrão (fd 1) são os pontos: qualquer coisa que 'tee' grave na saída padrão é enviada de volta ao ptm. Erro padrão (fd 2) são os pontos pertencentes ao terminal de controle.

Embrulhando-o

O script a seguir usa a técnica descrita acima. Ele configura uma bashsessão interativa que pode ser injetada gravando em um descritor de arquivo. Está disponível aqui e documentado com explicações.

sh -cm 'cat <&9 &cat >&9|(             ### copy to/from host/slave
        trap "  stty $(stty -g         ### save/restore stty settings on exit
                stty -echo raw)        ### host: no echo and raw-mode
                kill -1 0" EXIT        ### send a -HUP to host pgrp on EXIT
        <>"$($pts <&9)" >&0 2>&1\
        setsid -wc -- bash) <&1        ### point bash <0,1,2> at slave and setsid bash
' --    9<>/dev/ptmx 2>/dev/null       ### open pty master on <>9
starfry
fonte
Com a bind '"\e[0n": "ls -l"'; printf '\e[5n'solução mais fácil , depois de toda a saída do ls -ltambém ^[[0nserá emitida no Terminal, uma vez que eu pressione a tecla Enter, portanto, execute ls -l. Alguma idéia de como "esconder" isso, por favor? Obrigado.
Ali
1
Apresentei uma solução que fornece o efeito que você procura - na seção de saída limpa da minha resposta, sugiro adicionar um retorno ao prompt para ocultar o texto excedente. Eu tentei PS1="\r\e[M$PS1"antes de fazer bind '"\e[0n": "ls -l"'; printf '\e[5n'e isso deu o efeito que você descreve.
starfry 14/11
Obrigado! Perdi totalmente esse ponto.
Ali
20

Depende do que você quer dizer com bash apenas . Se você quer dizer uma única bashsessão interativa , a resposta é quase definitivamente não . E isso ocorre porque mesmo quando você digita um comando como ls -lna linha de comando em qualquer terminal canônico, bashainda não está ciente disso - e bashnem está envolvido nesse ponto.

Em vez disso, o que aconteceu até esse ponto é que a disciplina de linha tty do kernel armazenou e stty echoarmazenou a entrada do usuário apenas na tela. Ele libera essa entrada para o leitor - bash, no seu caso exemplo - linha por linha - e geralmente traduz \rretornos para \newlines em sistemas Unix também - e assim bashnão é - e o seu script de origem também não pode - ser informado de que há entrada até o usuário pressionar a ENTERtecla.

Agora, existem algumas soluções alternativas. O mais robusto não é uma solução alternativa, na verdade, e envolve o uso de vários processos ou programas especialmente escritos para sequenciar a entrada, ocultar a disciplina de linha -echodo usuário e gravar apenas na tela o que é considerado apropriado ao interpretar a entrada especialmente quando necessário. Isso pode ser difícil de fazer, porque significa escrever regras de interpretação que podem manipular char arbitrário por char quando ele chega e escrevê-lo simultaneamente sem erros, a fim de simular o que o usuário médio esperaria nesse cenário. É por esse motivo, provavelmente, que a E / S do terminal interativo raramente é bem compreendida - uma perspectiva difícil não é aquela que se presta a uma investigação mais aprofundada para a maioria.

Outra solução alternativa pode envolver o emulador de terminal. Você diz que um problema para você é uma dependência de X e dexdotool . Nesse caso, uma solução alternativa que estou prestes a oferecer pode ter problemas semelhantes, mas continuarei com a mesma.

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

Que irá trabalhar em um xtermw / o allowwindowOpsconjunto de recursos. Primeiro, ele salva os nomes dos ícones / janelas em uma pilha e depois define a cadeia de ícones do terminal como^Umy command solicitar que o terminal injete esse nome na fila de entrada e, por último, redefine-os para os valores salvos. Ele deve funcionar de forma invisível para bashshells interativos executados xterm com a configuração certa - mas provavelmente é uma má idéia. Por favor, veja os comentários de Stéphane abaixo.

Aqui, porém, está uma foto que tirei do meu terminal Terminology após executar o printfbit com uma sequência de escape diferente na minha máquina. Para cada nova linha no printfcomando, digitei CTRL+Ventão CTRL+Je depois pressionei a ENTERtecla. Não digitei nada depois, mas, como você pode ver, o terminal injetadomy command na fila de entrada da disciplina de linha para mim:

term_inject

A maneira real de fazer isso é com um arquivo aninhado. É como screenetmux um trabalho semelhante - ambos, a propósito, podem tornar isso possível para você. xtermna verdade, vem com um pequeno programa chamado luitque também pode tornar isso possível. Não é fácil, no entanto.

Aqui está uma maneira de você:

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

Isso não é portátil, mas deve funcionar na maioria dos sistemas Linux com as devidas permissões de abertura /dev/ptmx. Meu usuário está notty grupo que é suficiente no meu sistema. Você também vai precisar ...

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

... que, quando executado em um sistema GNU (ou qualquer outro com um compilador C padrão que também pode ler a partir de stdin) , gravará um pequeno binário executável chamado ptsque executará ounlockpt() função em seu stdin e grava em seu stdout o nome do dispositivo pty que acabou de desbloquear. Eu o escrevi ao trabalhar em ... Como faço para encontrar este arquivo e o que posso fazer com ele? .

De qualquer forma, o que o bit de código acima faz é executar um bashshell em um pty uma camada abaixo do tty atual. bashé instruído a gravar toda a saída no pty escravo, e o tty atual é configurado não para -echosua entrada nem para armazená-lo em buffer, mas para transmiti-lo (principalmente) raw paracat qual o copia bash. E, enquanto isso, outro, o background catcopia toda a saída escrava para o tty atual.

Na maioria das vezes, a configuração acima seria totalmente inútil - apenas redundante, basicamente - exceto pelo fato de sermos lançados bashcom uma cópia do seu próprio pty master fd on <>9. Isso significa que bashpode gravar livremente em seu próprio fluxo de entrada com um redirecionamento simples. Tudo o que bashprecisa fazer é:

echo echo hey >&9

... para falar sozinho.

Aqui está outra foto:

insira a descrição da imagem aqui

mikeserv
fonte
2
Em quais terminais você conseguiu fazer isso funcionar? Esse tipo de coisa estava sendo abusado nos velhos tempos e deve ser desativado por padrão hoje em dia. Com xterm, você ainda pode consultar o título do ícone com, \e[20tmas somente se configurado com allowWindowOps: true.
Stéphane Chazelas
Isso é CVE-2003-0063
Stéphane Chazelas
@ StéphaneChazelas que funciona na Terminologia, mas tenho certeza de que também funciona no terminal gnome, no terminal kde (esqueci o nome e acho que há uma fuga diferente) e, como você diz, xtermp / o config. No entanto, com um xterm adequado, você pode ler e escrever o buffer de copiar / colar e, portanto, fica mais simples. O Xterm também possui seqüências de escape para alterar / afetar a própria descrição do termo.
mikeserv
Não consigo fazer isso funcionar em nada além de terminologia (que tem várias outras vulnerabilidades semelhantes). Se o CVE tivesse mais de 12 anos e fosse relativamente conhecido, ficaria surpreso se algum dos emuladores principais tivesse a mesma vulnerabilidade. Note-se que com xterm, isso é \e[20t(não \e]1;?\a)
Stéphane Chazelas
1
O ?a consulta é apenas para fonte e cor lá , não títulos
Stéphane Chazelas
8

Embora a ioctl(,TIOCSTI,) resposta de Stéphane Chazelas seja, é claro, a resposta certa, algumas pessoas podem estar felizes o suficiente com essa resposta parcial, mas trivial: basta pressionar o comando na pilha de histórico, então o usuário pode mover 1 linha acima do histórico para encontrar o comando.

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

Isso pode se tornar um script simples, com seu próprio histórico de 1 linha:

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

read -epermite a edição de linha de leitura da entrada, -pé um prompt.

meuh
fonte
Isso funcionará apenas em funções shell, ou se o script foi originado ( . foo.shou `source foo.sh, em vez de ser executado em uma subshell). Abordagem interessante, no entanto. Um hack semelhante que requer modificar o contexto do shell de chamada seria configurar uma conclusão personalizada que expandisse a linha vazia para algo e depois restaurasse o antigo manipulador de conclusão.
Peter Cordes
@ PeterCordes você está certo. Eu estava levando a pergunta muito literalmente. Mas eu adicionei um exemplo de um script simples que poderia funcionar.
meuh
@mikeserv Ei, é apenas uma solução simples que pode ser útil para algumas pessoas. Você pode até mesmo remover o evalse você tem comandos simples de editar, sem tubos e redirecionamento etc.
meuh
1

Oh, minha palavra, perdemos uma solução simples incorporada ao bash : o readcomando tem uma opção -i ...que, quando usada -e, empurra o texto para o buffer de entrada. Na página do manual:

-i texto

Se a linha de leitura estiver sendo usada para ler a linha, o texto será colocado no buffer de edição antes do início da edição.

Portanto, crie uma pequena função bash ou script de shell que execute o comando para apresentar ao usuário e execute ou avalie sua resposta:

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

Isso sem dúvida usa o ioctl (, TIOCSTI,) que existe há mais de 32 anos, como já existia no 2.9BSD ioctl.h .

meuh
fonte
1
Interessante, com um efeito semelhante, mas não injeta no prompt.
starfry
pensando bem, você está certo. O bash não precisa do TIOCSTI, pois está fazendo todo o I / O em si.
meuh 24/07/2015