Wie drucke ich Text im Terminal so, als würde er gerade eingegeben?

27

Ich habe einen einfachen echoAusdruck, den ich meinem hinzugefügt habe .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

Dies gibt eine Nachricht an das Terminal aus, aber ich möchte, dass sie so aussieht, als würde sie gerade getippt, mit einer gleichmäßigen Verzögerung zwischen den Zeichen.

SimplySimplified
quelle
1
Um realistisch zu sein, sollten Sie einen zufälligen Tippfehler mit einem oder mehreren Leerzeichen eingeben, um ihn zu korrigieren. Während ich zum Beispiel diesen Kommentar tippte, musste ich ein Leerzeichen über "durch" setzen, um ihn auf "geworfen" zu korrigieren. Dies erschwert die Beantwortung einer Frage, macht sie jedoch realistischer. Wenn sich Charaktere mit einem konstanten Tempo von 30 oder 60 CPS wiederholen, sieht es weniger menschlich aus. Bestimmte Tasten werden schneller zusammen eingegeben, während andere Tastenkombinationen langsamer erscheinen. Es ist eine Art Mustervergleich der Tastenkombinationen mit der Geschwindigkeit erforderlich.
WinEunuuchs2Unix
2
@ WinEunuuchs2Unix Ich glaube nicht, dass dies noch SimplySimplified ist. ;)
Dessert
1
Sehen Sie meine Antwort hier
bis auf weiteres angehalten.

Antworten:

28

Dies funktioniert nicht mit Wayland. Wenn Sie Ubuntu 17.10 verwenden und beim Anmelden nicht auf Xorg umgestellt haben, ist diese Lösung nichts für Sie.

Sie können dafür verwenden xdotool Installieren Sie xdotool. Wenn die Verzögerung zwischen den Tastenanschlägen konsistent sein soll , ist es so einfach:

xdotool type --delay 100 something

Dies erfolgt somethingmit einer Verzögerung von 100Millisekunden zwischen jedem Tastendruck.


Wenn die Verzögerung zwischen den Tastenanschlägen zufällig sein sollte , sagen wir zwischen 100 und 300 Millisekunden, wird es etwas komplizierter:

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

Diese forSchleife geht durch jeden einzelnen Buchstaben der Zeichenfolge in Variablen gespeichert text, das Drucken entweder key <letter>oder key spacein dem Fall eines Raums , gefolgt von sleep 0.und eine Zufallszahl zwischen 1 und 3 ( xdotool‚s sleepdie Zahl als Sekunden interpretiert). Die gesamte Ausgabe der Schleife wird dann weitergeleitet xdotool, wodurch die Buchstaben mit der zufälligen Verzögerung dazwischen gedruckt werden. Wenn Sie die Verzögerung ändern möchten, ändern Sie einfach den Teil, wobei es sich um die untere und die obere Grenze handelt - für 0,2 bis 0,5 Sekunden wäre dies der Fall .(RANDOM%x)+yyx-1+y(RANDOM%4)+2

Beachten Sie, dass dieser Ansatz nicht funktioniert drucken den Text, sondern geben Sie es genau wie der Benutzer tun würde, die Synthese einzelner Tastenfolgen . Infolgedessen wird der Text in das aktuell fokussierte Fenster eingegeben. Wenn Sie den Fokus ändern, wird ein Teil des Texts in das neu fokussierte Fenster eingegeben, das möglicherweise nicht Ihren Wünschen entspricht. In beiden Fällen werfen Sie einen Blick auf die anderen Antworten, die alle brillant sind!

Dessert
quelle
24

Nachdem ich die Antwort von @ dessert gelesen hatte, habe ich xdotool ausprobiert, konnte es aber aus irgendeinem Grund nicht zum Laufen bringen. Also habe ich mir Folgendes ausgedacht:

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

Füge deinen Text in den obigen Code ein und er wird wie geschrieben gedruckt. Sie können auch Zufälligkeit hinzufügen , indem Sie ersetzen sleep 0.1mit sleep 0.$((RANDOM%3)).

Erweiterte Version mit gefälschten Tippfehlern

