shell 入门 07控制脚本

有两种格式可以用来在bash shell脚本中创建函数。第一种格式采用关键字function,后跟
分配给该代码块的函数名。

function name { 
    commands 
}

name() { 
    commands 
}

函数返回值

# 要在脚本中使用函数,只需要像其他shell命令一样,在行中指定函数名就行了
# 每次引用函数名func1时,bash shell会找到func1函数的定义并执行你在那里定义的命令。
# 函数定义不一定非得是shell脚本中首先要做的事,但一定要小心。如果在函数被定义前使用函数,你会收到一条错误消息。
function func1 {
    echo "This is an example of a function"
}
func1
# 你也必须注意函数名。记住,函数名必须是唯一的,否则也会有问题。如果你重定义了函数,
# 新定义会覆盖原来函数的定义,这一切不会产生任何错误消息
function func1 {
    echo "This is an example of a function"
}

function func1 {
    echo "The second function"
}
func1

# bash shell会把函数当作一个小型脚本,运行结束时会返回一个退出状态码
# 有3种不同的方法来为函数生成退出状态码

# 1. 默认退出状态码
# 默认情况下,函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束
# 后,可以用标准变量 $? 来确定函数的退出状态码. 使用函数的默认退出状态码是很危险的
# 因为你无法知道函数中其他命令中是否成功运行
func1() {
    echo "trying to display a non-existent file"
    ls -l badfile
}
func1
echo "The exit status is: $?"

# 2. 使用 return 命令
# bash shell使用return命令来退出函数并返回特定的退出状态码。return命令允许指定一个
# 整数值来定义函数的退出状态码,从而提供了一种简单的途径来编程设定函数退出状态码
# 但当用这种方法从函数中返回值时,要小心了。记住下面两条技巧来避免问题
# * 记住,函数一结束就取返回值, 如果在用$?变量提取函数返回值之前执行了其他命令,函数的返回值就会丢失
# * 记住,退出状态码必须是0~255
function dbl {
    read -p "Enter a value: " value
    echo "doubling the value"
    return $(($value * 2))
}
dbl
echo "The new value is $?"

# 3. 使用函数输出
# 正如可以将命令的输出保存到shell变量中一样,你也可以对函数的输出采用同样的处理办
# 法。可以用这种技术来获得任何类型的函数输出,并将其保存到变量中:result='dbl'
function dbl {
    read -p "Enter a value: " value
    echo $(($value * 2))
}
result=$(dbl)
echo "The new value is $result"

# 这个例子中演示了一个不易察觉的技巧。你会注意到dbl函数实际上输出了两条消息。read
# 命令输出了一条简短的消息来向用户询问输入值。bash shell脚本非常聪明,并不将其作为STDOUT
# 输出的一部分,并且忽略掉它。如果你用echo语句生成这条消息来向用户查询,那么它会与输
# 出值一起被读进shell变量中

# 通过这种技术,你还可以返回浮点值和字符串值。这使它成为一种获取函数返回值的强大方法。

函数传参

# 向函数传递参数
# 由于 bash shell会将函数当作小型脚本来对待。这意味着你可以像普通脚本那样向函数传递参数。
# 函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。例如,函数名会在 $0
# 变量中定义,函数命令行上的任何参数都会通过$1、$2等定义。也可以用特殊变量$#来判断传给函数的参数数目。
# 在脚本中指定函数时,必须将参数和函数放在同一行
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

