参考教程

B站视频:尚硅谷Shell教程(shell自动化编程精讲)

Shell概述

Shell解析器

  • Linux提供的Shell解析器
1
2
3
4
5
6
7
[cuper@iZe4h75o51zvd0Z ~]$ cat /etc/shells
/bin/sh #重要
/bin/bash #重要
/sbin/nologin
/bin/dash
/bin/tcsh
/bin/csh
  • bash和sh的关系
1
2
3
[cuper@iZe4h75o51zvd0Z bin]$ ll | grep bash
-rwxr-xr-x 1 root root 1219248 Nov 9 2019 bash
lrwxrwxrwx 1 root root 4 Nov 9 2019 sh -> bash

sh为bash的软链接,功能一样。

  • CentOS默认的解析器
1
2
[cuper@iZe4h75o51zvd0Z bin]$ echo $SHELL
/bin/bash

Shell脚本入门

脚本格式

脚本以#!/bin/bash开头:指定解析器

Shell脚本注释即以”#“开头。

helloworld

创建脚本文件vim Helloworld.sh

1
2
#!/bin/bash
echo "Hello World!"

保存后执行:

1
2
3
bash Helloworld.sh
sh Helloworld.sh
./Helloworld.sh

以上三个命令效果相等,文件的绝对路径或相对路径均可。

注:./Helloworld.sh需要当前用户有可执行的权限,需要给所有者添加才可执行:chmod u+x Helloworld.sh

第一、二个命令本质是bash解析器帮你执行脚本,所以脚本本身不需要执行权限;第三个命令本质是脚本需要自己执行,所以需要执行权限。

多命令处理

一行一命令即可:

1
2
3
4
#!/bin/bash
cd /home/atguigu #路径跳转
touch cls.txt #创建文件
echo "I love cls" >>cls.txt #写入内容

脚本变量

系统变量

常用变量:

$HOME:当前用户的家目录
$PWD:当前绝对路径
$SHELL:当前脚本使用的解析器
$USER:当前用户

查看系统变量的值:echo $变量名$为取后面变量名对应的值的意思)

显示当前Shell中所有变量:set

使用一个定义过的变量,只要在变量名前面加$即可。

自定义变量

基本语法

  • 定义变量:变量=值(不用$
  • 撤销变量:unset 变量(不用$
  • 声明静态变量:readonly 变量=值(注意:静态变量只能一次赋值,无法unset,不能更改内容,不能重设,只有当前shell注销后才失效)

变量定义规则

  • 变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写
  • 等号两侧不能有空格。
  • 在bash中,变量默认类型都是字符串类型,无法直接进行数值运算。
  • 变量的值如果有空格,需要使用双引号或单引号括起来。

全局变量

把变量提升为全局环境变量,可供其他Shell程序使用:export 变量名

特殊变量

$n

传递给脚本的第n个参数值,n为数字,$0代表该脚本名称,$1-$9代表第一到第九个参数,十以上的参数需要用大括号包含(如${10}),执行时输入的实参直接空格跟在脚本执行命令后面:

1
./xxx.sh $1的值 $2的值 ...

$#

获取所有输入参数的个数,常用于循环,在脚本里直接调用$#

$*

代表命令行中所有的参数,把所有的参数看成一个整体

$@

代表命令行中所有的参数,把每个参数区分对待

$?

显示最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。

运算符

两种语法:

  1. $((运算式))$[运算式] :作为变量
  2. expr + , - , \*, /, % :分别为加,减,乘(需要转义),除,取余,直接运算在终端上,expr运算符间要有空格

条件判断(测试表达式)

语法

[ condition ]

注意:condition前后要有空格,条件非空即为true,[ xxx ]返回true,[] 返回false。

常用判断条件

  1. 两个整数之间比较:[ num1 -参数 num2 ]
  • = 字符串比较
  • -lt 小于(less than)
  • -le 小于等于(less equal)
  • -eq 等于(equal)
  • -gt 大于(greater than)
  • -ge 大于等于(greater equal)
  • -ne 不等于(Not equal)
  1. 按照文件权限进行判断:[ -参数 文件 ]
  • -r 有读的权限(read)
  • -w 有写的权限(write)
  • -x 有执行的权限(execute)
  1. 按照文件类型进行判断:[ -参数 文件 ]
  • -f 文件存在并且是一个常规的文件(file)
  • -e 文件存在(existence)
  • -d 文件存在并是一个目录(directory)

多条件判断

expression1 && expression2:与条件,表示expression1为真时,才执行expression2

expression1 || expression2: 或条件,表示expression1为假才执行expression2,若expression1为真则直接返回true

expression1 ; expression2:不管expression1执行是否正确,expression2都会执行(可用于脚本里把多个命令写在同一行)

常用:[ condition ] && echo 1 || echo 0 三元判断式用于直接测试condition是否返回true。

参考博文

流程控制

if 判断

语法:

1
2
3
4
5
6
7
8
if [ 条件判断式 ];then 
程序
elif [ 条件判断式 ];then
程序
...
else
程序
fi

1
2
3
4
5
6
7
8
9
10
if [ 条件判断式 ] 
then
程序
elif [ 条件判断式 ]
then
程序
...
else
程序
fi

注:

  • [ 条件判断式 ]中括号和条件判断式之间必须有空格
  • if后要有空格

case 语句

语法:

1
2
3
4
5
6
7
8
9
10
11
12
case $变量名 in 
"值1")
如果变量的值等于值1则执行的程序1
;;
"值2")
如果变量的值等于值2则执行的程序2
;;
…省略其他分支…
*)
如果变量的值都不是以上的值则执行的程序(即default)
;;
esac

