Shell脚本学习笔记


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时,会同时存在两种变量:

  1. 局部变量
    局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
  2. 环境变量
    所有的程序,包括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(不回显)

  1. read -p “请输入你的名字” your_name
    echo “hello ${your_name}”
    -p 可以指定提示信息,方便进行界面交互。
  2. read your_name your_age
    echo “your name is ${your_name },your age is ${your_age}”
    如果输入多个数据,则第一个数据给第一个变量,第二个数据给第二个变量,如果输入数据个数过多,则最后所有的值都给第一个变量。如果太少输入则不会结束。
  3. read -t 5 -p “请输入你的名字” your_name
    这时程序只会等待5秒,如果5秒内用户仍然没有输入,程序依然会继续运行。可以防止程序进行无线等待。
  4. 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为参数列表。

printfecho 的简单对比 ---- 输出"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 不仅有数字计算功能,其实它还具备操作字符串的能力:

运算表达式意义
matchmatch STRING REGEXPSTRING 中匹配 REGEXP 字符串并返回匹配字符串的长度,若找不到返回0
substrsubstr STRING POS LENGTH从 POS 位置获取长度为 LENGTH 的字符串
indexindex STRING SUBSTR查找子字符串的起始字符第一次出现的位置 找不到返回0
lengthlength 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

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的方式,还可以使用(())加>的方式

参数说明另一种写法
-eqequal,等于则为真((==))
-nenot equal,不等于则为真((!=))
-gtgreater than,大于则为真((>))
-gegreater&equal,大于等于则为真((>=))
-ltlower than,小于则为真((<))
-lelower&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

跳出循环

我们经常需要在未达到循环结束条件时强制跳出循环,breakcontinue是两种跳出循环的方式

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 的行数
将 Here Document 用在脚本中

#!/bin/bash

cat<<EOF
 这是Here Document测试
 这是Here Document测试
 这是Here Document测试
 这是Here Document测试
EOF

运行脚本得到
Here Document

/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 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。
使用. filenamesource 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】
被包含的文件不需要可执行权限
1. 什么是shell脚本Shell脚本是一种编程语言,它是在Unix和Linux操作系统中使用的一种脚本语言。它可以帮助用户自动化任务,并且可以运行一系列命令。 Shell脚本通常以.sh扩展名结尾。 2. 为什么要学习shell脚本学习shell脚本可以帮助你自动化任务,提高工作效率。Shell脚本也可以帮助你编写小工具,方便你自己或其他人使用。Shell脚本还可以帮助你更好地理解Linux和Unix操作系统。 3. 如何编写一个简单的shell脚本? 首先,在命令行中输入命令nano test.sh,创建一个名为test.sh的文件。然后,在文件中输入以下内容: #!/bin/bash echo "Hello World" 接着,按下Ctrl + X,然后按下Y,最后按下Enter,保存并退出文件。接下来,您需要在命令行中输入以下命令: chmod +x test.sh ./test.sh 这将使test.sh文件可执行,并运行脚本。在命令行中,您应该看到输出“Hello World”。 4. shell脚本中的注释是什么? 注释是用于向脚本中添加说明和文档的文本。在Shell脚本中,注释以“#”开头。注释不会被脚本解释器执行,但可以帮助其他人更好地理解脚本。 5. 如何在shell脚本中使用变量? 变量是一个用于存储值的占位符。在Shell脚本中,您可以使用以下语法来定义变量: my_variable="Hello World" 您可以使用echo命令来输出变量的值: echo $my_variable 6. 如何在shell脚本中使用条件语句? 在Shell脚本中,您可以使用条件语句来执行基于条件的操作。以下是一个示例条件语句: if [ $my_variable = "Hello World" ] then echo "The variable contains Hello World" else echo "The variable does not contain Hello World" fi 7. 如何在shell脚本中使用循环? 在Shell脚本中,您可以使用for循环或while循环来执行重复的操作。以下是一个示例for循环: for i in 1 2 3 4 5 do echo $i done 以上代码将输出数字1到5。 8. 如何在shell脚本中使用函数? 在Shell脚本中,您可以使用函数来组织和重复使用代码。以下是一个示例函数: function say_hello { echo "Hello World" } 您可以通过以下方式调用函数: say_hello 9. 如何从shell脚本中读取用户输入? 在Shell脚本中,您可以使用read命令来从用户那里读取输入。以下是一个示例: echo "What is your name?" read name echo "Hello $name" 以上代码将提示用户输入他们的名字,并输出“Hello”后跟用户的名字。 10. 如何在shell脚本中使用命令行参数? 在Shell脚本中,您可以使用$1、$2、$3等变量来访问命令行参数。例如,以下是一个示例脚本,它接受两个命令行参数并将它们相加: #!/bin/bash sum=$(($1 + $2)) echo "The sum of $1 and $2 is $sum" 您可以使用以下命令来运行脚本并传递两个参数: ./test.sh 2 3 以上代码将输出“The sum of 2 and 3 is 5”。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值