又到了毕业季,又得去找实习工作。。。
最近在某直聘软件上找实习工作,看到有许多工作都需要会Shell编程的,然后自己对Shell编程也是一知半解,所以趁着最后还有半个月在校时间,索性学习一下!
Shell是一个命令行解释器,它接收应用程序或用户命令,然后调用操作系统内核去实现操作!
Shell还是一个功能相当强大的编程语言,易编写、易调试、灵活性强。
Shell也可以说是一门脚本语言!
Shell 分为 bash 和 sh,他们两个从宏观来讲其实都是一个东西;
目录
一、Shell 脚本入门
1.1 Shell脚本的三种执行方式
二、变量
2.1 系统预定义变量
2.2 自定义变量
2.2.1 基本语法
2.2.2 变量定义规则
2.2.3 案例
2.3 特殊变量
2.3.1 $n
2.3.2 $#
2.3.3 $*
2.3.4 $@
2.3.5 $?
三、运算符
四、条件判断
4.1 基本语法
4.2 常用判断条件
4.2.1 字符串之间的比较
4.2.2 整数之间的比较
4.2.3 按照文件权限进行判断
4.2.4 按照文件类型进行判断
4.3 多条件判断 && ||
五、流程控制
5.1 if 判断
5.1.1 单分支
5.1.2 多分支
5.1.3 (( )) 在 if 中的使用
5.2 case 语句
5.2.1 基本语法
5.2.2 案例
5.3 for 循环
5.3.1 for
5.3.2 for in (增强for循环,使用的更多一点)
5.3.3 $* 与 $@ 的区别
5.4 while 循环
六、read 读取控制台输入
七、函数
7.1 系统函数
7.1.1 basename
7.1.2 dirname
7.2 自定义函数
7.2.1 基本语法
7.2.2 注意事项
7.2.3 案例
八、综合运用 - 实现文件归档
九、正则表达式入门
9.1 常规匹配
9.2 常用特殊字符
9.2.1 ^
9.2.2 $
9.2.3 .
9.2.4 *
9.2.5 字符区间(中括号):[ ]
9.2.6 \
9.3 练习,匹配手机号码
十、文本处理工具
10.1 cut
10.1.1 基本用法
10.1.2 选项参数说明
10.1.3 案例
10.2 gawk
10.2.1 基本用法
10.2.2 选项参数说明
10.2.3 案例
10.2.4 gawk 的内置变量
10.2.5 案例
十一、总结
一、Shell 脚本入门
Shell脚本一般都以 #!/bin/bash 开头(指定解析器)
且一般脚本文件默认都以 .sh 作为后缀(大家都是这样使用的)
# 是其注释符号;
如下,编写一个输出hello world!的脚本:
helloworld.sh
#!/bin/bash
echo "hello world!"
1.1 Shell脚本的三种执行方式
1.1.1 bash 脚本相对路或绝对路径 或者 sh 脚本相对路或绝对路径
例如:
bash + 脚本的绝对路径
ygt@YGT:~/share/knowledge$ bash /home/ygt/share/knowledge/Shell/helloworld.sh
hello world!
ygt@YGT:~/share/knowledge$
bash + 脚本的相对路径
ygt@YGT:~/share/knowledge/Shell$ bash ./helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$
sh + 脚本的绝对路径
ygt@YGT:~/share/knowledge/Shell$ sh /home/ygt/share/knowledge/Shell/helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$
sh + 脚本的相对路径
ygt@YGT:~/share/knowledge/Shell$ sh ./helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$
1.1.2 直接输入脚本的绝对路径或者相对路径执行脚本(此方式脚本必须要有执行的权限'x')
赋予脚本执行的权限:chmod +x helloworld.sh
相对路径执行脚本:
ygt@YGT:~/share/knowledge/Shell$ ./helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$
绝对路径执行脚本:
ygt@YGT:~/share/knowledge/Shell$ /home/ygt/share/knowledge/Shell/helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$
注意:第一种方式执行脚本,本质是bash解析器帮你执行脚本,所以脚本本身不需要执行权限‘x’也可以执行;第二种方式执行脚本,本质是脚本自己去执行,所以需要执行权限'x'.
3. 使用 ‘.’ 或者 source
跟第一种方式类型
ygt@YGT:~/share/knowledge/Shell$ . helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ source helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$
注意:第一种方式和第二种方式都是在当前Shell中打开一个子Shell来执行脚本内容,当脚本内容结束,则子Shell关闭,回到父Shell中;第三种方式是在当前Shell中执行脚本,而无需打开子Shell。
打开子Shell与不打开子Shell的区别在于,环境变量的继承关系,如在子Shell中设置的当前变量,父Shell是不可见的。
二、变量
变量分为局部变量和全局变量,局部变量即只能在本Shell中去使用,而全局变量可以在本Shell或者其子Shell中去使用。(系统预定义的变量是全局变量,自定义的是局部变量)
2.1 系统预定义变量
常用的系统变量:$HOME、$PWD、$SHELL、$USER
可以使用echo 或者 printenv 将变量中的值打印出来
ygt@YGT:~/share/knowledge/Shell$ printenv HOME
/home/ygt
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ echo $HOME
/home/ygt
ygt@YGT:~/share/knowledge/Shell$
可以使用 set 查看当前Shell中的所有变量;
2.2 自定义变量
2.2.1 基本语法
- 定义变量:变量名=变量值 注意,=号前后不能有空格
- 撤销变量:unset 变量名
- 声明静态变量(常量):readonly 变量名=变量值 注意,不能unset
2.2.2 变量定义规则
- 变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写;
- 等号两侧不能由空格;
- 在bash中,变量默认类型都是字符串类型,无法直接进行数值运行;
- 变量的值如果有空格,需要使用双引号或单引号括起来。
2.2.3 案例
2.2.3.1 定义变量name
ygt@YGT:~/share/knowledge/Shell$ name=hello
ygt@YGT:~/share/knowledge/Shell$ echo $name
hello
ygt@YGT:~/share/knowledge/Shell$
2.2.3.2 给变量name重新赋值
ygt@YGT:~/share/knowledge/Shell$ name=world
ygt@YGT:~/share/knowledge/Shell$ echo $name
world
ygt@YGT:~/share/knowledge/Shell$
2.2.3.3 撤销变量name
ygt@YGT:~/share/knowledge/Shell$ unset name
ygt@YGT:~/share/knowledge/Shell$ echo $name
ygt@YGT:~/share/knowledge/Shell$
2.2.3.4 声明静态变量(常量)age,静态变量不能修改,也不能撤销unset
ygt@YGT:~/share/knowledge/Shell$ readonly age=24
ygt@YGT:~/share/knowledge/Shell$ echo $age
24
ygt@YGT:~/share/knowledge/Shell$ age=25
-bash: age: 只读变量
ygt@YGT:~/share/knowledge/Shell$ unset age
-bash: unset: age: 无法取消设定: 只读 variable
ygt@YGT:~/share/knowledge/Shell$
2.2.3.5 在bash中,变量默认类型都是字符串类型,无法进行直接数值运算
ygt@YGT:~/share/knowledge/Shell$ sum=1+2
ygt@YGT:~/share/knowledge/Shell$ echo $sum
1+2
ygt@YGT:~/share/knowledge/Shell$
2.2.3.6 变量的值如果有空格,需要使用双引号或者单引号括起来
ygt@YGT:~/share/knowledge/Shell$ name="hello world!"
ygt@YGT:~/share/knowledge/Shell$ echo $name
hello world!
ygt@YGT:~/share/knowledge/Shell$
注意,以上创建的都是局部变量,只能在本Shell中进行使用!
2.2.3.7 把局部变量提升为全局变量,可供其他Shell程序使用
export 变量名
ygt@YGT:~/share/knowledge/Shell$ export sum
在helloworld.sh文件中输出sum的值
ygt@YGT:~/share/knowledge/Shell$ vim helloworld.sh
#!/bin/bash
echo "hello world!"
echo $sum
ygt@YGT:~/share/knowledge/Shell$ ./helloworld.sh
hello world!
1+2
注意,在子Shell中对父Shell的全局变量做修改,只能在子Shell中看到效果,而无法影响到父Shell;
例如,在helloworld.sh文件中修改sum的值,然后输出查看效果,执行helloworld.sh脚本,然后再执行echo $sum 查看sum的值
#!/bin/bash
echo "hello world!"
echo $sum
sum=100
echo $sum
ygt@YGT:~/share/knowledge/Shell$ ./helloworld.sh
hello world!
1+2
100
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ echo $sum
1+2
ygt@YGT:~/share/knowledge/Shell$
由此可见,sum的值没有被修改,还是1+2
但如果使用 '.' 或者 source 去执行脚本的话,就可以正常修改!
ygt@YGT:~/share/knowledge/Shell$ . helloworld.sh
hello world!
1+2
100
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ echo $sum
100
ygt@YGT:~/share/knowledge/Shell$
如果想要实现两数相加的话,可以使用括号( )括起来或者中括号括[ ]起来!
ygt@YGT:~/share/knowledge/Shell$ age=$((1+2))
ygt@YGT:~/share/knowledge/Shell$ echo $age
3
ygt@YGT:~/share/knowledge/Shell$ age=$[2+3]
ygt@YGT:~/share/knowledge/Shell$ echo $age
5
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ a=10
ygt@YGT:~/share/knowledge/Shell$ b=20
ygt@YGT:~/share/knowledge/Shell$ age=$[a+b]
ygt@YGT:~/share/knowledge/Shell$ echo $age
30
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ age=$[a+10]
ygt@YGT:~/share/knowledge/Shell$ echo $age
20
ygt@YGT:~/share/knowledge/Shell$
2.3 特殊变量
a = 10
注意,使用单引号 ' ' 将 变量 $a包起来,Shell会将其解析成字符串$a;如果使用双引号 " " 将变量$a包起来,Shell会将其解析成变量$a,即会获取到10这个值。
2.3.1 $n
$n (功能描述:n 为数字,$0 代表该脚本名称,$1-$9 代表第一到第九个参数,十以上的参数需要用大括号包含,如${10},${11}等)
例:
编写脚本,内容如下:
#!/bin/bash
echo '以下为$n的用法'
echo "文件名:$0"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "第三个参数:$3"
ygt@YGT:~/share/knowledge/Shell$ ./special.sh one 二 3
以下为$n的用法
文件名:./special.sh
第一个参数:one
第二个参数:二
第三个参数:3
ygt@YGT:~/share/knowledge/Shell$
2.3.2 $#
$# (功能描述:获取所有输入参数个数,常用于循环,判断参数的个数是否正确以及加强脚本的健壮性)
例:
编写脚本,内容如下:
#!/bin/bash
echo "参数个数:$#"
ygt@YGT:~/share/knowledge/Shell$ ./special.sh one 二 3
参数个数:3
ygt@YGT:~/share/knowledge/Shell$
2.3.3 $*
$* (功能描述:这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体)
例:
编写脚本,内容如下:
#!/bin/bash
echo '输出$*:'
echo $*
ygt@YGT:~/share/knowledge/Shell$ ./special.sh one 二 3
输出$*:
one 二 3
ygt@YGT:~/share/knowledge/Shell$
2.3.4 $@
$@ (功能描述:这个变量代表命令行中所有的参数,不过$@把每个参数区分对待)
即$@获取到的是存储所有参数的列表,或者说是存储所有参数的数组,可以使用for循环遍历一个一个将其获取出来(后面会讲到如何进行遍历)
例:
编写脚本,内容如下:
#!/bin/bash
echo '输出$@'
echo $@
ygt@YGT:~/share/knowledge/Shell$ ./special.sh one 二 3
输出$@
one 二 3
ygt@YGT:~/share/knowledge/Shell$
2.3.5 $?
$? (功能描述:最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非 0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了)
例:
判断helloworld.sh脚本是否执行成功
root@YGT:/home/ygt/share/knowledge/Shell# ./helloworld.sh
hello world!
100
root@YGT:/home/ygt/share/knowledge/Shell# echo $?
0
root@YGT:/home/ygt/share/knowledge/Shell#
root@YGT:/home/ygt/share/knowledge/Shell# ./helloworld1.sh
bash: ./helloworld1.sh: 没有那个文件或目录
root@YGT:/home/ygt/share/knowledge/Shell# echo $?
127
root@YGT:/home/ygt/share/knowledge/Shell#
三、运算符
通过上面我们知道,Shell是无法直接进行运算的,例如 a=1+2 输出遍历a的结果就是1+2;
Shell有两种方式可以进行运行:“$((运算式))” 或 “$[运算式]”
例:
root@YGT:/home/ygt/share/knowledge/Shell# a=$((10 + 20))
root@YGT:/home/ygt/share/knowledge/Shell# echo $a
30
root@YGT:/home/ygt/share/knowledge/Shell# a=$[10*20]
root@YGT:/home/ygt/share/knowledge/Shell# echo $a
200
root@YGT:/home/ygt/share/knowledge/Shell#
root@YGT:/home/ygt/share/knowledge/Shell# a=10
root@YGT:/home/ygt/share/knowledge/Shell# b=20
root@YGT:/home/ygt/share/knowledge/Shell#
root@YGT:/home/ygt/share/knowledge/Shell# c=$(($a + $b))
root@YGT:/home/ygt/share/knowledge/Shell# echo $c
30
root@YGT:/home/ygt/share/knowledge/Shell# a=$[$b + $c]
root@YGT:/home/ygt/share/knowledge/Shell# echo $a
50
root@YGT:/home/ygt/share/knowledge/Shell#
或者 c=$((a + b)) a=$[b + c] 这样也是可以的!
案例,新建add.sh文件,实现参数输入两个数,相加后输出
#!/bin/bash
sum=$[$1+$2]
echo sum=$sum
root@YGT:/home/ygt/share/knowledge/Shell# ./add.sh 10 20
sum=30
root@YGT:/home/ygt/share/knowledge/Shell#
四、条件判断
4.1 基本语法
(1). test 条件
(2). [ 条件 ] (注意 条件 前后要有空格)
(比较完成后,使用 $? 查看结果,0 表示匹配(真),1 表示不匹配(假))
4.2 常用判断条件
4.2.1 字符串之间的比较
(注:如果是字符串之间的比较 ,用等号 “=” 判断相等;用 “!=” 判断不等。)
4.2.1.1 = 等于
ygt@YGT:~/share/knowledge/Shell$ a=hello
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ test $a = hello # 注意,等号两边需要有空格
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$ test $a = Hello # 注意,等号两边需要有空格
ygt@YGT:~/share/knowledge/Shell$ echo $?
1
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ a=hello
ygt@YGT:~/share/knowledge/Shell$ b=world
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ [ $a = $b ] # 注意,[ ] 内部也需要有空格与条件分割
ygt@YGT:~/share/knowledge/Shell$ echo $?
1
ygt@YGT:~/share/knowledge/Shell$ b=hello
ygt@YGT:~/share/knowledge/Shell$ [ $a = $b ] # 注意,[ ] 内部也需要有空格与条件分割
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$
注意,如果等号两边没有空格,那么Shell会将其解析成一个值,所以结果会是0(真);当条件中什么也没有的时候,结果就为1(假) ,如下:
ygt@YGT:~/share/knowledge/Shell$ a=hello
ygt@YGT:~/share/knowledge/Shell$ test $a=Hello
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ a=hello
ygt@YGT:~/share/knowledge/Shell$ [ $a=hello ]
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ [ sdfjogwoegjowg ]
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ [ ]
ygt@YGT:~/share/knowledge/Shell$ echo $?
1
ygt@YGT:~/share/knowledge/Shell$
4.2.1.2 != 不等于
ygt@YGT:~/share/knowledge/Shell$ a=hello
ygt@YGT:~/share/knowledge/Shell$ [ $a != hello ]
ygt@YGT:~/share/knowledge/Shell$ echo $?
1
ygt@YGT:~/share/knowledge/Shell$ [ $a != Hello ]
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$
4.2.2 整数之间的比较
-eq | 等于(equal) |
-ne | 不等于(not equal) |
-lt | 小于(less than) |
-le | 小于等于(less equal) |
-gt | 大于(greater than) |
-ge | 大于等于(greater equal) |
ygt@YGT:~$ a=10
ygt@YGT:~$ b=20
ygt@YGT:~$ [ $a -eq $b ] # 判断10是否等于20
ygt@YGT:~$ echo $?
1
ygt@YGT:~$ [ $a -lt $b ] # 判断10是否小于20
ygt@YGT:~$ echo $?
0
ygt@YGT:~$ [ 10 -ge 10 ] # 判断10是否大于等于10
ygt@YGT:~$ echo $?
0
ygt@YGT:~$
4.2.3 按照文件权限进行判断
-r | 判断文件是否有读的权限(read) |
-w | 判断文件是否有写的权限(write) |
-x | 判断文件是否有执行的权限(execute) |
ygt@YGT:~$ touch test.txt
ygt@YGT:~$ ll
总用量 1
drwxr-xr-x 9 ygt ygt 4096 6月 5 23:07 ./
drwxr-xr-x 3 root root 4096 11月 16 2022 ../
-rw-rw-r-- 1 ygt ygt 0 6月 5 23:07 test.txt
ygt@YGT:~$
ygt@YGT:~$ [ -r test.txt ] # 判断test.txt文件是否有读的权限
ygt@YGT:~$ echo $?
0
ygt@YGT:~$ [ -x test.txt ] # 判断test.txt文件是否有执行的权限
ygt@YGT:~$ echo $?
1
ygt@YGT:~$
4.2.4 按照文件类型进行判断
-e | 判断文件是否存在(existence) |
-f | 判断文件是否存在并且是一个常规的文件(file) |
-d | 判断文件是否存在并且是一个目录(directory) |
ygt@YGT:~$ ll
总用量 2
drwxr-xr-x 10 ygt ygt 4096 6月 5 23:17 ./
drwxr-xr-x 3 root root 4096 11月 16 2022 ../
drwxrwxr-x 2 ygt ygt 4096 6月 5 23:17 test/
-rw-rw-r-- 1 ygt ygt 0 6月 5 23:07 test.txt
ygt@YGT:~$
ygt@YGT:~$ [ -e test.txt ]
ygt@YGT:~$ echo $?
0
ygt@YGT:~$ [ -e test ]
ygt@YGT:~$ echo $?
0
ygt@YGT:~$ [ -f test ]
ygt@YGT:~$ echo $?
1
ygt@YGT:~$ [ -d test ]
ygt@YGT:~$ echo $?
0
ygt@YGT:~$ [ -f test.txt ]
ygt@YGT:~$ echo $?
0
ygt@YGT:~$
4.3 多条件判断 && ||
多条件判断(&& 表示前一条命令执行成功时,才执行后一条命令;|| 表示上一条命令执行失败后,才执行下一条命令)
也可以用于短路执行!
ygt@YGT:~$ a=10
ygt@YGT:~$ [ $a -gt 5 ] && echo "$a > 5"
10 > 5
ygt@YGT:~$ [ $a -lt 5 ] || echo "$a 不小于 5"
10 不小于 5
ygt@YGT:~$
ygt@YGT:~$ [ $a -lt 20 ] && echo "$a < 20" || echo "$a >= 20"
10 < 20
ygt@YGT:~$
ygt@YGT:~$ a=30
ygt@YGT:~$ [ $a -lt 20 ] && echo "$a < 20" || echo "$a >= 20"
30 >= 20
ygt@YGT:~$
也可以用于命令当中!
ygt@YGT:~$ ls || ll
5 test test.txt
ygt@YGT:~$
当 ls 命令执行成功后,其后面的 ll 命令就不会执行了!
五、流程控制
流程控制在任何程序都是非常重要的,例如有if、case、for、while;
5.1 if 判断
5.1.1 单分支
if [ 条件判断式 ]; then # 注意,这里的 ; 是有的
程序
fi
或者
if [ 条件判断式 ]
then
程序
fi
新建文件写入以下内容: (传入一个整数,判断其是否小于10)
#!/bin/bash
if [ $1 -lt 10 ]; then
echo "$1 < 10"
fi
if [ "$2"x = "abc"x ] # 特殊用法,即使没有参数2,也可以正常运行
then
echo "相等"
fi
ygt@YGT:~/share/knowledge/Shell$ ./process.sh 5
5 < 10
当然,还可以使用 && 或者 || 进行多条件就判断;
ygt@YGT:~/share/knowledge/Shell$ a=20
ygt@YGT:~/share/knowledge/Shell$ if [ $a -gt 18 ] && [ $a -lt 40 ]; then echo OK; fi
OK
&& 和 || 可以使用 -a 和 -o 替换,然后条件写在一个 [ ] 内即可;(-a 等于 and -o 等于 or)
ygt@YGT:~/share/knowledge/Shell$ a=20
ygt@YGT:~/share/knowledge/Shell$ if [ $a -gt 18 -a $a -lt 40 ]; then echo OK; fi
OK
5.1.2 多分支
if [ 条件判断式 ]; then
程序
elif [ 条件判断式 ]; then
程序
else
程序
fi
新建文件写入以下内容:
#!/bin/bash
if [ $1 -lt 18 ]; then
echo "未成年"
else
echo "已成年"
fi
if [ $2 -ge 0 -a $2 -lt 60 ]; then
echo "不及格"
elif [ $2 -ge 60 -a $2 -le 90 ]; then
echo "及格"
elif [ $2 -gt 90 -a $2 -le 100 ]; then
echo "优秀"
else
echo "成绩有误"
fi
[centos7@localhost Shell]$ ./process.sh 24 70
已成年
及格
[centos7@localhost Shell]$ ./process.sh 16 -1
未成年
成绩有误
[centos7@localhost Shell]$
5.1.3 (( )) 在 if 中的使用
使用 (( )) 即可在括号里面使用类似C语言的用法;当然,一般不推荐这样去使用!
ygt@YGT:~/share/knowledge/Shell$ a=10
ygt@YGT:~/share/knowledge/Shell$ if (( $a > 20 )); then echo OK; else echo notOK; fi
notOK
5.2 case 语句
5.2.1 基本语法
case $变量名 in
"值 1")
如果变量的值等于值 1,则执行程序 1
;;
"值 2")
如果变量的值等于值 2,则执行程序 2
;;
*)
如果变量的值都不是以上的值,则执行此程序(defualt)
;;
esac
注意事项:
- case 行尾必须为单词 “in” ,每一个模式匹配必须以右括号 “)” 结束;
- 双分号 “;;” 表示命令序列结束,相当于 C/C++ 中的 break;
- 最后的 “*)” 表示默认模式,相当于 C/C++ 中的 default;
主要注意以上语法不同,其他用法和C/C++的switch case用法一致。
5.2.2 案例
新建文件写入以下内容:
#!/bin/bash
case $1 in
"1")
echo "one"
;;
"2")
echo "two"
;;
"3")
echo "three"
;;
*)
echo "other"
;;
esac
[centos7@localhost Shell]$ ./process_case.sh 2
two
5.3 for 循环
for 循环有两种方式,for 和 for in
5.3.1 for
使用 (( )) 即可在括号里面使用类似C语言的用法;for 循环可以这样去使用!
for (( 初始值; 循环控制条件; 变量变化 ))
do
程序
done
新建文件写入以下内容: (计算1+...+n)
#!/bin/bash
sum=0
for (( i=1; i <= $1; i++ )); do
sum=$[ $sum + $i ]
#sum=$[ sum + i ] # 貌似这样写也是可以的
#sum=$((sum + i)) # 貌似这样写也是可以的
#let sum=sum+i
done
echo "sum = $sum"
注意,随着Linux Shell的更新,可以使用 let 关键字去执行现在高级语言的用法
例如:let sum=sum+i let sum++ 等等
ygt@YGT:~/share/knowledge/Shell$ ./sum.sh 10
sum = 55
5.3.2 for in (增强for循环,使用的更多一点)
for 变量 in 值 1 值 2 值 3…
do
程序 # 变量 的取值为 in 后面的值
done
#!/bin/bash
for os in Linux Windows Macos; do
echo $os;
done
ygt@YGT:~/share/knowledge/Shell$ ./sum.sh
Linux
Windows
Macos
ygt@YGT:~/share/knowledge/Shell$
当然,in 后面还可以给他一个集合,使用 { .. } 包起来,例如:
ygt@YGT:~/share/knowledge/Shell$ for i in {1..100}; do sum=$[ $sum + $i ]; done; echo "sum = $sum"
sum = 5050
ygt@YGT:~/share/knowledge/Shell$
5.3.3 $* 与 $@ 的区别
$*和$@都表示传递给函数或脚本的所有参数,不被双引号“”包含时,都以$1 $2 …$n的形式输出所有参数。
$*:将参数当作一个整体;
$@:将参数当作一个一个独立的个体
新建文件输入以下内容:
(如果代码中 $* 和 $@ 不用双引号括起来,他们实现的效果是一样的,就没法看出区别了)
#!/bin/bash
echo '$*:'
for pro in "$*"; do # 注意这里$*一定要用双引号括起来,就可以看出其将参数看作是一个整体
echo $pro
done
echo '$@:'
for pro in "$@"; do # 注意这里$@一定要用双引号括起来,就可以看出其将参数看作是分开的独立个体
echo $pro
done
ygt@YGT:~/share/knowledge/Shell$ ./process_for.sh 1 2 3 a b c
$*:
1 2 3 a b c
$@:
1
2
3
a
b
c
ygt@YGT:~/share/knowledge/Shell$
$* 将参数看成了整体的一个字符串,而$@ 却将参数看出独立的个体
5.4 while 循环
while [ 条件判断式 ]
do
程序
done
#!/bin/bash
i=1
sum=0
#while (( i <= $1 )); do # 也可以使用 (( )) 替代[ ]
while [ $i -le $1 ]; do
sum=$[ $sum + $i ]
i=$[ $i + 1 ]
#let sum=sum+i
#let i++
done
echo "sum = $sum"
(可以使用 let 关键字修饰执行高级语言的用法!)
代码中,(( )) 和 [ ] 之间都可以互换使用!
一般while都是使用 [ ]
ygt@YGT:~/share/knowledge/Shell$ ./process_while.sh 100
sum = 5050
六、read 读取控制台输入
read (选项) (参数)
①选项:
-p:指定读取值时的提示符;
-t:指定读取值时等待的时间(秒)如果 -t 不加表示一直等待
(加了 -t 时间结束后,会结束read操作,继续往下执行)
②参数
变量:指定读取值的变量名
新建文件输入以下内容:
#!/bin/bash
read -t 7 -p "请输入您的名字:" name
#if [ "$name" == "" ]; then
if [ ! $name ]; then
echo "Name is NULL"
else
echo "Your name is $name"
fi
ygt@YGT:~/share/knowledge/Shell$ ./process_read.sh
请输入您的名字:李华
Your name is 李华
ygt@YGT:~/share/knowledge/Shell$ ./process_read.sh
请输入您的名字:Name is NULL
ygt@YGT:~/share/knowledge/Shell$
七、函数
小案例,拼接文件名(date函数),格式为:文件名_log_时间戳
#!/bin/bash
filename="$1"_log_$(date +%s)
echo $filename
date 是一个系统函数, +%s 是给其传入的参数,从而获取时间戳返回
ygt@YGT:~/share/knowledge/Shell$ ./date.sh test
test_log_1686280637
ygt@YGT:~/share/knowledge/Shell$
系统函数的调用方式:$(系统函数) 又名:命令替换
7.1 系统函数
这里介绍两个比较常用的系统函数,basename 和 dirname;
7.1.1 basename
7.1.1.1 基本语法
basename [string / pathname] [suffix]
(功能描述:basename 命令会删掉所有的前缀包括最后一个(‘/’)字符,然后将字符串显示出来。
basename 可以理解为取路径里的文件名称
选项: suffix 为后缀,如果 suffix 被指定了,basename 会将 pathname 或string 中的suffix 去掉。
注意,basename只能截取带有 / 的字符串!
7.1.1.2 案例
ygt@YGT:~/share/knowledge/Shell$ basename /home/ygt/share/knowledge/Shell/date.sh
date.sh
ygt@YGT:~/share/knowledge/Shell$ basename /home/ygt/share/knowledge/Shell/date.sh .sh
date
ygt@YGT:~/share/knowledge/Shell$ basename ./date.sh
date.sh
ygt@YGT:~/share/knowledge/Shell$ basename /abcsdf/asdgwr/erg43
erg43
7.1.2 dirname
7.1.2.1 基本语法
dirname 文件绝对路径
(功能描述:从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录的部分))
dirname 可以理解为取文件路径的绝对路径名称
7.1.2.2 案例
ygt@YGT:~/share/knowledge/Shell$ dirname /home/ygt/share/knowledge/Shell/date.sh
/home/ygt/share/knowledge/Shell
ygt@YGT:~/share/knowledge/Shell$ dirname ./date.sh
.
ygt@YGT:~/share/knowledge/Shell$ dirname /sadfsg/gwgwrga/agwgg
/sadfsg/gwgwrga
还有一个更好玩的玩法,在任何路径调用脚本,都输出脚本所在的全路径:
#!/bin/bash
echo $(cd $(dirname $0); pwd)
ygt@YGT:~/share/knowledge/Shell$ ./date.sh
/home/ygt/share/knowledge/Shell
ygt@YGT:~/share/knowledge/Shell$ cd /
ygt@YGT:/$ /home/ygt/share/knowledge/Shell/date.sh
/home/ygt/share/knowledge/Shell
ygt@YGT:/$ cd /home/ygt/share/knowledge/shared_bike/src/
ygt@YGT:~/share/knowledge/shared_bike/src$ ../../Shell/date.sh
/home/ygt/share/knowledge/Shell
7.2 自定义函数
7.2.1 基本语法
[ function ] funname [()]
{
Action;
[return int;]
}
7.2.2 注意事项
- 必须在调用函数地方之前,先声明函数,shell 脚本是逐行运行。不会像其它语言一样先编译;
- 函数返回值,只能通过$?系统变量获得,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。return 后跟数值 n(0-255);
- 因为通过$?获取到的返回值只能是0-255之间;所以可以使用echo将数据输出,然后使用$()去调用函数,即可获得echo输出的结果,即获得函数的返回值;
- 定义函数时无需指定参数,调用函数时直接传参,在函数内部使用$1、$2...$n去获取参数值!
7.2.3 案例
新建文件输入以下内容:
#!/bin/bash
function add() {
s=$[ $1 + $2 ]
return $s # 注意,只能返回0-255之间的数
}
read -p "请输入第一个整数:" a
read -p "请输入第二个整数:" b
add $a $b
echo "$a + $b = $?"
ygt@YGT:~/share/knowledge/Shell$ ./func.sh
请输入第一个整数:10
请输入第二个整数:20
10 + 20 = 30
ygt@YGT:~/share/knowledge/Shell$ ./func.sh
请输入第一个整数:100
请输入第二个整数:200
100 + 200 = 44
ygt@YGT:~/share/knowledge/Shell$
因为 $? 只能获取值在0-255之间,所以当超过时,就返回不了正确的值了;
为了解决这样的问题,可以使用 echo 将数值输出,然后使用$()调用函数去接收;如下:
#!/bin/bash
function add() {
s=$[ $1 + $2 ] # s=$(( $1 + $2 ))
echo $s
}
read -p "请输入第一个整数:" a
read -p "请输入第二个整数:" b
sum=$( add $a $b )
echo "$a + $b = $sum"
ygt@YGT:~/share/knowledge/Shell$ ./func.sh
请输入第一个整数:100
请输入第二个整数:200
100 + 200 = 300
注意,如果函数中有多个echo输出,在调用函数的地方只能获取到最后一个echo输出的值!
八、综合运用 - 实现文件归档
这是一个对以上前七章的知识点的综合运用,实现对文件归档操作!
实际生产应用中,往往需要对重要数据进行归档备份;
需求:实现一个每天对指定目录归档备份的脚本,输入一个目录名称(末尾不带/),将目录下所有文件按天归档保存,并将归档日期附加在归档文件名上,放在/hong/ygt 下;
这里用到了归档命令:tar
后面可以加上-c 选项表示归档,加上-z 选项表示同时进行压缩,得到的文件后缀名为.tar.gz。
脚本实现如下:
#!/bin/bash
# 首先判断输入的参数个数是否为1
if [ $# -ne 1 ]; then
echo "参数个数错误!应输入一个参数,作为归档目录名"
exit
fi
# 从参数中获取目录名称
if [ -d $1 ]; then
echo
else
echo
echo "目录不存在!"
echo
exit
fi
DIR_NAME=$(basename $1)
DIR_PATH=$(cd $(dirname $1); pwd)
# 获取当前日期
DATE=$(date +%y%m%d)
# 定义生成的归档文件名
FILE=Shell_${DIR_NAME}_${DATE}.tar.gz
DEST=/home/ygt/$FILE
# 开始归档目录文件
echo "开始归档..."
echo
tar -zcf $DEST $DIR_PATH/$DIR_NAME
if [ $? -eq 0 ]; then
echo
echo "归档成功!"
echo "归档文件为:$DEST"
echo
else
echo "归档失败!"
echo
fi
九、正则表达式入门
正则表达式使用单个字符串来描述、匹配一系列符合某个语法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。在Linux 中,grep,sed,awk 等文本处理工具都支持通过正则表达式进行模式匹配。
9.1 常规匹配
一串不包含特殊字符的正则表达式匹配它自己,例如:
ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep DATE
DATE=$(date +%y%m%d)
FILE=Shell_${DIR_NAME}_${DATE}.tar.gz
就可以匹配到包含DATE的行!
9.2 常用特殊字符
9.2.1 ^
^ 匹配一行的开头,例如:
ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep ^i
if [ $# -ne 1 ]; then
if [ -d $1 ]; then
if [ $? -eq 0 ]; then
ygt@YGT:~/share/knowledge/Shell$
9.2.2 $
$ 匹配一行的结束,例如:
ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep gz$
FILE=Shell_${DIR_NAME}_${DATE}.tar.gz
ygt@YGT:~/share/knowledge/Shell$
9.2.3 .
. 匹配一个任意的字符,例如:
ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep h.me
DEST=/home/ygt/$FILE
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep h...
if [ $# -ne 1 ]; then
echo "参数个数错误!应输入一个参数,作为归档目录名"
echo "目录不存在!"
FILE=Shell_${DIR_NAME}_${DATE}.tar.gz
DEST=/home/ygt/$FILE
echo "开始归档..."
echo "归档成功!"
echo "归档文件为:$DEST"
echo "归档失败!"
ygt@YGT:~/share/knowledge/Shell$
9.2.4 *
* 不单独使用,他和上一个字符连用,表示匹配上一个字符 0 次或多次,例如:
ygt@YGT:~/share/knowledge/Shell$ cat /etc/passwd | grep ro*t
root:x:0:0:root:/root:/bin/bash
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
ygt@YGT:~/share/knowledge/Shell$
会匹配 rt, rot, root, rooot, roooot 等所有行.
另外还有 + ,匹配1次或n次;? 匹配0次或1次!
另外:.* 结合起来使用,意思是,匹配任意多个任意字符!
9.2.5 字符区间(中括号):[ ]
[ ] 表示匹配某个范围内的一个字符,例如:
[6,8] —— 匹配 6 或者 8 [0-9]------匹配一个 0-9 的数字;
[0-9]* —— 匹配任意长度的数字字符串;
[a-z] —— 匹配一个 a-z 之间的字符;
[a-z]* —— 匹配任意长度的字母字符串;
[a-c, e-f] —— 匹配 a-c 或者 e-f 之间的任意字符;
ygt@YGT:~/share/knowledge/Shell$ echo "541rooabcbbat" | grep r[o,a,b,c]*t
541rooabcbbat
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ cat /etc/passwd | grep r[a-z]*t
root:x:0:0:root:/root:/bin/bash
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
ygt@YGT:~/share/knowledge/Shell$
9.2.6 \
\ 表示转义,并不会单独使用。由于所有特殊字符都有其特定匹配模式,当我们想匹配某一特殊字符本身时(例如,我想找出所有包含 '$' 的行),就会碰到困难。此时我们就要将转义字符和特殊字符连用,来表示特殊字符本身,例如:
ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep '\$'
if [ $# -ne 1 ]; then
if [ -d $1 ]; then
DIR_NAME=$(basename $1)
DIR_PATH=$(cd $(dirname $1); pwd)
DATE=$(date +%y%m%d)
FILE=Shell_${DIR_NAME}_${DATE}.tar.gz
DEST=/home/ygt/$FILE
tar -zcf $DEST $DIR_PATH/$DIR_NAME
if [ $? -eq 0 ]; then
echo "归档文件为:$DEST"
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep '/\$'
DEST=/home/ygt/$FILE
tar -zcf $DEST $DIR_PATH/$DIR_NAME
ygt@YGT:~/share/knowledge/Shell$
就可以匹配到包含有 $ 的行了!就可以匹配到包含有 /$ 的行了!
9.3 练习,匹配手机号码
ygt@YGT:~/share/knowledge/Shell$ echo "15712345678" | grep ^1[3,4,5,7,8][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]&
ygt@YGT:~/share/knowledge/Shell$ 15712345678
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ echo "15712345678" | grep -E ^1[3,4,5,7,8][0-9]{9}&
ygt@YGT:~/share/knowledge/Shell$ 15712345678
另外,正则表达式还有其他很多高级的用法,这里还没有介绍,后期有时间学习了,再补上!
十、文本处理工具
10.1 cut
cut 的工作就是“剪”,具体的说就是在文件中负责剪切数据用的。cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段输出;
10.1.1 基本用法
cut [选项参数] filename
说明:默认分隔符是制表符
10.1.2 选项参数说明
选项参数 | 功能 |
---|---|
-f | 列号,提取第几列 |
-d | 分隔符,按照指定分隔符分割列,默认是制表符“\t” |
-c | 按字符进行切割 后加加 n 表示取第几列 比如 -c 1 |
10.1.3 案例
10.1.3.1 准备数据
ygt@YGT:~/share/knowledge/Shell$ vim cut.txt
ygt@YGT:~/share/knowledge/Shell$ cat cut.txt
guang shen dong
zhou zhen guan
wo wo wo
lai lai lai
le le le
10.1.3.2 以空格分割,获取第一列
ygt@YGT:~/share/knowledge/Shell$ cut -d " " -f 1 cut.txt
guang
zhou
wo
lai
le
10.1.3.3 以空格分割,获取第二、三列
ygt@YGT:~/share/knowledge/Shell$ cut -d " " -f 2,3 cut.txt
shen dong
zhen guan
wo wo
lai lai
le le
10.1.3.4 在 cut.txt 文件中切割出 guang
ygt@YGT:~/share/knowledge/Shell$ cat cut.txt | grep guang | cut -d " " -f 1
guang
10.1.3.5 切割出/etc/passwd 文件的用户、路径和bash
ygt@YGT:~/share/knowledge/Shell$ cat /etc/passwd | grep bash$
root:x:0:0:root:/root:/bin/bash
ygt:x:1000:1000:ygt,,,:/home/ygt:/bin/bash
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ cat /etc/passwd | grep bash$ | cut -d ":" -f 1,5,7
root:root:/bin/bash
ygt:ygt,,,:/bin/bash
ygt@YGT:~/share/knowledge/Shell$
10.1.3.6 截取某一范围内的列
cat /etc/passwd | grep bash$ | cut -d ":" -f 2-4
cat /etc/passwd | grep bash$ | cut -d ":" -f -4
cat /etc/passwd | grep bash$ | cut -d ":" -f 3-
10.1.3.7 截取ip地址
ygt@YGT:~/share/knowledge/Shell$ ifconfig | grep "广播"
inet 地址:192.168.160.128 广播:192.168.160.255 掩码:255.255.255.0
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ ifconfig | grep "广播" | cut -d " " -f 12 | cut -d ":" -f 2
192.168.160.128
ygt@YGT:~/share/knowledge/Shell$
10.2 gawk
一个强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理;
10.2.1 基本用法
gawk [选项参数] ‘/pattern1/{action1} /pattern2/{action2}...’ filename
pattern:表示 gawk 在数据中查找的内容,就是匹配模式;
action:在找到匹配内容时所执行的一系列命令;
10.2.2 选项参数说明
选项参数 | 功能 |
---|---|
-F | 指定输入文件分隔符 |
-v | 赋值一个用户定义变量 |
10.2.3 案例
注意:只有匹配了 pattern 的行才会执行 action
10.2.3.1 搜索 /etc/passwd 文件以 root 关键字开头的所有行,并输出该行的第7 列
ygt@YGT:~$ cat /etc/passwd | gawk -F ":" '/^root/{print $7}'
/bin/bash
10.2.3.2 搜索 /etc/passwd 文件以 root 关键字开头的所有行,并输出该行的第1 列和第7 列,中间以“,”号分割
ygt@YGT:~$ cat /etc/passwd | gawk -F ":" '/^root/{print $1","$7}'
root,/bin/bash
10.2.3.3 只显示/etc/passwd 的第一列和第七列,以逗号分割,且在所有行前面添加列名user,shell ,在最后一行添加"end!!!"
ygt@YGT:~$ cat /etc/passwd | gawk -F ":" 'BEGIN{print "user, shell"}/bash$/{print $1","$7} END{print "end!!!"}'
user, shell
root,/bin/bash
ygt,/bin/bash
end!!!
ygt@YGT:~$
注意:BEGIN 在所有数据读取行之前执行;END 在所有数据执行之后执行。
10.2.3.4 将 /etc/passwd 文件中的用户 id 增加数值 1 并输出
cat /etc/passwd | gawk -F ":" '{print $3}' # 这是没有+1的
cat /etc/passwd | gawk -v i=1 -F ":" '{print $3+i}' # 这是有+1的
10.2.4 gawk 的内置变量
变量 | 说明 |
---|---|
FILENAME | 文件名 |
NR | 已读的记录数(行号) |
NF | 浏览记录的域的个数(切割后,列的个数) |
10.2.5 案例
10.2.5.1 统计 /etc/passwd 文件名,每行的行号,每行的列数
ygt@YGT:~$ gawk -F : '{print "文件名:"FILENAME", 行号:"NR", 列号:"NF}' /etc/passwd
文件名:/etc/passwd, 行号:1, 列号:7
文件名:/etc/passwd, 行号:2, 列号:7
文件名:/etc/passwd, 行号:3, 列号:7
# 。。。中间省略。。。
文件名:/etc/passwd, 行号:24, 列号:7
文件名:/etc/passwd, 行号:25, 列号:7
10.2.5.2 查询 ifconfig 命令输出结果中的空行所在的行号
ygt@YGT:~$ ifconfig | gawk '/^$/ {print "空行:"NR}'
空行:9
空行:18
ygt@YGT:~$
十一、总结
至此,Linux Shell 基本入门教程完毕!
虽说没有很牛逼的知识点,但是学完这些,足以应付日常工作!
另外,本教程是本人在哔哩哔哩观看《尚硅谷》的视频学习而来!