注:

  • case行尾必须为单词in,每一个模式匹配必须以右括号)结束。
  • 双分号;;表示命令序列结束,相当于java中的break
  • 最后的*)表示默认模式,相当于java中的default

for 循环

语法1:

1
2
3
4
for (( 初始值;循环控制条件;变量变化 )) 
do
程序
done

语法2:

1
2
3
4
for 变量 in 值1 值2 值3… 
do
程序
done

注:in后面常用$*$@,在不带双引号时两者等价,都以$1 $2 … $n的形式输出所有参数。

若被双引号""包含时:

  • "$*"会将所有的参数作为一个整体,以"$1 $2 … $n"的形式输出所有参数,若在for循环的in里,所有参数看成是一个整体,所以for循环只会循环一次;
  • "$@"会将各个参数分开,以"$1" "$2""$n"的形式输出所有参数,若在for循环的in里,每个参数都看成是独立的,所以"$@"中有几个参数,就会循环几次 。

while 循环

语法:

1
2
3
4
while [ 条件判断式 ] 
do
程序
done

注:

  • [ 条件判断式 ]中括号和条件判断式之间必须有空格
  • while后要有空格

read读取控制台输入

语法:

1
read (选项) (参数)

选项:

  • -p:指定读取值时的提示符;

  • -t:指定读取值时等待的时间(秒)。

参数:

  • 变量:指定读取值的变量名

函数

系统函数

basename

1
basename [string或pathname] [suffix]

功能:去掉文件名的路径和后缀,会删掉所有的前缀包括最后一个/字符,然后将字符串显示出来,常用于从绝对路径里截取出文件名称。

选项:suffix为后缀名,如果suffix被指定了,basename会将pathname或string中的suffix同时去掉。

dirname

1
dirname 文件绝对路径

功能:从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录的部分)

自定义函数

实质:自定义一个终端命令

语法:

1
2
3
4
5
6
[ function ] funname[()] #这一行中括号里的function和小括号()均意为可写可不写
{
Action;
[return int;] #return可选
}
funname

注意:

  • 必须在调用函数地方之前先声明函数,shell脚本是逐行运行,不会像其它语言一样先编译。
  • 函数返回值只能通过系统变量$?获得,可以显示加return返回;如果不加,将以最后一条命令运行结果,作为返回值。return后跟数值n(0-255)。

Shell工具

cut

功能

cut的工作就是“剪”,具体的说就是在文件中负责剪切数据用的。cut命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段输出。

语法

1
cut [选项参数] filename

参数

选项参数 功能
-f 列号,提取第几列
-d 分隔符,按照指定分隔符分割列(默认值为制表符)

案例

  • 在以空格为分隔符的cut.txt文件中切割出在某一行第一列里的”guan”:
    1
    cat cut.txt | grep "guan" | cut -d " " -f 1
  • 选取系统PATH变量值,第2个:开始后的所有路径:
    1
    echo $PATH | cut -d : -f 2- #`n-`表示从第n列开始到最后的所有字符串,`-n`表示从字符串开始取到第n列,均包含第n列本身
  • 切割ifconfig后打印的IP地址:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    [cuper@iZe4h75o51zvd0Z ~]$ ifconfig
    eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
    inet 172.16.19.31 netmask 255.255.240.0 broadcast 172.16.31.255
    inet6 fe80::216:3eff:fe12:e6ca prefixlen 64 scopeid 0x20<link>
    ether 00:16:3e:12:e6:ca txqueuelen 1000 (Ethernet)
    RX packets 9518632 bytes 4811078305 (4.4 GiB)
    RX errors 0 dropped 0 overruns 0 frame 0
    TX packets 9940677 bytes 4450481278 (4.1 GiB)
    TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

    lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
    inet 127.0.0.1 netmask 255.0.0.0
    inet6 ::1 prefixlen 128 scopeid 0x10<host>
    loop txqueuelen 1000 (Local Loopback)
    RX packets 0 bytes 0 (0.0 B)
    RX errors 0 dropped 0 overruns 0 frame 0
    TX packets 0 bytes 0 (0.0 B)
    TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

    [cuper@iZe4h75o51zvd0Z ~]$ ifconfig eth0 | grep "inet " | cut -d 't' -f 2 | cut -d ' ' -f 2
    172.16.19.31

sed

功能

sed是一种流编辑器,它一次处理一行内容。处理时,把当前处理的行存储在临时缓冲区中,称为“模式空间”,接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变,除非使用重定向存储输出。

语法

1
sed [选项参数] 'command' filename

选项参数

