Как напечатать текст в терминале, как будто он печатается?

27

У меня есть простая echoраспечатка, которую я добавил к своему .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

Это печатает сообщение на терминал, но я хочу, чтобы оно выглядело так, как будто оно набирается, с постоянной задержкой между символами.

SimplySimplified
источник
1
Чтобы быть реалистичным, я думаю, что вы должны добавить случайную опечатку с одним или несколькими пробелами для исправления. Например, когда я набирал этот комментарий, мне приходилось возвращать пробел через «через», чтобы исправить его как «брошенный». Это затрудняет ответ, но делает его более реалистичным. Если персонажи повторяются с постоянной скоростью 30 CPS или 60 CPS, это выглядит менее человечно. Некоторые клавиши набираются быстрее вместе, в то время как другие комбинации отображаются медленнее. Необходим некоторый тип сопоставления комбинаций клавиш и скорости.
WinEunuuchs2Unix
2
@ WinEunuuchs2Unix Я не думаю, что это все еще просто упрощенный. ;)
десерт
1
Смотрите мой ответ здесь
Пауза до дальнейшего уведомления.

Ответы:

28

Это не работает с Wayland; если вы используете Ubuntu 17.10 и не переходили на Xorg при входе в систему, это решение не для вас.

Вы можете использовать xdotool Установите xdotoolдля этого. Если задержка между нажатиями клавиш должна быть постоянной , это так просто:

xdotool type --delay 100 something

Это происходит somethingс задержкой в 100миллисекунды между каждым нажатием клавиши.


Если задержка между нажатиями клавиш должна быть случайной , скажем, от 100 до 300 миллисекунд, все становится немного сложнее:

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

Этот forцикл проходит через каждую букву строки , сохраненной в переменной text, либо печати key <letter>или key spaceв случае пространства с последующим sleep 0.и случайное число между 1 и 3 ( xdotool«сек sleepинтерпретирует число в секундах). Затем выводится весь вывод цикла xdotool, который печатает буквы со случайной задержкой между ними. Если вы хотите изменить задержку, просто измените деталь, будучи нижним и верхним пределом - на 0,2-0,5 секунды это будет .(RANDOM%x)+yyx-1+y(RANDOM%4)+2

Обратите внимание , что этот подход не печатать текст, а тип его так же , как пользователь будет делать, синтезируя одиночные нажатия клавиш. В результате текст печатается в текущем окне; если вы измените фокус, часть текста будет напечатана во вновь сфокусированном окне, что может быть или не быть тем, что вы хотите. В любом случае посмотрите на другие ответы здесь, все из которых являются блестящими!

Десерт
источник
24

Я попробовал xdotool после прочтения ответа @sert, но по какой-то причине не смог заставить его работать. Итак, я придумал это:

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

Передайте текст в приведенный выше код, и он будет напечатан как напечатано. Вы также можете добавить случайность, заменив sleep 0.1на sleep 0.$((RANDOM%3)).

Расширенная версия с поддельными опечатками

