ShellShell教程

Shell 脚本编程(高级篇)

2018-12-03  本文已影响27人  rollingstarky

高级篇

一、处理用户输入

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 函数通过 $# 变量检查传递给它的参数的数量:

全局变量与局部变量

变量的作用域是一个很容易引起问题的点。
函数中定义的变量可以拥有区别于普通变量的作用域,即它们可以对脚本中的其他部分“不可见”。

函数使用两种类型的变量:

全局变量是在整个脚本中都保持有效的变量。默认情况下,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 脚本使用。

参考资料

Linux Command Line and Shell Scripting Bible 3rd Edition

上一篇下一篇

猜你喜欢

热点阅读