Jak wydrukować tekst w terminalu tak, jakby był wpisywany?


27

Mam prosty echowydruk, który dodałem do mojego .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

To wypisuje wiadomość do terminala, ale chcę, aby wyglądała tak, jakby była wpisywana, ze stałym opóźnieniem między znakami.


1
Aby być realistą, myślę, że powinieneś mieć losową literówkę z jednym lub więcej tylnymi spacjami do poprawienia. Na przykład podczas pisania tego komentarza musiałem cofnąć spację nad „przez”, aby poprawić go do „wyrzuconego”. Utrudnia to odpowiedź na pytanie, ale czyni go bardziej realistycznym. Jeśli postacie powtarzają się w stałym tempie 30 CPS lub 60 CPS, wyglądają mniej ludzko. Niektóre klawisze są wpisywane szybciej razem, podczas gdy inne kombinacje klawiszy wydają się wolniejsze. Potrzebne jest pewne dopasowanie wzorców kombinacji klawiszy do prędkości.
WinEunuuchs2Unix

2
@ WinEunuuchs2Unix Nie sądzę, że to nadal jest po prostu uproszczone. ;)
deser

1
Zobacz moją odpowiedź tutaj
wstrzymano do odwołania.

Odpowiedzi:


28

To nie działa z Waylandem; jeśli używasz Ubuntu 17.10 i nie zmieniłeś się na używanie Xorg przy logowaniu, to rozwiązanie nie jest dla ciebie.

Możesz xdotool Zainstaluj xdotooldo tego użyć . Jeśli opóźnienie między naciśnięciami klawiszy powinno być spójne , jest to tak proste:

xdotool type --delay 100 something

Typ ten występuje somethingz opóźnieniem 100milisekund między każdym naciśnięciem klawisza.


Jeśli opóźnienie między naciśnięciami klawiszy powinno być losowe , powiedzmy od 100 do 300 milisekund, sprawy stają się nieco bardziej skomplikowane:

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

Ta forpętla przechodzi przez każdą pojedynczą literę łańcucha zapisanego w zmiennej text, albo drukowania key <letter>lub key spacew przypadku spację sleep 0.i losową liczbę między 1 i 3 ( xdotool„s sleepinterpretuje jako liczba sekund). Następnie przesyłane jest całe wyjście pętli xdotool, które drukuje litery z losowym opóźnieniem pomiędzy nimi. Jeśli chcesz zmienić opóźnienie, po prostu zmień część, która jest dolną i górną granicą - na 0,2 do 0,5 sekundy .(RANDOM%x)+yyx-1+y(RANDOM%4)+2

Zauważ, że to podejście nie drukuje tekstu, a raczej wpisuje go dokładnie tak, jak zrobiłby to użytkownik, syntezując pojedyncze naciśnięcia klawiszy. W rezultacie tekst zostaje wpisany w aktualnie zaznaczonym oknie; jeśli zmienisz fokus, część tekstu zostanie wpisana w nowo zaznaczonym oknie, co może, ale nie musi być tym, czego chcesz. W obu przypadkach spójrz na inne odpowiedzi tutaj, z których wszystkie są genialne!


24

Próbowałem xdotool po przeczytaniu odpowiedzi @ deser, ale z jakiegoś powodu nie mogłem go uruchomić. Więc wpadłem na to:

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

Wklej tekst do powyższego kodu, a zostanie wydrukowany jak napisany na maszynie. Możesz także dodać losowość, zastępując sleep 0.1sleep 0.$((RANDOM%3)).

Wersja rozszerzona z fałszywymi literówkami

Ta wersja wprowadza co jakiś czas fałszywą literówkę i poprawia ją:

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

