¿Cómo imprimir texto en el terminal como si se estuviera escribiendo?

27

Tengo una echoimpresión simple que he agregado a mi .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

Esto imprime un mensaje en la terminal, pero quiero que parezca que se está escribiendo, con un retraso constante entre los caracteres.

Simplemente simplificado
fuente
1
Para ser realista, creo que debería tener errores tipográficos aleatorios con uno o más espacios posteriores para corregir. Por ejemplo, mientras escribía este comentario, tuve que retroceder el espacio "a través" para corregirlo a "arrojado". Hace que sea una pregunta más difícil de responder, pero la hace más realista. Si los personajes se repiten a un ritmo constante de 30 CPS o 60 CPS, se verá menos humano. Ciertas teclas se escriben más rápido juntas, mientras que otras combinaciones de teclas aparecen más lentamente. Se necesita algún tipo de coincidencia de patrones de combinaciones de teclas con la velocidad.
WinEunuuchs2Unix
2
@ WinEunuuchs2Unix No creo que esto siga siendo simplemente simplificado. ;)
postre
1
Vea mi respuesta aquí
pausa hasta nuevo aviso.

Respuestas:

28

Esto no funciona con Wayland; Si estás usando Ubuntu 17.10 y no cambiaste a Xorg al iniciar sesión, esta solución no es para ti.

Puedes usar xdotool Instalar xdotoolpara eso. Si el retraso entre las pulsaciones de teclas debe ser constante , es tan simple como eso:

xdotool type --delay 100 something

Esto escribe somethingcon un retraso de 100milisegundos entre cada pulsación de tecla.


Si el retraso entre las pulsaciones de teclas debe ser aleatorio , digamos de 100 a 300 milisegundos, las cosas se vuelven un poco más 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 -

