如何将多个用户提示与返回提示的功能链接在一起?
How do I chain multiple user prompts together with the ability to go back a prompt?
我想知道如何制作一个包含多个菜单的 bash 脚本。
例如,这是用户在 运行 上会看到的内容:
Type the number of choosing:
1-play
2-load
3-exit
1
What is your name:
::上一个::
Type the number of choosing:
1-play
2-load
3-exit
1
What is your name:
布拉德
Where are you from, Brad?
宾夕法尼亚州
What is your favourite colour?
1-blue
2-red
3-green
4-grey
5-magenta
,sdfhljalk:J;
What is your favourite colour?
1-blue
2-red
3-green
4-grey
5-magenta
2
What is your favourite toy?
火车
What would you like on your sandwich?
::上一个::
What is your favourite toy?
~`!@#$%^& * ()_+=-{}[]|\"':;?/>。 <,
What is your favourite toy?
::退出::
Exiting....
抱歉篇幅太长,我只想涵盖我将要制作的新游戏的所有基础知识。我希望这成为结束所有问题的问题。
我希望能够在任何地方输入 ::prev::
并让它返回到上一个问题,并且我希望 ::exit::
退出脚本。此外,我希望在带有编号响应的问题中无法识别的输入只是重新加载问题而不继续,并且对于包含可能导致脚本中断的字符的输入(类似于 :;!@#
...)重新加载问题而不是破.
非常感谢任何帮助!
顺便说一下,我正在使用 OS X Yosemite
在这种情况下,首先要做的是尝试思考通常如何实现这样的事情。可能最大的复杂性增加是使用 ::prev::
返回一个问题。这意味着我们需要以某种方式表示应用程序状态,以便我们可以向前或向后移动。
幸运的是,这非常简单:它基本上只是我们需要的堆栈的实现。一些视觉效果:
...
<--pop-- <--pop-- Location prompt <--pop-- ...
Name prompt Name prompt ...
Main menu --push-> Main menu --push-> Main menu --push-> ...
这也意味着程序的每个单独部分都需要独立。我们可以在 shell 函数脚本中轻松做到这一点。
所以我们需要几件:
- 显示菜单并允许用户选择值的函数。
- 显示文本提示并允许用户选择值的函数。
- 管理表示程序状态的堆栈的函数。
- 程序的每个部分都有各自的功能。
我们先来写我们的菜单提示函数。这部分很简单。 Bash 将使用 select
循环完成大部分工作,它会为我们打印一个菜单。我们只是包装它,以便我们可以处理自定义逻辑,比如期望 ::exit::
或 ::prev::
和一些漂亮的打印。
function show_menu {
echo "" # Print the prompt
PS3='> ' # Set prompt string 3 to '> '
select selection in "${menu_items[@]}" # Print menu using the menu_items array
do
if [[ "$REPLY" =~ ^(::exit::|::prev::)$ ]]; then
# If the user types ::exit:: or ::prev::, exit the select loop
# and return 1 from the function, with $selection containing
# the command the user entered.
selection="$REPLY"
return 1
fi
# $selection will be blank if the user did not choose a valid menu item.
if [ -z "$selection" ]; then
# Display error message if $selection is blank
echo 'Invalid input. Please choose an option from the menu.'
else
# Otherwise, return a success return code.
return 0
fi
done
}
我们现在可以像这样使用这个函数了:
menu_items=('Item 1' 'Item 2' 'Item 3')
if ! show_menu 'Please choose an item from the menu below.'; then
echo "You entered the command $selection."
fi
echo "You chose $selection."
太棒了!现在进入议程的下一个项目,编写接受用户文本输入的代码。
# Prompt for a required text value.
function prompt_req {
# do...while
while : ; do
# Print the prompt on one line, then '> ' on the next.
echo ""
printf '> '
read -r selection # Read user input into $selection
if [ -z "$selection" ]; then
# Show error if $selection is empty.
echo 'A value is required.'
continue
elif [[ "$selection" =~ ^(::exit::|::prev::)$ ]]; then
# Return non-success return code if ::exit:: or ::prev:: were entered.
return 1
elif [[ "$selection" =~ [^a-zA-Z0-9\'\ ] ]]; then
# Make sure input only contains a whitelist of allowed characters.
# If it has other characters, print an error and retry.
echo "Invalid characters in input. Allowed characters are a-z, A-Z, 0-9, ', and spaces."
continue
fi
# This break statement only runs if no issues were found with the input.
# Exits the while loop and the function returns a success return code.
break
done
}
太棒了。此函数的工作方式与第一个类似:
if ! prompt_req 'Please enter a value.'; then
echo "You entered the command $selection."
fi
echo "You entered '$selection'."
现在我们已经处理了用户输入,我们需要用堆栈管理函数来处理程序流。这在 bash
中使用数组很容易实现。
当程序的一部分运行s 完成时,它会要求流程管理器运行 执行下一个函数。流程管理器会将下一个函数的名称压入堆栈,或者更确切地说,将其添加到数组的末尾,然后 运行 它。如果输入 ::prev::
,它将从堆栈中弹出最后一个函数的名称,或者删除数组的最后一个元素,然后 运行 它之前的函数。
少说话,多代码:
# Program flow manager
function run_funcs {
# Define our "stack" with its initial value being the function name
# passed directly to run_funcs
funcs=("")
# do...while
while : ; do
# Reset next_func
next_func=''
# Call the last function name in funcs.
if "${funcs[${#funcs[@]}-1]}"; then
# If the function returned 0, then no :: command was run by the user.
if [ -z "$next_func" ]; then
# If the function didn't set the next function to run, exit the program.
exit 0
else
# Otherwise, add the next function to run to the funcs array. (push)
funcs+=("$next_func")
fi
else
# If the function returned a value other than 0, a command was run.
# The exact command run will be in $selection
if [ "$selection" == "::prev::" ]; then
if [ ${#funcs[@]} -lt 2 ]; then
# If there is only 1 function in funcs, then we can't go back
# because there's no other function to call.
echo 'There is no previous screen to return to.'
else
# Remove the last function from funcs. (pop)
unset funcs[${#funcs[@]}-1]
fi
else
# If $selection isn't ::prev::, then it's ::exit::
exit 0
fi
fi
# Add a line break between function calls to keep the output clean.
echo
done
}
我们的 run_funcs
函数需要:
- 以第一个函数的名称调用 运行,并且
- 如果必须继续执行程序,运行s 的每个函数都会将下一个函数的名称输出到 运行
next_func
。
好的。这应该很容易使用。现在开始实际编写程序:
function main_menu {
menu_items=('Play' 'Load' 'Exit')
if ! show_menu 'Please choose from the menu below.'; then
return 1
fi
if [ "$selection" == 'Exit' ]; then
exit 0
fi
if [ "$selection" == 'Load' ]; then
# Logic to load game state
echo 'Game loaded.'
fi
next_func='prompt_name'
}
function prompt_name {
if ! prompt_req 'What is your name?'; then
return 1
fi
name="$selection"
next_func='prompt_location'
}
function prompt_location {
if ! prompt_req "Where are you from, $name?"; then
return 1
fi
location="$selection"
next_func='prompt_colour'
}
function prompt_colour {
menu_items=('Blue' 'Red' 'Green' 'Grey' 'Magenta')
if ! show_menu 'What is your favourite colour?'; then
return 1
fi
colour="$selection"
next_func='prompt_toy'
}
function prompt_toy {
if ! prompt_req 'What is your favourite toy?'; then
return 1
fi
toy="$selection"
next_func='print_player_info'
}
function print_player_info {
echo "Your name is $name."
echo "You are from $location."
echo "Your favourite colour is $colour."
echo "Your favourite toy is $toy."
# next_func is not set, so the program will exit after running this function.
}
echo 'My Bash Game'
echo
# Start the program, with main_menu as an entry point.
run_funcs main_menu
现在一切都井井有条。让我们试试我们的程序吧!
$ ./bash_game.sh
My Bash Game
Please choose from the menu below.
1) Play
2) Load
3) Exit
> ::prev::
There is no previous screen to return to.
Please choose from the menu below.
1) Play
2) Load
3) Exit
> 2
Game loaded.
What is your name?
> Stephanie
Where are you from, Stephanie?
> ::prev::
What is your name?
> Samantha
Where are you from, Samantha?
> Dubl*n
Invalid characters in input. Allowed characters are a-z, A-Z, 0-9, ', and spaces.
Where are you from, Samantha?
> Dublin
What is your favourite colour?
1) Blue
2) Red
3) Green
4) Grey
5) Magenta
> Aquamarine
Invalid input. Please choose an option from the menu.
> 8
Invalid input. Please choose an option from the menu.
> 1
What is your favourite toy?
> Teddie bear
Your name is Samantha.
You are from Dublin.
Your favourite colour is Blue.
Your favourite toy is Teddie bear.
给你了。
我想知道如何制作一个包含多个菜单的 bash 脚本。
例如,这是用户在 运行 上会看到的内容:
Type the number of choosing:
1-play
2-load
3-exit
1
What is your name:
::上一个::
Type the number of choosing:
1-play
2-load
3-exit
1
What is your name:
布拉德
Where are you from, Brad?
宾夕法尼亚州
What is your favourite colour?
1-blue
2-red
3-green
4-grey
5-magenta
,sdfhljalk:J;
What is your favourite colour?
1-blue
2-red
3-green
4-grey
5-magenta
2
What is your favourite toy?
火车
What would you like on your sandwich?
::上一个::
What is your favourite toy?
~`!@#$%^& * ()_+=-{}[]|\"':;?/>。 <,
What is your favourite toy?
::退出::
Exiting....
抱歉篇幅太长,我只想涵盖我将要制作的新游戏的所有基础知识。我希望这成为结束所有问题的问题。
我希望能够在任何地方输入 ::prev::
并让它返回到上一个问题,并且我希望 ::exit::
退出脚本。此外,我希望在带有编号响应的问题中无法识别的输入只是重新加载问题而不继续,并且对于包含可能导致脚本中断的字符的输入(类似于 :;!@#
...)重新加载问题而不是破.
非常感谢任何帮助!
顺便说一下,我正在使用 OS X Yosemite
在这种情况下,首先要做的是尝试思考通常如何实现这样的事情。可能最大的复杂性增加是使用 ::prev::
返回一个问题。这意味着我们需要以某种方式表示应用程序状态,以便我们可以向前或向后移动。
幸运的是,这非常简单:它基本上只是我们需要的堆栈的实现。一些视觉效果:
...
<--pop-- <--pop-- Location prompt <--pop-- ...
Name prompt Name prompt ...
Main menu --push-> Main menu --push-> Main menu --push-> ...
这也意味着程序的每个单独部分都需要独立。我们可以在 shell 函数脚本中轻松做到这一点。
所以我们需要几件:
- 显示菜单并允许用户选择值的函数。
- 显示文本提示并允许用户选择值的函数。
- 管理表示程序状态的堆栈的函数。
- 程序的每个部分都有各自的功能。
我们先来写我们的菜单提示函数。这部分很简单。 Bash 将使用 select
循环完成大部分工作,它会为我们打印一个菜单。我们只是包装它,以便我们可以处理自定义逻辑,比如期望 ::exit::
或 ::prev::
和一些漂亮的打印。
function show_menu {
echo "" # Print the prompt
PS3='> ' # Set prompt string 3 to '> '
select selection in "${menu_items[@]}" # Print menu using the menu_items array
do
if [[ "$REPLY" =~ ^(::exit::|::prev::)$ ]]; then
# If the user types ::exit:: or ::prev::, exit the select loop
# and return 1 from the function, with $selection containing
# the command the user entered.
selection="$REPLY"
return 1
fi
# $selection will be blank if the user did not choose a valid menu item.
if [ -z "$selection" ]; then
# Display error message if $selection is blank
echo 'Invalid input. Please choose an option from the menu.'
else
# Otherwise, return a success return code.
return 0
fi
done
}
我们现在可以像这样使用这个函数了:
menu_items=('Item 1' 'Item 2' 'Item 3')
if ! show_menu 'Please choose an item from the menu below.'; then
echo "You entered the command $selection."
fi
echo "You chose $selection."
太棒了!现在进入议程的下一个项目,编写接受用户文本输入的代码。
# Prompt for a required text value.
function prompt_req {
# do...while
while : ; do
# Print the prompt on one line, then '> ' on the next.
echo ""
printf '> '
read -r selection # Read user input into $selection
if [ -z "$selection" ]; then
# Show error if $selection is empty.
echo 'A value is required.'
continue
elif [[ "$selection" =~ ^(::exit::|::prev::)$ ]]; then
# Return non-success return code if ::exit:: or ::prev:: were entered.
return 1
elif [[ "$selection" =~ [^a-zA-Z0-9\'\ ] ]]; then
# Make sure input only contains a whitelist of allowed characters.
# If it has other characters, print an error and retry.
echo "Invalid characters in input. Allowed characters are a-z, A-Z, 0-9, ', and spaces."
continue
fi
# This break statement only runs if no issues were found with the input.
# Exits the while loop and the function returns a success return code.
break
done
}
太棒了。此函数的工作方式与第一个类似:
if ! prompt_req 'Please enter a value.'; then
echo "You entered the command $selection."
fi
echo "You entered '$selection'."
现在我们已经处理了用户输入,我们需要用堆栈管理函数来处理程序流。这在 bash
中使用数组很容易实现。
当程序的一部分运行s 完成时,它会要求流程管理器运行 执行下一个函数。流程管理器会将下一个函数的名称压入堆栈,或者更确切地说,将其添加到数组的末尾,然后 运行 它。如果输入 ::prev::
,它将从堆栈中弹出最后一个函数的名称,或者删除数组的最后一个元素,然后 运行 它之前的函数。
少说话,多代码:
# Program flow manager
function run_funcs {
# Define our "stack" with its initial value being the function name
# passed directly to run_funcs
funcs=("")
# do...while
while : ; do
# Reset next_func
next_func=''
# Call the last function name in funcs.
if "${funcs[${#funcs[@]}-1]}"; then
# If the function returned 0, then no :: command was run by the user.
if [ -z "$next_func" ]; then
# If the function didn't set the next function to run, exit the program.
exit 0
else
# Otherwise, add the next function to run to the funcs array. (push)
funcs+=("$next_func")
fi
else
# If the function returned a value other than 0, a command was run.
# The exact command run will be in $selection
if [ "$selection" == "::prev::" ]; then
if [ ${#funcs[@]} -lt 2 ]; then
# If there is only 1 function in funcs, then we can't go back
# because there's no other function to call.
echo 'There is no previous screen to return to.'
else
# Remove the last function from funcs. (pop)
unset funcs[${#funcs[@]}-1]
fi
else
# If $selection isn't ::prev::, then it's ::exit::
exit 0
fi
fi
# Add a line break between function calls to keep the output clean.
echo
done
}
我们的 run_funcs
函数需要:
- 以第一个函数的名称调用 运行,并且
- 如果必须继续执行程序,运行s 的每个函数都会将下一个函数的名称输出到 运行
next_func
。
好的。这应该很容易使用。现在开始实际编写程序:
function main_menu {
menu_items=('Play' 'Load' 'Exit')
if ! show_menu 'Please choose from the menu below.'; then
return 1
fi
if [ "$selection" == 'Exit' ]; then
exit 0
fi
if [ "$selection" == 'Load' ]; then
# Logic to load game state
echo 'Game loaded.'
fi
next_func='prompt_name'
}
function prompt_name {
if ! prompt_req 'What is your name?'; then
return 1
fi
name="$selection"
next_func='prompt_location'
}
function prompt_location {
if ! prompt_req "Where are you from, $name?"; then
return 1
fi
location="$selection"
next_func='prompt_colour'
}
function prompt_colour {
menu_items=('Blue' 'Red' 'Green' 'Grey' 'Magenta')
if ! show_menu 'What is your favourite colour?'; then
return 1
fi
colour="$selection"
next_func='prompt_toy'
}
function prompt_toy {
if ! prompt_req 'What is your favourite toy?'; then
return 1
fi
toy="$selection"
next_func='print_player_info'
}
function print_player_info {
echo "Your name is $name."
echo "You are from $location."
echo "Your favourite colour is $colour."
echo "Your favourite toy is $toy."
# next_func is not set, so the program will exit after running this function.
}
echo 'My Bash Game'
echo
# Start the program, with main_menu as an entry point.
run_funcs main_menu
现在一切都井井有条。让我们试试我们的程序吧!
$ ./bash_game.sh
My Bash Game
Please choose from the menu below.
1) Play
2) Load
3) Exit
> ::prev::
There is no previous screen to return to.
Please choose from the menu below.
1) Play
2) Load
3) Exit
> 2
Game loaded.
What is your name?
> Stephanie
Where are you from, Stephanie?
> ::prev::
What is your name?
> Samantha
Where are you from, Samantha?
> Dubl*n
Invalid characters in input. Allowed characters are a-z, A-Z, 0-9, ', and spaces.
Where are you from, Samantha?
> Dublin
What is your favourite colour?
1) Blue
2) Red
3) Green
4) Grey
5) Magenta
> Aquamarine
Invalid input. Please choose an option from the menu.
> 8
Invalid input. Please choose an option from the menu.
> 1
What is your favourite toy?
> Teddie bear
Your name is Samantha.
You are from Dublin.
Your favourite colour is Blue.
Your favourite toy is Teddie bear.
给你了。