bash - How do I chain multiple user prompts together with the ability to go back a prompt? -
i wondering how can make bash script has multiple menus in it.
for example, here's user see upon running it:
type number of choosing: 1-play 2-load 3-exit
1
what name:
::prev::
type number of choosing: 1-play 2-load 3-exit
1
what name:
brad
where from, brad?
pennsylvania
what favourite colour? 1-blue 2-red 3-green 4-grey 5-magenta
,sdfhljalk:j;
what favourite colour? 1-blue 2-red 3-green 4-grey 5-magenta
2
what favourite toy?
train
what on sandwich?
::prev::
what favourite toy?
~`!@#$%^& * ()_+=-{}[]|\"':;?/>.<,
what favourite toy?
::exit::
exiting....
i apologize being long, want cover bases new game i'm going making. want question end questions.
i want able type ::prev::
wherever , have go previous question, , i'd ::exit::
exit script wherever is. also, i'd unrecognized input during questions numbered responses reload question without continuing, , input containing characters may cause script break (something :;!@#
...) reload question instead of breaking.
any appreciated!
by way, i'm using os x yosemite
first thing in situation try , think of how, generally, implement this. biggest addition complexity using ::prev::
go question. means need represent application state in way such can move forward or backward.
luckily, pretty simple: it's implementation of stack need. visuals:
... <--pop-- <--pop-- location prompt <--pop-- ... name prompt name prompt ... main menu --push-> main menu --push-> main menu --push-> ...
this means each individual piece of program needs self-contained. can in shell scripting functions.
so need several pieces:
- function displays menu , allows user choose value.
- function displays text prompt , allows user choose value.
- function manages stack represents state of program.
- individual functions each piece of program.
let's first write our menu prompt function. part pretty easy. bash of work using select
loop, prints menu us. we'll wrap can handle custom logic, expecting ::exit::
or ::prev::
, pretty-printing.
function show_menu { echo "$1" # print prompt ps3='> ' # set prompt string 3 '> ' select selection in "${menu_items[@]}" # print menu using menu_items array if [[ "$reply" =~ ^(::exit::|::prev::)$ ]]; # if user types ::exit:: or ::prev::, exit select loop # , return 1 function, $selection containing # command user entered. selection="$reply" return 1 fi # $selection blank if user did not choose valid menu item. if [ -z "$selection" ]; # display error message if $selection blank echo 'invalid input. please choose option menu.' else # otherwise, return success return code. return 0 fi done }
we can use function so:
menu_items=('item 1' 'item 2' 'item 3') if ! show_menu 'please choose item menu below.'; echo "you entered command $selection." fi echo "you chose $selection."
great! on next item on agenda, writing code accepts text input user.
# prompt required text value. function prompt_req { # do...while while : ; # print prompt on 1 line, '> ' on next. echo "$1" printf '> ' read -r selection # read user input $selection if [ -z "$selection" ]; # show error if $selection empty. echo 'a value required.' continue elif [[ "$selection" =~ ^(::exit::|::prev::)$ ]]; # return non-success return code if ::exit:: or ::prev:: entered. return 1 elif [[ "$selection" =~ [^a-za-z0-9\'\ ] ]]; # make sure input contains whitelist of allowed characters. # if has other characters, print error , retry. echo "invalid characters in input. allowed characters a-z, a-z, 0-9, ', , spaces." continue fi # break statement runs if no issues found input. # exits while loop , function returns success return code. break done }
great. function works first:
if ! prompt_req 'please enter value.'; echo "you entered command $selection." fi echo "you entered '$selection'."
now have user input handled, need handle program flow our stack-managing function. easy implement in bash
using array.
when part of program runs , completes, ask flow manager run next function. flow manager push name of next function onto stack, or rather, add end of array, , run it. if ::prev::
entered, pop last function's name off of stack, or remove last element of array, , run function before it.
less talk, more code:
# program flow manager function run_funcs { # define our "stack" initial value being function name # passed directly run_funcs funcs=("$1") # do...while while : ; # reset next_func next_func='' # call last function name in funcs. if "${funcs[${#funcs[@]}-1]}"; # if function returned 0, no :: command run user. if [ -z "$next_func" ]; # if function didn't set next function run, exit program. exit 0 else # otherwise, add next function run funcs array. (push) funcs+=("$next_func") fi else # if function returned value other 0, command run. # exact command run in $selection if [ "$selection" == "::prev::" ]; if [ ${#funcs[@]} -lt 2 ]; # if there 1 function in funcs, can't go # because there's no other function call. echo 'there no previous screen return to.' else # remove last function funcs. (pop) unset funcs[${#funcs[@]}-1] fi else # if $selection isn't ::prev::, it's ::exit:: exit 0 fi fi # add line break between function calls keep output clean. echo done }
our run_funcs
function expects:
- to called name of first function run, and
- that each function runs output name of next function run
next_func
if execution of program must proceed.
alright. should pretty simple work with. let's write program now:
function main_menu { menu_items=('play' 'load' 'exit') if ! show_menu 'please choose menu below.'; return 1 fi if [ "$selection" == 'exit' ]; exit 0 fi if [ "$selection" == 'load' ]; # logic load game state echo 'game loaded.' fi next_func='prompt_name' } function prompt_name { if ! prompt_req 'what name?'; return 1 fi name="$selection" next_func='prompt_location' } function prompt_location { if ! prompt_req "where from, $name?"; return 1 fi location="$selection" next_func='prompt_colour' } function prompt_colour { menu_items=('blue' 'red' 'green' 'grey' 'magenta') if ! show_menu 'what favourite colour?'; return 1 fi colour="$selection" next_func='prompt_toy' } function prompt_toy { if ! prompt_req 'what favourite toy?'; return 1 fi toy="$selection" next_func='print_player_info' } function print_player_info { echo "your name $name." echo "you $location." echo "your favourite colour $colour." echo "your favourite toy $toy." # next_func not set, program exit after running function. } echo 'my bash game' echo # start program, main_menu entry point. run_funcs main_menu
everything in order now. let's try out our program!
$ ./bash_game.sh bash game please choose menu below. 1) play 2) load 3) exit > ::prev:: there no previous screen return to. please choose menu below. 1) play 2) load 3) exit > 2 game loaded. name? > stephanie from, stephanie? > ::prev:: name? > samantha from, samantha? > dubl*n invalid characters in input. allowed characters a-z, a-z, 0-9, ', , spaces. from, samantha? > dublin favourite colour? 1) blue 2) red 3) green 4) grey 5) magenta > aquamarine invalid input. please choose option menu. > 8 invalid input. please choose option menu. > 1 favourite toy? > teddie bear name samantha. dublin. favourite colour blue. favourite toy teddie bear.
and there have it.
Comments
Post a Comment