bash: reading text from a string one character at a time, with whitespace
Asked Answered
E

3

7

I'm bored, and decided to write a script for a text-based adventure of mine using bash. Basically, it's supposed to animate a typewriter in certain cases for dramatic storytelling. I can do this manually in a file like so:

sleep 0.1 
echo -n "h"
sleep 0.1 
echo -n "e"
sleep 0.1 
echo -n "l"
sleep 0.1 
echo -n "l"
sleep 0.1 
echo -n "o"

As you can imagine, it's dreadfully tedious. Instead, I want to pull characters from a string (or file) one character at a time, and apply the sleep command to each character.

So far, I have the following, that reads from a file. IFS= allows the spaces to retain, but not any other type of whitespace (such as newline).

IFS=
while read -n1 achar
do
   echo $achar
done < aFile

Is there a way I can use this to get all whitespace? As a bonus question, can you tell me how to apply this to a user-defined script, so I don't have to read from a separate file? So, for example, lets say I have a string called "hello", and I can simply pass it to my function to animate as a typewriter whenever I run my file in the bash terminal.

Any help would be appreciated. Thanks!

Electrolier answered 5/5, 2012 at 4:5 Comment(3)
How about converting newlines to spaces using tr(1)?Shamus
If I understand the man page correctly, do I use the tr[:print:] option?Electrolier
+1 just because you decided to fight boredom with bash-scripting :)Unwilled
W
12

How about

#!/bin/bash

function typewriter
{
    text="$1"
    delay="$2"

    for i in $(seq 0 $(expr length "${text}")) ; do
        echo -n "${text:$i:1}"
        sleep ${delay}
    done
}


typewriter "Typewriters are cool." .1
echo # <-- Just for a newline
Woodcutter answered 5/5, 2012 at 4:21 Comment(4)
Wow, thanks! This is exactly what I needed. I'm still learning the syntax for shell scripting, so this helps a lot.Electrolier
No problem shell scripting is fun :-)Woodcutter
@derp: No need for the external seq and expr utilities: for ((i = 0; i < ${#text}; i++))Hunsinger
That makes it look more like c++, which I am used to. :)Electrolier
I
1

To answer the original question, you need to use an empty delimiter as well:

#!/bin/bash
getc() {
  IFS= read -r -n1 -d '' "$@"
}

typewriter() {
  while getc ch; do
    sleep 0.1; echo "$ch"
  done
}

Basically, read will stop at newlines (the default delimiter) even if it hasn't consumed enough characters yet. The -r flag also tells it to leave backslashes alone.

Full explanation here: http://jayferd.us/posts/2011-01-12-bash-adventures-read-a-single-character-even-if-its-a-newline

Illiberal answered 21/1, 2013 at 23:6 Comment(0)
S
0

@derp no you need to use tr -s [:blank:] to squeeze multiple blanks to a single one.

HTH.

Shamus answered 5/5, 2012 at 4:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.