接上一部分shell脚本编程基础
使用read命令来接受输入
使用read来把输入值分配给一个或多个shell变量,read从标准输入中读取值,给每个单词分配一个变 量,所有剩余单词都被分配给最后一个变量,如果变量名没有指定,默认标准输入的值赋值给系统内置 变量REPLY
read [options] [name ...]
#常见选项
-p #指定要显示的提示
-s #静默输入,一般用于密码
-n N #指定输入的字符长度N
-d 'CHAR' #输入结束符
-t N #TIMEOUT为N秒
bash shell的配置文件很多,可以分成下面类别
全局配置:针对所有用户皆有效
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc ------------------------ /etc/bash.bashrc #ubuntu
个人配置:只针对特定用户有效
~/.bash_profile
~/.bashrc
交互式登录
直接通过终端输入账号密码登录
使用 su - UserName 切换的用户
配置文件生效和执行顺序:
#放在每个文件最前
/etc/profile
/etc/profile.d/*.sh
/etc/bashrc
~/ .bash_ profile
~/ .bashrc
/etc/bashrc #此文件执行两次
#放在每个文件最后
/etc/profile.d/*.sh
/etc/bashrc
/etc/profile
/etc/bashrc #此文件执行两次
~/.bashrc
~/.bash_profile
注意:文件之间的调用关系,写在同一个文件的不同位置,将影响文件的执行顺序
非交互式登录
su UserName
图形界面下打开的终端
执行脚本
任何其它的bash实例
执行顺序:
/etc/profile.d/*.sh
/etc/bashrc
~/.bashrc
按功能划分分类
内容 | Profile类 | Bashrc类 |
---|---|---|
交互式登录shell配置 | 是 | 否 |
非交互式登录shell配 置 | 是 | 是 |
全局配置文件 | /etc/profile,/etc/profile.d/*.sh | /etc/bashrc |
个人配置文件 | ~/.bash_profile | ~/.bashrc |
功能作用 | 定义环境变量,运行命令或脚本 | 定义命令别名和函数,定义本地 变量 |
修改profile和bashrc文件后需生效两种方法:
重新启动shell进程
source|. 配置文件
注意:source 会在当前shell中执行脚本,所有一般只用于执行配置文件,或在脚本中调用另一个脚本的 场景
保存在~/.bash_logout文件中(用户),在退出登录shell时运行
功能:
创建自动备份
清除临时文件
if COMMANDS; then COMMANDS; [ elif COMMANDS; then COMMANDS; ]... [ else COMMANDS; ] fi
#单分支
if 判断条件;then
条件为真的分支代码
fi
str1=def;
if [ $str1 = "abc" ];then
echo "str1 is abc";
fi
#双分支
if 判断条件; then
条件为真的分支代码
else
条件为假的分支代码
fi
str1=def;
if [ $str1 = "abc" ];then
echo "str1 is abc";
else
echo "str1 is not abc"
fi
#多分支
if 判断条件1; then
条件1为真的分支代码
elif 判断条件2; then
条件2为真的分支代码
elif 判断条件3; then
条件3为真的分支代码
...
else
以上条件都为假的分支代码
fi
str=$1;
if [ "$str" = "abc" ]; then
echo "is abc";
elif [ "$str" = "def" ]; then
echo "is def";
elif [ "$str" = "xyz" ]; then
echo "is xyz";
else
echo "not abc,not def"
fi
说明:
多个条件时,逐个条件进行判断,第一次遇为“真”条件时,执行其分支,而后结束整个if语句
if 语句可嵌套
str1=$1
str2=$2
if [ "$str1" = "abc" ];then
echo "str1 is abc";
if [ "$str2" = "def" ];then
echo "str2 is def";
fi
else
echo "str1 is not abc";
fi
case WORD in [PATTERN [| PATTERN]...) COMMANDS ;;]... esac
case 变量引用 in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
默认分支
;;
esac
#case支持glob风格的通配符
* #任意长度任意字符
? #任意单个字符
[] #指定范围内的任意单个字符
| #或者,a|b
将某代码段重复运行多次,通常有进入循环的条件和退出循环的条件
重复运行次数
循环次数事先已知
循环次数事先未知
常见的循环的命令:for, while, until
程序先进行语句判断,如真则执行循环语句,然后再进行语句判断,直至语句判断失败才跳出;
for NAME [in WORDS ... ] ; do COMMANDS; done
#方式1
for 变量名 in 列表;do
循环体
done
#方式2
for 变量名 in 列表
do
循环体
done
执行机制:
依次将列表中的元素赋值给“变量名”;每次赋值后即执行一次循环体;直到列表中的元素耗尽,循 环结束
如果省略 [in WORDS ... ] ,此时使用位置参数变量 in "$@"
for 循环列表生成方式:
直接给出列表
整数列表:如 {start..end}
返回列表的命令:如 $(COMMAND)
使用glob,如:*.sh *
变量引用,如:$@,$*,$#
双小括号方法,即((…))格式,也可以用于算术运算,双小括号方法也可以使bash Shell实现C语言风格的 变量操作:
for (( exp1; exp2; exp3 )); do COMMANDS; done
for ((控制变量初始化;条件判断表达式;控制变量的修正表达式))
do
循环体
done
示例
for((sum=0,i=1;i<=100;i++));do
let sum+=i
done
echo sum=$sum
for((sum=0,i=1;i<=100;sum+=i,i++));do
true
done
echo sum=$sum
[root@ubuntu1804 ~]#bash sum.sh
sum=5050
sum=5050
说明:
控制变量初始化:仅在运行到循环代码段时执行一次
控制变量的修正表达式:每轮循环结束会先进行控制变量修正运算,而后再做条件判断
while CONDITION; do COMMANDS; done
while CONDITION; do
循环体
done
while CONDITION
do
循环体
done
说明:
CONDITION:循环控制条件;进入循环之前,先做一次判断;每一次循环之后会再次做判断;条件为 “true”,则执行一次循环;直到条件测试状态为“false”终止循环,因此:CONDTION一般应该有循环控 制变量;而此变量的值会在循环体不断地被修正
进入条件:CONDITION为 true
退出条件:CONDITION为 false
无限循环
while true; do
循环体
done
while : ; do
循环体
done
while 循环的特殊用法,遍历文件或文本的每一行
while read line; do
循环体
done < /PATH/FROM/SOMEFILE
说明:依次读取/PATH/FROM/SOMEFILE文件中的每一行,且将行赋值给变量line
until CONDITION; do COMMANDS; done
until CONDITION; do
循环体
done
说明:
进入条件: CONDITION 为false
退出条件: CONDITION 为true
无限循环
until false; do
循环体
done
continue [N]:提前结束第N层的本轮循环,而直接进入下一轮判断;最内层为第1层
while CONDITION1; do
CMD1
...
if CONDITION2; then
continue
fi
CMDn
...
done
break [N]:提前结束第N层整个循环,最内层为第1层
while CONDITION1; do
CMD1
...
if CONDITION2; then
break
fi
CMDn
...
done
shift [n] 用于将参量列表 list 左移指定次数,缺省为左移一次。
参量列表 list 一旦被移动,最左端的那个参数就从列表中删除。while 循环遍历位置参量列表时,常用到 shift
示例:
until [ -z "$1" ]; do
echo "$@ "
shift
done
[root@ubuntu2204 ~]# bash s.sh a b c d e f g
a b c d e f g
b c d e f g
c d e f g
d e f g
e f g
f g
g
select NAME [in WORDS ... ;] do COMMANDS; done
select NAME in list ;do
循环体命令
done
说明:
select 循环主要用于创建菜单,按数字顺序排列的菜单项显示在标准输出上,并显示 PS3 提示符, 等待用户输入
用户输入菜单列表中的某个数字,执行相应的命令
用户输入菜单列表中的某个数字,会将对应的WORD值赋值给NAME变量
用户输入被保存在内置变量 REPLY 中
select 是个无限循环,因此要用 break 命令退出循环,或用 exit 命令终止脚本。也可以按 ctrl+c 退出循环
select 经常和 case 联合使用
与 for 循环类似,可以省略 in list,此时使用位置参量
函数介绍
函数 function
是由若干条shell命令组成的语句块,实现代码重用和模块化编程
它与shell程序形式上是相似的,不同的是它不是一个单独的进程,不能独立运行,而是shell程序的一部 分
函数和shell程序区别
Shell程序在子Shell中运行
函数在当前Shell中运行。因此在当前Shell中,函数可对shell中变量进行修改
函数由两部分组成:函数名和函数体
#语法一
func_name(){
...函数体...
}
#语法二
function func_name {
...函数体...
}
#语法三
function func_name(){
...函数体...
}
#查看当前已定义的函数名
declare -F
#查看当前已定义的函数定义
declare -f
#查看指定当前已定义的函数名
declare -f func_name
#查看当前已定义的函数名定义
declare -F func_name
unset func_name
函数的调用方式
可在交互式环境下定义函数
可将函数放在脚本文件中作为它的一部分
可放在只包含函数的单独文件中
调用:函数只有被调用才会执行,通过给定函数名调用函数,函数名出现的地方,会被自动替换为函数 代码
函数的生命周期:被调用时创建,返回时终止
交互式环境下定义和使用函数
#定义
[root@ubuntu2204 ~]# test_func(){
> echo "this is cli function"
> }
#调用
[root@ubuntu2204 ~]# test_func
this is cli function
#查看
[root@ubuntu2204 ~]# declare -f test_func
test_func ()
{
echo "this is cli function"
}
函数在使用前必须定义,因此应将函数定义放在脚本开始部分,直至shell首次发现它后才能使用,调用 函数仅使用其函数名即可
可以将经常使用的函数存入一个单独的函数文件,然后将函数文件载入shell,再进行调用函数 函数文件名可任意选取,但最好与相关任务有某种联系,例如:functions
一旦函数文件载入shell,就可以在命令行或脚本中调用函数。
可以使用delcare -f 或set 命令查看所有定义的函数,其输出列表包括已经载入shell的所有函数 若要改动函数,首先用unset命令从shell中删除函数。改动完毕后,再重新载入此文件
实现函数文件的过程:
创建函数文件,只存放函数的定义
在shell脚本或交互式shell中加载函数文件
调用函数
函数默认返回值是函数体中最后一条命令的退出状态码;
也可以使用return 自定义函数的返回值,在函数中使用 return,return 之后的语句将不会被执行
return 的用法:
return 语句 | 返回值 |
---|---|
return | 由return 语句的前一行命令执行结果决定 |
return 0 | 无错误返回 |
return 1-255 | 有错误返回 |
类拟于环境变量,也可以定义环境函数,使子进程也可使用父进程定义的函数
定义环境函数:
export -f function_name
declare -xf function_name
查看环境函数:
export -f
declare -xf
函数可以接受参数:
传递参数给函数:在函数名后面以空白分隔给定参数列表即可,如:testfunc arg1 arg2 ...
在函数体中当中,可使用$1, $2, ...调用这些参数;还可以使用$@, $*, $#等特殊变量
变量作用域:
变量类型 | 特点 |
---|---|
普通变量 | 只在当前shell进程中有效,函数外定义,可以在函数内修改 |
环境变量 | 当前shell和子shell有效 |
本地变量 | 作用域在函数内,函数结束会被自动销毁 |
在函数中定义本地变量
local NAME=VALUE
#示例:本地变量只作用在函数内
var1=123
var_func1(){
echo "func1 start ========================"
echo $var1
local var1=456
local var2=789
echo $var1 $var2
echo "func1 end ========================="
}
echo $var1 $var2
var_func1
echo $var1 $var2
#执行
[root@ubuntu2204 ~]# bash var2.sh
123
func1 start ========================
123
456 789
func1 end =========================
123
函数递归:
函数直接或间接调用自身,注意递归层数,可能会陷入死循环
递归特点:
函数内部自已调用自已
必须有结束函数的出口语句,防止死循环
阶乘
阶乘是基斯顿·卡曼(Christian Kramp,1760~1826)于 1808 年发明的运算符号,是数学术语。
一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1。自然数n的阶乘 写作n!
阶乘公式
n!=1×2×3×...×(n-1)×n
0!=1, n!=(n-1)!×n
用递归实现阶乘
fac(){
if [ $1 -gt 1 ];then
echo $[$1 * $(fac $[$1-1])]
else
echo 1
fi
}
fac $1
斐波拉契数列
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多·斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”
指的是这样一个数列:1、1、2、3、5、8、13、21、34、……
在数学上,斐波那契数列以如下被以递推的方法定义
F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
用递归求斐波拉契数列第N项的值
fib(){
if [ $1 -gt 1 ];then
echo $[ $(fib $[$1-1]) + $(fib $[$1-2]) ]
else
echo $1
fi
}
fib $1
逻辑炸弹
fork 炸弹是一种恶意程序,它的内部是一个不断在 fork 进程的无限循环,实质是一个简单的递归程序。 由于程序是递归的,如果没有任何限制,这会导致这个简单的程序迅速耗尽系统里面的所有资源
:(){ :|:& };:
bomb(){ bomb | bomb & };bomb
trap 命令可以捕捉信号,修改信号原来的功能,实现自定义功能
在脚本或程序的执行过程中,我们可以通过发送信号的方式,打断或终止程序的执行过程,为了避免这 种情况,我们可以使用信号捕捉,来自定义信号处理。
trap [-lp] [[arg] signal_spec ...]
#常用选项
-l #显示所有信号
-p #显示所有自定义的信号
trap 'command' signal #自定义指定信号的处理方式
trap '' signal #忽略指定的信号
trap '-' signal #恢复信号默认操作
trap func EXIT #退出时执行func
#查看所有信号
trap -l
#信号的三种表示方法
3) SIGQUIT
3 #信号ID
SIGQUIT #完整写法,大小写都支持
QUIT #简短写法,大小写都支持
mktemp 命令用于创建并显示临时文件,可避免冲突
mktemp [OPTION]... [TEMPLATE]
#常用选项
-d|--directory #创建目录
-u|--dry-run #只输出命令,不执行
-p DIR|--tmpdir[=DIR] #指明临时文件所存放目录位置
-t #将文件保存到$TMPDIR 定义的目录中,如果该变量未定义,则保存到/tmp 目录中
install 功能相当于cp,chmod,chown,chgrp ,mkdir 等相关工具的集合
install [OPTION]... [-T] SOURCE DEST
install [OPTION]... SOURCE... DIRECTORY
install [OPTION]... -t DIRECTORY SOURCE...
install [OPTION]... -d DIRECTORY...
#常用选项
-m|--mode=MODE #指定权限,默认755
-v|--verbose #显示过程
-o|--owner=OWNER #指定属主
-g|--group=GROUP #指定属组
-d|--directory DIR #指定目录,如果不存在就创建
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间,相当于多个变量的集合
数组名和索引
索引的编号从0开始,属于数值索引
索引可支持使用自定义的格式,而不仅是数值格式,即为关联索引,bash 4.0版本之后开始支持 bash的数组支持稀疏格式(索引不连续)
#普通数组可以不事先声明,直接使用
declare -a ARRAY_NAME
#关联数组必须先声明,再使用
declare -A ARRAY_NAME
注意:两者不可相互转换
一次只赋值一个元素
ARRAY_NAME[INDEX]=VALUE
#示例
[root@ubuntu2204 ~]# weekdays[0]="Sunday"
[root@ubuntu2204 ~]# weekdays[4]="Thursday"
一次赋值全部元素
ARRAY_NAME=("VAL1" "VAL2" "VAL3" ...)
#示例
[root@ubuntu2204 ~]# title=("ceo" "coo" "cto")
[root@ubuntu2204 ~]# num=({0..10})
[root@ubuntu2204 ~]# alpha=({a..g})
[root@ubuntu2204 ~]# file=( *.sh )
只赋值特定元素
ARRAY_NAME=([0]="VAL1" [3]="VAL2" ...)
#示例
[root@ubuntu2204 ~]# weekdays=([0]="Sunday" [4]="Thursday")
交互式数组值对赋值
read -a ARRAY
#示例
[root@ubuntu2204 ~]# read -a test
a b c d
declare -a
#如果省略[INDEX]表示引用下标为0的元素
${ARRAY_NAME[INDEX]}
#示例
[root@ubuntu2204 ~]# declare -a title=([0]="ceo" [1]="coo" [2]="cto")
[root@ubuntu2204 ~]# echo ${title[1]}
coo
[root@ubuntu2204 ~]# echo ${title}
ceo
[root@ubuntu2204 ~]# echo ${title[2]}
cto
[root@ubuntu2204 ~]# echo ${title[3]}
[root@rocky86 ~]
#区分这三种写法
[root@ubuntu2204 ~]# echo $title
ceo
[root@ubuntu2204 ~]# echo ${title[0]}
ceo
[root@ubuntu2204 ~]# echo $title[0]
ceo[0]
引用数组所有元素
${ARRAY_NAME[*]}
${ARRAY_NAME[@]}
数组的长度,即数组中元素的个数(多加一个#号)
${#ARRAY_NAME[*]}
${#ARRAY_NAME[@]}
数组的所有下标(多一个!号)
${!ARRAY_NAME[*]}
${!ARRAY_NAME[@]}
删除数组中的某元素,会导致稀疏格式
unset ARRAY[INDEX]
#删除整个数组
unset ARRAY
数组切片
${ARRAY[@]:offset:number}
${ARRAY[*]:offset:number}
offset #要跳过的元素个数
number #要取出的元素个数
#取偏移量之后的所有元素
{ARRAY[@]:offset}
{ARRAY[*]:offset}
${ARRAY[*]::number} #取数组中最前面的 number 个元素
向数组中追加元素
ARRAY[${#ARRAY[*]}]=value
ARRAY[${#ARRAY[@]}]=value
关联数组与普通数组区别:
关联数组要先声明,才能使用,普通数组可以不用声明
关联数组可以自定义下标,普通数组必须用数字
declare -A ARRAY_NAME
ARRAY_NAME=([idx_name1]='val1' [idx_name2]='val2‘...)
${#var} #返回字符串变量var的字符的长度,一个汉字算一个字符
${var:offset} #返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,到最后的部分,offset的取值在0 到 ${#var}-1 之间(bash4.2后,允许为负值)
${var:offset:number} #返回字符串变量var中从第offset个字符后(不包括第offset个字符)的字符开始,长度为number的部分
${var: -length} #取字符串的最右侧几个字符, 注意:冒号后必须有一空白字符
${var:offset:-length} #从最左侧跳过offset字符,一直向右取到距离最右侧lengh个字符之前的内容,即:掐头去尾
${var: -length:-offset} #先从最右侧向左取到length个字符开始,再向右取到距离最右侧offset个字符之间的内容,注意:-length前空格,并且length必须大于offset
#其中word可以是指定的任意字符,自左而右,查找var变量所存储的字符串中,第一次出现的word, 删除字符串开头至第一次出现word字符串(含)之间的所有字符,即懒惰模式,以第一个word为界删左留右
${var#*word}
#从var变量的值中删除以word开头的部分
${var#word}
#同上,贪婪模式,不同的是,删除的是字符串开头至最后一次由word指定的字符之间的所有内容,即贪婪模式,以最后一个word为界删左留右
${var##*word}
${var##word}
#查找var所表示的字符串中,第一次 被pattern所匹配到的字符串,以substr替换之,懒惰模式
${var/pattern/substr}
#查找var所表示的字符串中,所有 能被pattern所匹配到的字符串,以substr替换之,贪婪模式
${var//pattern/substr}
#查找var所表示的字符串中,行首 被pattern所匹配到的字符串,以substr替换之
${var/#pattern/substr}
#查找var所表示的字符串中,行尾 被pattern所匹配到的字符串,以substr替换之
${var/%pattern/substr}
#删除var表示的字符串中第一次 被pattern匹配到的字符串,懒惰模式
${var/pattern}
#删除var表示的字符串中所有 被pattern匹配到的字符串,贪婪模式
${var//pattern}
#删除var表示的字符串中所有以pattern为行首 匹配到的字符串
${var/#pattern}
#删除var所表示的字符串中所有以pattern为行尾 所匹配到的字符串
${var/%pattern}
#把var中的所有小写字母转换为大写
${var^^}
#把var中的所有大写字母转换为小写
${var,,}
#示例
[root@ubuntu2204 ~]# str=abcd1234ABCD12345
#所有小写转大写
[root@ubuntu2204 ~]# echo ${str^^}
ABCD1234ABCD12345
#所有大写转小写
[root@ubuntu2204 ~]# echo ${str,,}
abcd1234abcd12345
#tr实现大小写转换
[root@ubuntu2204 ~]# echo $str | tr 'a-z' 'A-Z'
ABCD1234ABCD12345
#扩展以所有prefix开头的变量
${!prefix*}
${!prefix@}
$str 为变量名,expr 为具体字符串 这些组合可以省掉一些 if,else 的判断代码
变量配置方式 | str没有配置 | str为空字符串 | str己配置为非空字符串 |
---|---|---|---|
var=${str-expr} | var=expr | var= | var=$str |
var=${str:-expr} | var=expr | var=expr | var=$str |
var=${str+expr} | var= | var=expr | var=expr |
var=${str:+expr} | var= | var= | var=expr |
var=${str=expr} | str=expr; var=expr | str不变; var= | str不变; var=$str |
var=${str:=expr} | str=expr; var=expr | str=expr; var=expr | str不变; var=$str |
var=${str?expr} | expr 输出至 stderr | var= | var=$str |
var=${str:?expr} | expr 输出至 stderr | expr输出至stderr | var=$str |
Shell变量一般是无类型的,但是bash Shell提供了declare和typeset两个命令用于指定变量的类型,两 个命令是等价的
declare [-aAfFgilnrtux] [-p] [name[=value] ...]
#选项:
-f #显示已定义的所有函数名及其内容
-F #仅显示已定义的所有函数名
-p #显示每个变量的属性和值
-a #声明或显示定义为数组的变量
-A #将变量定义为关联数组
-i #声明或显示定义为整型的变量
-l #声明或显示定义为小写的变量
-n #变量引用另外一个变量的值
-r #声明或显示只读变量
-t #声明或显示具有trace(追踪)属性的变量
-u #声明或显示定义为大写的变量
-x #显示环境变量和函数,相当于export
eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。该命令适用于那些一次扫描无法实 现其功能的变量,该命令对变量进行两次扫描
示例
[root@ubuntu2204 ~]# CMD=whoami
[root@ubuntu2204 ~]# echo $CMD
whoami
[root@ubuntu2204 ~]# eval $CMD
root
[root@ubuntu2204 ~]# n1=6
[root@ubuntu2204 ~]# echo {1..$n1}
{1..6}
#两次展开
[root@ubuntu2204 ~]# eval echo {1..$n1}
1 2 3 4 5 6
如果第一个变量的值是第二个变量的名字,从第一个变量引用第二个变量的值就称为间接变量引用 variable1的值是variable2,而variable2又是变量名,variable2的值为value,间接变量引用是指通过 variable1获得变量值value的行为
variable1=variable2
variable2=value
#示例
[root@ubuntu2204 ~]# var1=str
[root@ubuntu2204 ~]# str=abcd
[root@ubuntu2204 ~]# echo $var1
str
[root@ubuntu2204 ~]# echo \$$var1
$str
[root@ubuntu2204 ~]# eval echo \$$var1
abcd
bash Shell提供了两种格式实现间接变量引用
#方法1
#变量赋值
eval tempvar=\$$variable1
#显示值
eval echo \$$variable1
eval echo '$'$variable1
echo $tmpvar
#方法2
#变量赋值
tempvar=${!variable1}
#显示值
echo ${!variable1}
#示例
[root@ubuntu2204 ~]# ceo=name
[root@ubuntu2204 ~]# name=123
[root@ubuntu2204 ~]# eval echo \$$ceo
123