Як надрукувати текст у терміналі так, ніби він вводиться?


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

Це друкує повідомлення на терміналі, але я хочу, щоб воно виглядало так, ніби воно вводиться, з послідовною затримкою між символами.


1
Щоб бути реалістичним, я думаю, що для виправлення вам слід запустити випадкові друкарські помилки з одним або декількома задніми пробілами. Наприклад, під час введення цього коментаря мені довелося повернути пробіл на "крізь", щоб виправити його на "кинуто". Це робить складніше питання відповісти, але робить його більш реалістичним. Якщо символи повторюються у постійному темпі 30 CPS або 60 CPS, вони виглядають менш людськими. Деякі клавіші вводяться швидше разом, тоді як інші комбінації клавіш виглядають повільніше. Потрібно якесь узгодження шаблонів комбінацій клавіш із швидкістю.
WinEunuuchs2Unix

2
@ WinEunuuchs2Unix Я не думаю, що це все ще просто спрощено. ;)
десерт

Відповіді:


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 ( xdotools sleepінтерпретує число як секунди). Потім виводиться весь висновок циклу xdotool, який друкує літери з випадковою затримкою між ними. Якщо ви хочете змінити затримку, просто змініть частину, будучи нижньою та верхньою межею - протягом 0,2 до 0,5 секунди .(RANDOM%x)+yyx-1+y(RANDOM%4)+2

Зауважте, що цей підхід не друкує текст, а скоріше вводить його так, як це робив би користувач, синтезуючи окремі натискання клавіш. Внаслідок цього текст набирається у вікно, орієнтоване на даний момент; якщо ви зміните фокус-частину, текст набереться у щойно зосередженому вікні, що може бути, а може і не бути тим, що ви хочете. У будь-якому випадку погляньте на інші відповіді тут, всі вони є геніальними!


24

Я спробував xdotool, прочитавши відповідь @ десерту, але не зміг змусити його працювати. Тому я придумав таке:

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

1
Таке використання ПВ просто чудове.
Себастьян Старк

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. Перл їх просто ігнорує, тому ми можемо, наприклад, написати 1_000(== 1000) або навіть 1_0_00якщо вважаємо, що це легше читати.
  • rand() повертає випадкове число між 0 і заданим аргументом, тож разом це проходить між 100 000 і 299 999 мікросекундами (0,1-0,3 секунди).

Просто з цікавості: rand()повертає число від 0 до аргументу (100 к 300 к у вашому прикладі) або між ними (100 к + 1 до 300 к-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

6

Я здивований, що про це ніхто ще не згадав, але ви можете досягти цього за допомогою інструментів та петлі:

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

Він просто перетинає символи на вхідних символах і виводить їх із затримкою після кожного. Єдиний складний біт - це те, що ви повинні встановити свій IFS на порожній рядок, щоб bash не намагався розділити пробіли.

Це рішення є мертвим простим, тому додавання змінних затримок між символами, помилок друку, що б там не було дуже просто.

EDIT (спасибі, @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. Щоб зберегти пробіл, використовуйте лапки.


Хороша пропозиція, хоча слід зазначити, що застосовується розділення слів. Наприклад, typeit foo<space>barпризведе foo bar, в той час як typeit foo<space><space>barбуде також приводити foo bar. Ви повинні процитувати це, щоб переконатися, що це дослівно. @dessert не соромтеся запропонувати зміни. Я можу це зробити самостійно, але хочу дати вам шанс отримати за це кредит.
де

+1 для того, щоб навчити мене про те read -n1(що, btw. 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 змушує виводити (дещо), як людину, яка насправді набирає текст. Людські затримки з’являються між персонажами. (Алгоритм заснований на розподілі Weibull, з модифікаціями відповідно до цієї конкретної програми.) Цей вихід керується значенням змінної "send_human" ...

Дивіться https://www.tcl.tk/man/expect5.31/expect.1.html

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.