É 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 source
um 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 enter
para executá-lo.
Isso pode ser alcançado com, xdotool
mas 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?
Respostas:
Com
zsh
, você pode usarprint -z
para colocar algum texto no buffer do editor de linha para o próximo prompt:prepararia o editor de linha com o
echo test
qual você pode editar no próximo prompt.Eu não acho que
bash
tenha um recurso semelhante, no entanto, em muitos sistemas, você pode preparar o buffer de entrada do dispositivo terminal com oTIOCSTI
ioctl()
:Seria inserido
echo test
no buffer de entrada do dispositivo do terminal, como se recebido do terminal.Uma variação mais portátil da
Terminology
abordagem de @ mike e que não sacrifica a segurança seria enviar ao emulador de terminal umaquery status report
sequência de escape bastante padrão :<ESC>[5n
quais terminais respondem invariavelmente (como entrada)<ESC>[0n
e vinculam isso à string que você deseja inserir:Se no GNU
screen
, você também pode: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.
(assumindo aqui que você
sleep
suporta resolução de menos de um segundo).Idealmente, você gostaria de fazer algo como:
No entanto
bash
(ao contrário dezsh
) não tem suporte para umwait-until-the-response-arrives
que não leia a resposta.No entanto, possui um
has-the-response-arrived-yet
recurso comread -t0
: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.
fonte
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^[[0n
impresso antes da minha solicitação. Eu descobri que isso é causado quando$PS1
contém um subshell. Você pode reproduzi-lo executandoPS1='$(:)'
antes do comando bind. Por que isso aconteceria e algo pode ser feito sobre isso?\r
eturno na cabeça de$PS1
? Isso deve funcionar se$PS1
for longo o suficiente. Se não, então coloque^[[M
lá.r
faz o truque. Obviamente, isso não impede a saída, é apenas substituído antes que os olhos a vejam. Acho que^[[M
apaga 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)?Esta resposta é fornecida como esclarecimento do meu próprio entendimento e é inspirada em @ StéphaneChazelas e @mikeserv antes de mim.
TL; DR
bash
sem ajuda externa;ioctl
masbash
solução viável mais fácil usabind
.A solução fácil
O Bash possui um shell interno chamado
bind
que 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.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
echo
que gera o texto a ser injetado, podemos injetar esse texto sempre que quisermos solicitando o status do dispositivo e isso é feito enviando uma<ESC>[5n
sequência de escape.Isso funciona e provavelmente é suficiente para responder à pergunta original porque não há outras ferramentas envolvidas. É puro,
bash
mas 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
ENTER
faz com que seja executado.Adicione
\n
ao 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:
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
TIOCSTI
comando 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
bash
para 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 usaperl
:Aqui
0x5412
está o código para oTIOCSTI
comando.TIOCSTI
é uma constante definida nos arquivos de cabeçalho C padrão com o valor0x5412
. Tentegrep -r TIOCSTI /usr/include
ou procure/usr/include/asm-generic/ioctls.h
; está incluído nos programas C indiretamente por#include <sys/ioctl.h>
.Você pode então fazer:
Implementações em alguns outros idiomas são mostradas abaixo (salve em um arquivo e em seguida
chmod +x
):Perl
inject.pl
Você pode gerar
sys/ioctl.ph
quais define, emTIOCSTI
vez de usar o valor numérico. Veja aquiPython
inject.py
Rubi
inject.rb
C
inject.c
ajuntar com
gcc -o inject inject.c
**! ** Existem outros exemplos aqui .
Usar
ioctl
para 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:
Ele também envia uma nova linha por padrão, mas, semelhante a
echo
, fornece uma-n
opção para suprimi-la. A opção--t
ou--tty
requer um argumento - otty
do terminal a ser injetado. O valor para isso pode ser obtido nesse terminal: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:ou apenas
para injetar o terminal atual.
A injeção em outro terminal requer direitos administrativos que podem ser obtidos por:
root
,sudo
,CAP_SYS_ADMIN
capacidade ousetuid
Para atribuir
CAP_SYS_ADMIN
:Para atribuir
setuid
: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 (
\r
sem avanço de linha) e limpar a linha atual (<ESC>[M
):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
stty
para fazer isso:onde
inject
é uma das soluções descritas acima ou substituída porprintf '\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:
Se você usa tmux , o multiplexador de terminal, pode fazer o seguinte:
onde
-t
seleciona qual sessão e painel injetar. O GNU Screen tem uma capacidade semelhante com seustuff
comando:Se sua distribuição inclui o pacote console-tools , você pode ter um
writevt
comando que usaioctl
como 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
zsh
que pode ser feitoprint -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/ptmx
obtém um novo pseudo-terminal: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.
(salve como
pts.c
e compile comgcc -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.
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).Então inicie o processo:
Os processos gerados por esta linha de comando são melhor ilustrados com
pstree
: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 (xterm
ou similar).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 <&3
para desbloquear o pontos associados ao descritor de arquivo 3 (criado na etapa anteriorexec 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:
O subshell executa este comando:
É executado
bash(6527,6527)
no modo interativo (-i
) em uma nova sessão (setsid -c
observe 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 detee(6528,6524)
modo que seja gravado em umlog
arquivo e nos pts. Isso fornece outra maneira de ver a saída do subshell:Como o subshell está em execução
bash
interativamente, ele pode receber comandos para executar, como este exemplo, que exibe os descritores de arquivo do subshell:A leitura da saída do subshell (
tail -f log
oucat <&3
) revela: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
:E uma olhada nos descritores de arquivo de
tee
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
bash
sessão interativa que pode ser injetada gravando em um descritor de arquivo. Está disponível aqui e documentado com explicações.fonte
bind '"\e[0n": "ls -l"'; printf '\e[5n'
solução mais fácil , depois de toda a saída dols -l
também^[[0n
será emitida no Terminal, uma vez que eu pressione a tecla Enter, portanto, executels -l
. Alguma idéia de como "esconder" isso, por favor? Obrigado.PS1="\r\e[M$PS1"
antes de fazerbind '"\e[0n": "ls -l"'; printf '\e[5n'
e isso deu o efeito que você descreve.Depende do que você quer dizer com
bash
apenas . Se você quer dizer uma únicabash
sessão interativa , a resposta é quase definitivamente não . E isso ocorre porque mesmo quando você digita um comando comols -l
na linha de comando em qualquer terminal canônico,bash
ainda não está ciente disso - ebash
nem está envolvido nesse ponto.Em vez disso, o que aconteceu até esse ponto é que a disciplina de linha tty do kernel armazenou e
stty echo
armazenou 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\r
retornos para\n
ewlines em sistemas Unix também - e assimbash
não é - e o seu script de origem também não pode - ser informado de que há entrada até o usuário pressionar aENTER
tecla.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
-echo
do 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 de
xdotool
. Nesse caso, uma solução alternativa que estou prestes a oferecer pode ter problemas semelhantes, mas continuarei com a mesma.Que irá trabalhar em um
xterm
w / oallowwindowOps
conjunto 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 parabash
shells interativos executadosxterm
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
printf
bit com uma sequência de escape diferente na minha máquina. Para cada nova linha noprintf
comando, digiteiCTRL+V
entãoCTRL+J
e depois pressionei aENTER
tecla. Não digitei nada depois, mas, como você pode ver, o terminal injetadomy command
na fila de entrada da disciplina de linha para mim:A maneira real de fazer isso é com um arquivo aninhado. É como
screen
etmux
um trabalho semelhante - ambos, a propósito, podem tornar isso possível para você.xterm
na verdade, vem com um pequeno programa chamadoluit
que também pode tornar isso possível. Não é fácil, no entanto.Aqui está uma maneira de você:
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 ...... 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
pts
que 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
bash
shell 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-echo
sua entrada nem para armazená-lo em buffer, mas para transmiti-lo (principalmente)raw
paracat
qual o copiabash
. E, enquanto isso, outro, o backgroundcat
copia 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
bash
com uma cópia do seu próprio pty master fd on<>9
. Isso significa quebash
pode gravar livremente em seu próprio fluxo de entrada com um redirecionamento simples. Tudo o quebash
precisa fazer é:... para falar sozinho.
Aqui está outra foto:
fonte
xterm
, você ainda pode consultar o título do ícone com,\e[20t
mas somente se configurado comallowWindowOps: true
.xterm
p / 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.\e[20t
(não\e]1;?\a
)?
a consulta é apenas para fonte e cor lá , não títulosEmbora 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.Isso pode se tornar um script simples, com seu próprio histórico de 1 linha:
read -e
permite a edição de linha de leitura da entrada,-p
é um prompt.fonte
. foo.sh
ou `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.eval
se você tem comandos simples de editar, sem tubos e redirecionamento etc.Oh, minha palavra, perdemos uma solução simples incorporada ao bash : o
read
comando tem uma opção-i ...
que, quando usada-e
, empurra o texto para o buffer de entrada. Na página do manual: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:
Isso sem dúvida usa o ioctl (, TIOCSTI,) que existe há mais de 32 anos, como já existia no 2.9BSD ioctl.h .
fonte