Comment imprimer du texte dans le terminal comme s'il était tapé?


27

J'ai une echoimpression simple que j'ai ajoutée à mon .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

Cela imprime un message sur le terminal, mais je veux qu'il semble être en cours de frappe, avec un délai constant entre les caractères.


1
Pour être réaliste, je pense que vous devriez avoir une faute de frappe aléatoire avec un ou plusieurs espaces arrière pour corriger. Par exemple, en tapant ce commentaire, j'ai dû reculer d'espace sur "à travers" pour le corriger en "jeté". Cela rend la question plus difficile à répondre, mais elle la rend plus réaliste. Si les personnages se répètent à un rythme régulier de 30 CPS ou 60 CPS, cela semble moins humain. Certaines touches sont saisies plus rapidement ensemble, tandis que d'autres combinaisons de touches semblent plus lentes. Une sorte de correspondance de motifs des combinaisons de touches à la vitesse est nécessaire.
WinEunuuchs2Unix

2
@ WinEunuuchs2Unix Je ne pense pas que ce soit toujours SimplySimplified. ;)
dessert

1
Voir ma réponse ici
pause jusqu'à nouvel ordre.

Réponses:


28

Cela ne fonctionne pas avec Wayland; si vous utilisez Ubuntu 17.10 et n'avez pas changé pour utiliser Xorg à la connexion, cette solution n'est pas pour vous.

Vous pouvez l'utiliser xdotool Installer xdotoolpour cela. Si le délai entre les frappes doit être cohérent , c'est aussi simple que cela:

xdotool type --delay 100 something

Cela tape somethingavec un délai de quelques 100millisecondes entre chaque frappe.


Si le délai entre les frappes doit être aléatoire , disons de 100 à 300 millisecondes, les choses se compliquent un peu:

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

Cette forboucle parcourt chaque lettre de la chaîne enregistrée en variable text, en imprimant soit key <letter>ou key spacedans le cas d'un espace suivi d' sleep 0.un nombre aléatoire compris entre 1 et 3 ( xdotools sleepinterprète le nombre en secondes). La sortie entière de la boucle est ensuite dirigée vers xdotool, qui imprime les lettres avec le retard aléatoire entre les deux. Si vous voulez changer le retard, changez simplement la partie, étant la limite inférieure et la limite supérieure - pendant 0,2 à 0,5 seconde, ce serait .(RANDOM%x)+yyx-1+y(RANDOM%4)+2

Notez que cette approche n'imprime pas le texte, mais plutôt le tape exactement comme le ferait l'utilisateur, en synthétisant des touches uniques. En conséquence, le texte est tapé dans la fenêtre actuellement focalisée; si vous modifiez la partie de mise au point du texte sera tapé dans la fenêtre nouvellement mise au point, qui peut ou non être ce que vous voulez. Dans les deux cas, jetez un œil aux autres réponses ici, qui sont toutes géniales!


24

J'ai essayé xdotool après avoir lu la réponse de @ dessert mais je n'ai pas pu le faire fonctionner pour une raison quelconque. J'ai donc trouvé ceci:

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

Canalisez votre texte dans le code ci-dessus et il sera imprimé comme tapé. Vous pouvez également ajouter un caractère aléatoire en le remplaçant sleep 0.1par sleep 0.$((RANDOM%3)).

Version étendue avec fausses fautes de frappe

Cette version introduira une fausse faute de temps en temps et la corrigera:

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

