Construa um comando colocando uma string em um tty

15

Eu consegui fazer isso

echo -n " command "> / dev / tty1

As letras aparecem e o cursor se move, mas são "fantasmas" - se você pressionar Enter, nada acontece (eles não estão em stdin).

Editar:

No meio da captura de tela abaixo, você vê por que vejo o uso disso. (A linha com uma legenda vermelha, logo abaixo da linha com uma legenda amarela.) Como está agora, você não está realmente "editando" o texto da nota; você é solicitado a escrever um novo texto, que substituirá o texto da nota que você está (na verdade) não editando. Assim, pensei que poderia ser remediado simplesmente colando o texto antigo no tty: se o usuário pressionar enter, nenhuma modificação será feita. (Este programa está em Perl / MySQL, mas pensei que seria mais interessante solicitar uma solução geral do que "como faço isso no Perl".)

exemplo

Edição 2:

Aqui está o código Perl, que usa o código C abaixo (funciona exatamente como pretendido), bem como uma nova captura de tela - espero que isso esclareça as coisas além da dúvida :) Novamente, olhe para o meio da captura de tela, onde a edição é feita para o texto da nota - desta vez, o texto antigo está lá; por exemplo, se você apenas deseja corrigir um erro de digitação, não precisará digitar novamente o texto inteiro da nota.

my $edit_note_text = $edit_note_data[2];
print BOLD, RED, " new text: ", RESET;
system("writevt /dev/tty \"$edit_note_text\"");
my $new_text = <$in>;
$new_text = fix_input($new_text);
my $set_text = "UPDATE notes SET note = \"$new_text\" WHERE id = $edit_note_id";
$db->do($set_text);

better_example

Emanuel Berg
fonte
Eu fiz isso em Python no Stack Overflow, se você estiver interessado. stackoverflow.com/a/29616465/117471
Bruno Bronosky,
Sua declaração de problema não está clara. Qual é o problema?

Respostas:

3

Acabei de encontrar um pequeno programa C chamado writevtque faz o truque. Pegue o código fonte aqui . Para compilar, gccremova primeiro as seguintes linhas:

#include <lct/cline.h>
#include <lct/utils.h>

Update . O comando agora faz parte do console-tools , disponível em sistemas mais recentes, a menos que sua distribuição use o kbd em vez do console-tools ; nesse caso, você pode compilá-lo da fonte (versão muito mais recente, nenhuma modificação necessária).

Uso:

sudo writevt /dev/ttyN command 

Observe que, por algum motivo, você precisa usar '\r'(ou '\x0D') em vez de '\n'(ou '\x0A') para enviar um retorno.

PersianGulf
fonte
Isso funciona, mas há muito mais errado do que apenas aqueles incluídos. Eu tive que abandonar a função de uso, fazer um prognamee _, e comentário fora algumas chamadas de função emmain()
Michael Mrozek
@MichaelMrozek A _()função geralmente é um sinal de gettext sendo usado. Parece um pouco exagerado para um pedaço de código de demonstração tão simples, mas não machuca, suponho.
Jw013 13/09/12
O link na resposta acima está quebrado. Encontrei outro writevt.c aqui (em github.com/  grawity ) ; parece ser essencialmente o mesmo programa.
G-Man diz 'Reinstate Monica'
Não funciona para mim - apenas imprime o comando. Dending \ r ou \ n prinst rn resperctively para qualquer outra coisa; /
Antoniossss
10

Um terminal funciona como duas coisas: um dispositivo de entrada (como um teclado) e um dispositivo de exibição (como um monitor). Quando você lê do terminal, obtém o que vem do dispositivo de entrada. Quando você escreve no terminal, os dados vão para o dispositivo de exibição.

Não existe uma maneira geral de forçar a entrada em um terminal. Raramente é necessário fazer isso. Se você precisar interagir com um programa que exija um terminal, use um emulador de terminal dedicado, como Expect ou Empty , ou um invólucro de terminal programável, como Screen ou Tmux . Você pode forçar a entrada em um console Linux com um ioctl . Você pode forçar a entrada em um emulador de terminal X11 com ferramentas como xdotool ou xmacro .

Gilles 'SO- parar de ser mau'
fonte
Fiz uma edição no meu post. Dê uma olhada e você verá o meu pensamento.
Emanuel Berg
@EmanuelBerg Sua edição é difícil de entender. Você está tentando alimentar de maneira programática a entrada em um programa que também está usando interativamente? Se é isso que você deseja, execute o programa em screenou tmuxe use o comando stuff(screen) ou send-key(tmux) ou o recurso de buffer de colagem.
Gilles 'SO- stop be evil'
Foi feita uma segunda edição com o código Perl incluído - a chamada do binário C está lá. Eu não sei ... como era tão simples (apenas uma linha de código) - é realmente melhor fazer do seu jeito (com as ferramentas screenou tmux)?
Emanuel Berg
@EmanuelBerg Então, sim, você está procurando screen -X stuff 'note version one'.
Gilles 'SO- stop be evil'
7

Pelo menos Linux e BSDs têm o TIOCSTI ioctl para enviar caracteres de volta ao buffer de entrada do terminal (até um limite [4096 caracteres no Linux]):

#include <sys/ioctl.h>
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>

void stackchar(char c)
{
  if (ioctl(0, TIOCSTI, &c) < 0) {
    perror("ioctl");
    exit(1);
  }
}
int main(int argc, char *argv[])
{
  int i, j;
  char c;

  for (i = 1; i < argc; i++) {
    if (i > 1) stackchar(' ');
    for (j=0; (c = argv[i][j]); j++) {
      stackchar(c);
    }
  }
  exit(0);
}

Compile e chame-o como:

cmd foo bar < "$some_tty"

para empurrar caracteres de volta em alguns tty.

E em perl:

require "sys/ioctl.ph";
ioctl(STDIN, &TIOCSTI, $_) for split "", join " ", @ARGV;

Edit : Percebo agora que é o mesmo ioctl da solução writevt . O comentário e o nome do comando são enganosos, pois o TIOCSTI funciona em qualquer terminal, não apenas nos VTs.

Stéphane Chazelas
fonte
Confira minha segunda edição da pergunta. Eu já compilei o código que recebi do @htor - o que posso ver, funciona muito bem. Você vê alguma vantagem ao usar esse código? (Mas obrigado por seu esforço em ambos os casos.)
Emanuel Berg
Sim. Veja minha edição recente. O ponto é usar o TIOCSTI ioctl. O código que forneci faz exatamente isso no descritor de arquivo 0 (stdin).
Stéphane Chazelas
3

Eu tenho uma demonstração mais completa no Stack Overflow .

Em python, você pode fazer:

import fcntl
import sys
import termios

with open('/dev/tty1', 'w') as fd:
    for char in "ls -la\n":
        fcntl.ioctl(fd, termios.TIOCSTI, char)

Isso pressupõe um "command"valor simples ls -lae o caminho tty especificado por OP.

Bruno Bronosky
fonte