Command Grouping 主要有两种形式:linux
(command;command[;command;command...])
{ command;[command;command;...] }
;
两种命令分组的区别在于,使用进程列表将会建立出一个子shell来执行分组中的命令,而另外一种形式则不会如此。shell
coproc 是一个 shell 关键字,其用法为 coproc [name] command
,它将在后台生成一个子 shell,并在其中执行 command 。协程的名字默认为 COPROC,当显式命名时,command 部分应采用命令分组的形式(注意若使用进程列表,将产生嵌套的子 shell )。express
为了将命令的输出提取出来以赋给变量,可使用以下两种形式执行命令:编程
下面的脚本将当前日期及时间嵌入到特定字串中输出:bash
#!/bin/bash my_var=`date` # or my_var=$(date) echo "The date and time are: " $my_var
Bourne shell 提供了一个特别的命令,即expr,来处理数学表达式,该命令可以识别少数的数字及字符串操做符,此外该命令对数字的支持仅限于整数。而且,应注意到,在shell中使用时还须要对一些操做符进行转义。ide
在 bash shell 中,为了不转义操做符带来的麻烦,应采用这种形式 $[ operation ]
来执行数学表达式。函数
z shell 提供了完整的浮点数算术支持。而在bash中,为了避开数学运算的整数限制,常见的方案是使用内建的bash计算器,即bc 。oop
下面是一个在脚本中使用bc的简单例子:测试
#!/bin/bash res=$( echo "scale=2; var1=3.1415; var1 / 2" | bc ) echo "Result is $res"
bc有一个内建变量 scale ,默认值为0。该变量控制着在含有除法的浮点数运算中,结果所保留的小数位数。浮点乘法不受其影响。ui
if command then commands fi
if command; then commands fi
if-then 语句首先执行 command 部分并测试其退出状态码( exit status ),若是为0,则执行 commands 部分。test 命令,提供了测试更多条件的途径。test condition
若是 condition 成立,test 命令就会返回退出状态码 0,不然返回非零的退出状态码。
bash shell 提供了 test 命令的另外一种更简便的写法,注意 condition 与方括号间的空格是必须的。
if [ condition ] then commands fi
n1 -op n2
, op: eq ne ge gt le ltstr1 op str2
, op: = != < >-op str1
, op: n z-op file
, op: d e f r s w x O Gfile1 -op file2
, op: nt otif-then 语句容许使用布尔逻辑来组合测试,有两种布尔运算符可用:
[ condition1 ] op [ condition2 ]
, op: && ||
若是测试中的操做数包含了变量,那么建议使用引号将变量替换表达式引住。这是由于,若是变量为空(这在使用命令替换给变量赋值时可能会发生),那么变量替换将使得某些测试缺乏操做数,从而形成错误;或者若是变量的值中包含空格,那么对于测试,将会出现非法或多余的参数。
经过使用 [ "$var" op val ]
这种形式,能够明确告知 test 命令,那里有一个参数,即使参数是空值或包含空格。
(( expression ))
,其中可以使用各类各样的数学运算符,且不须要对诸如 < > 这样的操做符进行转义。
此外,因为 expression 能够是任意的数学赋值或比较表达式,故也可将双括号命令用做给变量赋值的普通命令。
[[ expression ]]
,expression 使用了 test 命令中采用的标准字符串比较,同时提供了额外的特性:模式匹配,经过使用双等号操做符来使用这一特性。
case variable in pattern1 | pattern2) commands1;; pattern3) commands2;; *) default_commands;; esac
shell 中的 case 不须要 "break" 语句便可跳转出去。
IFS 环境变量定义了 bash shell 用做字段分隔符的一系列字符。默认的字段分隔符有:
for var in list do commands done
for var in list; do commands done
for (( variable assignment ; condition ; iteration process ))
注意,有些部分没有遵循 bash shell 的标准:
下面是一个采用该风格但没有实际意义的例子:
for (( a = 1, b = 10; a<=10 && b > 6; a++, b-- )) do echo "$a - $b" done
并非全部的 shell 都像 bash 同样支持 C 语言风格的 for 命令,如此,就须要写出一大串数字序列做为 list ,不过,幸亏有 seq 命令能够帮助咱们生成数字序列。
seq [option]... [first] [increment] last
默认 first 和 increment 为 1 。
while test_command do commands done
其中 test_command 与 if-then 语句中该部分的用法相同。此外,能够定义多个 test_command,但只有最后一个测试命令的退出状态码被用来决定是否结束循环,注意,每一个测试命令都应出如今单独的一行上。
下面是一个定义有多个 test_command 但没有什么实际意义的脚本:
#!/bin/bash var=5 while echo $var [ $var -gt 0 ] do echo "still in the loop" (( var = $var - 1 )) done
输出为:
5 still in the loop 4 still in the loop 3 still in the loop 2 still in the loop 1 still in the loop 0
until test_command do commands done
与 while 命令相似,until 命令也能够定义多个测试命令,但只有最后一个命令的退出状态码起做用。
break n
,默认状况下,n 为1,表示当即结束当前层的循环;若 n 为2,则该命令会中止下一级的外部循环。
continue n
,n 默认为1,表示跳过当前循环中剩余的命令。
能够对循环的输出使用管道或进行重定向,只需在 done 命令后添加一个处理命令便可。
bash shell 将被称为位置参数的特殊变量对应分配给输入到命令行中的全部参数,包括 shell 所执行的脚本名称。位置参数变量是标准的数字,其中 $0
是脚本程序名,$1
对应第一个参数,以此类推,直到第九个参数 $9
,对于这以后更多的参数,须要在变量数字周围加上花括号,如第十一个参数对应 ${11}
。
$0
变量所保存的一般是带有路径的脚本名,而非 basename 。不过,使用外部命令 basename 便可提取出想要的不带路径的脚本名。
特殊变量 $#
统计了命令行中输入的参数的个数(注意,这不包含程序名,不要混淆了),能够经过该变量直接获取最后一个参数,不过应写成这种形式:${!#}
(注意,当没有输入任何参数时,获取到的是程序名,而不是参数)。
下面的脚本将遍历并输出全部参数,其采用了 C 语言风格的 for 命令,并使用了嵌套的参数替换:
#!/bin/bash for (( i = 1; i <= $#; i++ )) do echo "param: ${!i}" done
特殊变量 $*
将全部参数做为一个总体以当成一个单词来保存;而特殊变量 $@
将全部参数看成同一字符串中的多个独立的单词来保存,从而使得能够直接经过 for 命令来遍历这些参数。
默认状况下,该命令会将除 $0
外的每一个位置参数变量向左移动一个位置,其中变量 $1
的值将在每次移动中被遗弃,取而代之是 $2
的值(若是有的话)。能够给该命令提供一个参数,指明每次要移动的距离。
注意,该命令同时也影响着特殊变量 $#
, $*
以及 $@
的值。
选项 是跟在单破折线(-
)后的单个字母,对于 shell 脚本的内部处理来讲,它是特殊的参数,而 参数 对应着普通的参数。
当同时使用选项与普通参数时,标准的处理方式是使用特殊字符将二者分开,该字符会告诉脚本什么时候结束选项以及普通参数什么时候开始。对 Linux 来讲,这个特殊字符是双破折线(--
)。
-m value
;-ac
;-mValue
。用户在命令行中每每采用更为温馨的输入习惯来输入选项及普通参数,这给脚本对选项的处理带来了难度。经过使用 getopt 命令,可将习惯性的输入格式化为标准的脚本选项及普通参数输入。
getopt optstring parameters
optstring 定义了命令行中有效的选项字母,并定义了哪些选项须要参数值。首先列出全部有效的选项字母,然后在带有参数值的选项字母后加上一个冒号。该命令会基于 optstring 来解析 parameters ,并返回其标准形式。
$ getopt ab:cd -acb val1 -d val2 val3 -a -c -b val1 -d -- val2 val3
在脚本中使用该命令时,每每搭配 set 命令来将脚本的命令行参数替换为其标准形式,只须要在脚本开始处添加这样一条语句:set -- $(getopt -qu optstring "$@")
,其中 -q
是 getopt 命令的选项,表示忽略错误消息,好比发现了无效的选项;而 --
是 set 命令的一个选项(真是很不标准的一个选项),使得 set 将其所在脚本对应的命令行参数替换为该选项的参数值。
-q
选项致使的错误当 getopt 命令仅带上 -q
选项后,除了会忽略错误消息外,其输出还会发生微小的变化:
$ getopt -q ab:cd -acb val1 -d val2 val3 -a -c -b 'val1' -d -- 'val2' 'val3'
当脚本想要输出 val2
时,实际上会输出 'val2'
(虽然 echo 'hello'
仅会打印出 hello
)。若是不想要这种输出,能够再添加一个 -u
选项来避免输出被引号包围,事实上,-u
选项每每是必要的,由于在数值比较中,i<10
是有效的,而 i<'10'
显然是无效的。
此外,也能够不带任何选项,而只需将标准错误重定向到 /dev/null
便可。
下面的脚本带有三个选项,-d
要求打印出分界符,-u
及其参数值表示一个单元的内容,-n
及其参数值决定要将该单元打印多少次,默认为 1 。
#!/bin/bash set -- $(getopt -qu u:n:d "$@") num=1 delimit=no while [ -n "$1" ] do case "$1" in -u) unit=$2 shift ;; -n) num=$2 shift ;; -d) delimit=yes ;; --) shift break ;; *) echo "some error" exit 1 ;; esac shift done if [ $delimit = yes ]; then echo "->" fi if [ -z "$unit" ]; then echo "error: need unit" else for (( i=0; i<$num; i++ )) do echo -n "$unit" done echo fi if [ $delimit = yes ]; then echo "<-" fi if [ $# -gt 0 ]; then echo "[$#]remained param: $*" fi
$ ./necho.sh -du\& -n5 "hello world" -> &&&&& <- [2]remained param: hello world $ getopt -qu u:n:d -du"a b" -n5 "hello world" -d -u a b -n 5 -- hello world
可见,getopt 命令能够将合并选项拆开,也能够处理选项与其参数值间没有空格的状况,可是,它却没法把 hello world
或 a b
当成是一个参数来处理。
getopts optstring variable
optstring 与 getopt 的相似,首先列出有效的选项字母,然后在须要的参数值的选项后加上冒号。不像 getopt ,getopts 命令一次只处理一个选项,当处理完全部选项后,它会返回一个大于0的退出状态码。
getopts 命令将当前选项保存在 variable 中(不带单破折线),若是该选项有参数值的话,则将参数值保存在 OPTARG 中。此外,环境变量 OPTIND 始终保存着下一个要解析的命令行参数的位置,位置索引从1开始,所以,当解析完毕后,只须要 shift $[ $OPTIND - 1 ]
便可方便地处理余下的普通参数。
能够在 optstring 以前加上一冒号,这会使得 getopts 在发现错误时保持静默,但这并不意味着它不会处理错误。
?
,而选项字母被存放在 OPTARG 中;:
,同时该选项字母会被存放在 OPTARG 中。若 optstring 不以冒号开始,
?
,且 OPTARG 会被 unset ,此外还会打印出一条诊断消息。用 getopts 重写上面实例的选项处理部分:
#!/bin/bash num=1 delimit=no while getopts :u:n:d opt do case "$opt" in u) unit=$OPTARG ;; n) num=$OPTARG ;; d) delimit=yes ;; :) echo "Required argument for $OPTARG is not found." exit 1 ;; *) echo "[$opt] Unknown option: $OPTARG" ;; esac done shift $[ $OPTIND - 1 ] # - - -
$ ./necho.sh -hdu"a b" -n5 "hello world" [?] Unknown option: h -> a ba ba ba ba b <- [1]remained param: hello world
脚本正确地识别出选项 -u
的参数为 a b
,这是由于 getopts 正确地认为其是一个参数。此外,hello world
被认为是一个参数,这是由于对于命令行参数来讲,确是如此。
但 getopts 命令并不总能识别出缺乏参数的选项:
$ ./necho.sh -du Required argument for u is not found. $ ./necho.sh -u -d -d
[fd]{<|>|<>}{[ ]file|&[ ]fd}
[src]{<|>|<>}{dst}
,若没有给出 src 部分,默认 >
为 1>
,<
为 0<
。
默认状况下,重定向都是临时的,能够经过 exec 命令在脚本执行期间永久重定向,例如
#!/bin/bash echo "[1]This output should be shown at screen" tmpfile=$( mktemp out.XXXXXX ) echo "[1]This output should go to the $tmpfile file" >> $tmpfile echo "[2]This output should be shown at screen" exec 3>& 1 exec >> $tmpfile echo "[2]This output should go to the $tmpfile file" exec >&3 echo "[3]This output should be shown at screen" exec <$tmpfile echo "The content of the $tmpfile file:" while read do echo $REPLY done echo "That's over" rm $tmpfile
$ ./test.sh [1]This output should be shown at screen [2]This output should be shown at screen [3]This output should be shown at screen The content of the out.rmmGdo file: [1]This output should go to the out.rmmGdo file [2]This output should go to the out.rmmGdo file That's over
要关闭文件描述符,将其重定向到特殊文件描述符 -
便可。
function name { commands }
name() { commands }
函数名是惟一的,若是后定义的函数使用了重复的函数名,以前的函数将会被覆盖,且这一切不会有任何提示。
像使用通常的命令同样,直接键入函数名便可调用该函数。且函数在使用前应该被定义,不然将产生错误消息。
能够将函数的退出状态码当成是返回值,默认状况下,函数的退出状态码就是函数中最后一条命令的退出状态码。经过在函数中使用 return 命令,能够退出函数并指定一个整数值做为退出状态码。
因为退出状态码的取值为 0~255,因此 return 一个非法的值时,将产生一个错误值(这并不会产生任何错误消息)。
经过命令替换能够将函数的标准输出赋给变量,这是一种获取函数返回值的更好的方法。
bash shell 将函数看成是小型的脚原本对待,这意味着能够像普通脚本那样向函数传递参数。
默认状况下,在脚本中任何位置定义的任何变量都是全局变量。但对于函数中的变量来讲,状况有些特殊。
local locVar
or local locVar=expr
注意,与初始化全局变量不一样,使用 local 声明或定义局部变量的操做将更新特殊变量 $?
,这是由于 local
是一个内建命令,而命令具备退出状态码。
若是想经过命令替换给一个局部变量赋值,同时获取到该命令的退出状态码的话,应该使用以下方式:
local locVar locVar=`command(s)` exitStatus=$?
#!/bin/bash factorial() { if [ $1 -eq 1 ]; then echo 1 else local ret=` factorial $[ $1 - 1 ]` echo $[ $ret * $1 ] fi } read -p "Input a number: " num ret=` factorial $num ` echo "Result: $ret" exit 0
能够建立一个只包含有函数的公用库文件,然后在多个脚本中引用该文件。
该命令会在当前 shell 上下文中执行命令,而不是建立一个新的 shell。能够经过该命令在脚本中运行库文件,这样就可使用库中的函数了。
source 命令有一个别名,即 点操做符 ( dot operator ),写做 .
。
select variable in list do commands done
list
是由空格分隔的文本选项列表,select 将每一个列表项显示成一个带编号的选项,然后循环进行如下步骤:
variable
中(若输入无效,则 [ -z $variable ]
将为真);commands
,若是遇到 break ,则退出循环。#!/bin/bash PS3="Enter your chioce: " clear select option in \ "Exit program" \ "Display memory usage" \ "Display disk space" \ "Display date and time" do case $option in "Exit program") break ;; "Display memory usage") free -h ;; "Display disk space") df -h ;; "Display date and time") date ;; *) echo "Sorry, wrong selection" ;; esac done clear exit 0
为了绘制 TUI (Text-based User Interface ),可使用 ncurces
或 newt
库,只是注意在使用前确认一下是否安装了其 -dev
包便可。
使用 dialog
或 whiptail
命令,便可在终端中绘制各类窗口来制做 TUI 。在 apt 中,对 whiptail 包的描述为:
Displays user-friendly dialog boxes from shell scripts
Whiptail is a "dialog" replacement using newt instead of ncurses. It provides a method of displaying several different types of dialog boxes from shell scripts. This allows a developer of a script to interact with the user in a much friendlier manner.
whiptail 命令的选项分为两类:option、box-option 。其中,后者决定了要绘制的窗口及其内容,而前者用于调整后者的一些细节。
该命令经过退出状态码来告知脚本是哪个按钮被选择了,当选择 yes-button
或 ok-button
时,返回 0;而当选择 no-button
或 cancel-button
时,返回 1(当有错误发生或按下 ESC
时,退出状态码将是 255)。
inputbox
会将用户输入的文本输出到 STDERR 中,一样,menu
也会将用户所选项的 tag
文本输出到 STDERR 中,这使得没法经过命令替换来将用户的输入或菜单选择存储到变量中。解决这一问题的其中一个方法是,临时交换 STDERR 与 STDIN 。
下面的实例在上面文本菜单的基础上,提供了 TUI 。
#!/bin/bash esCheck() { if [ $? -ne 0 ]; then echo "exit from $1." >&2 exit $2 fi } # inputUser validUser inputUser() { local user user=`whiptail --inputbox "Input your user name please." 8 40 "w" 3>&1 1>&2 2>&3` esCheck "inputbox" 1 if [ "$user" != $1 ]; then echo "Illegal user" >&2 exit 1 fi return 0 } # inputPassword chanceTimes correctPassword inputPassword() { local password local chanceTimes=$[ $1 - 1 ] while [ $chanceTimes -ge 0 ] do password=`whiptail \ --title "password dialog" \ --passwordbox "Input your secret password." 8 60 \ 3>&1 1>&2 2>&3` esCheck "passwordbox" 1 if [ "$password" = $2 ]; then return 0 else if [ $chanceTimes = 0 ]; then echo "No chance for password inputing." >&2 exit 1 fi whiptail \ --title "remained chances: $chanceTimes" \ --msgbox "The password is wrong, try it again." 8 40 esCheck "msgbox" 1 fi chanceTimes=$[ $chanceTimes - 1 ] done } # menu :opt menu() { local opt local es opt=`whiptail \ --title "menu dialog" \ --menu "Select your chioce" \ 25 60 20 \ "e" "Exit program" \ 1 "Display memory usage" \ 2 "Display disk space" \ 3 "Display date and time" \ 3>&1 1>&2 2>&3` es=$? echo $opt return $es } inputUser $USER inputPassword 3 "123456" temp=` mktemp tmp.XXXXXX ` while : do opt=`menu` esCheck "menu" 1 case $opt in "e" ) break ;; 1 ) free -h > $temp whiptail --title "Memory usage" --textbox $temp 25 90 --scrolltext ;; 2 ) df -h > $temp whiptail --title "Disk space" --textbox $temp 25 60 --scrolltext ;; 3 ) whiptail --title "Date and time" --msgbox "`date`" 8 60 ;; * ) echo "This message shouldn't be shown!" >&2; exit 1 ;; esac done rm $temp for i in `seq 100` do sleep 0.1 echo $i done | whiptail --gauge "Please wait for the program to exit..." 8 60 0 exit 0