Je pense que je ferais cela à la place: while IFS= read -r line; do for (( i = 0; i < ${#line}; i++ )); do sleep 0.1; printf "%s" "${line:i:1}"; done; echo; done(remplacer ;par des retours à la ligne et une bonne indentation si nécessaire). Le IFS= read -ret faire en printf "%s"sorte que les caractères blancs et spéciaux ne sont pas traités différemment. Et la grepdivision de chaque ligne en caractères n'est pas nécessaire - une simple forboucle sur chaque caractère de la ligne suffit.
Digital Trauma

18

Vous mentionnez un délai constant entre les caractères, mais si vous voulez vraiment qu'il ressemble à sa frappe, le timing ne sera pas parfaitement cohérent. Pour ce faire, vous pouvez enregistrer votre propre frappe avec la scriptcommande et la lire avec 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 ...

$ 

L'enregistrement est arrêté en appuyant sur CTRL-D.

Passer le -tparamètre pour l' scriptinstruire génère également des informations de synchronisation, que j'ai redirigées vers le script.timingfichier. J'ai passé sed dune commande àscript car c'est simplement un moyen d'absorber l'entrée (et cela enregistre les frappes) sans effets secondaires.

Si vous voulez aussi faire toutes les choses tput/ reset, vous voudrez peut-être faire un scriptenregistrement pour chacune de vos lignes, et les lire, entrelacées avec les commandes tput/ reset.


11

Une autre possibilité consiste à utiliser Demo Magic , ou, pour être plus précis, la fonction d'impression de cette collection de scripts, ce qui revient à

#!/bin/bash

. ./demo-magic.sh -w2

p "this will look as if typed"

Sous le capot, cela utilise du pv , que vous pouvez bien sûr également utiliser pour obtenir directement l'effet souhaité, la forme de base se présente comme suit:

echo "this will look as if typed" | pv -qL 20

1
Cette utilisation de PV est tout simplement géniale.
Sebastian Stark

3
Pas besoin de diriger echovers pv, utilisez simplement pv -qL20 <<< "Hello world"si votre coque prend en charge les chaînes de caractères.
dr01

8

Conformément à mon surnom, je peux proposer une autre solution:

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

Ça a l'air bizarre, non?

  • -MTime::HiRes=usleepimporte la fonction usleep(veille microseconde) du Time::HiResmodule car l'habituel sleepn'accepte que des secondes entières.
  • -F''divise l'entrée donnée en caractères (le délimiteur étant vide '') et place les caractères dans le tableau @F.
  • BEGIN {$|=1} désactive la mise en mémoire tampon de sortie afin que chaque caractère soit immédiatement imprimé.
  • for (@F) { print; usleep(100_000+rand(200_000)) } itère juste sur les personnages
  • mettre des traits de soulignement en chiffres est une façon courante d'utiliser une sorte de milliers de séparateurs en Perl. Ils sont simplement ignorés par Perl, nous pouvons donc par exemple écrire 1_000(==1000 ) ou même 1_0_00si nous considérons que c'est plus facile à lire.
  • rand() renvoie un nombre aléatoire compris entre 0 et l'argument donné, donc ensemble, il dort entre 100 000 et 299 999 microsecondes (0,1-0,3 secondes).

Juste par curiosité: rand()renvoie un nombre de 0 à l'argument (100k à 300k dans votre exemple) ou entre eux (100k + 1 à 300k-1 dans votre exemple)?
dessert

1
Il renvoie un nombre dans l'intervalle [0,200k), c'est-à-dire incluant 0 mais excluant 200k. Le comportement exact est documenté ici : "Renvoie un nombre fractionnaire aléatoire supérieur ou égal à 0 et inférieur à la valeur de EXPR. (EXPR doit être positif.)"
PerlDuck

1
Cela ne fonctionne pas sans -a et -n
rrauenza

@rrauenza Depuis Perl 5.20 c'est le cas. -Fimplique -aet -aimplique -n.
PerlDuck

Ah, ok j'utilisais le 5.16, ce qui est sur CentOS7.
rrauenza

6

Un autre outil qui pourrait fonctionner, qui ne dépend pas de x11 ou autre, est l' asciicinema . Il enregistre tout ce que vous faites dans votre terminal et vous permet de rejouer cela comme s'il s'agissait d'une capture d'écran, alors il est uniquement basé sur ascii! Vous devrez peut-être désactiver temporairement votre invite pour qu'elle soit purement visuellement propre. Comme d'autres l'ont souligné, l'ajout d'un retard cohérent ne semblera pas naturel, et le taper vous-même pourrait être l'un des looks les plus naturels que vous puissiez obtenir.

Après avoir enregistré le texte, vous pouvez faire quelque chose comme:

$ asciinema play [your recording].cast; cmatrix

6

Je suis surpris que personne n'en ait encore parlé, mais vous pouvez le faire avec des outils de stock et une boucle:

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

Il boucle simplement le caractère entré caractère par caractère et les imprime avec un retard après chacun. Le seul problème est que vous devez définir votre IFS sur une chaîne vide afin que bash n'essaie pas de séparer vos espaces.

Cette solution est extrêmement simple, donc ajouter des délais variables entre les caractères, les fautes de frappe, tout ce qui est super facile.

EDIT (merci, @dessert): Si vous voulez une interface légèrement plus naturelle, vous pouvez plutôt faire

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

Cela vous permettrait d'appeler la fonction typeit foo barplutôt que typeit 'foo bar'. Sachez que sans guillemets, les arguments sont sujets au fractionnement des mots de bash, donc par exemple typeit foo<space><space>bars'impriment foo<space>bar. Pour préserver les espaces, utilisez des guillemets.


Bonne suggestion, mais il convient de noter que le fractionnement de mots s'applique. Par exemple, typeit foo<space>barse traduira par foo bar, tandis que typeit foo<space><space>barse traduira également par foo bar. Vous devez le citer pour vous assurer qu'il est textuel. @dessert n'hésitez pas à suggérer une modification. Je peux le faire moi-même, mais je veux vous donner la chance d'en avoir le mérite.
whereeswalden

+1 pour m'avoir enseigné read -n1(qui, en fait, est read -k1en zsh)
Sebastian Stark

5

Tout d'abord, "on dirait qu'il est en train d'être tapé, avec un décalage constant entre les caractères ..." est un peu contradictoire, comme d'autres l'ont souligné. Un élément tapé n'a pas de délai constant. Lorsque vous voyez quelque chose produit avec un retard incohérent, vous aurez des frissons. "Qu'est-ce qui a pris mon ordinateur !!! ??!?"

En tous cas...

Je dois faire un cri à expect, qui devrait être disponible sur la plupart des distributions Linux. Old School, je sais, mais - en supposant qu'il soit installé - cela pourrait difficilement être plus simple:

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

Depuis la page de manuel:

L'indicateur -h force la sortie à être envoyée (quelque peu) comme un humain tapant réellement. Des retards de type humain apparaissent entre les personnages. (L'algorithme est basé sur une distribution de Weibull, avec des modifications adaptées à cette application particulière.) Cette sortie est contrôlée par la valeur de la variable "send_human" ...

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

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.