Shell脚本编程
在ROS中我们很难避免使用Linux,为了简化一些ROS或者linux的操作,我们还要学会使用和编写Shell脚本,本教程将深入带你编写Shell脚本。如果您对 Linux和Shell 熟练掌握,请自行跳过该篇。
第一章 编程基础
Linus:Talk is cheap, show me the code
1 程序组成
程序:算法+数据结构数据:是程序的核心算法:处理数据的方式
数据结构:数据在计算机中的类型和组织方式
2 程序编程风格
过程式:以指令为中心,数据服务于指令,shell脚本程序提供了编程能力,解释执行对象式:以数据为中心,指令服务于数据,java,C#,python等
3 编程语言
计算机:运行二进制指令
编程语言:人与计算机之间交互的语言。分为两种:低级语言和高级语言低级编程语言:
机器:二进制的0和1的序列,称为机器指令。与自然语言差异太大,难懂、难写
汇编:用一些助记符号替代机器指令,称为汇编语言
如:ADD A,B 将寄存器A的数与寄存器B的数相加得到的数放到寄存器A中汇编语言写好的程序需要汇编程序转换成机器指令
汇编语言稍微好理解,即机器指令对应的助记符,助记符更接近自然语言高级编程语言:
编译:高级语言-->编译器-->机器代码文件-->执行,如:C,C++
解释:高级语言-->执行-->解释器-->机器代码,如:shell,python,php,JavaScript,perl
编译和解释型语言
4 编程逻辑处理方式
三种处理逻辑:顺序执行选择执行循环执行
第二章 shell脚本语言的基本结构
1 shell脚本的用途
2 shell脚本基本结构
shell脚本编程:是基于过程式、解释执行的语言编程语言的基本结构:
shell脚本:包含一些命令或声明,并符合一定格式的文本文件格式要求:首行shebang机制
复制 #!/bin/bash
#!/usr/bin/python
#!/usr/bin/perl
3 创建shell脚本过程
第一步:使用文本编辑器来创建文本文件第一行必须包括shell声明序列:#!
示例:
添加注释,注释以#开头第二步:加执行权限
给予执行权限,在命令行上指定脚本的绝对或相对路径
第三步:运行脚本
直接运行解释器,将脚本作为解释器程序的参数运行
4 脚本注释规范
5 第一个脚本
shell脚本范例:
复制 #!/bin/bash
echo -e "\033[1;32mStarting backup...\033[0m"
sleep 2
cp -av /etc/ /data/etc`date +%F`/
echo -e "\033[1;32mBackup is finished\033[0m"
6 脚本调试
检测脚本中的语法错误
复制 bash -n /path/to/some_script
调试执行
复制 bash -x /path/to/some_script
7 变量
7.1 变量
变量表示命名的内存空间,将数据放在内存空间中,通过变量名引用,获取数据
7.2 变量类型
变量类型:
不同的变量存放的数据不同,决定了以下
变量数据类型:
7.3 编程语言分类
静态和动态语言
静态编译语言:使用变量前,先声明变量类型,之后类型不能改变,在编译时检查,如:java,c
动态编译语言:不用事先声明,可随时改变类型,如:bash,Python
强类型和弱类型语言
强类型语言:不同类型数据操作,必须经过强制转换才同一类型才能运算,如java , c# ,python
如:以下python代码
复制 print('jubot'+ 10) 提示出错,不会自动转换类型
print('jubot'+str(10)) 结果为jubot10,需要显示转换类型
弱类型语言:语言的运行时会隐式做数据类型转换。无须指定类型,默认均为字符型;参与运算会 自动进行隐式类型转换;变量无须事先定义可直接调用
如 :bash ,php,javascript
7.4 Shell中变量命名法则
只能使用数字、字母及下划线,且不能以数字开头,注意:不支持短横线 “ - ”
统一命名规则:驼峰命名法, studentname,大驼峰StudentName 小驼峰studentName 变量名大写
7.5 变量定义和引用
变量的生效范围等标准划分变量类型
普通变量:生效范围为当前shell进程;对当前shell之外的其它shell进程,包括当前shell的子shell 进程均无效
本地变量:生效范围为当前shell进程中某代码片断,通常指函数
变量赋值:
value 可以是以下多种形式
复制 直接字串:name='root'
变量引用:name="$USER"
命令引用:name=`COMMAND` 或者 name=$(COMMAND)
弱引用和强引用
"$name " 弱引用,其中的变量引用会被替换为变量值
'$name '强引用,其中的变量引用不会被替换为变量值,而保持原字符串
显示已定义的所有变量:
删除变量:
范例:
复制 #!/bin/bash
COLOR="\033[1;31m" END="\033[0m"
echo -e "\033[1;32m-----------------Host systeminfo $END"
echo -e "HOSTNAME: $COLOR`hostname`$END"
echo -e "IPADDR: $COLOR` ifconfig eth0|grep -Eo '([0-9]{1,3}\.){3}[0-9]
{1,3}' |head -n1`$END"
echo -e "OSVERSION: $COLOR`cat /etc/redhat-release`$END" echo -e "KERNEL: $COLOR`uname -r`$END"
echo -e "CPU: $COLOR`lscpu|grep 'Model name'|tr -s ' '|cut -d : - f2`$END"
echo -e "MEMORY: $COLOR`free -h|grep Mem|tr -s ' ' : |cut -d : -f2`$END"
7.6 环境变量
变量声明和赋值:
复制 export name=VALUE
declare -x name=VALUE
变量引用:
显示所有环境变量:
复制 env
printenv
export
declare -x
删除变量:
bash内建的环境变量:
复制 PATH SHELL USER UID HOME PWD SHLVL LANG MAIL HOSTNAME HISTSIZE
_ 下划线 表示前一命令的最后一个参数
7.7 只读变量
只读变量:只能声明定义,但后续不能修改和删除
声明只读变量:
复制 readonly name
declare -r name
查看只读变量:
复制 readonly [-p]
declare -r
7.8 位置变量
位置变量:在bash shell中内置的变量, 在脚本代码中调用通过命令行传递给脚本的参数
复制 $1, $2, ... 对应第1个、第2个等参数,shift [n]换位置
$0 命令本身,包括路径
$* 传递给脚本的所有参数,全部参数合为一个字符串
$@ 传递给脚本的所有参数,每个参数为独立字符串
$# 传递给脚本的参数的个数
注意:$@ $* 只在被双引号包起来的时候才会有差异
清空所有位置变量
7.9 退出状态码变量
进程执行后,将使用变量 $? 保存状态码的相关数字,不同的值反应成功或失败,$?取值范例 0-255
复制 $?的值为0 代表成功
$?的值是1到255 代表失败
范例:
复制 ping -c1 -W1 hostdown &> /dev/null
echo $?
用户可以在脚本中使用以下命令自定义退出状态码
注意:
脚本中一旦遇到exit命令,脚本会立即终止;终止退出状态取决于exit命令后面的数字
如果未给脚本指定退出状态码,整个脚本的退出状态码取决于脚本中执行的最后一条命令的状态码
7.10 展开命令行
展开命令执行顺序
复制 把命令行分成单个命令词展开别名
展开大括号的声明({}) 展开波浪符声明(~)
命令替换$() 和 ``
再次把命令行分成命令词
展开文件通配(*、?、[abc]等等) 准备I/0重导向(<、>)
运行命令
防止扩展
加引号来防止扩展
复制 单引号(’’)防止所有扩展
双引号(”“)也可防止扩展,但是以下情况例外:$(美元符号)
变量扩展
复制 `` : 反引号,命令替换
\:反斜线,禁止单个字符扩展
!:叹号,历史命令替换
7.11 脚本安全和set
set 命令:可以用来定制shell环境
$-变量
h:hashall,打开选项后,Shell 会将命令所在的路径hash下来,避免每次都要查询。通过set +h将h选项关闭
i:interactive-comments,包含这个选项说明当前的 shell 是一个交互式的 shell。所谓的交互式shell,
在脚本中,i选项是关闭的
m:monitor,打开监控模式,就可以通过Job control来控制进程的停止、继续,后台或者前台执行等B:braceexpand,大括号扩展
H:history,H选项打开,可以展开历史列表中的命令,可以通过!感叹号来完成,例如“!!”返回上最近的一个历史命令,“!n”返回第 n 个历史命令
set命令实现脚本安全
-u 在扩展一个没有设置的变量时,显示错误信息, 等同set -o nounset
-e 如果一个命令返回一个非0退出状态值(失败)就退出, 等同set -o errexit
-o option 显示,打开或者关闭选项显示选项:set -o
打开选项:set -o 选项关闭选项:set +o 选项
-x 当执行命令时,打印命令及其参数,类似 bash -x
8 格式化输出 printf
格式
复制 printf "指定的格式" "文本1" ”文本2“…
常用格式替换符
相对应的参数中包含转义字符时,可以使用此替换符进行替换,对应的转义字符会被转 义
说明:%s 中的数字代表此替换符中的输出字符宽度,不足补空格,默认是右对齐,%-10s表示10个字符宽,- 表示左对齐
常用转义字符
实例:
复制 #%-10s 表示宽度10个字符,左对齐
[jubot@jubot ~]#printf "%-10s %-10s %-4s %s \n" 姓名 性别 年龄 体重 小明 男 20 70
小 红 女 18 50
姓名 性别 年龄 体重小明 男 20 70
小红 女 18 50
9 算术运算
bash中的算术运算:
+, -, , /, %取模(取余), * (乘方) 乘法符号有些场景中需要转义
实现算术运算:
复制 (1)let var=算术表达式
(2)var=$[算术表达式]
(3)var=$((算术表达式))
(4)var=$(expr arg1 arg2 arg3 ...)
(5)declare –i var = 数值
(6)echo ‘算术表达式’ | bc
内建的随机数生成器变量:
范例:
复制 #生成 0 - 49 之间随机数
echo $[$RANDOM%50]
#随机字体颜色
[jubot@jubot ~]#echo -e "\033[1;$[RANDOM%7+31]jubot\033[0m" jubot
增强型赋值:
复制 +=10 相当于 i=i+10
i-=j 相当于 i=i-j
++,++i 相当于 i=i+1
i--,--i 相当于 i=i-1
10 逻辑运算
复制 true, false 1, 0
与:&
1 与 1 = 1
1 与 0 = 0
0 与 1 = 0
0 与 0 = 0
或:|
1 或 1 = 1
1 或 0 = 1
0 或 1 = 1
0 或 0 = 0
非:!
! 1 = 0 ! true
! 0 = 1 ! false
异或:^
异或的两个值,相同为假,不同为真
范例:
复制 [jubot@jubot ~]#true [jubot@jubot ~]#echo $? 0
[jubot@jubot ~]#false [jubot@jubot ~]#echo $? 1
[jubot@jubot ~]#! true [jubot@jubot ~]#echo $? 1
[jubot@jubot ~]#! false [jubot@jubot ~]#echo $? 0
[jubot@jubot ~]#x=10;y=20;temp=$x;x=$y;y=$temp;echo x=$x,y=$y x=20,y=10
[jubot@jubot ~]#x=10;y=20;x=$[x^y];y=$[x^y];x=$[x^y];echo x=$x,y=$y x=20,y=10
短路运算
CMD1 短 路 与 CMD2
第一个CMD1结果为 0 (假 ),总的结果必定为0,因此不需要执行CMD2
第一个CMD1结果为 1 (真),第二个CMD2必须要参与运算,才能得到最终的结果
CMD1 短路或 CMD2
第一个CMD1结果为1 (真),总的结果必定为1,因此不需要执行CMD2
第一个CMD1结果为0 (假 ),第二个CMD2 必须要参与运算,,才能得到最终的结果
11 条件测试命令
条件测试:判断某需求是否满足,需要由测试机制来实现,专用的测试表达式需要由测试命令辅助完成测 试过程
评估布尔声明,以便用在条件性执行中
若真,则状态码变量 返回 若假, 则状态码变量? 返回1
条件测试命令
注意:EXPRESSION前后必须有空白字符
11.1 变量测试
-v VAR 变量VAR是否设置
示例:判断 NAME 变量是否定义
范例:
复制 [jubot@jubot ~]#unset x [jubot@jubot ~]#test -v x [jubot@jubot ~]#echo $?
1
[jubot@jubot ~]#x=10 [jubot@jubot ~]#test -v x [jubot@jubot ~]#echo $?
0
[jubot@jubot ~]#y="" [jubot@jubot ~]#test -v y [jubot@jubot ~]#echo $?
0
[jubot@jubot ~]#[ -v y ] [jubot@jubot ~]#echo $? 0
11.2 数值测试
复制 -gt 是否大于
-ge 是否大于等于
-eq 是否等于
-ne 是否不等于
-lt 是否小于
-le 是否小于等于
范例:
复制 [jubot@jubot ~]#i=10 [jubot@jubot ~]#j=8 [jubot@jubot ~]#[ $i -lt $j ] [jubot@jubot ~]#echo $?
1
[jubot@jubot ~]#[ $i -gt $j ] [jubot@jubot ~]#echo $?
0
[jubot@jubot ~]#[ i -gt j ]
-bash: [: i: integer expression expected
11.3 字符串测试
复制 -z "STRING" 字符串是否为空,空为真,不空为假
-n "STRING" 字符串是否不空,不空为真,空为假
= 是否等于
!= 是否不等于
> ascii码是否大于ascii码
< 是否小于
== 左侧字符串是否和右侧的PATTERN相同
注意:此表达式用于[[ ]]中,PATTERN为通配符
=~ 左侧字符串是否能够被右侧的PATTERN所匹配
注意: 此表达式用于[[ ]]中;扩展的正则表达式
范例:
复制 [jubot@jubot ~]#unset str
[jubot@jubot ~]#[ -z "$str" ]
[jubot@jubot ~]#echo $?
0
[jubot@jubot ~]#str=""
[jubot@jubot ~]#[ -z "$str" ]
[jubot@jubot ~]#echo $?
0
[jubot@jubot ~]#str=" "
[jubot@jubot ~]#[ -z "$str" ]
[jubot@jubot ~]#echo $?
1
[jubot@jubot ~]#[ -n "$str" ]
[jubot@jubot ~]#echo $?
0
[jubot@jubot ~]#unset str
[jubot@jubot ~]#[ -n "$str" ]
[jubot@jubot ~]#echo $?
[jubot@jubot ~]#FILE=test.log
[jubot@jubot ~]#[[ "$FILE" == *.log ]]
[jubot@jubot ~]#echo $?
0
[jubot@jubot ~]#FILE=test.txt
[jubot@jubot ~]#[[ "$FILE" == *.log ]]
[jubot@jubot ~]#echo $?
1
[jubot@jubot ~]#[[ "$FILE" != *.log ]]
[jubot@jubot ~]#echo $?
0
[jubot@jubot ~]#[[ "$FILE" =~ \.log$
[jubot@jubot ~]#echo $?
1
11.4 文件测试
存在性测试
复制 -a FILE: 同 -e
-e FILE: 文件存在性测试,存在为真,否则为假
-b FILE:是否存在且为块设备文件
-c FILE:是否存在且为字符设备文件
-d FILE:是否存在且为目录文件
-f FILE:是否存在且为普通文件
-h FILE 或 -L FILE:存在且为符号链接文件
-p FILE:是否存在且为命名管道文件
-S FILE:是否存在且为套接字文件
范例:
复制 [jubot@jubot ~]#[ -w /etc/shadow ]
[jubot@jubot ~]#echo $?
0
[jubot@jubot ~]#[ -x /etc/shadow ]
[jubot@jubot ~]#echo $?
1
文件属性测试
复制 -s FILE: 是否存在且非空
-t fd: fd 文件描述符是否在某终端已经打开
-N FILE:文件自从上一次被读取之后是否被修改过
-O FILE:当前有效用户是否为文件属主
-G FILE:当前有效用户是否为文件属组
FILE1 -ef FILE2: FILE1是否是FILE2的硬链接
FILE1 -nt FILE2: FILE1是否新于FILE2(mtime) FILE1 -ot FILE2: FILE1是否旧于FILE2
12 关于() 和 {}
( list ) 会开启子shell,并且list中变量赋值及内部命令执行后,将不再影响后续的环境, 帮助参看:man bash 搜索(list) { list; } 不会启子shell, 在当前shell中运行,会影响当前shell环境, 帮助参看:man bash 搜索{ list; } 范例: () 和 {}
复制 [jubot@jubot ~]#umask 0022
[jubot@jubot ~]#(umask 066;touch f1.txt)
[jubot@jubot ~]#ll f1.txt
-rw------- 1 root root 0 Dec 23 16:58 f1.txt
[jubot@jubot ~]#umask
0022
[jubot@jubot ~]#( cd /data;ls ) test.log
[jubot@jubot ~]#pwd
/root
[jubot@jubot ~]#{ cd /data;ls; } test.log
13 组合测试条件
第一种方式:
复制 [ EXPRESSION1 -a EXPRESSION2 ] 并且
[ EXPRESSION1 -o EXPRESSION2 ] 或者
[ ! EXPRESSION ] 取反
说明: -a 和 -o 需要使用测试命令进行,[[ ]] 不支持
范例:
复制 [jubot@jubot ~]#chmod -x /data/script40/test.sh
[jubot@jubot ~]#ll /data/script40/test.sh
-rw-r--r-- 1 root root 382 Dec 23 09:32 /data/script40/test.sh
[jubot@jubot ~]#[ -f $FILE -o -x $FILE ]
[jubot@jubot ~]#echo $? 0
第三章 bash的配置文件
bash shell的配置文件很多,可以分成下面类别
1 按生效范围划分两类
全局配置:
个人配置:
2 shell登录两种方式分类
2.1 交互式登录
(1)直接通过终端输入账号密码登录
(2)使用“su - UserName” 切换的用户
配置文件执行顺序:
复制 /etc/profile --> /etc/profile.d/*.sh --> ~/.bash_profile --> ~/.bashrc -->/etc/bashrc
2.2 非交互式登录
(1) su UserName
(2) 图形界面下打开的终端
(3)执行脚本
(4)任何其它的bash实例
执行顺序:
复制 /etc/profile.d/*.sh --> /etc/bashrc -->~/.bashrc
3 按功能划分分类
profile类和bashrc类
3.1 profile类
profile类为交互式登录的shell提供配置
全局:/etc/profile, /etc/profile.d/*.sh
个人:~/.bash_profile
功用:
3.2 Bashrc类
bashrc类:为非交互式和交互式登录的shell提供配置
全局:/etc/bashrc
个人:~/.bashrc 功用:
4 编辑配置文件生效
修改profile和bashrc文件后需生效两种方法:
范例:
5 Bash 退出任务
保存在~/.bash_logout文件中(用户),在退出登录shell时运行功能:
第四章 流程控制
1 条件选择
1.1 选择执行if语句
格式:
复制 if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
单分支
复制 if 判断条件;then
条件为真的分支代码
fi
双分支
复制 if 判断条件; then
条件为真的分支代码
else
条件为假的分支代码
fi
多分支
复制 if 判断条件1; then
条件1为真的分支代码
elif 判断条件2; then
条件2为真的分支代码
elif 判断条件3; then
条件3为真的分支代码
...
else以上条件都为假的分支代码
fi
说明:
多个条件时,逐个条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句
范例:
复制 #根据命令的退出状态来执行命令
if ping -c1 -W2 station1 &> /dev/null;
then echo 'station1 is UP'
elif grep -q 'station1' ~/maintenance.txt;
then echo 'station1 is undergoing maintenance'
else
echo 'station1 is unexpectedly DOWN!' exit 1
fi
1.2 条件判断case语句
格式:
复制 case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac
复制 case 变量引用 in PAT1)
分支1
;; PAT2)
分支2
;;
...
*)
默认分支
;;
esac
case支持glob风格的通配符:
复制 *: 任意长度任意字符
?: 任意单个字符
[]:指定范围内的任意单个字符
|: 或,如 a或b
2 循环
2.1 循环执行介绍
将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件重复运行次数
常见的循环的命令:for, while, until
2.2 for循环
格式1:
复制 for 变量名 in 列表;do
循环体
done
执行机制:
依次将列表中的元素赋值给“变量名”; 每次赋值后即执行一次循环体; 直到列表中的元素耗尽,循环结束
for循环列表生成方式:
直接给出列表
整数列表:
复制 {start..end}
$(seq [start [step]] end)
返回列表的命令:
使用glob,如:*.sh
变量引用,如:$@,$#
范例: 九九乘法表
复制 #!/bin/bash
for i in {1..9};do
for j in `seq $i`;do
echo -e "${j}x${i}=$[i*j]\t\c"
done
echo
done
格式2
双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使bash Shell实现C语言风格的变量操作
I=10;((I++))
复制 for ((控制变量初始化;条件判断表达式;控制变量的修正表达式)) do
循环体
done
说明:
控制变量初始化:仅在运行到循环代码段时执行一次
控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断
范例:
复制 for((sum=0,i=1;i<=100;sum+=i,i++));do
true
done
echo $sum
2.3 while循环
格式:
复制 while CONDITION; do
循环体
done
说明:
CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为“true”,则执行一次循环;直到条件测试状态为“false”终止循环,因此:CONDTION一般应该有循环控 制变量;而此变量的值会在循环体不断地被修正 进入条件:CONDITION为true 退出条件:CONDITION为false
无限循环
复制 while true; do
循环体
done
2.4 until循环
格式:
复制 until CONDITION; do
循环体
done
说明:
进入条件: CONDITION 为false
退出条件: CONDITION 为true
无限循环
复制 until false; do
循环体
Done
2.5 循环控制语句continue
continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
格式:
复制 while CONDITION1; do CMD1
...
if CONDITION2;
then continue
fi CMDn
...
done
2.5 循环控制语句 break
break [N]:提前结束第N层整个循环,最内层为第1层
格式:
复制 while CONDITION1; do CMD1
...
if CONDITION2;
then break
fi CMDn
...
done
范例:
复制 NUM=$[RANDOM%10]
while read -p "输入 0-9 之间的数字: " INPUT ;do
if [ $INPUT -eq $NUM ];then
echo "恭喜你猜对了!"
break
elif [ $INPUT -gt $NUM ];
then echo "数字太大了,重新猜!"
else
echo "数字太小了,重新猜!"
fi done
2.6 循环控制 shift 命令
shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift
复制 arguments # Usage: doit.sh [args]
while [ $You can't use 'macro parameter character #' in math mode# -gt 0 ] # or (( $# > 0 ))
do
echo $*
shift
done
./doit.sh a b c d e f g h
范例:
复制 until [ -z "$1" ]
do
echo "$1" shift
done echo
./shfit.sh a b c d e f g h
2.7 while read特殊用法
while 循环的特殊用法,遍历文件或文本的每一行
格式:
复制 while read line; do
循环体
done < /PATH/FROM/SOMEFILE
说明:依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
范例:
复制 [jubot@jubot ~]#echo jubot | read X ; echo $X
[jubot@jubot ~]#echo jubot | while read X ; do echo $X;done
jubot
[jubot@jubot ~]#echo jubot | { read X ; echo $X; }
jubot
2.8 select 循环与菜单
格式:
复制 select variable in list ;do
循环体命令
done
说明:
select 循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准错误上,并显示 PS3 提示符,等待用户输入
用户输入菜单列表中的某个数字,执行相应的命令用户输入被保存在内置变量 REPLY 中
select 是个无限循环,因此要记住用 break 命令退出循环,或用 exit 命令终止脚本。也可以按
与 for 循环类似,可以省略 in list,此时使用位置参量
第五章 函数介绍
函数function是由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部分
函数和shell程序比较相似,区别在于
Shell程序在子Shell中运行,而Shell函数在当前Shell中运行。因此在当前Shell中,函数可对shell中变量进行修改
1 管理函数
函数由两部分组成:函数名和函数体
帮助参看:help function
1.1 定义函数
复制 # 语 法 一 :
func_name (){
...函数体...
}
#语法二:
function func_name {
...函数体...
}
#语法三:
function func_name () {
...函数体...
}
1.2 查看函数
复制 #查看当前已定义的函数名declare -F
#查看当前已定义的函数定义declare -f
#查看指定当前已定义的函数名declare -f func_name
#查看当前已定义的函数名定义declare -F func_name
1.3 删除函数
格式:
2 函数调用
函数的调用方式
调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数 代码
函数的生命周期:被调用时创建,返回时终止
2.1 交互式环境调用函数
交互式环境下定义和使用函数
范例:
复制 [jubot@jubot ~]#dir() {
> ls -l
> }
[jubot@jubot ~]#dir total 4
-rw-------. 1 root root 1559 Nov 7 19:33 anaconda-ks.cfg
2.2 在脚本中定义及使用函数
函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用,调用函数仅使用其函数名即可
复制 #!/bin/bash
#name:func1 hello(){
echo "Hello there today's date is `date +%F`"
}
echo "now going to the function hello" hello
echo "back from the function"
2.3 使用函数文件
可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入shell,再进行调用函数文件名可任意选取,但最好与相关任务有某种联系,例如:functions
一旦函数文件载入shell,就可以在命令行或脚本中调用函数。可以使用delcare -f 或set 命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数
若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件
实现函数文件的过程:
在shell脚本或交互式shell中调用函数文件,格式如下
复制 . filename 或 source filename
范例:
复制 #!/bin/bash
#functions hello(){
echo Run hello Function
}
hello2(){
echo Run hello2 Function
}
3 函数返回值
函数的执行结果返回值:
函数的退出状态码:
自定义退出状态码,其格式为:
return 从函数中返回,用最后状态命令决定返回值
4 环境函数
类拟于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数定义
环境函数:
复制 export -f function_name
declare -xf function_name
查看环境函数:
5 函数参数
函数可以接受参数:
传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1 arg2 ...
在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用$@, $*, $#等特殊变量
6 函数变量
变量作用域:
普通变量:只在当前shell进程有效,为执行脚本会启动专用子shell进程;因此,本地变量的作用范围是当前shell脚本程序文件,包括脚本中的函数
本地变量:函数的生命周期;函数结束时变量被自动销毁
注意:
如果函数中定义了普通变量,且名称和局部变量相同,则使用本地变量
由于普通变量和局部变量会冲突,建议在函数中只使用本地变量
由于普通变量和局部变量会冲突,建议在函数中只使用本地变量
7 函数递归
函数递归:函数直接或间接调用自身,注意递归层数,可能会陷入死循环递归示例:
阶乘是基斯顿·卡曼于 1808 年发明的运算符号,是数学术语,一个正整数的阶乘(factorial)是所有小
于及等于该数的正整数的积,并且有0的阶乘为1,自然数n的阶乘写作n!
n!=1×2×3×...×n
阶乘亦可以递归方式定义:0!=1,n!=(n-1)!×n n!=n(n-1)(n-2)...1
n(n-1)! = n(n-1)(n-2)!
范例:
复制 #!/bin/bash #
fact() {
if [ $1 -eq 0 -o $1 -eq 1 ];
then echo 1
else
echo $[$1*$(fact $[$1-1])]
fi
}
fact $1
fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
函数实现
复制 :(){ :|:& };:
bomb() { bomb | bomb & }; bomb
脚本实现
复制 cat Bomb.sh #!/bin/bash
./$0|./$0&
第六章 其它脚本相关工具
1 信号捕捉 trap
trap '触发指令' 信号
进程收到系统发出的指定信号后,将执行自定义指令,而不会执行原操作
trap '' 信号
忽略信号的操作
trap '-' 信号
恢复原信号的操作
trap -p
列出自定义信号操作
trap finish EXIT
当脚本退出时,执行finish函数
范例:
复制 finish(){
echo finish| tee -a /root/finish.log
}
trap finish exit
while true ;do
echo running
sleep 1
done
2 创建临时文件 mktemp
mktemp 命令用于创建并显示临时文件,可避免冲突
格式:
复制 mktemp [OPTION]... [TEMPLATE]
说明:TEMPLATE: filenameXXX,X至少要出现三个常见选项:
-d 创建临时目录
-p DIR或--tmpdir=DIR 指明临时文件所存放目录位置
范例:
复制 mktemp /tmp/testXXX
tmpdir=`mktemp –d /tmp/testdirXXX`
mktemp --tmpdir=/testdir testXXXXXX
3 安装复制文件 install
install命令格式:
复制 install [OPTION]... [-T] SOURCE DEST 单文件
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY...创建空目录
选项:
范例:
复制 install -m 700 -o jubot -g admins srcfile desfile
install –m 770 –d /testdir/installdir
4 交互式转化批处理工具 expect
expect 是由Don Libes基于Tcl( Tool Command Language )语言开发的,主要应用于自动化交互式操作的场景,借助 expect 处理交互的命令,可以将交互过程如:ssh登录,ftp登录等写在一个脚本上,使之自动化完成。尤其适用于需要对多台服务器执行相同操作的环境中,可以大大提高系统管理人员的工作效率
expect 语法:
复制 expect [选项] [ -c cmds ] [ [ -[f|b] ] cmdfile ] [ args ]
常见选项:
-c:从命令行执行expect脚本,默认expect是交互地执行的
示例:
复制 expect -c 'expect "\n" {send "pressed enter\n"}'
expect -d ssh.exp
expect中相关命令
exp_continue 匹配多个字符串在执行动作后加此命令
范例:
复制 #!/usr/bin/expect
spawn scp /etc/fstab 10.0.0.7:/data expect {
"yes/no" { send "yes\n";exp_continue } "password" { send "jubot\n" }
}
expect eof
第七章 数组
1 数组介绍
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引
索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash4.0版本之后开始支持
2 声明数组
复制 #普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME
#关联数组必须先声明,再使用
declare -A ARRAY_NAME
注意:两者不可相互转换
3 数组赋值
数组元素的赋值
一次只赋值一个元素
复制 ARRAY_NAME[INDEX]=VALUE
范例:
复制 weekdays[0]="Sunday"
weekdays[4]="Thursday"
一次赋值全部元素
复制 ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
范例:
复制 title=("ceo" "coo" "cto")
num=({0..10})
alpha=({a..g})
file=( *.sh )
只赋值特定元素
复制 ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
交互式数组值对赋值
范例:
复制 [jubot@jubot ~]#declare -A course
[jubot@jubot ~]#declare -a course
-bash: declare: course: cannot convert associative to indexed array
4 显示所有数组
显示所有数组:
5 引用数组
引用数组元素
复制 ${ARRAY_NAME[INDEX]}
#如果省略[INDEX]表示引用下标为0的元素
范例:
复制 [jubot@jubot ~]#declare -a title=([0]="ceo" [1]="coo" [2]="cto")
[jubot@jubot ~]#echo ${title[1]}
coo
[jubot@jubot ~]#echo ${title} ceo
引用数组所有元素
复制 ${ARRAY_NAME[*]}
${ARRAY_NAME[@]}
范例:
复制 [jubot@jubot ~]#echo ${title[@]} ceo coo cto
[jubot@jubot ~]#echo ${title[*]} ceo coo cto
数组的长度,即数组中元素的个数
复制 ${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}
范例:
复制 [jubot@jubot ~]#echo ${#title[*]}
3
6 删除数组
删除数组中的某元素,会导致稀疏格式
复制 [jubot@jubot ~]#echo ${title[*]} ceo coo cto
[jubot@jubot ~]#unset title[1]
[jubot@jubot ~]#echo ${title[*]} ceo cto
删除整个数组
范例:
复制 [jubot@jubot ~]#unset title
[jubot@jubot ~]#echo ${title[*]}
[jubot@jubot ~]#
7 数组数据处理
数组切片:
复制 ${ARRAY[@]:offset:number} offset #要跳过的元素个数
number #要取出的元素个数
#取偏移量之后的所有元素
{ARRAY[@]:offset}
向数组中追加元素:
复制 ARRAY[${#ARRAY[*]}]=value
ARRAY[${#ARRAY[@]}]=value
范例:
复制 [jubot@jubot ~]#num[${#num[@]}]=11
[jubot@jubot ~]#echo ${#num[@]}
12
[jubot@jubot ~]#echo ${num[@]}
0 1 2 3 4 5 6 7 8 9 10 11
8 关联数组
复制 declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
注意:关联数组必须先声明再调用
范例:
复制 [jubot@jubot ~]#name[ceo]=jubot
[jubot@jubot ~]#name[cto]=bot
[jubot@jubot ~]#name[coo]=zhang
[jubot@jubot ~]#echo ${name[ceo]} zhang
[jubot@jubot ~]#echo ${name[cto]} zhang
[jubot@jubot ~]#echo ${name[coo]} zhang
[jubot@jubot ~]#echo ${name} zhang
[jubot@jubot ~]#declare -A name
-bash: declare: name: cannot convert indexed to associative array
[jubot@jubot ~]#unset name
[jubot@jubot ~]#declare -A name [jubot@jubot ~]#name[ceo]=jubot
[jubot@jubot ~]#name[cto]=bot
[jubot@jubot ~]#name[coo]=zhang [jubot@jubot ~]#echo ${name[coo]} zhang
[jubot@jubot ~]#echo ${name[ceo]} jubot
[jubot@jubot ~]#echo ${name[cto]} bot
[jubot@jubot ~]#echo ${name[*]} jubot bot zhang
范例:生成10个随机数保存于数组中,并找出其最大值和最小值
复制 #!/bin/bash
declare -i min max
declare -a nums
for ((i=0;i<10;i++));do nums[$i]=$RANDOM
[ $i -eq 0 ] && min=${nums[0]} && max=${nums[0]}&& continue
[ ${nums[$i]} -gt $max ] && max=${nums[$i]}
[ ${nums[$i]} -lt $min ] && min=${nums[$i]} done
echo "All numbers are ${nums[*]}"
echo Max is $max
echo Min is $min
第八章 字符串处理
1 字符串切片
基于偏移量取字符串
复制 #返回字符串变量var的长度
${#var}
#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分, offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
${var:offset}
#返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部 分
${var:offset:number}
#取字符串的最右侧几个字符,取字符串的最右侧几个字符, 注意:冒号后必须有一空白字符
${var: -length}
#从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容,即:掐头去尾
${var:offset:-length}
#先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容,注意:- length前空格
${var: -length:-offset}
基于模式取子串
复制 #其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符
${var#*word}:
#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容
${var##*word}:
#其中word可以是指定的任意字符,功能:自右而左,查找var变量所存储的字符串中,第一次出现的word,
#删除字符串最后一个字符向左至第一次出现word字符串(含)之间的所有字符
${var%word*}
#同上,只不过删除字符串最右侧的字符向左至最后一次出现word字符之间的所有字符
${var%%word*}
实例:
复制 [jubot@jubot ~]#file="/var/log/messages"
[jubot@jubot ~]#echo ${file%/*}
var/log
[jubot@jubot ~]#echo ${file%%/*} var
2 查找替换
复制 #查找var所表示的字符串中,第一次被pattern所匹配到的字符串,以substr替换之
${var/pattern/substr}
#查找var所表示的字符串中,所有能被pattern所匹配到的字符串,以substr替换之
${var//pattern/substr}
#查找var所表示的字符串中,行首被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}
#查找var所表示的字符串中,行尾被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}
3 查找并删除
复制 #删除var表示的字符串中第一次被pattern匹配到的字符串
${var/pattern}
删除var表示的字符串中所有被pattern匹配到的字符串
${var//pattern}
删除var表示的字符串中所有以pattern为行首匹配到的字符串
${var/#pattern}
删除var所表示的字符串中所有以pattern为行尾所匹配到的字符串
${var/%pattern}
4 字符大小写转换
复制 #把var中的所有小写字母转换为大写
${var^^}
#把var中的所有大写字母转换为小写
${var,,}
第九章 高级变量
1 高级变量赋值
范例:
复制 [jubot@jubot ~]#title=ceo
[jubot@jubot ~]#name=${title-mage}
[jubot@jubot ~]#echo $name
ceo
[jubot@jubot ~]#title=
[jubot@jubot ~]#name=${title-mage}
[jubot@jubot ~]#echo $name
2 高级变量用法-有类型变量
Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两个命令是等价的
复制 declare [选项] 变量名
-r 声明或显示只读变量
-i 将变量定义为整型数
-a 将变量定义为数组
-A 将变量定义为关联数组
-f 显示已定义的所有函数名及其内容
-F 仅显示已定义的所有函数名
-x 声明或显示环境变量和函数,相当于export
-l 声明变量为小写字母 declare –l var=UPPER
-u 声明变量为大写字母 declare –u var=lower
3 变量间接引用
3.1 eval命令
eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实现其功能的变量,该命令对变量进行两次扫描
范例:
复制 [root@server ~]# CMD=whoami
[root@server ~]# echo $CMD whoami
[root@server ~]# eval $CMD root
[root@server ~]# n=10
[root@server ~]# echo {0..$n}
{0..10}
[root@server ~]# eval echo {0..$n} 0 1 2 3 4 5 6 7 8 9 10
3.2 间接变量引用
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过variable1获得变量值value的行为
复制 variable1=variable2
variable2=value
bash Shell提供了两种格式实现间接变量引用
复制 eval tempvar=\$$variable1
tempvar=${!variable1}
范例:
复制 [jubot@jubot ~]#echo $ceo name
[jubot@jubot ~]#echo $$ceo 33722ceo
[jubot@jubot ~]#echo $BASHPID 33722
[jubot@jubot ~]#echo \$$ceo
$name