Shell 脚本编程(高级篇)
高级篇
一、处理用户输入
1. 读取脚本参数
Bash Shell 将命令行中传递给脚本的参数赋值给一组特殊的变量,叫做位置变量(positional parameters)。位置变量用 $number
的形式表示。
如 $0
表示脚本文件的名称,$1
表示脚本收到的第一个参数,$2
表示第二个参数,以此类推,直到 $9
表示第九个参数。
从第十个参数起,使用 ${number}
的形式。即第十个参数表示为 ${10}
。
示例程序:
$ cat add.sh
#!/bin/bash
total=$[ $1 + $2 ]
echo "The first parameter is $1"
echo "The second parameter is $2"
echo "The total value is $total"
$ ./add.sh 2 5
The first parameter is 2
The second parameter is 5
The total value is 7
2. 参数检查
在 Shell 脚本中使用命令行参数时,一般需要先对传入的参数进行检查。如脚本执行时没有接收到预想的参数,往往会在执行过程中报出错误。如:
$ ./add.sh 2
./add.sh: line 3: 2 + : syntax error: operand expected (error token is " ")
The first parameter is 2
The second parameter is
The total value is
所以参数检查是写脚本时很有必要的步骤:
$ cat check_parameter.sh
#!/bin/bash
if [ -n "$1" ]
then
echo Hello $1, glad to meet you.
else
echo "Sorry, you did not identify youself"
fi
$ ./check_parameter.sh starky
Hello starky, glad to meet you.
$ ./check_parameter.sh
Sorry, you did not identify youself
3. 特殊参数变量
参数计数
上面有提到,应该在使用命令行参数前先检查其是否符合要求。对于接收多个参数的脚本,有时候需要获取命令行中输入的参数数量。
Shell 脚本中的 $#
变量保存了该脚本执行时接收到的参数的数量。如:
$ cat count_parameters.sh
#!/bin/bash
echo There were $# parameters supplied.
$ ./count_parameters.sh
There were 0 parameters supplied.
$ ./count_parameters.sh test
There were 1 parameters supplied.
$ ./count_parameters.sh test test
There were 2 parameters supplied.
通过 $#
变量的使用,可以将之前的 add.sh
脚本优化为以下形式:
$ cat add2.sh
#!/bin/bash
if [ $# -ne 2 ]
then
echo Usage: add2.sh a b
else
total=$[ $1 + $2 ]
echo The total is $total
fi
$ ./add2.sh
Usage: add2.sh a b
$ ./add2.sh 2
Usage: add2.sh a b
$ ./add2.sh 2 4
The total is 6
PS: 虽然
$#
变量表示脚本接收到的参数的数量,但是脚本接收到的最后一个参数并不能使用${$#}
表示,而是用${!#}
获取所有参数
某些情况下需要获取命令行提供的所有参数。除了通过 $#
变量使用循环,还可以直接使用另外两个特殊的变量。
$*
和 $@
变量都可以包含命令行输入的所有参数。
其中 $*
变量接收所有参数并将它们保存在一个单一的字符串中。
$@
变量接收所有参数并将它们保存在分开的字符串中。
即 $*
变量将收到的所有参数作为整体的一个参数对待,而 $@
变量将收到的所有参数作为不同的多个对象,可以使用 for
命令进行遍历。
这两个变量的区别可以通过以下脚本来区分:
$ cat iterate_parameters.sh
#!/bin/bash
count=1
for param in "$*"
do
echo "\$* Parameter $count = $param"
count=$[ $count + 1 ]
done
echo
count=1
for param in "$@"
do
echo "\$@ Parameter $count = $param"
count=$[ count + 1 ]
done
$ ./iterate_parameters.sh rich barbara katie jessica
$* Parameter 1 = rich barbara katie jessica
$@ Parameter 1 = rich
$@ Parameter 2 = barbara
$@ Parameter 3 = katie
$@ Parameter 4 = jessica
4. shift 命令
shift
命令可以用来操作命令行参数,对它们进行整体的移位。
默认情况下,shift
命令会将所有的命令行参数整体的向左移动一个位置。
即 $3
变量的值移动到 $2
,$2
变量的值移动到 $1
。如:
$ cat shift.sh
#!/bin/bash
echo "The original parameters: $*"
shift 2
echo "Here's the new parameters: $*"
$ ./shift.sh 1 2 3 4 5
The original parameters: 1 2 3 4 5
Here's the new parameters: 3 4 5
二、函数
1. 函数定义
Bash Shell 脚本中的函数可以用以下两种格式定义:
function name {
commands
}
或
name() {
commands
}
具体示例如下:
$ cat func1.sh
#!/bin/bash
function func1 {
echo "This is an example of a function"
}
count=1
while [ $count -le 5 ]
do
func1
count=$[ $count + 1 ]
done
echo "This is the end of the loop"
func1
echo "Now this is the end of the script"
$ ./func1.sh
This is an example of a function
This is an example of a function
This is an example of a function
This is an example of a function
This is an example of a function
This is the end of the loop
This is an example of a function
Now this is the end of the script
2. 函数返回值
退出状态
默认情况下,某个函数的完成状态(即退出码)即是该函数中最后一条命令的退出码。
在该函数执行后,可以使用 $?
变量获取其退出状态码。
$ cat exit_status.sh
#!/bin/bash
func1() {
echo "trying to display a non-existent file"
ls -l badfile
}
echo "testing the function: "
func1
echo "The exit status is: $?"
$ ./exit_status.sh
testing the function:
trying to display a non-existent file
ls: badfile: No such file or directory
The exit status is: 1
因为函数 func1
中的最后一条命令 ls -l badfile
没有执行成功,所以函数执行完后,变量 $?
的值为 1 而不是 0
return 命令
Bash Shell 可以使用 return
命令指定函数退出时的状态值(整数)。
$ cat return.sh
#!/bin/bash
function db1 {
read -p "Enter a value: " value
echo "doubling the value"
return $[ $value *2 ]
}
db1
echo "The new value is $?"
$ ./return.sh
Enter a value: 32
doubling the value
The new value is 64
PS: 注意函数执行结束后需要立即使用 $?
获取其返回值,前面不能隔有其他命令;
使用 return
指定的退出码必须介于 0 到 255 之间
3. 函数输出
就像可以把命令的输出内容赋值给 Shell 变量一样,函数的输出同样也可以通过 variable=$(function_name)
的形式赋值给某个变量。
$ cat func2.sh
#!/bin/bash
function db1 {
read -p "Enter a value: " value
echo $[ $value * 2 ]
}
result=$(db1)
echo "The new value is $result"
$ ./func2.sh
Enter a value: 32
The new value is 64
4. 函数中的变量
向函数传递参数
Bash Shell 对待函数就像对待普通的脚本文件一样。我们可以向脚本程序传递参数,也可以以类似的方式向函数传递参数。
函数可以使用标准参数(如 $#
)环境变量代表它从命令语句里接收到的参数。
如函数本身的名称由 $0
定义,$1
代表第一个参数,$#
代表接收到的参数的数目,$*
表示函数接收到的所有参数 ($1 $2 ...)
,"$@"
表示函数接收到的所有参数,且每一个参数都被双引号包裹 ("$1" "$2" ...)
$ cat parameter.sh
#!/bin/bash
function addem {
if [ $# -eq 0 ] || [ $# -gt 2 ]
then
echo -1
elif [ $# -eq 1 ]
then
echo $[ $1 + $1 ]
else
echo $[ $1 + $2 ]
fi
}
echo "Adding 10 and 15:"
value=$(addem 10 15)
echo $value
echo "Try adding just one number:"
value=$(addem 10)
echo $value
echo "Now trying adding no numbers:"
value=$(addem)
echo $value
echo "Finally, try adding three numbers:"
value=$(addem 10 15 20)
echo $value
$ ./parameter.sh
Adding 10 and 15:
25
Try adding just one number:
20
Now trying adding no numbers:
-1
Finally, try adding three numbers:
-1
上述脚本中的 addem
函数通过 $#
变量检查传递给它的参数的数量:
- 如果参数数量等于 0 或大于 2(
[ $# -eq 0 ] || [ $# -gt 2 ]
),则返回 -1 表示程序非正常退出。 - 如果参数数量等于 1(
[ $# -eq 1 ]
),则返回两倍于该参数的值($[ $1 + $1 ]
)。 - 如果参数数量等于 2(
[ $# -eq 2 ]
),则返回这两个参数的加和($[ $1 + $2 ]
)
全局变量与局部变量
变量的作用域是一个很容易引起问题的点。
函数中定义的变量可以拥有区别于普通变量的作用域,即它们可以对脚本中的其他部分“不可见”。
函数使用两种类型的变量:
- 全局变量
- 局部变量
全局变量是在整个脚本中都保持有效的变量。默认情况下,Shell 脚本中的任何变量都是全局变量。
$ cat global.sh
#!/bin/bash
function db1 {
value=$[ $value * 2 ]
}
read -p "Enter a value: " value
db1
echo "The new value is: $value"
$ ./global.sh
Enter a value: 24
The new value is: 48
区别于函数中的全局变量,任何只在函数内部生效的变量可以声明为局部变量,只需要在变量的声明前面加上 local
关键字即可。
$ cat local.sh
#!/bin/bash
function func1 {
local temp=$[ $value + 5 ]
result=$[ $temp * 2 ]
}
temp=4
value=6
func1
echo "The result is $result"
echo "The temp value is $temp"
$ ./local.sh
The result is 22
The temp value is 4
由于使用了 local
关健字指定函数内部的 $temp
变量为局部变量,所以函数 func1
中 $temp
变量值的变化(变为 11)并不影响函数外部 $temp
变量的值(仍为 4)。
5. 函数递归
这里用一个计算阶乘的示例简单说明下 Shell 脚本中的函数递归。
$ cat factorial.sh
#!/bin/bash
function factorial {
if [ $1 -eq 1 ]
then
echo 1
else
local temp=$[ $1 - 1 ]
local result=$(factorial $temp)
echo $[ $result * $1 ]
fi
}
read -p "Enter value: " value
result=$(factorial $value)
echo "The factorial of $value is: $result"
$ ./factorial.sh
Enter value: 4
The factorial of 4 is: 24
6. 库
函数的使用可以减少脚本中的重复代码。即可以在脚本的其他部分直接使用函数名调用函数,完成该函数定义的功能,而无需再重新输入一遍定义该函数的大段语句。
这种形式的代码复用可以扩展到多个脚本文件。Bash Shell 允许用户创建库文件,其他 Shell 脚本可以通过引用该库文件使用其中定义的函数。
示例如下:
$ cat myfuncs
function addem {
echo $[ $1 + $2 ]
}
function multem {
echo $[ $1 * $2 ]
}
function divem {
if [ $2 -ne 0 ]
then
echo $[ $1 / $2 ]
else
echo -1
fi
}
$ cat use_myfuncs.sh
. ./myfuncs
value1=10
value2=5
result1=$(addem $value1 $value2)
result2=$(multem $value1 $value2)
result3=$(divem $value1 $value2)
echo "The result of adding them is: $result1"
echo "The result of multiplying them is: $result2"
echo "The result of dividing them is: $result3"
$ ./use_myfuncs.sh
The result of adding them is: 15
The result of multiplying them is: 50
The result of dividing them is: 2
其中 myfuncs
库文件中分别定义了 addem
multem
divem
三个函数,use_myfuncs.sh
脚本用来引用 myfuncs
库并使用其中定义的函数(这两个文件都需要有执行权限)。
重点在于 use_myfuncs.sh
脚本中的第一行命令 . ./myfuncs
。其中第一个 .
是 source
命令的缩写。
source
命令的作用是在当前语境下调用另一个脚本,而不是创建一个新的 Shell 会话。这样 myfuncs
中的函数就可以直接被 use_myfuncs.sh
脚本使用。