文章目录
Shell变量
1. 定义
变量名不加美元符号($),直接定义即可,如name=“lucy”
,变量名和等号之间不能有空格
变量名的命名须遵循如下规则:
- 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
- 中间不能有空格,可以使用下划线(_)。
- 不能使用标点符号。
- 不能使用bash里的关键字(可用help命令查看保留关键字)。
NAME
LIB_PATH
_var
value1
除了显式地直接赋值,还可以用语句给变量赋值,如:
for file in `ls /etc`
或者for file in $(ls /etc)
将/etc目录下文件循环显示
2. 使用变量
使用一个定义过的变量,只要在变量名前面加美元符号($)即可。
name="lucy"
echo $name #输出lucy
echo ${name} #输出lucy
变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界
for skill in Ada Coffe Action Java; do
echo "I am good at ${skill}Script"
done
如果不给skill变量加花括号,写成echo "I am good at $skillScript"
,解释器就会把$skillScript
当成一个变量(其值为空),代码执行结果就与我们期望的不符。
【tips】
给所有变量加上花括号是一个好习惯
已定义的变量可以重新被定义
name="tom"
echo $name #输出tom
name="lucy"
echo $name #输出lucy
3. 只读变量
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
#!/bin/bash
value=10
readonly value
value=20 #报错 value: 只读变量
4. 删除变量
使用 unset 命令可以删除变量。unset variable_name
,变量被删除后不能再次使用。unset 命令不能删除只读变量。
#!/bin/sh
myUrl="https://www.baidu.com"
unset myUrl
echo $myUrl #无输出
5. 变量类型
运行shell时,会同时存在两种变量:
- 局部变量
局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。 - 环境变量
所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量。export variable_name=value
或者variable_name=value 再 export variable_name
(使用env可以查看环境变量)*
Shell 字符串
字符串是shell编程中最常用最有用的数据类型,字符串可以用单引号,也可以用双引号,也可以不用引号。
1. 单引号
str='this is a string'
单引号字符串的限制:
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的;
- 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但可成对出现,作为字符串拼接使用,但需要理解字符串拼接并不是引号的套用。
2. 双引号
name='lucy'
str="Hello, I am \"$name\"! \n"
echo -e $str # Hello, I am "lucy"!
双引号的优点:
- 双引号里可以有变量
- 双引号里可以出现转义字符
3. 拼接字符串
name="lucy"
# 使用双引号拼接
greeting="hello, "$name" !"
greeting_1="hello, ${name} !"
echo $greeting $greeting_1 # hello, lucy! hello, lucy!
# 使用单引号拼接
greeting_2='hello, '$name' !' # '字符串' + 变量 + '字符串'
greeting_3='hello, ${name} !'
echo $greeting_2 $greeting_3 # hello, lucy! hello, ${name} !
4. 获取字符串长度
string="abcd"
echo ${#string} # 输出 4
5. 提取子字符串
string="lucy is a name"
echo ${string:1:4} # 输出"ucy " 从字符串第2个字符开始截取长度为4个字符(索引下标从0开始)
6. 查找子字符串
string="lucy is a name"
echo `expr index "$string" io` # 输出6 查找字符 i 或 o 的位置(哪个字母先出现就计算哪个,index的索引下标从1开始)
注意
这里是反引号`
不是单引号'
截取文件名,后缀和路径的方法:
shell截取文件名和文件目录
Shell 注释
单行注释
在行前加#
就可以了,注释掉的内容会被解释器忽略
多行注释
方法一:
可以通过多个当前行注释来注释多行,就是每一行前都加#
进行注释。
方法二:
在开发过程中,经常会遇到大段的代码需要临时注释起来,过后又取消注释。就可以把要注释的代码段用一对花括号括起来,定义成一个函数,不去调用这个函数,这块代码就不会执行,达到了和注释一样的效果。
方法三:
多行注释还可以使用以下格式:
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
这里的EOF相当于识别注释段结束位置的标识,也可以换成其他符号。当然要避免与不想注释的代码发生冲突。
:<<'
注释内容...
注释内容...
注释内容...
'
Shell 传递参数
在执行脚本时也是可以进行传参的,脚本内获取参数的格式为:$n,其中n代表数字,表示要执行脚本的第几个参数。
举个例子:vim test.sh
#!/bin/bash
echo "Shell 传递参数实例!";
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
echo "第三个参数为:$3";
在运行前,要为脚本设置可执行权限:chmod +x test.sh
也可以直接sh test.sh
这样运行,不用赋予可执行权限
x表示可执行权限,r表示可读,w表示可写,+表示增加权限,-表示取消权限
输入参数 ./test.sh 1 2 3
我们会看到输出:
执行的文件名:./test.sh
第一个参数为:1
第二个参数为:2
第三个参数为:3
还有几个特殊字符用来处理参数:
字符 | 作用说明 |
---|---|
$# | 传递到脚本的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数。如果使用时加引号,相当于以 " $1 $2 … $n " 的形式输出所有参数。 |
$$ | 脚本运行的当前进程ID号 |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与$* 相同,但是如果使用时加引号,会在引号中返回每个参数。相当于以 " $1 " " $2 " … " $n " 的形式输出所有参数。 |
$- | 显示Shell使用的当前选项,与set命令功能相同。 |
$? | 显示最后命令的退出状态或函数返回值。0表示没有错误,其他任何值表明有错误(注意:$? 仅对其上一条指令负责,一旦函数返回后其返回值没有立即保存入参数,那么其返回值将不再能通过$? 获得) |
$*
与 $@
- 相同点:都是引用所有参数。
- 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 “1 2 3”(传递了一个参数),而 “@” 等价于 “1” “2” “3”(传递了三个参数)。
#!/bin/bash
echo "-- \$* 演示 ---"
for i in "$*"; do
echo $i
done
echo "-- \$@ 演示 ---"
for i in "$@"; do
echo $i
done
输出结果为:
-- $* 演示 ---
1 2 3
-- $@ 演示 ---
1
2
3
Shell 数组
数组中可以存放多个值。Bash Shell 只支持一维数组,不支持多维数组,且初始化时不需要定义数组大小,数组元素的下标由0开始。
数组的定义
#!/bin/bash
#Shell 数组用括号来表示,元素用"空格"符号分割开
#array1=(value1 value2 valuen)
array1=(A B "C" D)
#也可以用元素下标
array2[0]=value0
array2[1]=value1
array2[2]=value2
数组元素的读取
#!/bin/bash
array1=(A B "C" D)
#一般格式为${array_name[index]}
echo "第一个元素为: ${array1[0]}"
echo "第二个元素为: ${array1[1]}"
echo "第三个元素为: ${array1[2]}"
echo "第四个元素为: ${array1[3]}"
#获取数组中所有元素
echo "数组的元素为: ${array1[*]}"
echo "数组的元素为: ${array1[@]}"
输出结果如下:
第一个元素为: A
第二个元素为: B
第三个元素为: C
第四个元素为: D
数组的元素为: A B C D
数组的元素为: A B C D
获取数组长度
获取数组长度的方法与获取字符串长度的方法相同。利用@
或*
,可以将数组扩展成列表,然后使用#
来获取数组元素的个数
#!/bin/bash
array1[0]=A
array1[1]=B
array1[2]=C
array1[3]=D
echo "数组元素个数为: ${#array1[*]}"
echo "数组元素个数为: ${#array1[@]}"
输出结果如下:
数组元素个数为: 4
数组元素个数为: 4
Shell echo命令
echo命令用于字符串输出,最简单的格式就是在echo后面加要输出的字符串echo "this is a test string"
,这里也可以不加双引号echo this is a test string
,效果是一样的。
也可以实现更复杂的格式输出:
1.使用转义字符
echo "\"this is a test string\"" # 输出 "this is a test string"
2.显示变量
read 命令从标准输入中读取一行,并把输入行的每个字段的值指定给 shell 变量,就可以把标准输入的变量提取显示了。
read命令 -p(提示语句) -n(字符个数) -t(等待时间) -s(不回显)
- read -p “请输入你的名字” your_name
echo “hello ${your_name}”
-p 可以指定提示信息,方便进行界面交互。- read your_name your_age
echo “your name is ${your_name },your age is ${your_age}”
如果输入多个数据,则第一个数据给第一个变量,第二个数据给第二个变量,如果输入数据个数过多,则最后所有的值都给第一个变量。如果太少输入则不会结束。- read -t 5 -p “请输入你的名字” your_name
这时程序只会等待5秒,如果5秒内用户仍然没有输入,程序依然会继续运行。可以防止程序进行无线等待。- read -n1 -p “请输入y/n” choice
-n选项,后接数值1,指示read命令只要接受到一个字符就退出。只要按下一个字符进行回答,read命令立即
接受输入并将其传给变量。无需按回车键。
#test.sh
#!/bin/sh
read var
echo "your input var is $var"
运行过后程序会等待用户输入,获取标准输入的内容。
3.换行显示
echo -e "OK! \n" # -e 开启转义
echo "It is a test"
输出结果
OK!
It is a test
4.不换行显示
#!/bin/sh
echo -e "OK! \c" # -e 开启转义 \c 不换行
echo "It is a test"
#输出 OK! It is a test
5.显示结果定向
echo "It is a test" > myfile
6.用单引号原样输出字符串,不进行转义或取变量
echo '$name\"' #输出 $name\"
【tips】
除非你知道自己在做什么,否则不要随便使用单引号,这会引起某些细微的错误
7.显示命令执行结果
echo `date` #这里是反引号,来表示命令
echo $(date) #使用 $()也可以表示命令行
printf 输出
shell除了echo
可以输出,也可以使用pintf
进行输出。printf
命令模仿 C 程序库里的 printf()
程序。printf
由 POSIX 标准所定义,因此使用 printf
的脚本比使用 echo
移植性好。
printf
使用引用文本或空格分隔的参数,外面可以在 printf
中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认 printf
不会自动添加换行符,我们需要手动添加 \n
。
格式如下: printf format-string [arguments...]
其中format-string为格式控制字符串,arguments为参数列表。
printf
和 echo
的简单对比 ---- 输出"Hello, shell" :
echo "Hello, Shell"
printf "Hello, Shell\n"
printf
可以完成更多:
printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg
printf "%-10s %-8s %-4.2f\n" 张三 男 68.0591
printf "%-10s %-8s %-4.2f\n" 李四 男 55.6565
printf "%-10s %-8s %-4.2f\n" 王五 女 45.9324
运行脚本 得到结果如下:
我们来解释一下这些数据:熟悉其他语言的朋友们都知道,%s %c %d %f 这类都是格式替代符,%s 输出一个字符串,%d 整型输出,%c 输出一个字符,%f 以小数形式输出实数。
这些格式替代符前面的数字就是规定的字符宽度,%5s就表示任何字符都被显示在5字符宽度的字符内,不足5字符的以空格补充,超过也会将内容全部显示出来。默认的字符显示是右对齐的,加上-
就表示左对齐,例如%-5s就是左对齐且为五字符宽度输出字符串。%f前面的.
表示保留几位小数,例如%4.2f就是宽度为4的小数、右对齐、保留两位小数,%-4.4f表示宽度为4的小数、左对齐、保留四位小数。
# 格式化字符串为双引号
printf "%d %s\n" 1 "abc"
# 单引号与双引号效果一样
printf '%d %s\n' 1 "abc"
# 没有引号也可以输出
printf %s abcdef
# 格式只指定了一个参数,但多出的参数仍然会按照该格式输出,格式化字符串被重用
printf %s abc def
printf "%s\n" abc def
printf "%s %s %s\n" a b c d e f g h i j
# 如果没有参数列表,那么 %s 会用NULL代替,%d 用 0 代替
printf "%s and %d \n"
最终的输出效果如下:
Shell 基本运算符
Shell 和其他编程语言一样,支持多种运算符,包括:算数运算符、关系运算符、布尔运算符、字符串运算符、文件测试运算符。
原生bash不支持简单的数学运算,但是可以通过其他命令来实现。
Linux expr
expr 是一款表达式计算工具,使用它能完成表达式的求值操作,是常用的linux命令,比如:
val=`expr 2 + 3` # 两个数相加
这里要注意:
表达式和运算符之间一定要有空格,例如 2+2 是不对的,必须写成 2 + 2
完整的表达式要被``
包含 ,这里是反引号,不是单引号
算数运算符 | 解释 |
---|---|
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法 |
% | 取余 |
= | 将把变量等号后的值赋给等号前的值。 |
== | 用于比较两个数字,相同则返回true |
!= | 用于比较两个数字,不相同则返回true |
注意:这里后面的两个条件表达式要放在方括号
[]
之间,并且要有空格,例如:[$a==$b]
是错误的,必须写成[ $a == $b ]
a=10
b=20
val=`expr $a + $b`
echo "a + b : $val" //a + b : 30
val=`expr $a - $b`
echo "a - b : $val" //a - b : -10
val=`expr $a \* $b` # '*'在shell中属于特殊字符 要使用反斜杠'\'将星号'*'做反转
echo "a * b : $val" //a * b : 200
val=`expr $b / $a`
echo "b / a : $val" //b / a : 2
val=`expr $b % $a`
echo "b % a : $val" //b % a : 0
if [ $a == $b ] #这里是if条件判断语句 后续内容将会涉及
then
echo "a 等于 b"
fi
if [ $a != $b ]
then
echo "a 不等于 b" //a 不等于 b
fi
expr 不仅有数字计算功能,其实它还具备操作字符串的能力:
运算 | 表达式 | 意义 |
---|---|---|
match | match STRING REGEXP | STRING 中匹配 REGEXP 字符串并返回匹配字符串的长度,若找不到返回0 |
substr | substr STRING POS LENGTH | 从 POS 位置获取长度为 LENGTH 的字符串 |
index | index STRING SUBSTR | 查找子字符串的起始字符第一次出现的位置 找不到返回0 |
length | length STRING | 计算字符串的长度 |
num1=$(expr match "123 456 789" "5")
echo $num1 //6
num1=$(expr match "123 456 789" "103")
echo $num1 //0
num2=$(expr substr " this is a test" 3 5)
echo $num2 //his i
num3=$(expr index "test for the game" "e")
echo $num3 //2
num3=$(expr index "test for the game" "afgh")
echo $num3 //6
num3=$(expr index "test for the game" "xxxx")
echo $num3 //0
num4=$(expr length "this is a test")
echo $num4 //14
Linux awk
先随便写个文件log.txt,内容如下
this is a test
test1 test2 test31,test32,test33 test4
we,should,have,some,other,symbol
用法一:
awk '{[pattern] action}' {filenames}
# 行匹配语句 awk ’ ’ 只能用单引号
#每行按空格或TAB分割,输出文本中的1、4项
awk '{print $1,$4}' log.txt
#得到的结果
this test
test1 test4
we,should,have,some,other,symbol
用法二:
awk -F
# -F相当于内置变量FS, 指定分割字符
#使用","分割
awk -F, '{print $1,$2}' log.txt
#或者使用内建变量
awk 'BEGIN{FS=","} {print $1,$2}' log.txt
#得到的结果
this is a test #由于没有逗号,所以整行作为$1
test1 test2 test31 test32 #这里$1=test1 test2 test31 , $2=test32
we should
# 使用多个分隔符.先使用空格分割,然后对分割结果再使用","分割
awk -F '[ ,]' '{print $1,$2,$4}' log.txt
#得到的结果
this is test
test1 test2 test32 #这里使用两种分隔符切割后 每个词都被独立出来了
we should some
[注]
这里本人还没有找到有效的方式可以直接使用上述方式将[]作为切割符
比如:i want to get [value1] and [value2] in one cut
用法三:
awk -v
# 设置变量
#设置一个变量a
awk -va=1 '{print $1,$1+a}' log.txt
#得到的结果
this 1 # $1+a =1
test1 1
we,should,have,some,other,symbol 1
#设置变量a和b
awk -va=1 -vb=s '{print $1,$1+a,$1b}' log.txt
#得到的结果
this 1 thiss # $1+a=1 $1b=thiss
test1 1 test1s # $1+a=1 $1b=test1s
we,should,have,some,other,symbol 1 we,should,have,some,other,symbols
这里我们可以看到,不带+
时会直接进行字符串拼接,带+
时是进行算数运算,而字符串被转换成0,所以我们看到的$1+a
都等于1,我们再换个例子验证一下:
在log.txt后面加上一行
this is a test
test1 test2 test31,test32,test33 test4
we,should,have,some,other,symbol
2 3 4
#再次运行awk -va=1 -vb=s '{print $1,$1+a,$1b}' log.txt 得到结果如下
this 1 thiss
test1 1 test1s
we,should,have,some,other,symbol 1 we,should,have,some,other,symbols
2 3 2s #此时$1=2 $1+a=2+1=3
用法四:
awk -f {awk脚本} {文件名}
使用awk脚本
awk -f cal.awk log.txt
运算符
运算符 | 描述 |
---|---|
= += -= *= /= %= ^= **= | 赋值 |
?: | C条件表达式 |
|| | 逻辑或 |
&& | 逻辑与 |
~ 和 !~ | 匹配正则表达式和不匹配正则表达式 |
< <= > >= != == | 关系运算符 |
空格 | 连接 |
+ - | 加,减 |
* / % | 乘,除与求余 |
+ - ! | 一元加,减和逻辑非 |
^ *** | 求幂 |
++ -- | 增加或减少,作为前缀或后缀 |
$ | 字段引用 |
in | 数组成员 |
过滤第一列大于2的行 awk '$1>2' log.txt
过滤第一列等于2的行 awk '$1==2 {print $1,$3}' log.txt
过滤第一列大于2并且第二列等于’Are’的行 awk '$1>2 && $2=="Are" {print $1,$2,$3}' log.txt
正则表达式
菜鸟教程-正则表达式-元字符
菜鸟教程-正则表达式-匹配规则
内建变量
变量 | 描述 |
---|---|
$n | 当前记录的第n个字段,字段间由FS分隔 |
$0 | 完整的输入记录 |
ARGC | 命令行参数的数目 |
ARGIND | 命令行中当前文件的位置(从0开始算) |
ARGV | 包含命令行参数的数组 |
CONVFMT | 数字转换格式(默认值为%.6g)ENVIRON环境变量关联数组 |
ERRNO | 最后一个系统错误的描述 |
FIELDWIDTHS | 字段宽度列表(用空格键分隔) |
FILENAME | 当前文件名 |
FNR | 各文件分别计数的行号 |
FS | 字段分隔符(默认是任何空格) |
IGNORECASE | 如果为真,则进行忽略大小写的匹配 |
NF | 一条记录的字段的数目 |
NR | 已经读出的记录数,就是行号,从1开始 |
OFMT | 数字的输出格式(默认值是%.6g) |
OFS | 输出字段分隔符,默认值与输入字段分隔符一致。 |
ORS | 输出记录分隔符(默认值是一个换行符) |
RLENGTH | 由match函数所匹配的字符串的长度 |
RS | 记录分隔符(默认是一个换行符) |
RSTART | 由match函数所匹配的字符串的第一个位置 |
SUBSEP | 数组下标分隔符(默认值是/034) |
打印其中一些出来举个例子:
awk 'BEGIN{printf "%4s %4s %4s %4s %4s %4s\n","FILENAME","ARGC","FNR","FS","NF","NR";printf "------------------------------------\n"} {printf "%4s %4s %4s %4s %4s %4s\n",FILENAME,ARGC,FNR,FS,NF,NR}' log.txt
得到的结果:
FILENAME ARGC FNR FS NF NR
------------------------------------
log.txt 2 1 4 1
log.txt 2 2 4 2
log.txt 2 3 1 3
log.txt 2 4 3 4
FS是分隔符,默认都是空格,现在我们换一下分隔符再打印看看:
awk -F, 'BEGIN{printf "%4s %4s %4s %4s %4s %4s\n","FILENAME","ARGC","FNR","FS","NF","NR";printf "------------------------------------\n"} {printf "%4s %4s %4s %4s %4s %4s\n",FILENAME,ARGC,FNR,FS,NF,NR}' log.txt
得到的结果:
FILENAME ARGC FNR FS NF NR
------------------------------------
log.txt 2 1 , 1 1
log.txt 2 2 , 1 2
log.txt 2 3 , 1 3
log.txt 2 4 , 1 4
匹配分割后第一列等于test的行,输出行号和第一列
awk '$1=="test1" {print NR,$1}' log.txt // 2 test1
关于BEGIN和END
BEGIN模块后紧跟着动作块,这个动作块在awk处理任何输入文件之前执行。所以它可以在没有任何输入的情况下进行测试。它通常用来改变内建变量的值,如OFS,RS和FS等,以及打印标题。
如awk 'BEGIN{FS=",";OFS=";"}{print $1,$2,$3}' log.txt
得到结果为:
this is a test;;
test1 test2 test31;test32;test33 test4
we;should;have
2 3 4;;
END不匹配任何的输入文件,但是执行动作块中的所有动作,它在整个输入文件处理完成后被执行。
如awk 'BEGIN{print "BEGIN & END TEST\n---------------";FS=",";OFS=";"}{print $1,$2,$3} END{print"---------------";print "the number of records is " NR}' log.txt
得到结果为:
BEGIN & END TEST
---------------
this is a test;;
test1 test2 test31;test32;test33 test4
we;should;have
2 3 4;;
---------------
the number of records is 4
模糊匹配
~
表示模式开始。//
之间的内容是模式。
比如:awk '$2 ~ /s/ {print NR,$2,$4}' log.txt
匹配第二列含有s的行,并打印匹配的行号,第二列,第四列
1 is test
2 test2 test4
比如:awk '/s/ {print NR,$2,$4}' log.txt
匹配所有含有s的行,同样打印匹配上的行号,第二列,第四列
1 is test
2 test2 test4
3 #第3行含有s 但没有 $2 和 $4
忽略大小写进行模糊匹配:awk 'BEGIN{IGNORECASE=1} /This/' log.txt
得到结果:
this is a test #这里虽然查找的是This,但因为忽略了大小写,所以查到了this
模式取反
awk '$2 !~ /s/ {print NR,$1}' log.txt
匹配第二列不含有s的行,并输出行号和第一列,得到结果:
3 we,should,have,some,other,symbol
4 2
awk '!/a/ {print NR,$1}' log.txt
匹配不含有a的所有行,并输出行号和第一列,得到结果:
2 test1
4 2
布尔运算符和逻辑运算符
运算符 | 说明 |
---|---|
! | 布尔运算符,非运算,(非真为假,非假为真)即表达式结果取反 |
-o | 布尔运算符,两边的表达式进行或运算 |
-a | 布尔运算符,两边的表达式进行与运算 |
&& | 逻辑运算符,逻辑与 从左到右有一个表达式为真就为真 |
|| | 逻辑运算符,逻辑或 两个表达式都为真才能为真 |
其他运算符参考后文test测试中的使用
Shell test 命令
Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。
test expr 相当于 [ expr ] ,他们可以互相替代
test的三个基本作用是判断文件、判断字符串、判断整数。支持使用 ”与或非“ 将表达式连接起来。
test中可用的比较运算符只有==和!=,两者都是用于字符串比较的,不可用于整数比较,整数比较只能使用-eq, -gt这种形式
1.数值测试
整数比较可以使用[]加-gt的方式,还可以使用(())加>的方式
参数 | 说明 | 另一种写法 |
---|---|---|
-eq | equal,等于则为真 | ((==)) |
-ne | not equal,不等于则为真 | ((!=)) |
-gt | greater than,大于则为真 | ((>)) |
-ge | greater&equal,大于等于则为真 | ((>=)) |
-lt | lower than,小于则为真 | ((<)) |
-le | lower&equal,小于等于则为真 | ((<=)) |
num1=100
num2=100
if test ${num1} -eq ${num2}
then
echo '两个数相等!'
else
echo '两个数不相等!'
fi
if ((${num1}==${num2}));then
echo '两个数相等!'
fi
a=5
b=6
result=$[a + b] # $[]也可以执行算术运算 注意等号两边不能有空格
echo "result 为: $result"
这里 result=$[a + b]
等同于 result=`expr $a + $b`
2.字符串测试
参数 | 说明 |
---|---|
= | 等于则为真 |
!= | 不相等则为真 |
-z 字符串 | zero,字符串的长度为零则为真 |
-n 字符串 | not zero,字符串的长度不为零则为真 |
num1="simple"
num2="sample"
if test ${num1}x = ${num2}x #或者 if [ ${num1}x = ${num2}x ]
then
echo '两个字符串相等!'
else
echo '两个字符串不相等!'
fi
#单等号判断字符串是否相等,这里在变量后面多加了一个x,
#当${num1}为空时表达式会变成if [ x = ${num2}x ],并不会出现问题,否则可能会报错
if test -n "${num1}";then
echo '不为空'
fi
if [ -z "${num1}" ];then
echo '为空'
fi
#判断字符串是否为空,也可以利用字符串本身
if [ "${num1}" ];then
echo '等同于-n'
fi
3.文件测试
参数 | 说明 |
---|---|
-e | 判断对象是否存在 |
-d | 判断对象是否存在,并且为目录 |
-f | 判断对象是否存在,并且为普通文件 |
-L | 判断对象是否存在,并且为符号链接 |
-h | 判断对象是否存在,并且为软链接 |
-s | 判断对象是否存在,并且长度不为0 |
-r | 判断对象是否存在,并且可读 |
-w | 判断对象是否存在,并且可写 |
-x | 判断对象是否存在,并且可执行 |
-c | 判断对象是否存在,并且为字符型特殊文件 |
-b | 判断对象是否存在,并且为块特殊文件 |
-O | 判断对象是否存在,并且属于当前用户 |
-G | 判断对象是否存在,并且属于当前用户组 |
-nt | 判断file1是否比file2新 |
-ot | 判断file1是否比file2旧 |
另外,Shell 还提供了与( -a )、或( -o )、非( ! )三个逻辑操作符用于将测试条件连接起来,其优先级为: ! 最高, -a 次之, -o 最低
其中-a又等同于逻辑运算符&&,-o等同于|| ,区别就是-a这种形式需要写在[]内,而&&这种形式要写在外面。但如果使用[[]]则是支持&&写在里面的
文件夹不存在则创建
if [ ! -d "/data/" ];then
mkdir /data
else
echo "文件夹已经存在"
fi
如果是单纯的想要创建文件夹,也可以直接写mkdir -p /data
文件存在则删除
if [ ! -f "/data/filename" ];then
echo "文件不存在"
else
rm -f /data/filename
fi
判断文件夹是否存在
if [ -d "/data/" ];then
echo "文件夹存在"
else
echo "文件夹不存在"
fi
判断文件是否存在
if [ -f "/data/filename" ];then
echo "文件存在"
else
echo "文件不存在"
fi
与或非例子:两个文件同时判断
if test -e ./notFile -o -e ./bash
then
echo "至少有一个文件存在!"
else
echo "两个文件都不存在"
fi
if [ -e ./notFile -a -e ./bash ];then
echo "两个都存在"
fi
if [ -e ./notFile -a -e ./file] || [ -e ./bash ];then
echo "xxxxxxxxxxxxx"
fi
if [[ -e ./notFile && -e ./file || -e ./bash ]];then
echo "xxxxxxxxxxxxx"
fi
文件路径的双引号可以去掉,[]
内部两端要有空格、-
参数和其他内容之间要有空格, 如果 then
另起一行的话 then
前不需要加 ;
否则需要在 then
前加 ;
if test -d /data/;then
echo '文件夹存在!'
else
echo '文件夹不存在!'
fi
【tips】
01.
[ ]和[[ ]]的不同:
在[ ]中,=和==的作用相同,都可以判断字符串是否相等,但是字符串必须要加上" "进行修饰,否则会报错。且<是要使用\进行转译的。
而在[[ ]]中,==可以用来进行模糊匹配,[[ $a = z* ]]判断$a是否以z开头。且<是直接就可以使用的。
值得一提的是,不论是[ ]还是[[ ]] 只要字符串加上了" ",那么进行的就是字符匹配,比如
[[ $a = "z*" ]]是判断$a是否等于z*这个字符串,并没有进行模糊匹配
02.
可以利用&&的短路特性来简化if语句
[ -f "/etc/shadow" ] && echo "This computer uses shadow passwors" # &&左边为真才会继续执行右边
等同于:
if [ -f "/etc/shadow" ]; then
echo "This computer uses shadow passwors
fi
利用||的短路特性检查语句正确性
[ -f /usr/sbin/dhcpd ] || exit 0
make -j || exit 0
循环控制
for 循环
#一般格式
for var in item1 item2 ... itemN
do
command1
command2
...
commandN
done
#可以写成一行
for var in item1 item2 ... itemN; do command1; command2… done;
当变量值在列表里,for循环即执行一次所有命令,使用变量名获取列表中的当前取值。命令可为任何有效的shell命令和语句。in列表可以包含替换、字符串和文件名。
例如,顺序输出当前列表中的数字:
for loop in 1 2 3 4 5
do
echo "The value is: $loop"
done
输出列表中的动物:
for animal in dog cat pig
do
echo "HI, ${animal}"
done
输出当前目录文件:
filelist=$(ls)
for filename in $filelist
do
echo $filename
done
for循环除了上面的写法,还有一种类似C语言for循环的写法
#通常情况下 shell 变量调用需要加 $,但是 for 的 (()) 中不需要
for((assignment;condition;next));do
command1;
command2;
done;
举个例子
for((i=1;i<=5;i++));do
echo "这是第 $i 次调用";
done;
【tips】
与 C 中相似,赋值和下一步执行可以放到代码之前循环语句之中执行
但要注意:如果要在循环体中进行 for 中的 next 操作,记得变量要加 $,不然程序会变成死循环
if 判断
#一般格式
if condition
then
command1
command2
...
commandN
fi #将if倒过来 作为if语句结束的标志
#if else
if condition
then
command1
else #else语句不能为空 如果没有执行语句就不要写else
command2
fi
#if elseif else
if condition1
then
command1
elif condition2
then
command2
else
command3
fi
比较两个数大小的实例:
echo "please input two var to compare:"
read var1 var2
if [ $var1 = $var2 ] # = 也可以写成 == 或 -eq
then
echo "$var1 等于 $var2";
elif [ $var1 -gt $var2 ]
then
echo "$var1 大于 $var2";
elif [ $var1 -lt $var2 ]
then
echo "$var1 小于 $var2";
else
echo "没有符合的条件";
fi
输出效果如下:
please input two var to compare:
5 8
5 小于 8
please input two var to compare:
5 5
5 等于 5
please input two var to compare:
9 6
9 大于 6
if 可以写成一行,适用于终端命令提示符if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi
if else 语句经常与 test 命令结合使用
num1=$(2*3)
num2=$(1+5)
if test $(num1) -eq $(num2)
then
echo '两个数字相等!'
else
echo '两个数字不相等!'
fi
输出如下:
两个数字相等
while 循环
while循环用于不断执行一系列命令,也用于从输入文件中读取数据;命令通常为测试条件。
while condition #condition是循环条件
do
command
done #do ... done控制while循环
使用while循环输出1到5的数字
i=1
while(($i<=5))
do
echo $i
let "i++" #使用了 Bash let 命令
done
【注】关于Bash let 命令
是 BASH 中用于计算的工具,用于执行一个或多个表达式,变量计算中不需要加上 $ 来表示变量
如果表达式中包含了空格或其他特殊字符,则必须用引用符('或")引起来
let命令具有返回值.当计算结果(若有多个表达式时,以最后一个为准)为0时,返回值为1,否则为0.
使用let时还需要注意的时,对于let x+y这样的式子,shell虽然计算了x+y的值,但却将结果丢弃,若不想这样,可以使用let sum=x+y将x+y的结果保存在变量sum中
另外还可以使用((和))操作符取代let命令,而且这样的话,还可以省去对算术表达式的引用,如果想返回表达式的值,则需用$(())的格式
语法格式
let arg [arg ...] # arg:要执行的表达式
例如
let a=5+4
let b=9-3
echo $a $b // 9 6
while循环可用于读取键盘信息
echo '按<ctrl-d>退出' # ctrl-d 在bash中表示一个特殊的二进制值‘EOF’,在shell中表示退出当前shell
echo -n '输入你最喜欢的电影: '
while read film
do
echo "是的!$film 是一部好电影"
done
死循环:
使用while循环while : do command done
或者
while true do command done
使用for循环
for (( ; ; ))
until 循环
until 循环执行一系列命令直至条件为 true 时停止。until 循环与 while 循环在处理方式上刚好相反。
一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用
until condition #condition 一般为条件表达式,如果返回值为 false,则继续执行循环体内的语句,否则跳出循环。
do
command
done
使用 until 循环输出0到9的数字
idx=0
until [ ! $idx -lt 10 ] #-lt ---> less than 小于
do
echo $idx
idx=`expr $idx + 1`
# 或者 idx++
done
case 语句
Shell case语句和c语言的switch case一样为多选择语句。可以用case语句匹配一个值与一个模式,如果匹配成功,执行相匹配的命令
case语句的value取值支持变量或常数,每一模式最后必须以右括号结束,且模式支持正则表达式
case value in #value后面 必须加'in'
mode1) # value的值会和这些mode做判断 要加')'
command1
command2
...
commandN
;; #value 和 mode 匹配后会执行mode下的command语句 一直到遇到';;'
mode2)
command1
command2
...
commandN
;;
*) #使用'*'捕获所有值,上面没有匹配到结果的话 就会执行这里的语句
command1
command2
...
commandN
;;
esac # 和if一样 case的结束标志也是将case反着写
switch case 可以根据需求可以不输入break,语句会继续执行到遇见一个break再跳出switch case,
但shell的case和c语言的switch case不同 ,虽然 ';;'的作用和break一样,是为了跳出case语句,但不可以不输入';;',如果不输入就会报错
但是shell的case可以使用另一种方式达到同样的目的
获取键盘1-4的输入
echo '输入1-4的数字:'
read input_num
case $input_num in
1)
echo '你选择了1'
;;
2)
echo '你选择了2'
;;
3)
echo '你选择了3'
;;
4)
echo '你选择了4'
;;
*)
echo '你没有输入正确的数字'
;;
esac
输出的结果:
输入1-4的数字:
5
你没有输入正确的数字
输入1-4的数字:
4
你选择了4
输入1-4的数字:
3
你选择了3
输入1-4的数字:
2
你选择了2
输入1-4的数字:
1
你选择了1
另一种写法 执行的效果和上面一样 但是减少了代码的冗余 在各个选项实现的功能相同时可以使用这种方式简化代码
echo '输入1-4的数字:'
read input_num
case $input_num in
1|2|3|4)
echo "你选择了$input_num"
;;
*)
echo '你没有输入正确的数字'
;;
esac
跳出循环
我们经常需要在未达到循环结束条件时强制跳出循环,break和continue是两种跳出循环的方式
break跳出循环是跳出整个循环,后续的循环也不会继续进行了。如果是嵌套的循环,则跳出当前所在的内层的循环,外层循环不受影响。
而continue只在它所在的位置结束当前轮次的循环,continue后面的代码不会继续执行,但后续的循环仍会继续进行。
说的很复杂,举个例子就好理解了
#!/bin/bash
while : #这里设置了死循环
do
echo -n "输入 1 到 5 之间的数字,输入 0 退出:"
read aNum
case $aNum in
1|2|3|4|5) echo "你输入的数字为 $aNum!"
;; #没有退出循环的语句 这里结束case语句会进行下一次循环
0)
echo "你输入了0,即将退出..."
break #break退出整个循环 while死循环不再起作用,所以会退出
;;
*) echo "你输入的数字不是 1 到 5 之间的!"
continue #continue结束当前循环 进行下一次循环 while循环不会结束 但下方的echo语句永远不会执行
echo "游戏结束"
;;
esac
done
运行效果如下:
输入 1 到 5 之间的数字,输入 0 退出:1
你输入的数字为 1!
输入 1 到 5 之间的数字,输入 0 退出:2
你输入的数字为 2!
输入 1 到 5 之间的数字,输入 0 退出:3
你输入的数字为 3!
输入 1 到 5 之间的数字,输入 0 退出:4
你输入的数字为 4!
输入 1 到 5 之间的数字,输入 0 退出:5
你输入的数字为 5!
输入 1 到 5 之间的数字,输入 0 退出:6
你输入的数字不是 1 到 5 之间的!
输入 1 到 5 之间的数字,输入 0 退出:7
你输入的数字不是 1 到 5 之间的!
输入 1 到 5 之间的数字,输入 0 退出:8
你输入的数字不是 1 到 5 之间的!
输入 1 到 5 之间的数字,输入 0 退出:9
你输入的数字不是 1 到 5 之间的!
输入 1 到 5 之间的数字,输入 0 退出:0
你输入了0,即将退出...
shell函数
shell中函数的定义格式如下:
[ function ] funname [()]
{
action;
[return int;]
}
【tips】
1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)
定义一个简单的函数进行调用 输出hello world
#!/bin/bash
funcA()
{
echo 'HELLO WORLD!'
}
echo '------function start------'
funcA #调用函数仅使用其函数名即可
echo '-------function end-------'
# 输出结果
# ------function start------
# HELLO WORLD!
# -------function end-------
定义一个带有返回值的函数 实现一个简单的加法
#!/bin/bash
returnAdd(){
echo '请输入两个数字 例如:2 5'
read a b
return $((${a}+${b}))
}
returnAdd
echo "输入的两个数字之和为 $?" # 调用该函数后 使用$?来获取函数的返回值
# 运行效果如下
# 请输入两个数字:
# 3 4
# 输入的两个数字之和为 7
【tips】
所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。
现在定义一个带有参数的函数
shell函数的参数不是写在括号里的,是在调用的时候直接给参数赋值,函数内部可以直接用$
获取
#!/bin/bash
Add(){
return $(($1+$2))
}
echo '请输入两个数字,例如:5 8'
read num1 num2
Add ${num1} ${num2}
echo "${num1}+${num2}=$?"
# 运行后的效果如下
# 请输入两个数字,例如:5 8
# 1 7
# 1+7=8
【tips】
这里的$1和$2分别表示函数的第一个和第二个参数,$n就表示第n个参数。但是值得注意的是,参数个数超过9以后就不能直接通过$来获得参数值。
以10为例,我们想要取得第十个参数必须要写成${10},否则我们取到的只是10这个数字
我们还可以用一些特殊字符来处理参数:
参数处理 | 说明 |
---|---|
$# | 传递到脚本或函数的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数 |
$$ | 脚本运行的当前进程ID号 |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与$* 相同,但是使用时加引号,并在引号中返回每个参数。 |
$- | 显示Shell使用的当前选项,与set命令功能相同。 |
$? | 显示最后命令的退出状态或函数返回值。0表示没有错误,其他任何值表明有错误。(注意:$? 仅对其上一条指令负责,一旦函数返回后其返回值没有立即保存入参数,那么其返回值将不再能通过$? 获得) |
输入输出重定向
默认情况下,标准输入(stdin)和标准输出(stdout)都是我们的终端,Unix程序会从标准输入读取数据,从标准输出输出数据,会向stderr流中写入错误信息。
现在我们可以重定义这些输入输出到我们想要的地方,比如一个文件。
【tips】
文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)
重定向命令列表如下:
命令 | 说明 |
---|---|
command > file | 将输出重定向到 file。 |
command < file | 将输入重定向到 file。 |
command >> file | 将输出以追加的方式重定向到 file。 |
n > file | 将文件描述符为 n 的文件重定向到 file。 |
n >> file | 将文件描述符为 n 的文件以追加的方式重定向到 file。 |
n >& m | 将输出文件 m 和 n 合并。 |
n <& m | 将输入文件 m 和 n 合并。 |
<< tag | 将开始标记 tag 和结束标记 tag 之间的内容作为输入。 |
输出重定向
使用 >
来重定向输出,command > file
会将commad命令输出到file文件,但是这个操作会覆盖file中之前存在的内容。
由于输出被重定向了,所以终端并不会有输出,可以使用cat或vim查看file文件确认输出内容。
如果想把内容追加到文件末尾,就要改用 >>
进行重定向输出。
输入重定向
输入重定向和输出重定向类似,使用 <
进行重定向,command1 < file1
会把本来需要从键盘获取输入的命令转移到从文件读取内容。
例如我们要查一下上面例子使用的文件行数,正常情况下我们执行 wc -l file_test
,会输出行数和文件名。
但是我们将输入重定向到文件本身,就只会输出行数,因为它仅仅知道从标准输入读取内容。
command < infile > outfile
同时重定向输入和输出,可以读取文件执行命令,并把该命令执行后输出到另一个文件中。
有关标准错误输出
stderr 重定向到 file:command 2>file
stderr 追加到 file 文件末尾:command 2>>file
将 stdout 和 stderr 合并后重定向到 file:command > file 2>&1
或者command >> file 2>&1
Here Document
Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。它的作用是将两个 delimiter 之间的内容(document) 作为输入传递给 command。
# 开始的delimiter前后的空格会被忽略掉。
command << delimiter
document
delimiter # 结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进
在命令行中通过 wc -l 命令计算 Here Document 的行数
将 Here Document 用在脚本中
#!/bin/bash
cat<<EOF
这是Here Document测试
这是Here Document测试
这是Here Document测试
这是Here Document测试
EOF
运行脚本得到
/dev/null 文件
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null,他就像一个垃圾桶一样,写入到它的内容都会被丢弃,如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它 command > /dev/null
,会起到"禁止输出"的效果。
屏蔽 stdout 和 stderr:command > /dev/null 2>&1
这里 command > /dev/null
把标准输出1重定向到 /dev/null,而 2>&1
的&可以理解为等同于的意思,2>&1,表示2的输出重定向等同于1,此时也就是/dev/null。所以也可以直接写成 1>/dev/null 2>&1
屏蔽stderr:2>/dev/null
【tips】
1. 这里的 2 和 > 之间不可以有空格,2> 是一体的时候才表示错误输出
2. 这里的&没有固定的意思,放在>后面的&,表示重定向的目标不是一个文件,而是一个文件描述符。
换言之 2>1 代表将stderr重定向到当前路径下文件名为1的常规文件中,而2>&1代表将stderr重定向到文件描述符为1的文件(即/dev/stdout)中。
而&>file是一种特殊的用法,也可以写成>&file,二者的意思完全相同,都等价于>file 2>&1。此处&>或者>&视作整体,分开没有单独的含义。
command > file 2>file 与 command > file 2>&1 的区别
command > file 2>file
的意思是将命令所产生的标准输出信息,和错误的输出信息送到file 中。command > file 2>file
这样的写法,stdout和stderr都直接送到file中,file会被打开两次,这样stdout和stderr会互相覆盖。
而 command >file 2>&1
这条命令就将stdout直接送向file,stderr 继承了标准输出的管道后,再被送往file,此时file 只被打开了一次,也只使用了一个输出管道,它包括了stdout和stderr的内容。
另外,前一条命令的效率也要比后面一条的命令效率要低。
shell 文件包含
和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。
使用. filename
或 source filename
引用其他文件,就可以在代码里使用该文件的变量函数等。
举个例子,我们创建两个shell文件,在第二个文件里引用第一个文件:
test1.sh
#!/bin/bash
url="http://www.baidu.com"
test2.sh
#!/bin/bash
#使用 . 号来引用test1.sh 文件, 添加test1.sh的路径
. ./test1.sh #注意点号 . 和文件名中间有一空格
# 或者使用以下包含文件代码
# source ./test1.sh
echo "百度网址:$url" #像使用自己的变量一样使用其他文件的变量
给test2.sh添加可执行权限后运行。
【tips】
被包含的文件不需要可执行权限