To be clear, the part you're asking about isn't something you'd use bash or any other shell for so if you're trying to learn the right way to do things in a UNIX environment: A shell is an environment from which to create/destroy files and processes, and to sequence calls to tools. If you want to do something like this using mandatory UNIX tools (i.e. the tools that exist on every UNIX box) then you'd use awk, not shell. The only shell part would be to call awk.
Given that, if you want to write a hangman program in "bash" here is one way you could do the prompting/guessing part using any awk in any shell on every UNIX system:
$ cat tst.awk
BEGIN {
secret = word
gsub(/./,"_",secret)
print "\nOutcome:", secret
printf "guess? "
}
{
guess = $0
while ( pos=index(word,guess) ) {
secret = substr(secret,1,pos-1) guess substr(secret,pos+1)
word = substr(word,1,pos-1) "_" substr(word,pos+1)
}
print "\nOutcome:", secret
if (word ~ /^_*$/) {
exit
}
printf "guess? "
}
and now the bash (or any other shell) part would just be:
$ awk -v word='abcdef' -f tst.awk
Outcome: ______
guess? a
Outcome: a_____
guess? e
Outcome: a___e_
guess? d
Outcome: a__de_
guess? f
Outcome: a__def
guess? b
Outcome: ab_def
guess? c
Outcome: abcdef
I decided to implement a full hangman shell script that generates words of 5 letters or more for you to guess as it was kinda fun to play so here you go:
$ cat ./hangman.sh
#!/usr/bin/env bash
declare -a words
trap 'printf "\nwords used:\n"; printf "%s\n" "${words[@]}"; exit' 0
prtwords() {
local dfltwordfile='/usr/share/dict/words'
{
if [[ -s "$dfltwordfile" ]]; then
cat "$dfltwordfile"
else
curl -s 'https://svnweb.freebsd.org/csrg/share/dict/words?view=co&content-type=text/plain'
fi
} |
tr 'A-Z' 'a-z' |
grep -E '.{5}' |
shuf
}
guesschars() {
awk -v word="$1" '
BEGIN {
secret = origWord = word
gsub(/./,"_",secret)
print "\nOutcome:", secret
printf "guess? "
maxFailures = 6
}
NF {
guess = substr($1,1,1)
isFailure = 1
while ( pos=index(word,guess) ) {
isFailure = 0
secret = substr(secret,1,pos-1) guess substr(secret,pos+1)
word = substr(word,1,pos-1) "_" substr(word,pos+1)
}
numFailures += isFailure
print "\nOutcome:", secret
if ( (word ~ /^_*$/) || (numFailures == maxFailures) ) {
print "The word was", origWord
exit
}
printf "guess? "
}
'
}
# See https://stackoverflow.com/a/41652573/1745001 for rationale on
# the file descriptor manipulation below.
exec 3</dev/tty || exec 3<&0 ## make FD 3 point to the TTY or stdin (as fallback)
echo 'Hit interrupt to stop' >&2
while IFS= read -r word; do ## |- loop over lines read from FD 0
words+=( "$word" )
guesschars "$word" <&3 ## |- run guesschars with its stdin copied from FD 3
echo '#####' >&2
done < <(prtwords) ## \-> ...while the loop is run with prtwords output on FD 0
exec 3<&- ## close FD 3 when done.
.
$ ./hangman.sh
Hit interrupt to stop
Outcome: __________
guess? a
Outcome: _______a__
guess? e
Outcome: _e__e__a_e
guess? s
Outcome: _e__e__a_e
guess? t
Outcome: _e__e__ate
guess? r
Outcome: _e_re__ate
guess? c
Outcome: _e_rec_ate
guess? d
Outcome: de_rec_ate
guess? p
Outcome: deprec_ate
guess? i
Outcome: depreciate
The word was depreciate
#####
Outcome: ______
guess?
words used:
depreciate
enrico