Эта версия будет время от времени вводить фальшивую опечатку и исправлять ее:

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
Себастьян Старк
источник
Я думаю, что я сделал бы это вместо этого: while IFS= read -r line; do for (( i = 0; i < ${#line}; i++ )); do sleep 0.1; printf "%s" "${line:i:1}"; done; echo; done(замените ;на новые строки и хорошие отступы при необходимости). IFS= read -rИ printf "%s"убедитесь , что пробелы и специальные символы не обрабатываются по -другому. И в grepкаждой строке разделять на символы не нужно - достаточно просто forцикл над каждым символом в строке.
Цифровая травма
18

Вы упоминаете о постоянной задержке между символами, но если вы действительно хотите, чтобы он выглядел так, как будто он напечатан, время не будет идеально согласованным. Для этого вы можете записать свой собственный набор текста с помощью scriptкоманды и воспроизвести его с помощью 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 ...

$ 

Запись останавливается нажатием CTRL-D.

Передав -tпараметр, scriptон также генерирует информацию о времени, которую я перенаправил в script.timingфайл. я прошелsed d как командуscript поскольку это просто способ поглотить ввод (и эту запись нажатия клавиш) без побочных эффектов.

Если вы также хотите сделать все tput/ resetматериал, вы можете сделать scriptзапись для каждой из ваших строк и воспроизвести их, чередуя с командами tput/ reset.

Цифровая травма
источник
11

Другая возможность - использовать Demo Magic или, если быть точнее, только функцию печати этой коллекции скриптов, которая в основном составляет

#!/bin/bash

. ./demo-magic.sh -w2

p "this will look as if typed"

Под капотом используется pv , который, конечно же, можно использовать и для непосредственного получения желаемого эффекта, основная форма выглядит следующим образом:

echo "this will look as if typed" | pv -qL 20
godfatherofpolka
источник
1
Это использование PV просто здорово.
Себастьян Старк
3
Нет необходимости в трубе echoне pvпросто использовать , pv -qL20 <<< "Hello world"если ваша оболочка опор herestrings.
Dr01
8

В соответствии с моим ником я могу предложить другое решение:

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

Выглядит странно, не правда ли?

  • -MTime::HiRes=usleepимпортирует функцию usleep(микросекундный сон) из Time::HiResмодуля, потому что обычныйsleep принимает только целые секунды.
  • -F''разбивает заданный ввод на символы (разделитель пустой '') и помещает символы в массив@F .
  • BEGIN {$|=1} отключает буферизацию вывода, поэтому каждый символ печатается сразу.
  • for (@F) { print; usleep(100_000+rand(200_000)) } просто перебирает персонажей
  • расстановка подчеркиваний в числах - это распространенный способ использовать в Perl какой-то разделитель тысяч. Perl просто игнорирует их, поэтому мы можем, например, написать 1_000(== 1000) или даже1_0_00 если мы посчитаем, что это легче читать.
  • rand() возвращает случайное число в диапазоне от 0 до заданного аргумента, так что вместе это составляет от 100 000 до 299 999 микросекунд (0,1–0,3 секунды).
PerlDuck
источник
Просто из любопытства: rand()возвращает ли число от 0 до аргумента (от 100k до 300k в вашем примере) или между ними (от 100k + 1 до 300k-1 в вашем примере)?
десерт
1
Возвращает число в интервале [0,200k), то есть, включая 0, но исключая 200k. Точное поведение задокументировано здесь : «Возвращает случайное дробное число, большее или равное 0 и меньшее, чем значение EXPR. (EXPR должен быть положительным.)»
PerlDuck
1
Это не работает без -a и -n
rrauenza
@rrauenza Начиная с Perl 5.20 . -Fподразумевает -aи -aподразумевает -n.
PerlDuck
О, хорошо, я использовал 5.16, что на CentOS7.
rrauenza
6

Еще один инструмент, который может работать, который не зависит от x11 или чего-либо еще, это asciicinema . Он записывает все, что вы делаете в своем терминале, и позволяет вам воспроизвести это, как если бы это был снимок экрана, только тогда он основан исключительно на ascii! Возможно, вам придется временно отключить ваше приглашение, чтобы оно было чисто визуально чистым. Как уже отмечали другие, добавление последовательной задержки не будет выглядеть естественным, и ввод ее самостоятельно может быть одним из самых естественных видов, которые вы можете достичь.

После записи текста вы можете сделать что-то вроде:

$ asciinema play [your recording].cast; cmatrix
rien333
источник
6

Я удивлен, что никто еще не упомянул об этом, но вы можете сделать это с помощью стандартных инструментов и цикла:

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

Он просто перебирает вводимый символ за символом и печатает их с задержкой после каждого. Единственный сложный момент заключается в том, что вы должны установить IFS на пустую строку, чтобы bash не пытался разделить ваши пробелы.

Это решение очень простое, поэтому добавление переменных задержек между символами, опечатками, что угодно, очень просто.

РЕДАКТИРОВАТЬ (спасибо, @dessert): Если вы хотите немного более естественный интерфейс, вы можете вместо этого сделать

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

Это позволит вам вызывать функцию как, typeit foo barа не typeit 'foo bar'. Имейте в виду, что без кавычек аргументы могут быть разбиты на слова в bash, поэтому, например, они typeit foo<space><space>barбудут напечатаны foo<space>bar. Чтобы сохранить пробелы, используйте кавычки.

whereswalden
источник
Хорошее предложение, хотя следует отметить, что применяется разделение слов. Например, typeit foo<space>barприведет foo bar, в то время как typeit foo<space><space>barбудет также приводить foo bar. Вы должны процитировать это, чтобы убедиться, что это дословно. @dessert не стесняйтесь предлагать редактирование. Я могу сделать это сам, но я хочу дать вам шанс получить за это кредит.
августа
+1 за то, что научил меня read -n1(что, кстати, read -k1в zsh)
Себастьян Старк
5

Во-первых, «похоже, что оно набирается с постоянной задержкой между символами ...», как указывали другие, немного противоречиво. Что-то набирается без постоянной задержки. Когда вы видите что-то, произведенное с непоследовательной задержкой, вы получите озноб. "Что захватило мой компьютер !!! ??!?"

Так или иначе...

Я должен сказать expect, что должно быть доступно в большинстве дистрибутивов Linux. Старая школа, я знаю, но - при условии, что она установлена ​​- это вряд ли может быть проще:

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

Со страницы руководства:

Флаг -h заставляет вывод (что-то) отправлять, как будто человек печатает. Человеческие задержки появляются между персонажами. (Алгоритм основан на распределении Вейбулла с модификациями, подходящими для данного конкретного приложения.) Эти выходные данные контролируются значением переменной "send_human" ...

См. Https://www.tcl.tk/man/expect5.31/expect.1.html.

Майк С
источник