- 函数的基本含义
- 函数的定义和使用
- 数组
- 字符串处理
- 特殊的处理变量用法
一、函数基本含义:
函数:多条Shell命令组成的语句块,实现代码重用、模块化编程
-
函数与Shell程序的区别:
- 函数不能独立运行,Shell程序可以独立运行
- 函数只能在Shell程序中运行,Shell程序可以在其他Shell程序建立的子Shell中运行
- 函数可以修改Shell中的变量,子Shell程序无法修改父Shell程序的变量
使用函数前必须对其定义,定义中需要阐明函数名和函数体
-
函数的定义:函数的名称建议有一定含义,增加可读性
函数定义的语法格式共有3种,效果相同- 格式1:
function f_name {
函数体
......
} - 格式2:
function f_name () {
函数体
......
} - 格式3:
f_name() {
函数体
......
}
- 格式1:
函数的使用:给出函数名,则函数名位置自动被替换为函数代码
-
函数的定义和使用方式:
- 可以在交互式环境中直接定义函数
- 可以在脚本中定义和使用函数
- 可以把函数的定义编写在一个脚本文件中
-
函数的返回值:
- 函数体内echo语句输出值
- 函数体内调用命令的输出结果
-
函数的退出状态码
- 默认状态:退出状态码取决于函数体执行的最后一条命令的退出状态码
- 自定义退出状态码 return命令:
- return,默认状态的退出状态码:return前最后执行命令的退出状态码
- return NUM:以指定的数字NUM(0-255)作为退出状态码,一般0指代成功,1-255可以自定义各类错误
二、函数的定义与使用详解:
(一)函数的定义:
- 交互式环境定义
f_name () { //第一行输至{回车
>函数体语句 //>是提示符,后面输入函数体语句,回车换行继续输入
>函数体语句
> ......
>} //输入}后回车定义本函数完毕
在脚本中定义函数
shell是解释性语言,运行时从上至下执行语句,故函数的定义语句必须在其被调用之前函数定义文件
使用函数文件中定义的函数前,必须先执行函数定义文件,此时函数的定义将载入当前shell环境中,语法:. filename
或者source filename
(二)函数的使用
-
只需给出函数名即可调用函数
- set:查询当前所有定义的函数,这些函数都已经载入所在shell
语法:set f_name
, f_name:函数名称 - unset:撤销函数的定义,被撤销函数的定义将从shell中卸载
语法:unset f_name
,f_name:函数名称
- set:查询当前所有定义的函数,这些函数都已经载入所在shell
环境函数:在子进程中使用父进程定义的函数
语法:
声明环境函数:export -f f_name
,f_name函数名称
查看环境函数:export -f
或者declare -xf
-
函数的参数:函数可以接受参数,从而扩展函数的使用功能
- 传递参数给函数:在调用函数时,在函数名后直接附加以空格分隔的给定参数
语法:f_name arg1 arg2 arg3...
,f_name:函数名称;arg1,arg2,arg3:参数 - 函数体内调用参数,使用位置变量$1, $2, $3以及一些特殊变量:$*, $@, $#
- 传递参数给函数:在调用函数时,在函数名后直接附加以空格分隔的给定参数
-
函数变量的作用域:
- 环境变量:在父进程、子进程均有效的变量,使用
export var_name
声明 - 本地变量:仅在本进程范围内有效的变量,函数内对本地变量的修改将影响到函数外
- 局部变量:仅在函数的生命周期内有效,函数调用结束则自动消失,故函数内对局部变量的修改对函数外无影响,使用
local var_name
声明 - 当本地与局部变量名称相同时,从函数的声明周期角度分析,函数体内部使用局部变量运算,函数体外使用本地变量运算,两个变量相互隔离
- 环境变量:在父进程、子进程均有效的变量,使用
-
函数的递归调用:函数调用自身
- 函数的递回调用一般需要有结束条件,使子层函数的结果最终能够返回至上层函数中
- 不同的语言的最大递归层数不同
- fork炸弹:不断fork进程的无限循环,最终耗尽系统资源
- 函数实现
:(){ :|:& };:
,:冒号是函数名称,本函数与下面的函数写法相同
bomb() { bomb | bomb & }; bomb
,函数名为bomb - 脚本实现
#! /bin/bash ./$0|./$0&
- 函数实现
-
实验:
汉诺塔问题:三根柱子,在一根柱子上从下往上按照大小顺序摞着N片盘子。要求把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,在小圆盘上不能放大圆盘,在三根柱子之间一次只能移动一个圆盘,利用函数,实现N片盘的汉诺塔的移动步骤分析:
想要把N个盘子从一个柱子移动到另一个柱子,可以分为三个步骤:(1)把N-1个盘子从柱子A移动到柱子B;(2)把第N个盘子从柱子A移动到柱子C;(3)把N-1个盘子从柱子B移动到柱子C。而步骤1和步骤3都能够再分解为如上的三个步骤,所以汉诺塔问题是一个递归问题。通过递归函数,可以实现输出汉诺塔的移动步骤。代码:
read -p "please type the layer of hannuota: " num
from="A"
via="B"
to="C"
#第2步的函数实现
move () {
echo "plate:$1 $2 --> $3"
}
#实现递归函数
hannuota () {
# echo num:$1 from:$2 via:$3 to:$4
if [ $1 -eq 1 ]; then
move $1 $2 $4
else
#递归过程具体实现
hannuota $[ $1-1 ] $2 $4 $3
move $1 $2 $4
hannuota $[ $1-1 ] $3 $2 $4
fi
}
#调用函数
hannuota $num $from $via $to
执行结果如下:
三、数组:
(一)数组的定义:
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组的索引:从0开始编号,数字索引方式
数组的关联索引:自定义索引,使用字母、数字等符号作为索引,从bash 4.0开始支持
稀疏数组:数组的索引编号不连续,可以创建时不连续,也可能因数组的增删操作而变得不连续
(二)数组的语法介绍:
声明数组:普通数组和关联数组一经声明不可互相转换
普通数组:declare -a ARRAY_NAME
关联数组:declare -A ARRAY_NAME,关联数组必须先声明才能被调用数组元素的赋值:
(1)给单个元素赋值:
ARRAY_NAME[#]=VALUE
(2)一次将数组元素赋值:
ARRAY_NAME=("VAL1" "VAL2" "VAL3" "VAL4" ...)
(3)给特定的数组元素赋值:
ARRAY_NAME=([0]="VAL1" [2]="VAL2" ...)
(4)交互式赋值:
read -a ARRAY_NAME显示所有数组:
declare -a
(三)数组数据的处理:
引用数组元素:${ARRAY_NAME[#]}
注明:${ARRAY_NAME}相当于${ARRAY_NAME[0]}引用数组所有元素:
${ARRAY_NAME[*]} 或者 ${ARRAY_NAME[@]}数组的长度(数组元素的个数)
${#ARRAY_NAME[*]} 或者 ${#ARRAY_NAME[@]}添加数组元素至数组尾部
ARRAY_NAME[${#ARRAY_NAME[*]}]=VALUE删除数组元素
unset ARRAY_NAME[#]删除数组
unset ARRAY_NAME数组切片
${ARRAY_NAME[@]:offset:number}
offset:跳过元素的个数
number:取出元素的个数实验:如下图所示,实现转置矩阵matrix.sh
1 2 3 1 4 7
4 5 6 ===> 2 5 8
7 8 9 3 6 9
分析发现需要交换的数字其特点在于,两个数字所在位置的行号与列号正好相反。如数字2位于行1列2,而数字4位于行2列1。由此,可以分别采用两次循环操作行号和列号,并交换符合要求的数字。为了防止已经交换的数字再次交换,实际上需要虚幻的数字位于数字1至数字9所连接成的对角线上方数字即可。
代码如下:
#要求用户输入想要N*N的矩阵
read -p "please type the number of matrix: " matrix_num
#从0行0列开始数组编号,所以先对用户输入值自减1
let matrix_num--
num=1
#定义数组
declare -a matrix
#从1开始为矩阵赋值
for i in `seq 0 $matrix_num`; do
for j in `seq 0 $matrix_num`; do
matrix[$i$j]=$num
let num++
done
done
#定义函数,实现矩阵打印
func() {
for p in `seq 0 $matrix_num`; do
for q in `seq 0 $matrix_num`; do
echo -ne "${matrix[$p$q]}\t"
done
echo
done
}
#调用函数,打印矩阵
func
echo
# 将矩阵转置
for x in `seq 0 $matrix_num`; do
for y in `seq $x $matrix_num`; do
if ! [ $x -eq $y ]; then
temp=${matrix[$x$y]}
matrix[$x$y]=${matrix[$y$x]}
matrix[$y$x]=$temp
fi
done
done
#再次调用func函数,打印矩阵
func
执行结果如下:
四、字符串处理:
(一)字符串切片:
${#var}:返回字符串变量的长度
${var:offset}:返回字符串从第offset个(不含第offset个)到最后字符的部分
${var:offset:num}:返回字符串从第offset个(不含第offset个)共num个字符长度的部分
${var: -length}:返回字符串最右侧的length个字符,冒号后有一个空格
${var:offset:-length}:返回字符串从第offset个(不含第offset个)到距最右侧共length个字符的部分
${var: -length:-offset}:从最右侧向左取length个字符开始,至距最右侧offset个字符之间的字符,-length前有一个空格
(二)基于模式取子字符串:
${var#*word}:删除从字符串开始至第一次出现word之间的所有字符
${var##*word}:贪婪模式,删除从字符串开始至最后一次出现word之间的所有字符
${var%word*}:删除从最右侧开始至第一次出现word之间的所有字符
${var%%word*}:贪婪模式,删除从最右侧开始至最后一次出现word之间的所有字符
(三)查找替换
${var/pattern/substr}:将查找到的第一次匹配pattern的字符串替换为substr
${var//pattern/substr}:将查找到的所有匹配pattern的字符串替换为substr
${var/#pattern/substr}:将行首匹配pattern的字符串替换为substr
${var/%pattern/substr}:将行尾匹配pattern的字符串替换为substr
(四)查找删除
${var/pattern}:将查找到的第一次匹配pattern的字符串删除
${var//pattern}:将查找到的所有匹配pattern的字符串删除
${var/#pattern}:将行首匹配pattern的字符串删除
${var/%pattern}:将行尾匹配pattern的字符串删除
(五)大小写转换
${var^^}:字符串全部转换为大写
${var,,}:字符串全部转换为小写
五、特殊的处理变量用法:
(一)声明为有类型变量:declare 和 typeset,两者等价
语法:
declare [选项] 变量名
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示此脚本前定义过的所有函数名及其内容
-F 仅显示此脚本前定义过的所有函数名
-x 声明或显示环境变量和函数
-l 声明变量为小写字母
-u 声明变量为大写字母
(二)eval命令:
功能:先扫描一次命令行执行全部替换,再执行命令
适用于一次扫描不能实现功能的变量,eval命令可以实现两次扫描
(三)间接变量引用:
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用
实现方法:
eval tempvar=$$variable1
tempvar=${!variable1}
(四)创建临时文件 mktemp命令:
功能:创建并显示临时文件,可避免冲突
语法:
mktemp [OPTION]... [TEMPLATE]
TEMPLATE: filename.XXXX,至少要出现三个
OPTION:
-d: 创建临时目录
-p DIR或--tmpdir=DIR:指明临时文件所存放目录位置
(五)安装复制文件 install命令:
可以实现文件复制,所有者、所属组修改,权限修改在一条命令中执行
语法:
install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY...创建空目录选项:
-m MODE,默认755
-o OWNER
-g GROUP
(六)expect命令:
功能:实现自动化交互式操作场景
expect 语法:
expect [选项] [ -c cmds] [ [ -[f|b] ] cmdfile] [ args ]选项
-c:从命令行执行expect脚本,expect默认交互式执行
-d:可以输出调试信息expect中相关命令
spawn:启动新的进程
send:用于向进程发送字符串
expect:从进程接收字符串
interact:允许用户交互
exp_continue:匹配多个字符串在执行动作后加此命令-
expect最常用的语法(tcl语言:模式-动作)
单一分支模式语法:
expect “hi” {send “You said hi\n"}
匹配到hi后,会输出“you said hi”,并换行多分支模式语法:
expect "hi" { send "You said hi\n" }
"hehe" { send “Hehe yourself\n" }
"bye" { send “Good bye\n" }
匹配hi,hello,bye任意字符串时,执行相应输出。等同如下:
expect {
"hi" { send "You said hi\n"}
"hehe" { send "Hehe yourself\n"}
"bye" { send “Good bye\n"}
}