Este forbucle pasa a través de cada letra de la cadena guardado en la variable text, la impresión, ya sea key <letter>o key spaceen el caso de un espacio seguido de sleep 0.y un número aleatorio entre 1 y 3 ( xdotool's sleepinterpreta el número como segundos). Luego se canaliza toda la salida del bucle xdotool, que imprime las letras con el retraso aleatorio en el medio. Si desea cambiar la demora, simplemente cambie la parte, siendo el límite inferior y el superior, durante 0.2 a 0.5 segundos sería .(RANDOM%x)+yyx-1+y(RANDOM%4)+2

Tenga en cuenta que este enfoque no imprime el texto, sino que lo escribe exactamente como lo haría el usuario, sintetizando pulsaciones de teclas individuales. En consecuencia, el texto se escribe en la ventana actualmente enfocada; si cambia el foco, la parte del texto se escribirá en la ventana recién enfocada, que puede o no ser lo que desea. En cualquier caso, eche un vistazo a las otras respuestas aquí, ¡todas las cuales son brillantes!

postre
fuente
24

Intenté xdotool después de leer la respuesta de @ dessert pero no pude hacerlo funcionar por alguna razón. Entonces se me ocurrió esto:

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

Canalice su texto en el código anterior y se imprimirá como mecanografiado. También puede agregar aleatoriedad reemplazando sleep 0.1con sleep 0.$((RANDOM%3)).

Versión extendida con errores tipográficos

Esta versión introducirá un error tipográfico de vez en cuando y lo corregirá:

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
fuente
Creo que haría esto en su lugar: while IFS= read -r line; do for (( i = 0; i < ${#line}; i++ )); do sleep 0.1; printf "%s" "${line:i:1}"; done; echo; done(reemplazar ;con nuevas líneas y buena sangría según sea necesario). El IFS= read -ry printf "%s"asegúrese de que los espacios en blanco y los caracteres especiales no se traten de manera diferente. Y grepen cada línea para dividir en caracteres es innecesario: solo un forbucle sobre cada carácter en la línea es suficiente.
Trauma digital
18

Mencionas un retraso constante entre los caracteres, pero si realmente quieres que parezca que está siendo tipeado, el tiempo no será perfectamente consistente. Para lograr esto, puede grabar su propia escritura con el scriptcomando y reproducirlo con 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 ...

$ 

La grabación se detiene presionando CTRL-D.

Pasar el -tparámetro a scriptinstrucciones también genera información de tiempo, que he redirigido al script.timingarchivo. He pasado sed dcomo un comando a, scriptya que esta es simplemente una forma de absorber la entrada (y esto registra las pulsaciones de teclas) sin efectos secundarios.

Si desea hacer todas las cosas tput/ resettambién, es posible que desee hacer una scriptgrabación para cada una de sus líneas y reproducirlas, intercaladas con los comandos tput/ reset.

Trauma digital
fuente
11

Otra posibilidad es usar Demo Magic o, para ser más precisos, solo la función de impresión de esta colección de scripts, que básicamente equivale a

#!/bin/bash

. ./demo-magic.sh -w2

p "this will look as if typed"

Debajo del capó, esto usa pv , que por supuesto también puede usar para obtener directamente el efecto deseado, la forma básica se ve de la siguiente manera:

echo "this will look as if typed" | pv -qL 20
padrino de polka
fuente
1
Este uso de pv es simplemente genial.
Sebastian Stark
3
No hay necesidad de tubería echoa pv, sólo tiene que utilizar pv -qL20 <<< "Hello world"si es que admite la concha herestrings.
dr01
8

En línea con mi apodo, puedo ofrecer otra solución:

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

Se ve raro, ¿no?

  • -MTime::HiRes=usleepimporta la función usleep(suspensión de microsegundos) del Time::HiResmódulo porque lo habitual sleepsolo acepta segundos enteros.
  • -F''divide la entrada dada en caracteres (el delimitador está vacío '') y coloca los caracteres en la matriz @F.
  • BEGIN {$|=1} desactiva el almacenamiento en búfer de salida para que cada carácter se imprima inmediatamente.
  • for (@F) { print; usleep(100_000+rand(200_000)) } solo itera sobre los personajes
  • poner guiones bajos en números es una forma común de usar algún tipo de separadores de miles en Perl. Perl simplemente los ignora, por lo que podemos, por ejemplo, escribir 1_000(== 1000) o incluso 1_0_00si consideramos que es más fácil de leer.
  • rand() devuelve un número aleatorio entre 0 y el argumento dado, por lo que juntos duerme entre 100,000 y 299,999 microsegundos (0.1-0.3 segundos).
PerlDuck
fuente
Solo por curiosidad: ¿ rand()Devuelve un número de 0 al argumento (100k a 300k en su ejemplo) o entre ellos (100k + 1 a 300k-1 en su ejemplo)?
postre
1
Devuelve un número en el intervalo [0,200k), es decir, incluye 0 pero excluye 200k. El comportamiento exacto se documenta aquí : "Devuelve un número fraccionario aleatorio mayor o igual a 0 y menor que el valor de EXPR. (EXPR debe ser positivo)"
PerlDuck
1
Esto no funciona sin -a y -n
rrauenza
@rrauenza Desde Perl 5.20 lo hace. -Fimplica -ae -aimplica -n.
PerlDuck
Ah, ok, estaba usando 5.16, que es lo que está en CentOS7.
rrauenza
6

Otra herramienta que podría funcionar, que no depende de x11 o lo que sea, es el asciicinema . Registra todo lo que haces en tu terminal y te permite reproducirlo como si fuera una captura de pantalla, ¡solo entonces está basado exclusivamente en ASCII! Sin embargo, es posible que deba deshabilitar temporalmente su solicitud para que sea puramente visualmente limpio. Como otros han señalado, agregar un retraso constante no se verá natural, y escribirlo usted mismo podría ser uno de los looks más naturales que puede lograr.

Después de haber grabado el texto, puede hacer algo como:

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

Me sorprende que nadie haya mencionado esto todavía, pero puede lograr esto con herramientas de stock y un bucle:

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

Simplemente recorre la entrada carácter por carácter y los imprime con un retraso después de cada uno. Lo único complicado es que debe configurar su IFS en una cadena vacía para que bash no intente separar sus espacios.

Esta solución es muy simple, por lo que agregar retardos variables entre caracteres, errores tipográficos, lo que sea súper fácil.

EDITAR (gracias, @dessert): si quieres una interfaz un poco más natural, puedes hacerlo

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

Esto le permitiría llamar a la función en typeit foo barlugar de typeit 'foo bar'. Tenga en cuenta que sin comillas, los argumentos están sujetos a la división de palabras de bash, por lo que, por ejemplo, typeit foo<space><space>barse imprimirán foo<space>bar. Para preservar los espacios en blanco, use comillas.

Whereswalden
fuente
Buena sugerencia, aunque debe tenerse en cuenta que se aplica la división de palabras. Por ejemplo, typeit foo<space>bardará como resultado foo bar, mientras typeit foo<space><space>barque también dará como resultado foo bar. Tienes que citarlo para asegurarte de que sea literal. @dessert no dude en sugerir una edición. Puedo hacerlo yo mismo, pero quiero darle la oportunidad de obtener crédito por ello.
Whereswalden
+1 por enseñarme sobre read -n1(que, por cierto, está read -k1en zsh)
Sebastian Stark
5

Primero, "parece que está siendo mecanografiado, con un retraso constante entre los caracteres ..." es un poco contradictorio, como han señalado otros. Algo que se está escribiendo no tiene un retraso constante. Cuando vea algo producido con un retraso inconsistente, obtendrá escalofríos. "¡¿Qué ha tomado mi computadora ?!"

De todas formas...

Tengo que gritar expect, que debería estar disponible en la mayoría de las distribuciones de Linux. Old School, lo sé, pero, suponiendo que esté instalado, difícilmente podría ser más simple:

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

Desde la página del manual:

El indicador -h obliga a que la salida se envíe (de alguna manera) como si un humano estuviera escribiendo realmente. Aparecen retrasos similares a los humanos entre los personajes (El algoritmo se basa en una distribución de Weibull, con modificaciones para adaptarse a esta aplicación en particular). Esta salida está controlada por el valor de la variable "send_human" ...

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

Mike S
fuente