Myślę, że zrobiłbym to zamiast tego: while IFS= read -r line; do for (( i = 0; i < ${#line}; i++ )); do sleep 0.1; printf "%s" "${line:i:1}"; done; echo; done( ;w razie potrzeby zastąp nowymi liniami i dobrym wcięciem). IFS= read -rI printf "%s"upewnić się, że białe znaki i znaki specjalne nie są traktowane inaczej. A grepna każdej linii podzielić na bohaterów jest zbędne - po prostu forpętla nad każdym char w linii jest wystarczające.
Cyfrowa trauma

18

Wspominasz o spójnym opóźnieniu między postaciami, ale jeśli naprawdę chcesz, aby wyglądał jak napisany, czas nie będzie idealnie spójny. Aby to osiągnąć, możesz zapisać własne scriptpolecenie za pomocą polecenia i odtworzyć je za pomocą 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 ...

$ 

Nagrywanie jest zatrzymywane przez naciśnięcie CTRL-D.

Przekazanie -tparametru scriptinstruuje go również wygenerować informacje o taktowaniu, które przekierowałem do script.timingpliku. Przekazałem sed djako polecenie, scriptponieważ jest to po prostu sposób na wchłonięcie danych wejściowych (i ten zapis klawiszy) bez żadnych skutków ubocznych.

Jeśli chcesz zrobić wszystkie rzeczy tput/ reset, możesz zrobić scriptnagranie dla każdej linii i odtworzyć je, przeplatane poleceniami tput/ reset.


11

Inną możliwością jest użycie Demo Magic , a ściślej mówiąc, funkcji drukowania tej kolekcji skryptów, która w zasadzie wynosi

#!/bin/bash

. ./demo-magic.sh -w2

p "this will look as if typed"

Pod maską używa to pv , którego oczywiście można również użyć, aby bezpośrednio uzyskać pożądany efekt, podstawowa forma wygląda następująco:

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

1
To użycie pv jest po prostu świetne.
Sebastian Stark,

3
Nie ma potrzeby, aby rury echodo pv, wystarczy użyć pv -qL20 <<< "Hello world"jeśli obsługiwana powłoki herestrings.
dr01

8

Zgodnie z moim pseudonimem mogę zaoferować inne rozwiązanie:

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

Wygląda dziwnie, prawda?

  • -MTime::HiRes=usleepimportuje funkcję usleep(uśpienie mikrosekundowe) z Time::HiResmodułu, ponieważ zwykle sleepprzyjmuje tylko sekundy całkowite.
  • -F''dzieli podane dane wejściowe na znaki (separator jest pusty '') i umieszcza znaki w tablicy @F.
  • BEGIN {$|=1} wyłącza buforowanie danych wyjściowych, dzięki czemu każdy znak jest natychmiast drukowany.
  • for (@F) { print; usleep(100_000+rand(200_000)) } po prostu iteruje postacie
  • wstawianie znaków podkreślenia w liczbach jest powszechnym sposobem używania pewnego rodzaju tysięcy separatorów w Perlu. Są po prostu ignorowane przez Perla, więc możemy np. Napisać 1_000(== 1000), a nawet 1_0_00jeśli uważamy to za łatwiejsze do odczytania.
  • rand() zwraca losową liczbę od 0 do podanego argumentu, więc razem śpi to od 100 000 do 299 999 mikrosekund (0,1-0,3 sekundy).

Właśnie z ciekawości: czy rand()zwraca liczbę od 0 do argumentu (100k do 300k w twoim przykładzie) lub między nimi (100k + 1 do 300k-1 w twoim przykładzie)?
deser

1
Zwraca liczbę w przedziale [0,200k), tj. Obejmującą 0, ale wykluczającą 200k. Dokładne zachowanie jest tutaj udokumentowane : „Zwraca losową liczbę ułamkową większą lub równą 0 i mniejszą niż wartość WYRAŻ. (WYRAŻ powinna być dodatnia.)”
PerlDuck

1
To nie działa bez -a i -n
rrauenza

@rrauenza Od wersji 5.20 tak. -Fimplikuje -ai -aimplikuje -n.
PerlDuck

Ach, ok, korzystałem z 5.16, czyli na CentOS7.
rrauenza

6

Innym narzędziem, które może działać, które nie zależy od x11 lub cokolwiek innego, jest asciicinema . Rejestruje wszystko, co robisz w swoim terminalu i pozwala odtworzyć to tak, jakby to był zrzut ekranu, tylko wtedy jest to oparte wyłącznie na ascii! Być może trzeba tymczasowo wyłączyć monit, aby był on czysto wizualnie czysty. Jak zauważyli inni, dodanie spójnego opóźnienia nie będzie wyglądać naturalnie, a samodzielne wpisanie go może być jednym z najbardziej naturalnych efektów, jaki można uzyskać.

Po nagraniu tekstu możesz zrobić coś takiego:

$ asciinema play [your recording].cast; cmatrix

6

Dziwi mnie, że nikt o tym jeszcze nie wspominał, ale można to zrobić za pomocą standardowych narzędzi i pętli:

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

Po prostu zapętla znak wejściowy po znaku i drukuje je z opóźnieniem po każdym. Jedynym trudnym zadaniem jest ustawienie IFS na pusty ciąg znaków, aby bash nie próbował rozdzielać spacji.

To rozwiązanie jest bardzo proste, więc dodawanie zmiennych opóźnień między znakami, literówkami, cokolwiek jest super łatwe.

EDYCJA (dzięki, @dessert): Jeśli chcesz nieco bardziej naturalny interfejs, możesz to zrobić

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

Umożliwiłoby to wywołanie funkcji typeit foo barzamiast typeit 'foo bar'. Pamiętaj, że bez cudzysłowów argumenty podlegają podziałowi na słowa bash, więc na przykład typeit foo<space><space>barzostaną wydrukowane foo<space>bar. Aby zachować białe znaki, użyj cudzysłowów.


Dobra sugestia, choć należy zauważyć, że obowiązuje podział tekstu. Na przykład typeit foo<space>barspowoduje foo bar, natomiast typeit foo<space><space>barbędzie również skutkować foo bar. Musisz to zacytować, aby upewnić się, że jest dosłowne. @dessert możesz zaproponować edycję. Mogę to zrobić sam, ale chcę dać ci szansę na uznanie za to.
whereswalden

+1 za nauczenie mnie o tym read -n1(co, btw. Jest read -k1w Zsh)
Sebastian Stark

5

Po pierwsze, „wygląda na to, że jest pisany na maszynie, ze stałym opóźnieniem między postaciami ...” jest trochę sprzeczne, jak zauważyli inni. Coś, co jest wpisywane, nie ma spójnego opóźnienia. Kiedy zobaczysz coś wyprodukowanego z niespójnym opóźnieniem, poczujesz dreszcze. „Co przejęło mój komputer !!! ??!?”

Tak czy inaczej...

Muszę wykrzyczeć, że expectpowinien być dostępny w większości dystrybucji Linuksa. Wiem, że Old School, ale - zakładając, że jest zainstalowany - nie może być prostsze:

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

Ze strony podręcznika:

Flaga -h wymusza wysyłanie danych wyjściowych (w pewnym sensie), tak jak pisanie przez człowieka. Ludzkie opóźnienia pojawiają się między postaciami. (Algorytm oparty jest na rozkładzie Weibulla, z modyfikacjami dostosowanymi do tej konkretnej aplikacji.) Dane wyjściowe są kontrolowane przez wartość zmiennej „send_human” ...

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.