任务描述
本关任务:学习掌握什么是 shell 函数的返回值。
相关知识
bash shell 会把函数当作一个小型脚本,运行结束时,会返回一个退出状态码。有 3 种不同的方法来为函数生成退出状态码。
默认退出状态码
默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束后,可以用标准变量$?
来确定函数的退出状态码。 【实例1】已知 test1.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: $?"
运行脚本 bash test1.sh
执行结果如下:
- testing the function:
- trying to display a non-existent file
- ls: cannot access badfile: No such file or directory
- The exit status is: 2
函数的退出状态码是 2,这是因为函数中的最后一条命令没有成功运行。但你无法知道函数中其他命令中是否成功运行。带着这个疑问再看下面的例子:
【实例2】已知 test2.sh 脚本内容如下:
- #!/bin/bash
- func1() {
- ls -l badfile
- echo "trying to display a non-existent file"
- }
- echo "testing the function: "
- func1
- echo "The exit status is: $?"
运行脚本 bash test1.sh
执行结果如下:
- testing the function:
- ls: cannot access badfile: No such file or directory
- trying to display a non-existent file
- The exit status is: 0
这次,由于函数最后一条语句 echo 运行成功,该函数的退出状态码就是 0,尽管其中有一条命令并没有正常运行。由此可见,使用函数的默认退出状态码,来判断函数是否正常运行是很危险的。幸运的是,有几种办法可以解决这个问题。
return 命令
bash shell 使用 return 命令退出函数,并返回特定的退出状态码。return 命令允许指定一个整数值来定义函数的退出状态码,从而提供了一种简单的途径来设定函数退出状态码。 【实例3】已知 test3.sh 脚本的内容如下:
- #!/bin/bash
- func1() {
- ls -l badfile
- echo "trying to display a non-existent file"
- return 20
- }
- echo "testing the function: "
- func1
- echo "The exit status is: $?"
执行bash test.sh
显示如下:
- testing the function:
- ls: cannot access badfile: No such file or directory
- trying to display a non-existent file
- The exit status is: 20
这一次变量$?
返回的结果是 20,而按照默认的返回 $?
应该是 0,因为函数的最后一条命令正确执行。
任务描述
本关任务:掌握函数传递参数的方法以及如何处理变量。
相关知识
向函数传递参数
我们在 shell 变量那个实训,介绍过了一些特殊变量 $0
、$1
、$#
等。在 shell 中,函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。例如,函数名会在$0
变量中定义,函数命令行上的任何参数都会通过$1
、$2
等定义。也可以用特殊变量$#
,来判断传给函数的参数数目。请参考实例 1:
【实例1】已知脚本 test.sh 的内容如下:
- function addem {
- if [ $# -eq 0 ] || [ $# -gt 2 ] # 判断传递给函数的参数个数
- then
- echo -1
- elif [ $# -eq 1 ]
- then
- echo $[ $1 + $1 ]
- else
- echo $[ $1 + $2 ]
- fi
- }
- echo -n "Adding 10 and 15: "
- value=$(addem 10 15)
- echo $value
- echo -n "Let's try adding just one number: "
- value=$(addem 10)
- echo $value
- echo -n "Now trying adding no numbers: "
- value=$(addem)
- echo $value
- echo -n "Finally, try adding three numbers: "
- value=$(addem 10 15 20)
- echo $value
输出结果如下:
- Adding 10 and 15: 25
- Let's try adding just one number: 20
- Now trying adding no numbers: -1
- Finally, try adding three numbers: -1
这段脚本的意思是:脚本中的 addem 函数首先会检查脚本传给它的参数数目。如果没有任何参数,或者参数多于两个,addem 会返回值 -1;如果只有一个参数,addem 会将参数与自身相加;如果有两个参数,addem 会将它们进行相加。
注意:由于函数使用特殊参数环境变量作为自己的参数值,因此它无法直接获取脚本在命令行中的参数值。下面的【实例2】将会运行失败。
【实例2】:已知 test2.sh 的内容如下:
- function badfunc1 {
- echo $[ $1 * $2 ]
- }
- badfunc1
我在命令行执行 bash test2.sh 10 3
,发现脚本报错:
- [root@pre-host-work02 opt]# bash a.sh 1 10
- a.sh: line 3: * : syntax error: operand expected (error token is "* ")
尽管函数 badfunc1 也使用了 $1 和 $2 变量,但它们和脚本主体中的 $1 和 $2 变量并不相同。要在函数中使用这些值,必须在调用函数时手动将它们传过去。我们把脚本改动如下:
- function badfunc1 {
- echo $[ $1 * $2 ]
- }
- badfunc1 $1 $2
通过将$1
和$2
变量传给函数,它们就能跟其他变量一样供函数使用了,我在命令行执行 bash test2.sh 10 3
,结果符合预期:
- [root@pre-host-work02 opt]# bash a.sh 10 3
- 30
函数中处理变量
函数使用两种类型的变量:
- 全局变量
- 局部变量
全局变量
全局变量是在 shell 脚本中任何地方都有效的变量。如果你在脚本的主体部分定义了一个全局变量,那么可以在函数内读取它的值。类似地,如果你在函数内定义了一个全局变量,可以在脚本的主体部分读取它的值。 默认情况下,你在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内正常访问,请看【实例3】。
【实列3】查看 test3.sh 内容,执行cat test3.sh
得到如下内容:
- #!/bin/bash
- function dbl {
- value=$[ $value * 2 ]
- }
- value=450
- dbl
- echo "The new value is: $value"
执行脚本输出结果如下:
- The new value is: 900
$value
变量在函数外定义并被赋值。当 dbl 函数被调用时,该变量及其值在函数中都依然有效。如果变量在函数内被赋予了新值,那么在脚本中引用该变量时,新值也依然有效。但这其实很危险,尤其是如果你想在不同的 shell 脚本中使用函数的话。它要求你清清楚楚地知道函数中具体使用了哪些变量,包括那些用来计算非返回值的变量。请看【实例4】。
【实例4】:
- #!/bin/bash
- function func1 {
- temp=$[ $value + 5 ]
- result=$[ $temp * 2 ]
- }
- temp=4
- value=6
- func1 # 这个时候全局变量temp=6+5及为11,result=22
- echo "The result is $result"
- if [ $temp -gt $value ]
- then
- echo "temp is larger"
- else
- echo "temp is smaller"
- fi
执行脚本输出结果如下:
- The result is 22
- temp is larger
局部变量
无需在函数中使用全局变量,函数内部使用的任何变量都可以被声明成局部变量。要实现这一点,只要在变量声明的前面加上 local 关键字就可以了,比如local temp
。或者也可以在变量赋值语句中使用 local 关键字:local temp=$[ $value + 5 ]
。 local 关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量,那么 shell 将会保持这两个变量的值是分离的。现在你就能很轻松地将函数变量和脚本变量隔离开了,只共享需要共享的变量。
【实例5】:
- #!/bin/bash
- # demonstrating the local keyword
- function func1 {
- local temp=$[ $value + 5 ]
- result=$[ $temp * 2 ]
- }
- temp=4
- value=6
- func1 #这个时候全局部变量temp=6+5,但是全局变量temp=4.
- echo "The result is $result"
- if [ $temp -gt $value ]
- then
- echo "temp is larger"
- else
- echo "temp is smaller"
- fi
执行脚本输出结果如下:
- The result is 22
- temp is smaller
现在,在 func1 函数中使用 $temp
变量时,并不会影响在脚本主体中赋给$temp
变量的值。请重点分析【实例4】和【实例5】中的不同。
编程要求
本关不进行考核,希望大家能够真正的理解:
- 向函数传递变量的方法;
- 在函数中使用局部变量和全局变量的区别。 这两点非常重要,希望大家能够在命令行中自行练习本关【实例】中代码,能够充分的理解掌握这两点内容。
任务描述
本关任务:掌握文件包含方法的使用。
相关知识
文件包含方法
在 C,C++,PHP 中都是用 include 来包含文件,Go 和 Java 使用 import 来包含(导入)包,而在 shell 中,很简单,只需要一个点.
,然后跟着文件路径及文件名,或者使用source
关键字也可以,注意文件路径可以使用绝对路径和相对路径。请看实例 1。
【实例1】已知在同一目录下,有 test1.sh 和 test2.sh 两个脚本,test1.sh 内容如下:
- #!/bin/bash
- url="www.educoder.net"
同一目录下的 test2.sh 内容如下:
- #!/bin/bash
- . ./test1.sh # 第一个.是包含符号,第二个. 表示当前目录下。
- # 这是采用相对路径包含的学法,记得中间要空格。
- echo "编程平台地址是:$url"
在命令行执行bash test2.sh
,大家想想看这个时候输出的是什么? 输出的结果如下:
- 编程平台地址是:www.educoder.net
注意:
- 变量的定义也要考虑局部变量和全局变量;
- 如实例1 所示 test1.sh 并不需要赋予执行权限;
- 注意包含文件的路径。
【实例2】同一目录下 test1.sh、test2.sh、test3.sh 内容如下:
- [root@pre-host-work02 opt]# cat test1.sh
- #!/bin/bash
- test(){
- url='www.educoder.net'
- }
- [root@pre-host-work02 opt]# cat test2.sh
- #!/bin/bash
- hello(){
- echo "i am from test3.sh"
- }
- [root@pre-host-work02 opt]# cat test3.sh
- #!/bin/bash
- ##包含多个文件时,一行智能写一个文件,分开写。
- . ./test1.sh ##包含test1.sh ./test1.sh表示当前目录下
- . ./test2.sh ##包含test2.sh ./test2.sh表示当前目录下
- echo "编程平台:$url"
- hello
在命令行执行bash test3.sh
,输出结果如下:
- [root@pre-host-work02 opt]# bash test3.sh
- 编程平台:
- i am from test3.sh
- 因为在 test1.sh 中 url 变量定义在函数内,即为局部变量,所以包含过来之后不能被调用;
- test2.sh 中定义了函数 hello,在 test3.sh 通过
.
的方式包含进来,通过函数名 hello 的调用,调用了 test2.sh 中的 hello()函数,输出“i am from test3.sh” 。
编程要求
本关任务包含了三个脚本文件,它们分别是 step4/t1.sh
、step4/t2.sh
、 step4/main.sh
,点击右侧“代码文件”即可查看,如下图所示:
- 根据要求补全
step4/t1.sh
代码。 - 根据要求补全
step4/main.sh
代码。