Diese Version wird ab und zu einen gefälschten Tippfehler einführen und korrigieren:

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
quelle
Ich denke, ich würde dies stattdessen tun: while IFS= read -r line; do for (( i = 0; i < ${#line}; i++ )); do sleep 0.1; printf "%s" "${line:i:1}"; done; echo; done(Ersetzen Sie diese ;durch Zeilenumbrüche und Einrückungen, falls erforderlich). Die IFS= read -rund stellen printf "%s"sicher, dass Leerzeichen und Sonderzeichen nicht unterschiedlich behandelt werden. Und das grepAufteilen in Zeichen in jeder Zeile ist unnötig - nur eine forSchleife über jedes Zeichen in der Zeile ist ausreichend.
Digitales Trauma
18

Sie erwähnen eine konsistente Verzögerung zwischen Zeichen, aber wenn Sie wirklich möchten, dass es so aussieht, als ob es getippt wird, ist das Timing nicht perfekt konsistent. Um dies zu erreichen, können Sie Ihre eigene Eingabe mit dem scriptBefehl aufzeichnen und sie wiedergeben mit 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 ...

$ 

Die Aufnahme wird durch Drücken von STRG-D gestoppt.

Wenn Sie den -tParameter an scriptinstructs übergeben, werden auch Zeitinformationen generiert, die ich in die script.timingDatei umgeleitet habe . Ich habe sed dals Befehl übergeben, scriptda dies einfach eine Möglichkeit ist, Eingaben zu absorbieren (und dabei die Tastenanschläge aufzuzeichnen), ohne Nebenwirkungen zu haben.

Wenn Sie auch alle tput/ reset-Dateien ausführen möchten, möchten Sie möglicherweise scriptfür jede Ihrer Zeilen eine Aufnahme erstellen und diese mit den Befehlen tput/ interleaved wiedergeben reset.

Digitales Trauma
quelle
11

Eine andere Möglichkeit ist die Verwendung von Demo Magic , genauer gesagt, die Druckfunktion dieser Skriptsammlung, die im Grunde genommen eine Rolle spielt

#!/bin/bash

. ./demo-magic.sh -w2

p "this will look as if typed"

Unter der Haube wird hier pv verwendet , mit dem Sie natürlich auch direkt den gewünschten Effekt erzielen können. Die Grundform sieht folgendermaßen aus:

echo "this will look as if typed" | pv -qL 20
Pate von Polka
quelle
1
Diese Verwendung von pv ist einfach großartig.
Sebastian Stark
3
Kein Bedürfnis, verwenden nur , wenn Ihre Shell unterstützt herestrings. echopvpv -qL20 <<< "Hello world"
dr01
8

Entsprechend meinem Spitznamen kann ich eine andere Lösung anbieten:

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

Sieht komisch aus, nicht wahr?

  • -MTime::HiRes=usleepimportiert die Funktion usleep(Mikrosekunden-Ruhezustand) aus dem Time::HiResModul, da das Übliche sleepnur ganzzahlige Sekunden akzeptiert.
  • -F''teilt die angegebene Eingabe in Zeichen auf (wobei der Begrenzer leer ist '') und fügt die Zeichen in das Array ein @F.
  • BEGIN {$|=1} Deaktiviert die Ausgabepufferung, sodass jedes Zeichen sofort gedruckt wird.
  • for (@F) { print; usleep(100_000+rand(200_000)) } iteriert nur über die Zeichen
  • Das Einfügen von Unterstrichen in Zahlen ist eine gängige Methode, um in Perl einige Tausend Trennzeichen zu verwenden. Sie werden von Perl einfach ignoriert, sodass wir zB schreiben können 1_000(== 1000) oder auch, 1_0_00wenn wir dies für leichter lesbar halten.
  • rand() Gibt eine Zufallszahl zwischen 0 und dem angegebenen Argument zurück. Zusammen ergibt dies einen Ruhezustand zwischen 100.000 und 299.999 Mikrosekunden (0,1-0,3 Sekunden).
PerlDuck
quelle
Nur aus Neugier: Gibt rand()eine Zahl von 0 an das Argument zurück (100k bis 300k in Ihrem Beispiel) oder zwischen ihnen (100k + 1 bis 300k-1 in Ihrem Beispiel)?
Dessert
1
Es gibt eine Zahl im Intervall zurück [0,200k), dh einschließlich 0, jedoch ausschließlich 200.000. Das genaue Verhalten ist hier dokumentiert : "Gibt eine zufällige gebrochene Zahl größer oder gleich 0 und kleiner als der Wert von EXPR zurück. (EXPR sollte positiv sein.)"
PerlDuck
1
Das funktioniert nicht ohne -a und -n
rrauenza
@rrauenza Seit Perl 5.20 ist es so. -Fimpliziert -aund -aimpliziert -n.
PerlDuck
Ah, ok, ich habe 5.16 verwendet, was auch für CentOS7 gilt.
Rrauenza
6

Ein weiteres mögliches Tool, das nicht von x11 oder etwas anderem abhängt, ist das Asciicinema . Es zeichnet alles auf, was Sie in Ihrem Terminal tun, und lässt Sie wiedergeben, als wäre es eine Bildschirmaufnahme, nur dann ist es rein ascii-basiert! Möglicherweise müssen Sie Ihre Eingabeaufforderung jedoch vorübergehend deaktivieren, damit sie rein optisch sauber ist. Wie andere betont haben, erscheint das Hinzufügen einer konsistenten Verzögerung nicht natürlich, und das Eingeben selbst ist möglicherweise eines der natürlichsten Effekte, die Sie erzielen können.

Nachdem Sie den Text aufgenommen haben, können Sie Folgendes tun:

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

Ich bin überrascht, dass dies noch niemand erwähnt hat, aber Sie können dies mit Standardwerkzeugen und einer Schleife erreichen:

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

Es durchläuft die Eingabe nur zeichenweise und druckt sie jeweils verzögert aus. Das einzig schwierige ist, dass Sie Ihr IFS auf einen leeren String setzen müssen, damit bash nicht versucht, Ihre Leerzeichen aufzuteilen.

Diese Lösung ist denkbar einfach. Sie können also ganz einfach variable Verzögerungen zwischen Zeichen und Tippfehlern hinzufügen.

BEARBEITEN (danke, @dessert): Wenn Sie eine etwas natürlichere Oberfläche möchten, können Sie dies stattdessen tun

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

Auf diese Weise können Sie die Funktion as typeit foo baranstatt aufrufen typeit 'foo bar'. Beachten Sie, dass die Argumente ohne Anführungszeichen der Bash-Wortteilung unterliegen und beispielsweise typeit foo<space><space>bargedruckt werden foo<space>bar. Verwenden Sie Anführungszeichen, um Leerzeichen beizubehalten.

woanderswalden
quelle
Guter Vorschlag, obwohl zu beachten ist, dass die Wortteilung gilt. Zum Beispiel typeit foo<space>barführen wird foo bar, während typeit foo<space><space>barwird auch in Folge foo bar. Sie müssen es zitieren, um sicherzustellen, dass es wörtlich ist. @ Dessert zögern Sie nicht, eine Änderung vorzuschlagen. Ich kann es selbst tun, aber ich möchte Ihnen die Chance geben, dafür Anerkennung zu erhalten.
whereswalden
+1 für das Unterrichten über read -n1(die übrigens read -k1in zsh ist)
Sebastian Stark
5

Erstens ist es ein wenig widersprüchlich, "so auszusehen, als würde es mit einer konstanten Verzögerung zwischen den Zeichen getippt ...", wie andere darauf hingewiesen haben. Etwas, das getippt wird, hat keine konstante Verzögerung. Wenn Sie etwas sehen, das mit einer inkonsistenten Verzögerung produziert wurde, bekommen Sie Schüttelfrost. "Was hat meinen Computer übernommen !!! ??!?"

Sowieso...

Ich muss einen Shout machen expect, der auf den meisten Linux-Distributionen verfügbar sein sollte. Old School, ich weiß, aber wenn es installiert ist, könnte es kaum einfacher sein:

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

Von der Manpage:

Das Flag -h erzwingt, dass die Ausgabe (etwas) wie ein Mensch gesendet wird, der gerade tippt. Zwischen den Zeichen treten menschenähnliche Verzögerungen auf. (Der Algorithmus basiert auf einer Weibull-Verteilung und wurde an diese spezielle Anwendung angepasst.) Diese Ausgabe wird durch den Wert der Variablen "send_human" gesteuert.

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

Mike S
quelle