Como imprimir texto no terminal como se estivesse sendo digitado?

27

Tenho uma echoimpressão simples que adicionei ao meu .bashrc:

echo "$(tput setaf 2)Wake up....."
sleep 2s
reset
sleep 2s
echo "$(tput setaf 2)Wake up....."
sleep 2s
reset
echo "$(tput setaf 2)Wake up neo....."
sleep 2s
echo "$(tput setaf 2)The Matrix has you......"
sleep 2s
reset
echo "$(tput setaf 2)Follow the white rabbit......"
sleep 2s
reset
cmatrix

Isso imprime uma mensagem no terminal, mas quero que pareça estar sendo digitada, com um atraso consistente entre os caracteres.

SimplySimplified
fonte
1
Para ser realista, acho que você deve digitar um erro de digitação aleatório com um ou mais espaços para corrigir. Por exemplo, ao digitar esse comentário, tive que voltar ao espaço "através" para corrigi-lo para "jogado". Torna uma pergunta mais difícil de responder, mas torna-a mais realista. Se os personagens se repetem a um ritmo constante de 30 CPS ou 60 CPS, parece menos humano. Certas teclas são digitadas mais rapidamente juntas, enquanto outras combinações de teclas parecem mais lentas. É necessário algum tipo de correspondência de padrão das combinações de teclas para acelerar.
WinEunuuchs2Unix
2
@ WinEunuuchs2Unix Eu não acho que isso ainda seja SimplySimplified. ;)
dessert
1
Veja minha resposta aqui
Pausado até novo aviso.

Respostas:

28

Isso não funciona com Wayland; se você está usando o Ubuntu 17.10 e não mudou para o Xorg no login, esta solução não é para você.

Você pode usar xdotool Instale o xdotoolpara isso. Se o atraso entre as teclas for consistente , é simples assim:

xdotool type --delay 100 something

Isso digita somethingcom um atraso de 100milissegundos entre cada pressionamento de tecla.


Se o atraso entre as teclas for aleatório , digamos de 100 a 300 milissegundos, as coisas ficam um pouco mais complicadas:

$ text="some text"
  for ((i=0;i<${#text};i++));
  do
    if [[ "${text:i:1}" == " " ]];
    then
      echo -n "key space";
    else
      echo -n "key ${text:i:1}";
    fi;
  [[ $i < $((${#text}-1)) ]] && echo -n " sleep 0.$(((RANDOM%3)+1)) ";
  done | xdotool -

Esse forloop percorre todas as letras da sequência salva na variável text, imprimindo um key <letter>ou key spaceno caso de um espaço seguido por sleep 0.e um número aleatório entre 1 e 3 ( xdotool's sleepinterpreta o número como segundos). Toda a saída do loop é então canalizada xdotool, o que imprime as letras com o atraso aleatório no meio. Se você quiser alterar o atraso, basta alterar a parte, sendo o limite inferior e superior - por 0,2 a 0,5 segundos .(RANDOM%x)+yyx-1+y(RANDOM%4)+2

Observe que essa abordagem não imprime o texto, mas digite -o exatamente como o usuário faria, sintetizando pressionamentos de tecla únicos. Em conseqüência, o texto é digitado na janela atualmente focada; se você alterar a parte do foco, o texto será digitado na janela recém-focada, que pode ou não ser o que você deseja. Em ambos os casos, dê uma olhada nas outras respostas aqui, todas brilhantes!

sobremesa
fonte
24

Tentei o xdotool depois de ler a resposta da @ dessert, mas não consegui fazê-lo funcionar por algum motivo. Então, eu vim com isso:

while read line
do
    grep -o . <<<$line | while read a
    do
        sleep 0.1
        echo -n "${a:- }"
    done
    echo
done

Canalize seu texto no código acima e ele será impresso como digitado. Você também pode adicionar aleatoriedade substituindo sleep 0.1por sleep 0.$((RANDOM%3)).

Versão estendida com erros de digitação

Esta versão apresentará um erro de digitação de vez em quando e a corrigirá:

while read line
do
    # split single characters into lines
    grep -o . <<<$line | while read a
    do
        # short random delay between keystrokes
        sleep 0.$((RANDOM%3))
        # make fake typo every 30th keystroke
        if [[ $((RANDOM%30)) == 1 ]]
        then
            # print random character between a-z
            printf "\\$(printf %o "$((RANDOM%26+97))")"
            # wait a bit and delete it again
            sleep 0.5; echo -ne '\b'; sleep 0.2
        fi
        # output a space, or $a if it is not null
        echo -n "${a:- }"
    done
    echo
done
Sebastian Stark
fonte
Eu acho que faria isso: while IFS= read -r line; do for (( i = 0; i < ${#line}; i++ )); do sleep 0.1; printf "%s" "${line:i:1}"; done; echo; done(substitua ;por novas linhas e recuo conforme necessário). O IFS= read -re printf "%s"garantir que espaços em branco e caracteres especiais não são tratados de forma diferente. E o grepem cada linha para dividir em caracteres é desnecessário - apenas um forloop sobre cada caractere na linha é suficiente.
Digital Trauma
18

Você menciona um atraso consistente entre os caracteres, mas se realmente deseja que ele seja digitado, o tempo não será perfeitamente consistente. Para conseguir isso, você pode gravar sua própria digitação com o scriptcomando e reproduzi-lo com scriptreplay:

$ script -t -c "sed d" script.out 2> script.timing
Script started, file is script.out
Wake up ...
Wake up ...
Wake up Neo ...
Script done, file is script.out
$ 
$ scriptreplay script.timing script.out
Wake up ...
Wake up ...
Wake up Neo ...

$ 

A gravação é interrompida pressionando CTRL-D.

Passar o -tparâmetro para as scriptinstruções também gera informações de tempo, que eu redirecionei para o script.timingarquivo. Eu passei sed dcomo um comando para, scriptpois essa é simplesmente uma maneira de absorver a entrada (e isso registra as teclas digitadas) sem efeitos colaterais.

Se você quiser fazer todas as coisas tput/ resettambém, poderá fazer uma scriptgravação para cada uma das suas linhas e reproduzi-las, intercaladas com os comandos tput/ reset.

Trauma Digital
fonte
11

Outra possibilidade é usar o Demo Magic ou, para ser mais preciso, apenas a função de impressão dessa coleção de scripts, que basicamente equivale a

#!/bin/bash

. ./demo-magic.sh -w2

p "this will look as if typed"

Sob o capô, isso usa o pv , que obviamente você também pode usar para obter diretamente o efeito desejado, a forma básica é a seguinte:

echo "this will look as if typed" | pv -qL 20
godfatherofpolka
fonte
1
Esse uso de PV é ótimo.
Sebastian Stark
3
Não há necessidade de tubulação echopara pv, basta usar pv -qL20 <<< "Hello world"se seus apoios shell herestrings.
Dr01
8

De acordo com meu apelido, posso oferecer outra solução:

echo "something" | 
    perl \
        -MTime::HiRes=usleep \
        -F'' \
        -e 'BEGIN {$|=1} for (@F) { print; usleep(100_000+rand(200_000)) }'

Parece estranho, não é?

  • -MTime::HiRes=usleepimporta a função usleep(suspensão de microssegundos) do Time::HiResmódulo porque o habitual sleepaceita apenas segundos inteiros.
  • -F''divide a entrada fornecida em caracteres (o delimitador está vazio '') e coloca os caracteres na matriz @F.
  • BEGIN {$|=1} desativa o buffer de saída para que cada caractere seja impresso imediatamente.
  • for (@F) { print; usleep(100_000+rand(200_000)) } apenas itera sobre os caracteres
  • colocar sublinhados em números é uma maneira comum de usar algum tipo de separador de milhares em Perl. Eles são simplesmente ignorados pelo Perl, para que possamos, por exemplo, escrever 1_000(== 1000) ou mesmo 1_0_00se considerarmos mais fácil de ler.
  • rand() retorna um número aleatório entre 0 e o argumento fornecido, portanto, juntos, eles dormem entre 100.000 e 299.999 microssegundos (0,1-0,3 segundos).
PerlDuck
fonte
Apenas por curiosidade: rand()retorna um número de 0 ao argumento (100k a 300k no seu exemplo) ou entre eles (100k + 1 a 300k-1 no seu exemplo)?
dessert
1
Retorna um número no intervalo [0,200k), ou seja, inclui 0, mas exclui 200k. O comportamento exato é documentado aqui : "retorna um número fracionário aleatório maior ou igual a 0 e menor do que o valor de expr (expr deve ser positivo.)."
PerlDuck
1
Isso não funciona sem -a e -n
rrauenza
@rrauenza Desde o Perl 5.20, ele faz. -Fimplica -ae -aimplica -n.
PerlDuck
Ah, ok, eu estava usando o 5.16, que é o que está no CentOS7.
Rrauenza
6

Outra ferramenta que pode funcionar, que não depende de x11 ou o que seja, é o asciicinema . Ele grava tudo o que você faz no seu terminal e permite que você repita isso como se fosse uma captura de tela, só então é puramente baseada em ASCII! No entanto, talvez seja necessário desativar temporariamente o prompt para que ele seja visualmente limpo. Como outros já apontaram, adicionar um atraso consistente não parecerá natural, e digitar você mesmo pode ser um dos olhares mais naturais que você pode obter.

Depois de gravar o texto, você pode fazer algo como:

$ asciinema play [your recording].cast; cmatrix
rien333
fonte
6

Estou surpreso que ninguém tenha mencionado isso ainda, mas você pode fazer isso com ferramentas de estoque e um loop:

typeit() {
    local IFS=''
    while read -n1 c; do
        echo -n "$c"
        sleep .1
    done <<< "$1"
}

Ele apenas circula o caractere de entrada por caractere e os imprime com um atraso após cada um. A única parte complicada é que você deve definir seu IFS como uma string vazia para que o bash não tente separar seus espaços.

Essa solução é simples, adicionando atrasos variáveis ​​entre caracteres, erros de digitação, o que for super fácil.

EDIT (obrigado, @dessert): se você quiser uma interface um pouco mais natural, poderá fazer

typeit() {
    local IFS=''
    while read -n1 c; do
        echo -n "$c"
        sleep .1
    done <<< "$@"
}

Isso permitiria que você chamasse a função em typeit foo barvez de typeit 'foo bar'. Esteja ciente de que, sem aspas, os argumentos estão sujeitos à divisão de palavras do bash; portanto, por exemplo, typeit foo<space><space>barserão impressos foo<space>bar. Para preservar espaços em branco, use aspas.

whereswalden
fonte
Boa sugestão, embora se deva observar que a divisão de palavras se aplica. Por exemplo, typeit foo<space>barirá resultar em foo bar, ao passo que typeit foo<space><space>barirá também resultar em foo bar. Você precisa citá-lo para garantir que seja literal. @dessert fique à vontade para sugerir uma edição. Eu posso fazer isso sozinho, mas quero lhe dar a chance de obter crédito por isso.
whereswalden
+1 por me ensinar sobre read -n1(o que, aliás, está read -k1em zsh) #
Sebastian Stark
5

Primeiro, "parece que está sendo digitado, com um atraso consistente entre os caracteres ..." é um pouco contraditório, como outros já apontaram. Algo sendo digitado não tem um atraso consistente. Quando você vê algo produzido com um atraso inconsistente, fica arrepiado. "O que tomou conta do meu computador !!! ??!?"

De qualquer forma...

Eu tenho que gritar expect, o que deve estar disponível na maioria das distribuições Linux. A Old School, eu sei, mas, desde que esteja instalada, dificilmente poderia ser mais simples:

echo 'set send_human {.1 .3 1 .05 2}; send -h "The Matrix has you......\n"' | expect -f /dev/stdin

Na página do manual:

O sinalizador -h força a saída a ser enviada (um pouco) como um humano realmente digitando. Atrasos humanos aparecem entre os personagens. (O algoritmo é baseado em uma distribuição Weibull, com modificações para se adequar a esta aplicação específica.) Essa saída é controlada pelo valor da variável "send_human" ...

Consulte https://www.tcl.tk/man/expect5.31/expect.1.html

Mike S
fonte