一、 处理用户输入
1) 命令行参数
1.1) 读取参数
bash shell 会将一些称为位置参数的特殊变量分配给命令行输入的所有参数。位置参数变量是标准的数字:$0是程序名,$1是第一个参数,$2是第二个参数,以此类推,直 到第9个参数$9。
1.2) 读取程序名
你可以用$0参数来获取shell在命令行启动的程序的名字。但是有个小问题需要处理一下,例如执行/home/rich/test_bin.sh这个shell文件时,直接$0返回的是/home/rich/test_bin.sh这样一个完整的包含路径的程序名。有时我们可能只需要返回test_bin.sh这样一个程序名,则可以通过如下方式来获取:
programe_name=`basename $0`
1.3)测试参数
有时候,你需要测试参数是否存在。例如:
if [ -n $1 ]
then
echo "the first parameter exists"
else
echo "the first parameter doesn't exist"
fi
2) 特殊参数变量
2.1) 参数计数 $#
获取最后一个参数${!#}
2.2) 抓取所有的数据
有些情况下,你只想抓取命令行上提供的所有参数,然后遍历它们。你可以使用一组其他的特殊变量,而不用先用$#变量来判断命令行上有多少参数然后再遍历它 们。$*和$@变量提供了对所有参数的快速访问。这两个都能够在单个变量中存储所有的命令行参数。
$*变量会将命令行上提供的所有的参数当做单个单词保存。而$@会将其当做多个独立的单词。
3) 移动变量
bash shell工具链中的另一个工具是shift命令。bash shell提供了shift命令来帮助操作命令行参数。跟字面上的意思一样,shift命令会根据它们的相对位置来移动命令行参 数。在使用shift命令时,默认情况下它会将每个参数变量减1。所以$3的值会移动到$2,$2的值会移动到$1。而$1的值则会被删除(注意,$0的值是程序的名字)
例如:
另外,你也可以给shift命令提供一个参数来执行多位移动。
4) 处理选项
选项是跟在单破则号后面的单个字母,能改变命令的行为。
4.1) 查找选项
表面上看,命令行选项也没什么特殊。在命令行上,它们紧跟在脚本名之后,就跟命令行参数一样。实际上,如果愿意,你可以像处理命令行参数一样处理命令行
选项。
4.1.1) 处理简单命令行选项
4.1.2) 分离参数和选项
你经常会遇到想在shell脚本中同时使用选项和参数的情况。Linux中处理这个问题的标准方式是用特殊字符来将二者分开,该字符会告诉脚本选项何时结束以及普通参
数何时开始。对于Linux来说,这个特殊字符是双破折线(--)。shell会用双破折线来表明选项结束了。看到双破折线之后,脚本会安全地将剩下的命令当做参数来处理,而
不是选项。
4.1.3) 处理带值得选项
4.2) 使用getopt命令
getopt命令是一个在处理命令行选项和参数时非常方便的工具。它能够识别命令行参数,从而在脚本中解析它们时更方便。
4.2.1) 命令的格式
getopt options optstring parameters
其中,optstring是这个过程的关键所在。它定义了命令行有效的选项字母,还定义了哪些选项字母需要参数值。
首先,在optstring中列出你要在脚本中用到的每个命令行选项字母。然后,在每个需要参数值的选项字母后加一个冒号。getopt会基于你定义的optstring解析提供的参
数。例如:
# getopt ab:cd -a -b test1 -cd test2 test3
-a -b test1 -c -d -- test2 test3
optstring定义了4个有效选项字母,a、b、c、d。它还定义了选项字母b需要一个参数值。当getopt命令运行时,它会检查提供的参数列表,并基于提供的optstring解
析。注意它会自动将-cd选项分成两个单独的选项,并插入双破折线来分开行中的额外参数。
注意,如果你指定了一个不在optstring中的选项,默认情况下,getopt命令会产生一条错误消息。如果要忽略这条错误消息,可以在命令后加-q选项。
4.2.2) 在脚本中使用getopt
你可以在脚本中使用getopt来格式化输入给脚本的任何命令行选项。但是用起来略微复杂。方法是用getopt命令生成的格式化后的版本来替换已有的命令行选项和参数。
这个用set命令可以做到。set命令的选项之一就是双破折线,它会将命令行参数替换成set命令的命令行的值。使用方式看起来如下:
set -- `getopt -q ab:cd "$@"`
例如:
但是,上面这个程序仍存在一个小问题,例如当执行:./bash_1.sh -a -b test1 -c "test2 test3" test4时,会将"test2 test3"分开来解析。然而,我们可以用下面的方法来解决。
4.2.3) 使用更高级的getopts
每次调用它时,它只处理一个命令行上检测到的参数。处理完所有的参数后,它会退出并返回一个大于0的退出状态码。getopts命令的格式如下:
getopts optstring variable
optstring值类似于getopt命令中的那个。有效的选项字母都会列在optstring中,如果选项字母要求有个参数值,就加一个冒号。如果要去掉错误消息的话,可以
在optstring之前加一个冒号。getopts命令会将当前参数保存在命令行中定义的variable中。
getopts命令会用到两个环境变量。如果选项需要跟一个参数值,OPTARG环境变量就会保存这个值。OPTIND环境变量保存了参数列表中getopts正在处理的参数
位置。这样你就能在处理完选项之后继续处理其他命令行参数了。
例如:
5) 将选项标准化
6) 获得用户输入
6.1) 基本的读取
read命令接受从标准输入(键盘)或另外一个文件描述符的输入。在收到输入后,read命令会将数据放进一个标准变量。下面是read命令最简单的用法:
实际上,read命令包含了-p选项,允许你直接在read命令行指定提示符:
注意:read会将读取的数据分别保存到你指定的多个变量中。如: read -p "Please enter your info:" name age sex info。输入的每一个数据值都会分配给表中的下一个变
量。如果变量表在数据之前用完了,剩下的数据就会分配给最后一个变量。
另外,你可以在read命令行中不指定变量。如果那么做了,read命令会将它收到的任何数据都放进特殊环境变量REPLY中。
6.2) 带超时机制的read
使用read命令时有个危险,就是脚本很可能会等脚本用户的输入一直等下去。如果脚本必须执行下去,不管是否有数据输入,你可以用-t选项来指定一个计时器。-t选项
指定了read命令等待输入的秒数。当计时器过期以后,read命令会返回一个非零退出状态码。
例如:
此外,可以让read命令对输入的字符计数,而非对输入过程计时。当输入的字符达到预设的字符数时,它会自动退出,将输入的数据赋值给变量:
上例中,将-n选项和值1一起使用,告诉read命令在接受单个字符后退出。只要你按下单个字符回答后,read命令就会接受输入并将它传给变量,而不必按回车键。
6.3) 隐藏方式读取
有时你需要读取脚本用户的输入,但不想输入出现在屏幕上。典型的例子是输入的密码,但还有很多其他需要隐藏的数据类型。-s选项会阻止将传给read命令的数据显示在
显示器上(实际上,数据会被显示,只是read命令会将文本颜色设成跟背景色一样)。这里有个在脚本中使用-s选项的例子:
6.4) 从文件中读取
你也可以用read命令来读取Linux系统上文件里保存的数据。每次调用read命令会从文件中读取一行文本。当文件中再没有内容时,read命令会退出并返回非零退出状态码。例如:
二、呈现数据
1) 重定向数据
1.1) 只重定向错误
STDERR文件描述符被设成2。你可以选择只重定向错误消息,将该文件描述符值放在重定向符号前。该值必须紧紧的放在重定向符号前,否则不会工作。
# ls -al badfile 2> test4.log
1.2) 重定向错误和数据
# ls -al goodfile badfile 1>nomal.log 2>bad.log
2) 在脚本中重定向输出
2.1) 临时重定向
在重定向到文件描述符时,你必须在文件描述符数字之前加一个and符号(&)。例如:
# echo "This is an error message" >&2
2.2) 永久重定向
如果脚本中有大量数据需要重定向,那重定向每个echo语句就会很繁琐。取而代之,你可以用exec命令告诉shell在脚本执行期间重定向某个特定文件描述符。例如:
3) 在脚本中重定向输入
exec命令允许你将STDIN重定向到Linux系统上的文件中:
# exec 0<testfile
例如:
4) 创建自己的重定向
在脚本中重定向输入和输出时,并不局限于这3个默认的文件描述符。在shell中最多可以有9个打开的文件描述符。其他6个文件描述符会从3排到8,并且当做输入和输出
重定向都行。你可以将这些文件描述符的任意一个分配给文件,然后在脚本中使用它们。
4.1) 创建输出文件描述符
你可以用exec命令来给输出分配文件描述符。和标准的文件描述符一样,一旦你给一个文件位置分配了另外一个文件描述符,那个重定向就会一直有效,直到你重新分
配。这里有个在脚本中使用其他文件描述符的简单例子:
4.2) 重定向文件描述符
你可以分配另外一个文件描述符给标准文件描述符,反之亦然。这意味着你可以重定向STDOUT的原来位置到另外一个文件描述符,然后将该文件描述符重定向回STDOUT。可以理解为C语言中的指针概念。
如下图:
现在假如用另外一个文件描述符3保存monitor,那么我们可以将STDOUT(1)重定向到一个文件,然后再用3恢复STDOUT(1),使其在定向到monitor。
如下例子:
再看输入的例子:
4.3) 创建读写文件描述符
一般不建议这样做。但是可以采用如下的方式:
# exec 3<>testfile
4.4) 关闭文件描述符
# exec 3>&-
例如:
5) 列出打开文件 lsof
6) 阻止命令输出 (重定向到/dev/null)
7) 创建临时文件 mktemp
8) 记录消息(tee)
# date | tee testout.log
上面这条命令会同时在STDOUT与testout.log文件中显示当前的日期。
三、创建函数
1) 基本的脚本函数
1.1) 创建函数
有两种格式可以用来在bash shell脚本中创建函数。第一种格式采用关键字function,后跟分配该代码块的函数名:
function name{
commands
}
bash shell脚本中定义函数的第二种格式跟在其他编程语言中定义函数很像:
name(){
commands
}
1.2) 使用函数
要在脚本中使用函数,在行上指定函数名就行了,跟使用其他shell命令一样:
2) 返回值
bash shell会把函数当做小型脚本,运行结束时会返回一个特殊状态码。有3种不同的方法来为函数生成退出状态码。
2.1) 默认退出状态码
默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束后,你可以用标准的$?变量来决定函数的退出状态码.
2.2) 使用return命令
bash shell使用return命令来退出函数并返回特定的退出状态码。return命令允许指定一个整数值来定义函数的退出状态码,从而提供了编程设定函数退出状态码的简便途径:
请记住如下两点: 1) 函数一结束,就取返回值 2)退出状态码必须在0~255之间.
2.3) 使用函数输出
正如同可以将命令的输出保存到shell变量中一样,也可以将函数的输出保存到shell变量中,可以用这种技术来获取任何类型的输出,并将其保存到变量中。
例如:
3) 在函数中使用变量
3.1) 向函数传递参数
bash shell会将函数当做小型脚本来对待。这意味着你可以向函数传递参数,就跟普通脚本一样。
函数可以使用标准的参数环境变量来代表命令行上传给函数的参数。例如,函数名会再$0变量中定义,函数命令行上的任何参数都会通过$1,$2等定义。也可以用特殊
变量$#来判断传给函数的参数数目。
在脚本中指定函数时,必须将参数和函数放在同一行。例如:
注意:尽管在函数中也使用了$1,$2这样的环境变量,但是它却不能直接从脚本的命令行来获取参数值。你需要通过传参方式来向函数传递参数。
3.2) 在函数中处理变量
给shell脚本程序员经常带来麻烦的事是变量的作用域。作用域是什么情况下变量可见。函数中定义的变量可以跟普通变量的作用域不同,也就是说,它们可以在脚本的
其他部分隐藏起来。
函数会有两种类型的变量:
- 全局变量
- 局部变量
全局变量是在shell脚本中任何地方都有效的变量。如果你在脚本的主体部分定义了一个全局变量,那么你可以在函数内读取它的值。类似的,如果你在函数内定义了一
个全局变量,你可以在脚本的主体部分读取它的值。
默认情况下,你在脚本中定义的任何变量都是全局变量,在函数外定义的变量可以在函数内正常访问。
但是,上面这种用全局变量的方式其实很危险。因为其有可能在函数内部更改掉一个值。
3.2.2) 局部变量
不用在函数中使用全局变量,函数内部使用的任何变量都可以被声明为局部变量。要那么做,只要在变量声明的前面加上local关键字就可以了:
local temp
也可以在给变量赋值时在赋值语句中使用local关键字:
local temp=$ [ $value +5 ]
local关键字保证了该变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量,那么shell将会保持这两个变量的值是分离的。例如:
4) 数组变量和函数
4.1) 向函数传递数组参数
向脚本函数传递数组变量的方法会有些不好理解。将数组变量当做单个参数传递的话,它不会起作用。如果你试图将该数组变量当成一个函数参数,函数只会去数组变量
的第一个值。
要解决这个问题,你必须将该数组变量的值分解成单个值然后将这些值作为函数参数使用。在函数内部,你可以将所有的参数重组到新的数组变量中。例如:
再看下面一个例子:
4.2) 从函数返回数组
从函数里向shell脚本传回数组变量也用类似的方法。函数用echo语句来按正确的顺序输出单个数组值,然后脚本再将它们重新放进一个新的数组变量中:
5) 函数递归
请看如下求阶乘的例子:
6) 创建脚本函数库
这里创建脚本函数库很简单,但是在引用脚本函数库时必须用如下方法(例如我们创建了一个脚本函数库mylib.sh)
source ./mylib.sh
或者
. ./mylib.sh
之所以我们要用上面的方法,是因为当我们直接运行一个脚本时,一般会在原来的shell中再开启一个新的shell。这两个shell是独立的,因此不能直接调用。
7) 在命令行上使用函数
有两种方法。第一种是:
第二种是:
8) 在..bashrc中定义函数