วิธีพิมพ์ข้อความในเทอร์มินัลราวกับว่ากำลังพิมพ์อยู่?


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 ( xdotool's 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.1sleep 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วนรอบอักขระแต่ละตัวในบรรทัดก็เพียงพอแล้ว
Digital Trauma

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
การใช้ 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 microseconds (0.1-0.3 วินาที)

เพิ่งออกมาจากความอยากรู้: 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
ruuenza

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 จะไม่พยายามแยกช่องว่างออก

วิธีนี้เป็นวิธีที่ง่ายมากดังนั้นการเพิ่มความล่าช้าของตัวแปรระหว่างตัวอักษรพิมพ์ผิดอะไรก็สุดง่าย

แก้ไข (ขอบคุณ @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 อย่าลังเลที่จะแนะนำการแก้ไข ฉันทำได้ด้วยตัวเอง แต่ฉันต้องการให้โอกาสคุณได้รับเครดิต
whereswalden

+1 สำหรับการสอนฉันเกี่ยวกับread -n1(ซึ่ง btw. เป็นread -k1zsh)
เซบาสเตียนสตาร์

5

ครั้งแรก "ดูราวกับว่ามันถูกพิมพ์ด้วยความล่าช้าที่สอดคล้องกันระหว่างตัวละคร ... " เป็นความขัดแย้งเล็กน้อยตามที่คนอื่น ๆ ได้ชี้ให้เห็น สิ่งที่กำลังพิมพ์ไม่มีความล่าช้าที่สอดคล้องกัน เมื่อคุณเห็นบางสิ่งที่สร้างขึ้นโดยมีความล่าช้าที่ไม่สอดคล้องคุณจะรู้สึกหนาวสั่น "คอมพิวเตอร์ของฉันมีสิ่งที่ !!! ??!?"

อย่างไรก็ตาม...

ฉันต้องตะโกนออกมาexpectซึ่งควรจะมีอยู่ในลีนุกซ์ส่วนใหญ่ ฉันรู้ว่าโรงเรียนเก่า แต่ - สมมติว่าติดตั้งแล้ว - มันอาจจะง่ายกว่า:

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.