一、执行方式
1、 ./script.sh [参数] 需要添加x权限
2 、sh script.sh [参数] 不需要x权限,script.sh只是作为一个参数传入,类似python/lua的执行方式
这两种方式都是在当前shell中创建一个shell子进程来执行脚本,不继承当前shell的非export变量,在脚本中对环境变量的改变也不会反馈到当前shell中
3、source script.sh [参数] 不需要x权限
4、. script.sh [参数] 不需要x权限
这两种方式都是在当前shell中执行脚本,如果希望配置立即生效就用这种方式执行
在脚本中调用另一个脚本,source和.类似于C/C++的include,在当前脚本中可以直接使用被调用脚本的变量和函数
# aaa.sh
#!/bin/bash
#
TESTVAR=/usr/bin
function myprint()
{
echo 'This is a sub-script'
}
# bbb.sh
#!/bin/bash
#
#source aaa.sh
. ./aaa.sh
echo $TESTVAR
myprint
root@localhost:~/workspace # sh bbb.sh
/usr/bin
This is a sub-script
二、命令行参数
1、$0表示脚本文件名,包含路径
2、$1~$n表示传递给脚本的参数
/home/scrip.sh param1 param2
# $0 $1 $2
3、$#表示传递给脚本的参数个数,不包含$0,上例中$#为2
4、$@和$*都表示传递给脚本的所有参数,上例中$@为param1 param2
5、$$表示当前shell进程的ID
6、$?获取上一个命令或函数的退出状态,执行成功返回0,失败返回1
7、$- 当前shell的选项参数集合,可以通过set命令设置,比如i表示是否交互式
关于shell各选项的含义,请参考博客https://kodango.com/explain-shell-default-options
8、BASH_SOURCE,是一个数组,第一个元素表示当前脚本名称,包含路径,BASH_SOURCE[0] 等价于BASH_SOURCE
这个在source执行脚本或者在脚本中调用另一个脚本很有用,因为此时的$0为父脚本名称
root@localhost:~/workspace # cat test1.sh
#!/bin/bash
echo $0
echo ${BASH_SOURCE[0]}
root@localhost:~/workspace # source test1.sh
-bash
test1.sh
root@localhost:~/workspace # sh test1.sh
test1.sh
test1.sh
root@localhost:~/workspace # cat test2.sh
#!/bin/bash
. ./test1.sh #在test2.sh中执行脚本test1.sh
root@localhost:~/workspace # sh test2.sh
test2.sh
./test1.sh
9、FUNCNAME,是一个数组,函数名堆栈https://blog.csdn.net/qq_41586875/article/details/120989012
三、通配符
* | 0到任意个任意字符 |
? | 一个任意字符 |
[] | 在括号中的一个任意字符 |
[^] | 除了括号中的任意字符 |
四、字符串
Shell脚本中的字符串遵循以下规则:
1、若字符串不包含空白字符,则可以不加引号
PATH=/usr/bin
2、若字符串包含空白字符,则要用单引号或双引号括起来
USER='VBird Tsai'
USER="VBird Tsai"
3、字符串包含单引号,则用双引号括起来;反之字符串包含双引号,则用单引号括起来
content="That's all right"
content='The interpreter is "bash"'
4、一般情况下单引号和双引号表示的字符串没有区别,但是如果字符串中包含特殊字符,
则用双引号括起来仍保持原来的语义
var="lang is $LANG"
echo $var
#输出结果为:lang is zh_CN.UTF-8,$LANG进行了变量置换
用单引号括起来不具有原来的语义,只作为普通字符
var='lang is $LANG'
echo $var
#输出结果为lang is $LANG,$LANG作为普通字符输出
5、字符串中使用$(命令)或者反引号``,会先执行命令
kernel="The kernel name is $(uname -s)"
echo $kernel
#输出结果为The kernel name is Linux
#二者比较
# $(命令)嵌套比较直观,但不能保证所有shell都支持
# `命令`嵌套要用\转义,所有shell都支持
这里特别注意:
$()和``只会获取stdout的输出结果,而不会获取stderr的输出结果,请看例子
root@192:~ # result=`ip link show dev wlan0` #由于虚拟机没有无线网卡,命令执行出错
Device "wlan0" does not exist. #所以这里是stderr的输出结果
root@192:~ # echo $result #result的结果为空!
root@192:~ #
#想要把stderr的输出结果赋值给变量,正确写法如下
root@192:~ # result=`ip link show dev wlan0 2>&1` #2>&1表示把stderr重定向到stdout
root@192:~ # echo $result #result输出正确
Device "wlan0" does not exist.
root@192:~ #
同理,使用管道的grep命令也是如此,不会获取stderr的匹配结果
root@192:~ # result=`ip link show dev wlan0 | grep 'does not exist'`
Device "wlan0" does not exist. #这是stderr的输出,所以result为空!
root@192:~ # echo $result
root@192:~ #
#正确写法
root@192:~ # result=`ip link show dev wlan0 2>&1 | grep 'does not exist'`
root@192:~ # echo $result #result输出正确
Device "wlan0" does not exist.
root@192:~ #
#网上看到一位网友说Bash 4有一种快捷语法command1 2>&1 | command2,即command1 |& command2
#这应该只是语法的快捷方式,实际还是将stderr重定向到stdout
root@192:~ # result=`ip link show dev wlan0 |& grep "does not exist"`
root@192:~ # echo $result
Device "wlan0" does not exist.
root@192:~ #
五、变量
1、自定义变量
定义:Variable=value,=两边不要加空格
查看:echo $varname或者echo ${varname}
echo选项: -n 不要在最后自动换行
-e 处理字符串中的特殊字符,不把它当成普通字符输出,常用特殊字符如下:
\c 最后不加上换行符
\n 换行,echo "Hello echo\nThis is a test"
\t 插入tab
\nnn 插入八进制nnn表示的ASCII字符
2、环境变量
通过export或declare -x可以将变量声明一个环境变量
export VarName[=Value]
declare -x VarName[=Value]
###############
declare +x VarName #将环境变量变回自定义变量
3、变量类型
Shell脚本中变量默认的类型是字符串,还支持整数和数组类型,变量类型的声明使用declare命令
sum=1+2+3
echo $sum #输出结果为1+2+3
###############
declare -i sum=1+2+3 #sum为整数,echo $sum的结果为6
declare -a array #array为数组
declare -r name="John" #name为只读变量,不可修改
declare -A person #关联数组,Bash 4之后支持
person["name"]="Mark"
person["age"]=12
4、基本整数运算
Shell中可以使用let和(())进行基本整数运算
num=10
let num+=5
let var=num+5
#####################
#((exp))的用法更灵活,exp只要符合C语法即可
var=$(($num+5)) #num前的$加不加都可以
#((exp))还可以用来判断,比如
for ((i=0;i<5;i++))
if (($num<10))
更高级的算术运算用bc命令,支持浮点数
5、数组
定义:
#(1)直接赋值
array[0]=one
array[1]=two
array[2]=three
#(2)小括号赋值
array=(one two three)
#(3)混合赋值
array=([0]=one [1]=two [2]=three)
打印元素和长度:
arr=("a" "b" "c" "d" "e" "f")
[jianjunyang@~/top]$ echo ${arr[*]}
a b c d e f
[jianjunyang@~/top]$ echo ${arr[@]}
a b c d e f
echo ${#array[*]} => 6
echo ${#array[@]} => 6
数组切片:
${数组名[*或@]:起始索引:长度}
[jianjunyang@~/top]$ arr=("a" "b" "c" "d" "e" "f")
[jianjunyang@~/top]$ echo ${arr[*]:0:3}
a b c
[jianjunyang@~/top]$ echo ${arr[@]:2:3}
c d e
遍历:
#方法一
for ((i=0; i<${array[@]}; i++))
do
echo $array[i]
done
#方法二
for i in ${!array[@]} #下标列表
do
echo ${array[$i]}
done
#方法三
for element in ${array[@]}
do
echo $element
done
6、变量嵌套(二维数组)
root@localhost:~/workspace # cat test.sh
#!/usr/bin/bash
aaa_xxx=(111 222 333)
aaa_yyy=(iii jjj kkk)
for element in $(eval echo \${aaa_$1[*]})
do
echo $element
done
root@localhost:~/workspace # sh test.sh xxx
111
222
333
root@localhost:~/workspace # sh test.sh yyy
iii
jjj
kkk
root@localhost:~/workspace #
7、关联数组
定义
declare -A site
site["google"]="http://www.google.com"
site["runoob"]="http://www.runoob.com"
site["taobao"]="http://www.taobao.com"
访问元素
[jianjunyang@~/top]$ echo ${site["runoob"]}
http://www.runoob.com
[jianjunyang@~/top]$ echo ${site[*]}
http://www.google.com http://www.runoob.com http://www.taobao.com
[jianjunyang@~/top]$ echo ${site[@]}
http://www.google.com http://www.runoob.com http://www.taobao.com
访问键
[jianjunyang@~/top]$ echo ${!site[*]}
google runoob taobao
[jianjunyang@~/top]$ echo ${!site[@]}
google runoob taobao
获取长度
[jianjunyang@~/top]$ echo ${#site[*]}
3
[jianjunyang@~/top]$ echo ${#site[@]}
3
六、读取用户输入
学过C语言的都用过scanf函数,那么在Shell脚本中也有这样的功能,它就是read指令
用法: read [-pt] <变量名>
选项:-p 后接提示字符串
-t 后接等待时间(秒数)
示例:read -p "Please input your name:" -t 30 name #把用户输入的内容保存在变量name中
七、变量的设定
1、变量删除
设定方式 | 说明 |
${变量#关键词} | 从变量前面删除符合【关键词】的最短组合 |
${变量##关键词} | 从变量前面删除符合【关键词】的最长组合 |
${变量%关键词} | 从变量后面删除符合【关键词】的最短组合 |
${变量%%关键词} | 从变量后面删除符合【关键词】的最长组合 |
path=/usr/local/bin:/usr/bin:/usr/local/sbin:/home/dmtsai/local/bin:/home/dmtsai/bin
#===============
# 示例1
#===============
#假设想删除'/*/local/bin:'
path=${path#/*local/bin:}
echo $path
#输出结果为:/usr/bin:/usr/local/sbin:/home/dmtsai/local/bin:/home/dmtsai/bin
#删除了最短的组合/usr/local/bin
path=${path##/*/local/bin:}
echo $path
#输出结果为:/home/dmtsai/bin
#删除了最长的组合/usr/local/bin:/usr/bin:/usr/local/sbin:/home/dmtsai/local/bin:
#===============
# 示例2
#===============
#假设想删除最后一个目录
path=${path%:*bin}
echo $path
#输出结果为:/usr/local/bin:/usr/bin:/usr/local/sbin:/home/dmtsai/local/bin
#删除了:/home/dmtsai/bin
#假设只想保留第一个目录
path=${path%%:*bin}
echo $path
#输出结果为:/usr/local/bin
#删除了:/usr/bin:/usr/local/sbin:/home/dmtsai/local/bin:/home/dmtsai/bin
2、变量替换
设定方式 | 说明 |
${变量/旧字符串/新字符串} | 变量中第一个符合的【旧字符串】用【新字符串】替换 |
${变量//旧字符串/新字符串} | 变量中所有符合的【旧字符串】用【新字符串】替换 |
path=/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin
#将变量中的sbin替换为SBIN
echo ${path/sbin/SBIN}
#输出结果为/usr/bin:/usr/SBIN:/usr/local/bin:/usr/local/sbin
echo ${path//sbin/SBIN}
#输出结果为/usr/bin:/usr/SBIN:/usr/local/bin:/usr/local/SBIN
3、变量测试
有时候需要判断一个变量是否存在,或者判断是否被设定为空值;如果不存在或者为空,则给予一个默认值
设定方式 | str不存在 | str为空值 | str为非空 |
var=${str-expr} | var=expr | var=$str(即var也为空) | var=${str} |
var=${str:-expr} | var=expr | var=expr | var=${str} |
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} |
一般来说,str:会判断不存在或为空,str只判断不存在
4、变量截取
使用${}语法截取
[root@localhost ~]# str=/opt/loads/5GNR_t.1.0.0.r50883M_20240327_040706.pkg
# 从下标0开始截取11个字符
[root@localhost ~]# echo ${str:0:11}
/opt/loads/
# 截取下标11开始至末尾的全部字符
[root@localhost ~]# echo ${str:11}
5GNR_t.1.0.0.r50883M_20240327_040706.pkg
# 反向截取, 从末尾向前截取3个字符, 注意冒号后面有一个空格
[root@localhost ~]# echo ${str: -3}
pkg
使用cut命令截取
# 截取第1个到第11个字符
[root@localhost ~]# echo $str|cut -c 1-11
/opt/loads/
# 截取第12个到末尾的全部字符
[root@localhost ~]# echo $str|cut -c 12-
5GNR_t.1.0.0.r50883M_20240327_040706.pkg
八、[]条件判断式
因为在bash语法中中括号被用在很多地方,所以使用[]作为判断式需要特别注意:
- 中括号中的每个部分都要用空格隔开
- 中括号中的变量最好用双引号括起来,例如:[ "$HOME" == "$MAIL" ]
- 中括号中的常量都用单引号或双引号括起来,例如:[ "${name}" == "VBird" ]
1、逻辑运算符
与 | -a | 示例:[ "${yn}" == "Y" -a "${yn}" != "N" ] |
或 | -o | 示例:[ "${yn}" == "Y" -o "${yn}" == "y" ] |
非 | ! | 示例:[ ! -z str ] # -z判断str是否为空 |
注:[]判断式中不能直接使用&&、||,但两个[]判断式之间可以使用&&、||来连接,比如[ "${yn}" == "Y" ] || [ "${yn}" == "y" ]
2、整数比较
-eq | 两数相等(equal) |
-ne | 两数不相等(not equal) |
-gt | n1大于n2(great than) |
-lt | n1小于n2(less than) |
-ge | n1大于等于n2(great equal) |
-le | n1小于等于n2(less equal) |
3、字符串比较
[ -z "$str" ] | 判断str是否为空 |
[ -n "$str" ] | 判断str是否不为空 |
[ "X$str1" == "X$str2" ] | 判断str1是否等于str2 |
[ "X$str1" != "X$str2" ] | 判断str1是否不等于str2 |
注:在[]判断式中不能直接使用>和<,因为这两个符号在数据重定向中被使用,如果非要使用需进行转义,比如[ ab \< bc ]
注:[[ ... ]]结构也可以用作条件判断,它比[ ... ]更友好,能防止很多逻辑错误,比如&&、||、>、<可以直接用在[[ ... ]]中,但用在[ ... ]中就会报错,比如:
注:左右两边添加"X"的作用是防止因变量未设置或为空,导致语法错误,此时表达式可能为 [ = str2 ]或[ str1 = ]或[ = ]
if [[ $a != 1 && $a != 2 ]] 等价于
if [ $a -ne 1 ] && [ $a -ne 2 ] 等价于
if [ $a -ne 1 -a $a -ne 2 ]
4、判断指令test
test指令和[]判断式几乎一样,上面介绍的逻辑运算符、比较符都可以用在test指令中。此外,test还可以用来判断文件的属性:
判断文件类型,如test -e filename | |
-e | 该文件名是否存在 |
-f | 该文件是名否存在且为普通文件 |
-d | 该文件名是否存在且为目录 |
-L | 该文件名是否存在且为链接文件 |
判断文件权限,test -r filename | |
-r | 该文件是否存在且具有可读权限 |
-w | 该文件是否存在且具有可写权限 |
-x | 该文件是否存在且具有可执行权限 |
两个文件比较,test file1 -nt file2 | |
-nt | 判断file1是否比file2新 |
-ot | 判断file1是否比file2旧 |
-ef | 判断file1和file2是否为同一文件 |
#如果文件不存在则结束脚本
test ! -e ${filename} && echo "The file '${filename}' DO NOT exist" && exit 0
九、判断结构
1、if ... then
if [ 条件判断式一 ]; then
# do some thing
elif [ 条件判断式二 ]; then
# do some thing
else
# do some thing
fi
# 示例
if [ "${yn}" == "Y" -o "${yn}" == "y" ]; then
echo "OK, continue"
exit 0
fi
2、case ... esac
case $变量 in
"第一个值")
程序段
;;
"第二个值")
程序段
;;
*) #其余的值
程序段
;;
esac
十、循环结构
1、while do done
while [ 条件表达式 ]
do
程序段
done
2、until do done
until [ 条件表示式 ]
do
程序段
done
3、for ... do ... done
for 循环变量 in 序列
do
程序段
done
#for循环数值处理
for (( 初始值; 终止条件; 步长 ))
do
程序段
done
十一、函数
[function] 函数名(){
程序段
[return 返回值]
}
说明:
- function关键字可以省略
- ()中不需要写形参
- 可以有返回值
- 直接用函数名调用,可以传递参数,参数用空格分隔写在后面,不需要小括号:函数名 [arg1 arg2 ...]
- 函数中参数的获取跟shell一样,$0表示函数名,$1 $2 ...分别表示传递的实参
- 在调用程序中使用echo $?获取返回值
示例:
#!/bin/bash
#函数定义
function printit(){
#输出接收到的参数
echo "`basename $0` is called,the arguement is"
#$@表示所有参数
for arg in $@; do
echo ${arg}
done
return 0
}
#函数调用
printit one two three
#返回值获取
result=`echo $?`
#...
十二、数据流重定向
通常一条命令会把执行结果经由标准输出(stdin)或标准错误输出(stderr)显示到屏幕上,而通过数据流重定向机制则可以把命令执行结果输出到文件或其他设备中。
标准输入(stdin,代码为0) | <或<< | 由文件代替键盘输入数据 |
标准输出(stdout,代码为1) | >或>>或1>或1>> | 正常数据输出到文件中 |
标准错误输出(stderr,代码为2) | 2>或2>> | 错误数据输出到文件中 |
注: >以清空的方式输出到文件
>>以追加的方式输出到文件
<<表示输入结束字符,相当于Ctrl+d
/dev/null相当于回收站,把不关心的数据重定向到这里
示例:
#标准输出和标准错误输出重定向到不同的文件
find /home -name .bashrc > list_right 2> list_error
#标准输出和标准错误输出重定向到同一文件
find /home -name .bashrc &> list
find /home -name .bashrc > list 2>&1 # 2>&1意思是标准错误输出重定向到标准输出
find /home -name .bashrc 2> list 1>&2 # 1>&2意思是标准输出重定向到标准错误输出
#输入重定向
cat > catfile < ~/.bashrc # cat命令不加参数会要求从键盘输入内容
# < ~/.bashrc代替键盘输入,传给cat命令
# 相当于把.bashrc的内容复制到catfile
#输出到垃圾箱
echo "error message" 2> /dev/null 1>&2