# 由于函数使用特殊参数环境变量作为自己的参数值,因此它无法直接获取脚本在命令行中的参数值。下面的例子将会运行失败。
# ./test.sh 2 3
function badfunc1 {
    echo $(($1 * $2))
}
if [ $# -eq 2 ]; then
    value=$(badfunc1)
    echo "The result is $value"
else
    echo "Usage: badtest1 a b"
fi

# 尽管函数也使用了$1和$2变量,但它们和脚本主体中的$1和$2变量并不相同。要在函数中使用这些值,必须在调用函数时手动将它们传过去
# ./test.sh 2 3
function badfunc1 {
    echo $(($1 * $2))
}
if [ $# -eq 2 ]; then
    value=$(badfunc1 $1 $2)
    echo "The result is $value"
else
    echo "Usage: badtest1 a b"
fi

在函数中处理变量

给shell脚本程序员带来麻烦的原因之一就是变量的作用域。作用域是变量可见的区域。函数
中定义的变量与普通变量的作用域不同。也就是说,对脚本的其他部分而言,它们是隐藏的。
函数使用两种类型的变量:

  • 全局变量
  • 局部变量

下面几节将会介绍这两种类型的变量在函数中的用法

# 1. 全局变量
# 全局变量是在shell脚本中任何地方都有效的变量。如果你在脚本的主体部分定义了一个全局
# 变量,那么可以在函数内读取它的值。类似地,如果你在函数内定义了一个全局变量,可以在脚
# 本的主体部分读取它的值。默认情况下,你在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内正常访问。
function dbl {
    value=$(($value * 2))
}
read -p "Enter a value: " value
dbl
echo "The new value is: $value"

# 但这其实很危险,尤其是如果你想在不同的shell脚本中使用函数的话。它要求你清清楚楚地
# 知道函数中具体使用了哪些变量,包括那些用来计算非返回值的变量。这里有个例子可说明事情是如何搞砸的。
function func1 {
    temp=$(($value + 5))
    result=$(($temp * 2))
}
temp=4
value=6
func1
echo "The result is $result"
if [ $temp -gt $value ]; then
    echo "temp is larger"
else
    echo "temp is smaller"
fi

# 2.  局部变量
# 无需在函数中使用全局变量,函数内部使用的任何变量都可以被声明成局部变量。要实现这
# 一点,只要在变量声明的前面加上local关键字就可以了 
# local关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量,
# 那么shell将会保持这两个变量的值是分离的。现在你就能很轻松地将函数变量和脚本变量隔离开
# 了,只共享需要共享的变量
function func1 {
    local temp=$(($value + 5))
    result=$(($temp * 2))
}
temp=4
value=6
func1
echo "The result is $result"
if [ $temp -gt $value ]; then
    echo "temp is larger"
else
    echo "temp is smaller"
fi

关于局部变量和全局变量心得


val=123
val2=456

function func {
    val=234
}

function func2 {
    local val2=789
}

# 使用命令替换执行时都不会修改全局变量
$(func)
$(func2)
echo $val           # 123
echo $val2          # 456

`func` 
`func2` 
echo $val           # 123
echo $val2          # 456

# 直接执行时会修改全局变量, 但是在加入 local 修饰后也不会修改
func
func2
echo $val           # 234
echo $val2          # 456

# 不论哪种调用方法, 函数都无法获取脚本本身参数 
# test.sh 1 2       输出
# 0
funcc () {
    echo $#
    echo $@
}
func

# 间接传入
# test.sh 1 2       输出 
# 2
# 1 2
funcc () {
    echo $#
    echo $@
}
func $@

数组变量和函数

# 1. 向函数传数组参数
# 向脚本函数传递数组变量的方法会有点不好理解。将数组变量当作单个参数传递的话,它不会起作用
# 如果你试图将该数组变量作为函数参数,函数只会取数组变量的第一个值
function testit {
    echo "The parameters are: $@"
    thisarray=$1
    echo "The received array is ${thisarray[*]}"
}

myarray=(1 2 3 4 5)
echo "The original array is: ${myarray[*]}"
testit $myarray

# 要解决这个问题,你必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使
# 用。在函数内部,可以将所有的参数重新组合成一个新的变量。下面是个具体的例子
# 括号 ($@) 将单值 $@ 重新转成了数组
function testit { 
    local newarray 
    newarray=($@)
    echo "The new array value is: ${newarray[*]}" 
} 
myarray=(1 2 3 4 5)
echo "The original array is ${myarray[*]}" 
testit ${myarray[*]}

# 从函数返回数组
# ${#var[*]} 将获取数组长度, echo ${var[*]} 将数组所有元素取出并转为了单值, 
# 但是在结果赋值中又将其转为了数组, 因此第一个输出只有一个 2, 若没有括号, 则两个输出都是 2 4 6 8 10
function addarray {
    local var=($@)
    for ((i = 0; i < ${#var[*]}; i++)); do
        var[$i]=$((${var[$i]} * 2))
    done
    echo ${var[*]}
}

arr=(1 2 3 4 5)
result=($(addarray ${arr[*]}))
echo $result
echo ${result[*]}

以下是一些测试后的心得

# 数组
arr=(1 2 3)

# arr 数组所有元素转为单值
a=${arr[*]}

echo ${a[0]}    # 1 2 3
echo ${a[1]}    # 空

# a 根据单值 $a 重组成数组
a=($a)

echo ${a[0]}    # 1
echo ${a[1]}    # 2

# 单值
b="1 2 3"

echo ${b[0]}    # 1 2 3
echo ${b[1]}    # 空

# b 转为数组
b=($b)

echo ${b[0]}    # 1
echo ${b[*]}    # 1 2 3

function fun {
    echo $#
}

# b 此时为数组, $b 等于 ${b[0]}
fun $b          # 1

# 数组所有元素转为单值并传入
fun ${b[*]}     # 3

# 将单值多项数据作为一个数据传入
fun "${b[*]}" 1 # 2

函数递归

# 5! = 1 * 2 * 3 * 4 * 5 = 120 
# 使用递归,方程可以简化成以下形式:
function factorial {
    if [ $1 -eq 1 ]; then
        echo 1
    else
        local temp=$(($1 - 1))
        local result=$(factorial $temp)
        echo $(($result * $1))
    fi
}

factorial 5

创建库

bash shell允许创建函数库文件,然后在多个脚本中引用该库文件。
这个过程的第一步是创建一个包含脚本中所需函数的公用库文件。这里有个叫作myfuncs的
库文件,它定义了3个简单的函数

function addem {
    echo $(($1 + $2))
}
function multem {
    echo $(($1 * $2))
}
function divem {
    if [ $2 -ne 0 ]; then
        echo $(($1 / $2))
    else
        echo -1
    fi
}

下一步是在用到这些函数的脚本文件中包含myfuncs库文件。从这里开始,事情就变复杂了。
问题出在shell函数的作用域上。和环境变量一样,shell函数仅在定义它的shell会话内有效。
如果你在shell命令行界面的提示符下运行myfuncs shell脚本,shell会创建一个新的shell并在其中
运行这个脚本。它会为那个新shell定义这三个函数,但当你运行另外一个要用到这些函数的脚本
时,它们是无法使用的。这同样适用于脚本。如果你尝试像普通脚本文件那样运行库文件,函数并不会出现在脚本中。

使用函数库的关键在于source命令。source命令会在当前shell上下文中执行命令,而不是
创建一个新shell。可以用source命令来在shell脚本中运行库文件脚本。这样脚本就可以使用库
中的函数了。

source命令有个快捷的别名,称作点操作符(dot operator)。要在shell脚本中运行myfuncs
库文件,只需添加下面这行: . ./myfuncs

# 这个例子假定myfuncs库文件和shell脚本位于同一目录。如果不是,你需要使用相应路径访问该文件
. ./demo.sh

result=$(addem 10 15) 
echo "The result is $result"

在命令行上使用函数

可以用脚本函数来执行一些十分复杂的操作。有时也很有必要在命令行界面的提示符下直接
使用这些函数。和在shell脚本中将脚本函数当命令使用一样,在命令行界面中你也可以这样做。这个功能很
不错,因为一旦在shell中定义了函数,你就可以在整个系统中使用它了,无需担心脚本是不是在
PATH环境变量里。重点在于让shell能够识别这些函数。有几种方法可以实现。

# 1. 在命令行上创建函数
# 定义好后就可以直接使用
# 在命令行上创建函数时要特别小心。如果你给函数起了个跟内建命令或另一个命令相同
# 的名字,函数将会覆盖原来的命令。

# 2. 在.bashrc 文件中定义函数
# 在命令行上直接定义shell函数的明显缺点是退出shell时,函数就消失了。对于复杂的函数来,这可是个麻烦事。
# 一个非常简单的方法是将函数定义在一个特定的位置,这个位置在每次启动一个新shell的时候,都会由shell重新载入。
# 最佳地点就是.bashrc文件。bash shell在每次启动时都会在主目录下查找这个文件,不管是交互式shell还是从现有shell中启动的新shell。

# 直接定义函数

# 可以直接在主目录下的.bashrc文件中定义函数。许多Linux发行版已经在.bashrc文件中定义了
# 一些东西,所以注意不要误删了。把你写的函数放在文件末尾就行了。这里有个例子。
function addem { 
 echo $[ $1 + $2 ] 
} 
# 该函数会在下次启动新bash shell时生效。随后你就能在系统上任意地方使用这个函数了

# 读取函数文件
# 只要是在shell脚本中,都可以用source命令(或者它的别名点操作符)将库文件中的函数添加到你的.bashrc脚本中
. /home/shino/libraries/myfuncs 

# 要确保库文件的路径名正确,以便bash shell能够找到该文件。下次启动shell时,库中的所有
# 函数都可在命令行界面下使用了

# 更好的是,shell还会将定义好的函数传给子shell进程,这样一来,这些函数就自动能够用
# 于该shell会话中的任何shell脚本了。你可以写个脚本,试试在不定义或使用source的情况下,
# 直接使用这些函数. 甚至都不用对库文件使用source,这些函数就可以完美地运行在shell脚本中。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342