选项参数 功能
-e 将下一个参数解释为一个sed指令,只有当命令行上给出多个sed指令时才需要使用-e选项

命令功能

命令 功能描述
a 新增,a的后面可以接字符串,在下一行出现
d 删除
s 查找并替换

命令的使用:'command' = 'na xxxx'表示在第n行下面加入xxxx内容。

案例

  • 将’hello’这个单词插入到sed.txt第二行下,打印:
1
sed '2a hello' sed.txt

注意:文件并没有改变

  • 删除sed.txt文件所有包含hello的行:
1
sed '/wo/d' sed.txt
  • 将sed.txt文件中hello替换为hi:
1
sed 's/hello/hi/g' sed.txt

注意:g表示global,全部替换

  • 将sed.txt文件中的第二行删除并将hello替换为hi:
1
sed -e '2d' -e 's/hello/hi/g' sed.txt

awk

功能

一个强大的文本分析工具,把文件逐行地读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理。

语法

1
awk [选项参数] 'pattern1{action1} pattern2{action2} ...' filename
  • pattern:表示awk在数据中查找的内容,就是匹配模式,若没有则表示全部匹配
  • action:在找到匹配内容时所执行的一系列命令

选项参数

选项参数 功能
-F 指定输入文件的分隔符
-v var=value,赋值一个用户定义变量,方便action调用
-f 后接一个保存了awk程序的文件,代替在命令行指定awk程序

awk指令里的BEGIN块和END块

用法:

1
pattern{action}=BEGIN/END{action}
  • BEGIN用于初始化FS变量(列分隔符),打印标题,或者初始化后需要在程序中调用的全局变量
  • END用于执行最后的运算或者打印最终的输出结果
  • END块和BEGIN不是必须的

awk的内置变量

变量 说明
FILENAME 文件名
NR 已读的记录数
NF 浏览记录的域的个数(即切割后列的个数)

案例

  • 搜索passwd文件以root关键字开头的所有行,并输出该行的第1列和第7列,中间以,号分割:
1
awk -F : '/^root/{print $1","$7}' /etc/passwd #/^root/为匹配root开头的行

注意:只有匹配了pattern的行才会执行action,在awk中使用正则匹配,正则表达式必须要放在//

  • 只显示/etc/passwd的第一列和第七列,以逗号分割,且在所有行前面添加列名”user,shell”,在最后一行添加”The End”:
1
awk -F : 'BEGIN{print "user,shell"} {print $1","$7} END{print "The End."}' /etc/passwd
  • 将passwd文件中的用户id增加数值1并输出:
1
awk -F : -v i=1 '{print $3+i}' /etc/passwd
  • 统计passwd文件名,每行的行号,每行的列数:
1
awk -F : '{print FILENAME "," NR "," NF}' /etc/passwd
  • 切割IP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[cuper@iZe4h75o51zvd0Z ~]$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.16.19.31 netmask 255.255.240.0 broadcast 172.16.31.255
inet6 fe80::216:3eff:fe12:e6ca prefixlen 64 scopeid 0x20<link>
ether 00:16:3e:12:e6:ca txqueuelen 1000 (Ethernet)
RX packets 9566150 bytes 4829288932 (4.4 GiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 10006033 bytes 4463500274 (4.1 GiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1000 (Local Loopback)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

[cuper@iZe4h75o51zvd0Z ~]$ ifconfig eth0 | grep "inet " | awk -F 't' '{print $2}' | awk -F ' ' '{print $1}'
172.16.19.31
  • 查询sed.txt中空行所在的行号:
1
awk '/^$/{print NR}' sed.txt #^开头,$结尾,/^$/表示空行

sort

功能

将文件进行排序,并将排序结果标准输出。

语法

1
sort (选项) (参数)

选项

选项 说明
-n 依照数值的大小排序
-r 以相反的顺序来排序
-t 设置排序时所用的分隔字符
-k 指定需要排序的列

参数

指定待排序的文件列表

案例

  • 按照:分割后的第三列倒序排序passwd:
1
sort -t : -nrk 3 /etc/passwd

面试真题

京东

Q1.使用Linux命令查询file1中空行所在的行号:

1
awk '/^$/{print NR}' sed.txt

Q2.有文件chengji.txt内容如下:

张三 40
李四 50
王五 60

使用Linux命令计算第二列的和并输出:

1
cat chengji.txt | awk -F " " '{sum+=$2} END{print sum}'

搜狐&和讯网

Q.Shell脚本里如何检查一个文件是否存在?如果不存在该如何处理?

1
2
3
4
5
6
7
#!/bin/bash

if [ -f file.txt ]; then
echo "文件存在!"
else
echo "文件不存在!"
fi

新浪

Q.用shell写一个脚本,对文本中无序的一列数字排序,并计算总和

1
sort -n test.txt | awk '{a+=$0;print $0} END{print "SUM="a}'

金和网络

Q.请用shell脚本写出查找当前文件夹(/home)下所有的文本文件内容中包含有字符”shen”的文件名称

1
grep -r "shen" /home | cut -d ":" -f 1 #只用grep会包含":文件内容",需要cut一下