Shellscripting:函数
这是我的 Shellscripting 系列文章的第三部分,你可以在这里查看之前的文章:
对于函数,我们可以说它们是 shellscript 中的 shellscript。
我们使用函数的主要原因之一是遵循 DRY 原则,这意味着我们应该只编写一次函数,然后就可以多次使用它。这有时可以大大缩短脚本的长度,而且由于我们只需编辑和排除单个函数故障,因此维护起来也更加容易。
功能:
- 隐藏主脚本的实现细节,简化 Shell 脚本的主体代码
- 如果底层细节发生变化,则可以替换
- 可以作为较大脚本的一小部分反复测试,并通过改变输入值来证明代码是正确的
- 允许在脚本内部甚至多个 shell 脚本之间一致地重用代码。
退出状态
在深入研究函数之前,我们必须知道在 Shell 中执行的每个命令都会返回一个 0 到 255 范围内的退出状态。
实际的成功状态是0
,其他状态均为错误状态的代码。
这些代码可用于在脚本中抛出和检查错误。通常,我们可以通过查看错误代码的文档或查看源代码来了解各种退出状态的含义。
我们可以用它$?
来检查先前执行的命令的退出状态。
$ ls dir/
$ echo $?
如果dir
存在echo $?
则返回0
状态码,否则返回2
目录未找到的错误代码。
我们可以使用命令明确定义返回代码exit
:
#! bin/bash
HOST="google.com"
ping -c 1 $HOST
if ["$?" -ne "0"]
then
echo "$HOST unreachable"
exit 1
fi
exit 0
只需exit
在脚本中使用该命令,并在其后跟上 0 到 255 范围内的整数即可。如果我们未在命令中指定返回码,则 shell 脚本中先前执行的命令的退出状态将用作退出状态。即使我们完全不包含命令,exit
也是如此。exit
无论何时到达该
exit
命令,shellscript 将停止运行。
所有函数都有退出状态。我们可以在函数内部使用return
关键字显式返回退出状态:
function myFunc() {
return 1 # returning exit status code 1
}
此外,可以使用函数中执行的最后一个命令的退出状态隐式返回状态。
位置参数
位置参数是我们可以用于指定通过命令行传递给函数的参数的变量。例如,如果我们像这样执行脚本:
script.sh param1 param2 param3
在该脚本中,我们可以访问所有命令行参数,如下所示:
$0: "script.sh" # $0 is always the name of the script
$1: "param1" # $1 is the first parameter,
$2: "param2" # $2 is the second
$3: "param3" # $3 is the third, and so on...
注意:您不能更改这些变量的值
一个实际的例子:
# args.sh
#!/bin/bash
echo "I was called as $0"
echo "My first argument is: $1"
echo "My second argument is: $2"
echo "I was called with $# parameters."
$ ./args.sh one two
I was called as ./args.sh
My first argument is: one
My second argument is: two
I was called with 2 parameters.
我们可以用来$#
检查调用了多少个参数脚本,这是一个检查用户是否使用足够数量的参数执行脚本的好方法,例如:
$ cat argCheck.sh
#!/bin/bash
if [ "$#" -eq "2" ]; then
echo "The script was called with exactly two parameters. Let’s continue."
else
echo "You provided $# parameters, but 2 are required."
$@
当我们想要循环脚本参数时,我们可以使用变量:
# This will loop through all parameter passed to the script when executed
for USER in "$@"; do
passwd -l $USER # lock the account
done
创建函数
让我们回到函数。
需要注意的是,函数必须在调用之前定义,通常情况下,函数定义在文件开头,尽管这并非绝对必要。
定义为函数的代码块可以通过三种不同的方式声明,具体取决于所使用的 Shell。标准的 Bourne Shell 语法是:函数名后紧跟着一对圆括号()
和花括号,{}
将代码括起来:
# The most common way
myFunc() {
# Code
echo "Hello"
}
# function keyword is optional
# so this is also correct
function mySecondFunc () {
# More code
echo "World"
}
还有第三种语法,Bourne Shell 不接受这种语法,但 bash 和 ksh 都接受它。函数名后不再跟一对括号,而是在函数名前面加上关键字 function:function myFunc
据我目前了解,第一种(不带关键字function
)是最常用的,因为它被所有 shell 接受。第二种语法也经常使用,通过使用function
关键字,它更清晰地声明它是一个函数。至于第三种,我找不到任何相关信息,只知道它确实存在 :|
调用函数
我们只需在脚本中输入其名称即可调用并执行该函数(首先定义它之后):
function hello() {
echo "Hello World!"
}
hello
function hello() {
echo "Hello ${1}!"
}
hello World # Output: Hello World
函数也可以调用其他函数。这里我们可以看到,函数在声明之前就hello
调用了另一个函数,但这没关系,因为函数在被调用之前就被读入脚本,所以按照执行顺序,函数先被定义,然后再被使用。now
now
hello
function hello() {
now
}
function now() {
echo "$(date +%r)"
}
hello # Output: 02:15:36 PM
但是,例如,这样的事情是行不通的:
function hello() {
now
}
hello # hello is called before now is defined
function now() {
echo "$(date +%r)"
}
除了调用其他函数之外,shell 函数还可以递归调用自身。一个简单的例子就是数学中的阶乘函数。
$ cat factorial.sh
#!/bin/bash
function factorial() {
if [ "$1" -gt "1" ]; then
previous=`expr $1 - 1`
parent=`factorial $previous`
result=`expr $1 \* $parent`
echo $result
else
echo 1
fi
}
factorial $1
$ ./factorial.sh 6 720
但是,使用递归函数时应该非常小心,特别是当您在其中创建文件时,最终打开的文件数量可能会超过系统允许的数量。
函数也有positional parameters
,并且$@
也可以用来检索所有传递的参数的列表。
# $0 is the script itself, not the function name
function helloFunc(){
echo "Hello ${1} ${2}!"
}
helloFunc Shell World
# Output: Hello Shell World!
函数也可以访问所有全局变量。但是,需要提醒的是,最佳做法是仅
local
在函数内部使用变量,以避免副作用,因为副作用最终可能会导致错误。
关于 ShellScript 中的函数还有很多内容要讲,所以这篇文章可以作为它们在脚本中的用法的简单介绍。下一篇文章,我会稍微介绍一下通配符、字符类以及 ShellScript 的日志记录和调试。
感谢阅读!