函数可以处理传递给它的参数, 并且能返回它的退出状态码给脚本, 以便后续处理.
1 function_name $arg1 $arg2 |
函数以位置来引用传递过来的参数(就好像它们是位置参数),
例如, $1
, $2
, 等等.
例子 23-2. 带参数的函数
1 #!/bin/bash 2 # 函数和参数 3 4 DEFAULT=default # 默认参数值. 5 6 func2 () { 7 if [ -z "$1" ] # 第一个参数是否长度为零? 8 then 9 echo "-Parameter #1 is zero length.-" # 或者没有参数被传递进来. 10 else 11 echo "-Param #1 is \"$1\".-" 12 fi 13 14 variable=${1-$DEFAULT} # 这里的参数替换 15 echo "variable = $variable" #+ 表示什么? 16 # --------------------------- 17 # 为了区分没有参数的情况, 18 #+ 和只有一个null参数的情况. 19 20 if [ "$2" ] 21 then 22 echo "-Parameter #2 is \"$2\".-" 23 fi 24 25 return 0 26 } 27 28 echo 29 30 echo "Nothing passed." 31 func2 # 不带参数调用 32 echo 33 34 35 echo "Zero-length parameter passed." 36 func2 "" # 使用0长度的参数进行调用 37 echo 38 39 echo "Null parameter passed." 40 func2 "$uninitialized_param" # 使用未初始化的参数进行调用 41 echo 42 43 echo "One parameter passed." 44 func2 first # 带一个参数调用 45 echo 46 47 echo "Two parameters passed." 48 func2 first second # 带两个参数调用 49 echo 50 51 echo "\"\" \"second\" passed." 52 func2 "" second # 带两个参数调用, 53 echo # 第一个参数长度为0, 第二个参数是由ASCII码组成的字符串. 54 55 exit 0 |
但是, 传给脚本的命令行参数怎么办? 在函数内部, 这些传给脚本的命令行参数也可见么? 好, 现在让我们弄清楚这个问题.
例子 23-3. 函数与传递给脚本的命令行参数
1 #!/bin/bash 2 # func-cmdlinearg.sh 3 # 调用这个脚本, 并且带一个命令行参数. 4 #+ 类似于 $0 arg1. 5 6 7 func () 8 9 { 10 echo "$1" 11 } 12 13 echo "First call to function: no arg passed." 14 echo "See if command-line arg is seen." 15 func 16 # 不行! 命令行参数不可见. 17 18 echo "============================================================" 19 echo 20 echo "Second call to function: command-line arg passed explicitly." 21 func $1 22 # 现在可见了! 23 24 exit 0 |
与别的编程语言相比, shell脚本一般只会传值给函数. 如果把变量名(事实上就是指针)作为参数传递给函数的话, 那将被解释为字面含义, 也就是被看作字符串. 函数只会以字面含义来解释函数参数.
变量的间接引用(请参考例子 34-2)提供了一种笨拙的机制, 来将变量指针传递给函数.
例子 23-4. 将一个间接引用传递给函数
1 #!/bin/bash 2 # ind-func.sh: 将一个间接引用传递给函数. 3 4 echo_var () 5 { 6 echo "$1" 7 } 8 9 message=Hello 10 Hello=Goodbye 11 12 echo_var "$message" # Hello 13 # 现在,让我们传递一个间接引用给函数. 14 echo_var "${!message}" # Goodbye 15 16 echo "-------------" 17 18 # 如果我们改变"hello"变量的值会发生什么? 19 Hello="Hello, again!" 20 echo_var "$message" # Hello 21 echo_var "${!message}" # Hello, again! 22 23 exit 0 |
接下来的一个逻辑问题就是, 将参数传递给函数之后, 参数能否被解除引用.
例子 23-5. 对一个传递给函数的参数进行解除引用的操作
1 #!/bin/bash 2 # dereference.sh 3 # 对一个传递给函数的参数进行解除引用的操作. 4 # 此脚本由Bruce W. Clare所编写. 5 6 dereference () 7 { 8 y=\$"$1" # 变量名. 9 echo $y # $Junk 10 11 x=`eval "expr \"$y\" "` 12 echo $1=$x 13 eval "$1=\"Some Different Text \"" # 赋新值. 14 } 15 16 Junk="Some Text" 17 echo $Junk "before" # Some Text before 18 19 dereference Junk 20 echo $Junk "after" # Some Different Text after 21 22 exit 0 |
例子 23-6. 再来一次, 对一个传递给函数的参数进行解除引用的操作
1 #!/bin/bash 2 # ref-params.sh: 解除传递给函数的参数引用. 3 # (复杂的例子) 4 5 ITERATIONS=3 # 取得输入的次数. 6 icount=1 7 8 my_read () { 9 # 用my_read varname这种形式来调用, 10 #+ 将之前用括号括起的值作为默认值输出, 11 #+ 然后要求输入一个新值. 12 13 local local_var 14 15 echo -n "Enter a value " 16 eval 'echo -n "[$'$1'] "' # 之前的值. 17 # eval echo -n "[\$$1] " # 更容易理解, 18 #+ 但会丢失用户在尾部输入的空格. 19 read local_var 20 [ -n "$local_var" ] && eval $1=\$local_var 21 22 # "与列表": 如果"local_var"的测试结果为true, 则把变量"$1"的值赋给它. 23 } 24 25 echo 26 27 while [ "$icount" -le "$ITERATIONS" ] 28 do 29 my_read var 30 echo "Entry #$icount = $var" 31 let "icount += 1" 32 echo 33 done 34 35 36 # 感谢Stephane Chazelas提供这个例子. 37 38 exit 0 |
函数返回一个值, 被称为退出状态码. 退出状态码可以由return命令明确指定, 也可以由函数中最后一条命令的退出状态码来指定(如果成功则返回0, 否则返回非0值). 可以在脚本中使用$?来引用退出状态码. 因为有了这种机制, 所以脚本函数也可以象C函数一样有"返回值".
终止一个函数. return命令 [1] 可选的允许带一个整型参数, 这个整数将作为函数的"退出状态码"返回给调用这个函数的脚本, 并且这个整数也被赋值给变量$?.
例子 23-7. 取两个数中的最大值
1 #!/bin/bash 2 # max.sh: 取两个整数中的最大值. 3 4 E_PARAM_ERR=-198 # 如果传给函数的参数少于2个时, 就返回这个值. 5 EQUAL=-199 # 如果两个整数相等时, 返回这个值. 6 # 任意超出范围的 7 #+ 参数值都可能传递到函数中. 8 9 max2 () # 返回两个整数中的最大值. 10 { # 注意: 参与比较的数必须小于257. 11 if [ -z "$2" ] 12 then 13 return $E_PARAM_ERR 14 fi 15 16 if [ "$1" -eq "$2" ] 17 then 18 return $EQUAL 19 else 20 if [ "$1" -gt "$2" ] 21 then 22 return $1 23 else 24 return $2 25 fi 26 fi 27 } 28 29 max2 33 34 30 return_val=$? 31 32 if [ "$return_val" -eq $E_PARAM_ERR ] 33 then 34 echo "Need to pass two parameters to the function." 35 elif [ "$return_val" -eq $EQUAL ] 36 then 37 echo "The two numbers are equal." 38 else 39 echo "The larger of the two numbers is $return_val." 40 fi 41 42 43 exit 0 44 45 # 练习(简单): 46 # ----------- 47 # 把这个脚本转化为交互式脚本, 48 #+ 也就是, 修改这个脚本, 让其要求调用者输入2个数. |
为了让函数可以返回字符串或是数组, 可以使用一个在函数外可见的专用全局变量.
|
例子 23-8. 将阿拉伯数字转化为罗马数字
1 #!/bin/bash 2 3 # 将阿拉伯数字转化为罗马数字 4 # 范围: 0 - 200 5 # 比较粗糙, 但可以正常工作. 6 7 # 扩展范围, 并且完善这个脚本, 作为练习. 8 9 # 用法: roman number-to-convert 10 11 LIMIT=200 12 E_ARG_ERR=65 13 E_OUT_OF_RANGE=66 14 15 if [ -z "$1" ] 16 then 17 echo "Usage: `basename $0` number-to-convert" 18 exit $E_ARG_ERR 19 fi 20 21 num=$1 22 if [ "$num" -gt $LIMIT ] 23 then 24 echo "Out of range!" 25 exit $E_OUT_OF_RANGE 26 fi 27 28 to_roman () # 在第一次调用函数前必须先定义它. 29 { 30 number=$1 31 factor=$2 32 rchar=$3 33 let "remainder = number - factor" 34 while [ "$remainder" -ge 0 ] 35 do 36 echo -n $rchar 37 let "number -= factor" 38 let "remainder = number - factor" 39 done 40 41 return $number 42 # 练习: 43 # ----- 44 # 解释这个函数是如何工作的. 45 # 提示: 依靠不断的除, 来分割数字. 46 } 47 48 49 to_roman $num 100 C 50 num=$? 51 to_roman $num 90 LXXXX 52 num=$? 53 to_roman $num 50 L 54 num=$? 55 to_roman $num 40 XL 56 num=$? 57 to_roman $num 10 X 58 num=$? 59 to_roman $num 9 IX 60 num=$? 61 to_roman $num 5 V 62 num=$? 63 to_roman $num 4 IV 64 num=$? 65 to_roman $num 1 I 66 67 echo 68 69 exit 0 |
也请参考例子 10-28.
函数所能返回最大的正整数是255. return命令与退出状态码的概念被紧密联系在一起, 并且退出状态码的值受此限制. 幸运的是, 如果想让函数返回大整数的话, 有好多种不同的工作区能够应付这个情况. 例子 23-9. 测试函数最大的返回值
如果你想获得大整数"返回值"的话, 其实最简单的办法就是将"要返回的值"保存到一个全局变量中.
一种更优雅的做法是在函数中使用echo命令将"返回值输出到stdout", 然后使用命令替换来捕捉此值. 请参考Section 33.7中关于这种用法的讨论. 例子 23-10. 比较两个大整数
这是另一个能够捕捉函数"返回值"的例子. 要想搞明白这个例子, 需要一些awk的知识.
也请参考例子 A-7. 练习: 使用目前我们已经学到的知识, 来扩展之前的例子将阿拉伯数字转化为罗马数字, 让它能够接受任意大的输入. |
函数本质上其实就是一个代码块, 这就意味着它的stdin可以被重定向(比如例子 3-1).
例子 23-11. 从username中取得用户的真名
1 #!/bin/bash 2 # realname.sh 3 # 4 # 依靠username, 从/etc/passwd中获得"真名". 5 6 7 ARGCOUNT=1 # 需要一个参数. 8 E_WRONGARGS=65 9 10 file=/etc/passwd 11 pattern=$1 12 13 if [ $# -ne "$ARGCOUNT" ] 14 then 15 echo "Usage: `basename $0` USERNAME" 16 exit $E_WRONGARGS 17 fi 18 19 file_excerpt () # 按照要求的模式来扫描文件, 然后打印文件相关的部分. 20 { 21 while read line # "while"并不一定非得有"[ condition ]"不可. 22 do 23 echo "$line" | grep $1 | awk -F":" '{ print $5 }' # awk用":"作为界定符. 24 done 25 } <$file # 重定向到函数的stdin. 26 27 file_excerpt $pattern 28 29 # 是的, 整个脚本其实可以被缩减为 30 # grep PATTERN /etc/passwd | awk -F":" '{ print $5 }' 31 # 或 32 # awk -F: '/PATTERN/ {print $5}' 33 # 或 34 # awk -F: '($1 == "username") { print $5 }' # 从username中获得真名. 35 # 但是, 这些起不到示例的作用. 36 37 exit 0 |
还有一个办法, 或许能够更好的理解重定向函数的stdin. 它在函数内添加了一对大括号, 并且将重定向stdin的行为放在这对添加的大括号上.
1 # 用下面的方法来代替它: 2 Function () 3 { 4 ... 5 } < file 6 7 # 试试这个: 8 Function () 9 { 10 { 11 ... 12 } < file 13 } 14 15 # 同样的, 16 17 Function () # 没问题. 18 { 19 { 20 echo $* 21 } | tr a b 22 } 23 24 Function () # 不行. 25 { 26 echo $* 27 } | tr a b # 这儿的内嵌代码块是被强制的. 28 29 30 # 感谢, S.C. |
[1] | return命令是Bash内建命令builtin. |