Bash技巧:详解用select复合命令提供菜单列表给用户选择

 阅读约 10 分钟

本篇文章介绍 bash 的 select 复合命令,该命令可以提供菜单列表给用户进行选择。

  • select 命令格式
  • select 命令实例
  • 自定义提示信息
  • 修改菜单项的分割字符

select 命令格式

在 Linux 的 Bash shell 中,可以用 select 复合命令 (compound command) 提供一个菜单列表给用户选择,并根据用户的选择进行相应地处理。查看 man bash 里面对 select 命令的说明如下:

select name [ in word ] ; do list ; done
The list of words following "in" is expanded, generating a list of items. The set of expanded words is printed on the standard error, each preceded by a number. If the "in word" is omitted, the positional parameters are printed.

The PS3 prompt is then displayed and a line read from the standard input. If the line consists of a number corresponding to one of the displayed words, then the value of name is set to that word. If the line is empty, the words and prompt are displayed again. If EOF is read, the command completes. Any other value read causes name to be set to null.

The line read is saved in the virable REPLY. The list is executed after each selection until a break command is executed.

The exit status of select is the exit status of the list command executed in list, or zero if no commands were executed.

select 命令格式中,[ in word ] 表示 "in word" 是可选参数,用于提供菜单列表,实际输入的时候不需要输入 [] 这两个字符。而 name 变量会保存用户选择的菜单项内容,可以获取 name 变量值来查看用户的选择。每次用户输入选择后,会执行 list 指定的命令。

注意:这里的 [] 并不是 bash 里面的条件判断命令。Bash 有一个 [ 条件判断命令,其格式是 [ 参数... ],两者格式比较相似,注意区分,不要搞混。

当没有提供 in word 参数时,select 命令默认使用 in "$@" 参数,也就是传入脚本、或者传入函数的参数列表来作为菜单选项。

select 命令使用 in word 参数来指定菜单列表,不同的菜单项之间用空格隔开,不要用双引号把整个菜单列表括起来,否则会被当成一个菜单项。

当用双引号把菜单列表括起来时,整个菜单列表被当成一个选项。如果某个选择项的内容确实要包含空格,就可以单独用双引号把这个选择项的内容括起来,避免该选项该分割成多个选项。

选择之后,select 命令不会自动退出,而是等待用户继续选择,需要执行 break 命令来退出,也可以按 CTRL-D 来输入EOF进行退出。输入EOF退出后,name 变量值会保持之前的值不变,不会被自动清空。

select 命令的内部语句里面,可以用 exit 命令来直接退出整个脚本的执行,从而退出 select 的选择。如果是在函数内调用 select 命令,则可以用 return 命令来直接退出整个函数,从而退出 select 的选择。

select 命令实例

假设有一个 testselect.sh 脚本,内容如下:

#!/bin/bash

select animal in lion tiger panda flower; do
    if [ "$animal" = "flower" ]; then
        echo "Flower is not animal."
        break
    else
        echo "Choose animal is: $animal"
    fi
done

echo "++++ Enter new select ++++"
select animal in "lion tiger panda"; do
    echo "Your choose is: $animal"
    break
done

这个脚本的第一个 select 命令定义了四个菜单项:liontigerpandaflower,如果用户选择了 flower 则执行 break 命令退出 select 的选择,否则会打印出用户选择的动物名。

第二个 select 命令用双引号把菜单选项括起来,以便查看加了双引号后的效果。

执行该脚本,输出结果如下:

$ ./testselect.sh
1) lion
2) tiger
3) panda
4) flower
#? 1
Choose animal is: lion
#? 2
Choose animal is: tiger
#? 7
Choose animal is:
#? lion
Choose animal is:
#? 4
Flower is not animal.
++++ Enter new select ++++
1) lion tiger panda
#? 1
Your choose is: lion tiger panda

可以看到,select 命令要通过菜单项前面的数字来选择对应的项,并把对应项的名称赋值给指定的 animal 变量。

输入菜单项本身的字符串并不能选择这个菜单项。输入无效的菜单项编号不会报错。这两种情况都会把 animal 变量值清空。

自定义提示信息

上面的 #? 是 bash 的 PS3 提示符,我们可以为 PS3 变量赋值,再执行 select 命令,从而打印自定义的提示信息。举例如下:

$ PS3="Enjoy your choose:> "
$ select animal in lion tiger; do echo "Choose: $animal"; break; done
1) lion
2) tiger
Enjoy your choose:> 1
Choose: lion

可以看到,为 PS3 变量赋值后,select 命令会打印所赋值的内容,作为提示符。

修改菜单项的分割字符

我们可以修改 bash 的 IFS 变量值,指定不同菜单项之间的分割字符,但在使用上有一个注意事项,具体说明如下:

$ IFS=/
$ animal_list="big lion/small tiger"
$ select animal in $animal_list; do echo "Choose: $animal"; break; done
1) big lion
2) small tiger
#? 1
Choose: big lion
$ select animal in big lion/small tiger; do echo "Choose: $animal"; break; done
1) big
2) lion/small
3) tiger
#? 2
Choose: lion/small

上面的例子把 IFS 赋值为 /,然后定义 animal_list 变量,用 / 隔开了 big lionsmall tiger 两项,用 select 命令从 animal_list 变量获取菜单选项时,可以看到不再用空格来隔开选项,而是用 IFS 赋值后的 / 来隔开选项。

但是,当 select 命令不从 animal_list 变量获取菜单选项,而是直接写为 select animal in big lion/small tiger 命令时,它还是用空格来隔开选项,而不是用 IFS 赋值后的 / 来隔开选项。

原因在于,IFS 用于 bash 扩展后的单词拆分,使用 $animal_list 获取 animal_list 变量值就是一种扩展,从而发生单词拆分,用 IFS 的值来拆分成几个单词。

直接写为 in big lion/small tiger 没有发生扩展,所以没有使用 IFS 来拆分单词。

查看 man bash 对 IFS 的说明如下:

IFS
The Internal Field Separator that is used for word splitting after expansion and to split lines into words with the read builtin command. The default value is ``<space><tab><newline>''.

翻译成中文就是,IFS (Internal Field Separator) 用于在扩展后进行单词拆分,并使用 read 内置命令将行拆分为单词。

即,要在扩展之后,才会用 IFS 的值来拆分单词。

在通过 IFS 修改 select 命令的菜单项分割字符时,菜单列表需要保存到变量里面,然后在 select 命令里面获取该变量值,进行变量扩展,IFS 才会生效

阅读 192更新于 12月10日
推荐阅读
南木阁
用户专栏

考据党一枚,力求讲述的每个知识点都有出处,有理有据。如能恰好地解答您的疑问,欢迎点赞,谢谢!

0 人关注
30 篇文章
专栏主页
目录