函数可以处理传递给它们的参数,并向脚本返回一个 exit status 以供进一步处理。
function_name $arg1 $arg2 |
函数通过位置引用传递的参数(就像它们是 positional parameters 一样),即:$1, $2等等。
示例 24-2. 接受参数的函数
#!/bin/bash # Functions and parameters DEFAULT=default # Default param value. func2 () { if [ -z "$1" ] # Is parameter #1 zero length? then echo "-Parameter #1 is zero length.-" # Or no parameter passed. else echo "-Parameter #1 is \"$1\".-" fi variable=${1-$DEFAULT} # What does echo "variable = $variable" #+ parameter substitution show? # --------------------------- # It distinguishes between #+ no param and a null param. if [ "$2" ] then echo "-Parameter #2 is \"$2\".-" fi return 0 } echo echo "Nothing passed." func2 # Called with no params echo echo "Zero-length parameter passed." func2 "" # Called with zero-length param echo echo "Null parameter passed." func2 "$uninitialized_param" # Called with uninitialized param echo echo "One parameter passed." func2 first # Called with one param echo echo "Two parameters passed." func2 first second # Called with two params echo echo "\"\" \"second\" passed." func2 "" second # Called with zero-length first parameter echo # and ASCII string as a second one. exit 0 |
但是,传递给脚本的命令行参数呢?函数能看到它们吗?好吧,让我们澄清一下这个困惑。
示例 24-3. 函数和传递给脚本的命令行参数
#!/bin/bash # func-cmdlinearg.sh # Call this script with a command-line argument, #+ something like $0 arg1. func () { echo "$1" # Echoes first arg passed to the function. } # Does a command-line arg qualify? echo "First call to function: no arg passed." echo "See if command-line arg is seen." func # No! Command-line arg not seen. echo "============================================================" echo echo "Second call to function: command-line arg passed explicitly." func $1 # Now it's seen! exit 0 |
与其他某些编程语言不同,shell 脚本通常只将值参数传递给函数。变量名(实际上是 指针),如果作为参数传递给函数,将被视为字符串字面量。函数按字面意义解释它们的参数。
间接变量引用(参见 示例 37-2)提供了一种笨拙的机制,用于将变量指针传递给函数。
示例 24-4. 将间接引用传递给函数
#!/bin/bash # ind-func.sh: Passing an indirect reference to a function. echo_var () { echo "$1" } message=Hello Hello=Goodbye echo_var "$message" # Hello # Now, let's pass an indirect reference to the function. echo_var "${!message}" # Goodbye echo "-------------" # What happens if we change the contents of "hello" variable? Hello="Hello, again!" echo_var "$message" # Hello echo_var "${!message}" # Hello, again! exit 0 |
下一个合乎逻辑的问题是,参数在传递给函数之后是否可以被解引用。
示例 24-5. 解引用传递给函数的参数
#!/bin/bash # dereference.sh # Dereferencing parameter passed to a function. # Script by Bruce W. Clare. dereference () { y=\$"$1" # Name of variable (not value!). echo $y # $Junk x=`eval "expr \"$y\" "` echo $1=$x eval "$1=\"Some Different Text \"" # Assign new value. } Junk="Some Text" echo $Junk "before" # Some Text before dereference Junk echo $Junk "after" # Some Different Text after exit 0 |
示例 24-6. 再次,解引用传递给函数的参数
#!/bin/bash # ref-params.sh: Dereferencing a parameter passed to a function. # (Complex Example) ITERATIONS=3 # How many times to get input. icount=1 my_read () { # Called with my_read varname, #+ outputs the previous value between brackets as the default value, #+ then asks for a new value. local local_var echo -n "Enter a value " eval 'echo -n "[$'$1'] "' # Previous value. # eval echo -n "[\$$1] " # Easier to understand, #+ but loses trailing space in user prompt. read local_var [ -n "$local_var" ] && eval $1=\$local_var # "And-list": if "local_var" then set "$1" to its value. } echo while [ "$icount" -le "$ITERATIONS" ] do my_read var echo "Entry #$icount = $var" let "icount += 1" echo done # Thanks to Stephane Chazelas for providing this instructive example. exit 0 |
函数返回一个值,称为 exit status(退出状态)。这类似于命令返回的 exit status。退出状态可以通过 return 语句显式指定,否则它就是函数中最后一个命令的退出状态(成功时为 0,失败时为非零错误代码)。这个 exit status 可以在脚本中通过引用 $? 来使用。这种机制有效地允许脚本函数拥有类似于 C 函数的 “返回值”。
终止一个函数。return 命令可以选择接受一个 integer(整数)参数 [1],该参数将作为函数的 “退出状态” 返回给调用脚本,并且此退出状态被赋值给变量 $?。
示例 24-7. 两个数字的最大值
#!/bin/bash # max.sh: Maximum of two integers. E_PARAM_ERR=250 # If less than 2 params passed to function. EQUAL=251 # Return value if both params equal. # Error values out of range of any #+ params that might be fed to the function. max2 () # Returns larger of two numbers. { # Note: numbers compared must be less than 250. if [ -z "$2" ] then return $E_PARAM_ERR fi if [ "$1" -eq "$2" ] then return $EQUAL else if [ "$1" -gt "$2" ] then return $1 else return $2 fi fi } max2 33 34 return_val=$? if [ "$return_val" -eq $E_PARAM_ERR ] then echo "Need to pass two parameters to the function." elif [ "$return_val" -eq $EQUAL ] then echo "The two numbers are equal." else echo "The larger of the two numbers is $return_val." fi exit 0 # Exercise (easy): # --------------- # Convert this to an interactive script, #+ that is, have the script ask for input (two numbers). |
![]() | 要使函数返回字符串或数组,请使用专用变量。
|
示例 24-8. 将数字转换为罗马数字
#!/bin/bash # Arabic number to Roman numeral conversion # Range: 0 - 200 # It's crude, but it works. # Extending the range and otherwise improving the script is left as an exercise. # Usage: roman number-to-convert LIMIT=200 E_ARG_ERR=65 E_OUT_OF_RANGE=66 if [ -z "$1" ] then echo "Usage: `basename $0` number-to-convert" exit $E_ARG_ERR fi num=$1 if [ "$num" -gt $LIMIT ] then echo "Out of range!" exit $E_OUT_OF_RANGE fi to_roman () # Must declare function before first call to it. { number=$1 factor=$2 rchar=$3 let "remainder = number - factor" while [ "$remainder" -ge 0 ] do echo -n $rchar let "number -= factor" let "remainder = number - factor" done return $number # Exercises: # --------- # 1) Explain how this function works. # Hint: division by successive subtraction. # 2) Extend to range of the function. # Hint: use "echo" and command-substitution capture. } to_roman $num 100 C num=$? to_roman $num 90 LXXXX num=$? to_roman $num 50 L num=$? to_roman $num 40 XL num=$? to_roman $num 10 X num=$? to_roman $num 9 IX num=$? to_roman $num 5 V num=$? to_roman $num 4 IV num=$? to_roman $num 1 I # Successive calls to conversion function! # Is this really necessary??? Can it be simplified? echo exit |
另请参见 示例 11-29。
![]() | 函数可以返回的最大正整数是 255。return 命令与 exit status 的概念紧密相关,这解释了这种特定的限制。幸运的是,对于那些需要从函数返回较大整数值的情况,有各种 解决方法。 示例 24-9. 测试函数中的大返回值
获取较大整数 “返回值” 的一种解决方法是简单地将 “返回值” 赋值给全局变量。
一种更优雅的方法是让函数 echo 其 “返回值到 stdout”,然后通过 命令替换 捕获它。请参阅 第 36.7 节中对此的 讨论。 示例 24-10. 比较两个大整数
这是另一个捕获函数 “返回值” 的示例。理解它需要一些 awk 知识。
练习使用我们刚刚学到的知识,扩展之前的 罗马数字示例 以接受任意大的输入。 |
函数本质上是一个 代码块,这意味着它的stdin可以被重定向(如 示例 3-1 中所示)。
示例 24-11. 从用户名获取真实姓名
#!/bin/bash # realname.sh # # From username, gets "real name" from /etc/passwd. ARGCOUNT=1 # Expect one arg. E_WRONGARGS=85 file=/etc/passwd pattern=$1 if [ $# -ne "$ARGCOUNT" ] then echo "Usage: `basename $0` USERNAME" exit $E_WRONGARGS fi file_excerpt () # Scan file for pattern, { #+ then print relevant portion of line. while read line # "while" does not necessarily need [ condition ] do echo "$line" | grep $1 | awk -F":" '{ print $5 }' # Have awk use ":" delimiter. done } <$file # Redirect into function's stdin. file_excerpt $pattern # Yes, this entire script could be reduced to # grep PATTERN /etc/passwd | awk -F":" '{ print $5 }' # or # awk -F: '/PATTERN/ {print $5}' # or # awk -F: '($1 == "username") { print $5 }' # real name from username # However, it might not be as instructive. exit 0 |
还有一种替代方法,可能不那么令人困惑,可以重定向函数的stdin。这涉及到将stdin重定向到函数内嵌的带括号的代码块。
# Instead of: Function () { ... } < file # Try this: Function () { { ... } < file } # Similarly, Function () # This works. { { echo $* } | tr a b } Function () # This doesn't work. { echo $* } | tr a b # A nested code block is mandatory here. # Thanks, S.C. |
![]() | Emmanuel Rouat 的 示例 bashrc 文件 包含一些具有启发性的函数示例。 |
[1] | return 命令是 Bash builtin(内建命令)。 |