抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Shell脚本

​ shell是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。Shell 既是一种命令语言,又是一种程序设计语言。Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。

​ 在linux中有很多类型的shell,不同的shell具备不同的功能,shell还决定了脚本中函数的语法,Linux中默认的shell是bash,流行的shell有ash、bash、ksh、csh、zsh等,不同的shell都有自己的特点以及用途。

脚本规范

1
2
3
#!/bin/bash				[指定告知系统当前这个脚本要使用的shell解释器]
Shell相关指令

Bash 常用快捷键

快捷键 注释
ctrl+A 把光标移动到命令行开头。如果我们输入的命令过长,想要把光标移动到命令行开头时使用。
ctrl+E 把光标移动到命令行结尾。
ctrl+C 强制终止当前的命令。
ctrl+L 清屏,相当于clear命令。
ctrl+U 删除或剪切光标之前的命令。我输入了一行很长的命令,不用使用退格键一个一个字符的删除,使用这个快捷键会更加方便
ctrl+K 删除或剪切光标之后的内容。
ctrl+Y 粘贴ctrl+U或ctul+K剪切的内容。
ctrl+R 在历史命令中搜索,按下ctrl+R之后,就会出现搜索界面,只要输入搜索内容,就会从历史命令中搜索。
ctrl+D 退出当前终端。
ctrl+Z 暂停,并放入后台。这个快捷键牵扯工作管理的内容,我们在系统管理章节详细介绍。
ctrl+S 暂停屏幕输出。
ctrl+Q 恢复屏幕输出。

输入输出重定向

设备 设备名 文件描述符 类型
键盘 /dev/stdin 0 标准输入
显示器 /dev/stdout 1 标准输出
显示器 /dev/stderr 2 标准错误输出

输入重定向:是指不使用系统提供的标准输入端口,而进行重新的指定。换言之,输入重定向就是不使用标准输入端口输入文件,而是使用指定的文件作为标准输入设备。(重定向简单理解就是使用 “<”符来修改标准输入设备)

类型 符号(语法) 功能
标准输入 命令<文件1 命令把文件1的内容作为标准输入设备
标识符限定输入 命令<<标识符 命令把标准输入中读入内容,直到遇到“标识符”分解符为止
输入输出重定向(同时使用) 命令< 文件1 >文件2 命令把文件1的内容作为标准输入,把文件2作为标准输出

输出重定向:通俗的讲,重定向输出就是把要输出的文件信息写入到一个文件中去,而不是将要输出的文件信息输出到控制台,在linux中,默认的标准输出设备是控制台,用户输出的信息默认情况下都会显示到控制台

&表示全部文件,文件不管对错,1表示标准输出文件,2表示标准错误输出。

类型 符号 作用
标住输出重定向 命令 > 文件 以覆盖方式,把命令的正确输出内容输出到指定的文件或设备当中
标住输出重定向 命令 >> 文件 以追加方式,把命令的正确输出内容输出到指定的文件或设备当中
标准错误输出重定向 错误命令2 > 文件 以覆盖方式,把命令的错误输出输出到指定的文件或设备当中
标准错误输出重定向 错误命令2 >> 文件 以追加方式,把命令的错误输出输出到指定的文件或设备当中
正确输出和错误输出同时保存 命令 > 文件 2>&1 以覆盖的方式,把正确输出和错误输出都保存到同一个文件当中。
正确输出和错误输出同时保存 命令 >> 文件 2>&1 以追加的方式,把正确输出和错误输出都保存到同一个文件当中
正确输出和错误输出同时保存 命令 &> 文件 以覆盖的方式,把正确输出和错误输出都保存到同一个文件当中。
正确输出和错误输出同时保存 命令 &>> 文件 以追加的方式,把正确输出和错误输出都保存到同一个文件当中
正确输出和错误输出同时保存 命令 >> 文件1 2>>文件2 把正确的输出追加到文件1中,把错误的输出追加到文件2中

/dev/null

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到/dev/null中

1
[root@localhost ~]$  command > /dev/null

多命令顺序执行

多命令执行符 作用 格式
命令1 ;命令2 多个命令顺序执行,命令之间没有任何逻辑联系
&& 命令1 && 命令2 当命令1正确执行(? = 0 ) , 则 命 令 2 才 会 执 行 当 命 令 1 执 行 不 正 确 ( ?=0),则命令2才会执行 当命令1执行不正确(?=0),则命令2才会执行当命令1执行不正确(?≠0),则命令2不会执行
|| 命令1 || 命令2 当命令1执行不正确(? ≠ 0 ) , 则 命 令 2 才 会 执 行 当 命 令 1 正 确 执 行 ( ?≠0),则命令2才会执行当命令1正确执行(?=0),则命令2不会执行

shell脚本

shell脚本执行方式

1
2
3
4
5
6
7
8
9
10
11
12
[root@192 treeman]# vim demo.sh
#!/bin/bash
echo “hello world”
--第一种:给文件增加执行权限
[root@192 treeman]# chmod +x demo.sh
[root@192 treeman]# ./demo.sh
hello world
[root@192 treeman]#
--第二种:通过Bash调用执行脚本
[root@192 treeman]# bash ./demo.sh
hello world
[root@192 treeman]#

变量

变量的命名规则:

  1. 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头。
  2. 等号左右两侧不能有空格,可以使用下划线“_”,变量的值如果有空格,需要使用单引号或双引号包括。如:“test=“hello world!””。其中双引号括起来的内容“$”,“(”和反引号都拥有特殊含义,而单引号括起来的内容都是普通字符。
  3. 不能使用标点符号,不能使用bash里的关键字(可用help命令查看保留关键字)。
  4. 环境变量建议大写,便于区分
  5. 如果需要增加变量的值,那么可以进行变量值的叠加。不过变量需要用双引号包含”$变量名”或用${变量名}包含变量名。
1
2
3
4
5
6
7
8
[root@192 treeman]# x1=123 --赋值
[root@192 treeman]# x1="$x1"456 --叠加值
[root@192 treeman]# echo $x1
123456
[root@192 treeman]# x1=${x1}789 --叠加另一种方式
[root@192 treeman]# echo $x1
123456789
[root@192 treeman]#

关于单双引号的问题:
双引号能够识别变量,双引号能够实现转义(类似于“*”)
单引号是不能识别变量,只会原样输出,单引号是不能转义的

符号 作用
’ ’ 单引号。在单引号中所有的特殊符号,如“$”和”(反引号)都没有特殊含义。单引号括起来的都是普通字符,会原样输出
“ ” 双引号。在双引号中特殊符号都没有特殊含义,但是“$”,“`”(esc键下面)和“\”是例外,拥有“调用变量的值”、“引用命令”和“转义符”的特殊含义。
· · 反引号。反引号括起来的内容是系统命令,在Bash中会先执行它。和()作 用 一 样 ,不过推荐使用(),因为反引号非常容易看错。
$() 和反引号作用一样,用来引用系统命令。(推荐使用)
() 用于一串命令执行时,()中的命令会在子Shell中运行
{} 用于一串命令执行时,{ }中的命令会在当前Shell中执行。也可以用于变量变形与替换。
[ ] 用于变量的测试。
# 在Shell脚本中,#开头的行代表注释。
$ 用于调用变量的值,如需要调用变量name的值时,需要用$name的方式得到变量的值。
\ 转义符,跟在\之后的特殊符号将失去特殊含义,变为普通字符。如$将输出“$”符号,而不当做是变量引用

单引号和双引号

1
2
3
4
5
6
7
8
9
10
11
12
[root@192 treeman]# date
2024年 08月 08日 星期四 10:44:52 CST
[root@192 treeman]# echo `date`
2024年 08月 08日 星期四 10:45:00 CST
[root@192 treeman]# echo date
date
[root@192 treeman]# echo '`date`'
`date`
[root@192 treeman]# echo "`date`"
2024年 08月 08日 星期四 10:45:41 CST
[root@192 treeman]#

反引号

1
2
3
4
5
6
7
[root@192 treeman]# echo ll
ll
[root@192 treeman]# echo `ll`
总用量 4 -rwxr-xr-x. 1 root root 33 8月 8 10:15 demo.sh
[root@192 treeman]# echo $(ll)
总用量 4 -rwxr-xr-x. 1 root root 33 8月 8 10:15 demo.sh
-- $()与``等效

变量类型

  1. 用户自定义变量: 这种变量是最常见的变量,由用户自由定义变量名和变量的值。
  2. 环境变量: 这种变量中主要保存的是和系统操作环境相关的数据,比如当前登录用户,用户的家目录,命令的提示符等。环境变量的变量名可以自由定义,但是一般对系统起作用的环境变量的变量名是系统预先设定好的。
  3. 位置参数变量: 这种变量主要是用来向脚本当中传递参数或数据的,变量名不能自定义,变量作用是固定的。
  4. 预定义变量: 是Bash中已经定义好的变量,变量名不能自定义,变量作用也是固定的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
[root@192 treeman]# x1="tree man" --定义变量,有空格需要引号
[root@192 treeman]# echo $x1 --输出变量
tree man
[root@192 treeman]# set --查看变量的复制内容
BASH=/usr/bin/bash
XDG_RUNTIME_DIR=/run/user/0
XDG_SESSION_ID=5
_=jiaD
colors=/root/.dircolors
x1='tree man' --自定义的变量
[root@192 treeman]# set -u --u表示如果调用没有设定的变量会有报错。默认是没有任何输出的。
[root@192 treeman]# ll
总用量 4
-rwxr-xr-x. 1 root root 33 8月 8 10:15 demo.sh
[root@192 treeman]# echo $file
bash: file: 为绑定变量
[root@192 treeman]# set -x #如果设定了-x选项,会在每个命令执行之前,先把命令输出一遍
++ printf '\033]0;%s@%s:%s\007' root 192 /treeman
[root@192 treeman]# ll
+ ls --color=auto -l --color=auto
总用量 4
-rwxr-xr-x. 1 root root 33 8月 8 10:15 demo.sh
++ printf '\033]0;%s@%s:%s\007' root 192 /treeman
[root@192 treeman]# set +x --取消启动的x参数
+ set +x
[root@192 treeman]# ll
总用量 4
-rwxr-xr-x. 1 root root 33 8月 8 10:15 demo.sh
[root@192 treeman]# unset x1 --删除x1参数
[root@192 treeman]# echo $x1
bash: x1: 为绑定变量

[root@192 treeman]# export age=18
#使用export声明的变量即是环境变量
[root@192 treeman]# env |grep ag
age=18
#set命令可以查看所有变量,而env命令只能查看环境变量。
[root@192 treeman]# unset age -- 删除环境变量gender
[root@192 treeman]# env |grep ag

位置参数变量

位置参数变量 作用
$n n为数字,$0表示当前 Shell 脚本程序的名称,$1-9 代 表 第 一 到 第 九 个 参 数 , 十 以 上 的 参 数 需 要 用 大 括 号 包 含 , 如{10}
$* 这个变量代表命令行中所有的参数,$把所有的参数看成一个整体
$@ 这个变量也代表命令行中所有的参数,不过$@把每个参数区分对待
$# 这个变量代表命令行中所有参数的个数
demo1.sh
1
2
3
4
5
6
#!/bin/bash
echo "脚本名字:$0"
echo "这是第一个参数:$1"
echo "这是第二个参数:$2"
echo "这是第三个参数:$3"

示例:

bash demo1.sh 1 2 3 #带参数运行脚本
脚本名字:demo1.sh
这是第一个参数:1
这是第二个参数:2
这是第三个参数:3

$*会把接收的所有参数当成一个整体对待,而$@则会区分对待接收到的所有参数。举个例子:

demo2.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash
x=1
#定义变量x的值为1
echo '$*循环例子:
for i in "$*"
#定义for循环,in后面有几个值,for会循环多少次,注意“$*”要用双引号括起来
#每次循环会把in后面的值赋予变量i
#Shell把$*中的所有参数看成是一个整体,所以这个for循环只会循环一次
do
echo "The parameters is: $i , x = $x"
#打印变量$i的值
done
x=1
#定义变量x的值为1
echo '$@循环例子:'
for y in "$@"
#同样in后面的有几个值,for循环几次,每次都把值赋予变量y
#Shel1中把“$@”中的每个参数都看成是独立的,所以“$@”中有几个参数,就会循环几次
do
echo "The parameter $x is: $y"
#输出变量y的值
x=$(( $x +1 ))
#变量x每次循环都加1,为了输出时看的更清楚
done

#带参数使用脚本
bash demo2.sh t r e e m
$*循环例子:
The parameters is: t r e e m , x = 1
$@循环例子:
The parameter1 is: t
The parameter2 is: r
The parameter3 is: e
The parameter4 is: e
The parameter5 is: m

预定义变量

预定义变量 作用
$? 最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。
$$ 当前进程的进程号(PID)
$! 后台运行的最后一个进程的进程号(PID)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@192 treeman]# ls
demo1.sh demo2.sh demo.sh
#执行ls命令,正确执行和返回数据
[root@192 treeman]# echo $?
0
#则$?返回0,执行正确值
[root@192 treeman]# ls insatall.log
ls: 无法访问insatall.log: 没有那个文件或目录
#错误执行
[root@192 treeman]# echo $?
2
#返回错误的值,非0值都属于错误值。

###再来说说$$ $!
[root@localhost sh]$ vi demo3.sh
#!/bin/bash
echo "The current process is $$"
#输出当前进程的PID.
#这个PID就是variable.sh这个脚本执行时,生成的进程的PID
find /root -name hello.sh &
#使用find命令在root目录下查找hello.sh文件,符号&的意思是把命令放入后台执行
echo "The last one Daemon process is $!"
#输出这个后台执行命令的进程的PID,也就是输出find命令的PID号

只读变量

1
2
3
4
5
6
7
8
9
10
11
[root@192 treeman]# readonly a=123
##只读变量建立
[root@192 treeman]# echo $a
123
##可以正常读取
[root@192 treeman]# a=234
-bash: a: 只读变量
##修改不被允许
[root@192 treeman]# echo $a
123
##且只读变量不可被删除,注意使用

交互式键盘输入

read命令
1
2
3
4
5
6
7
8
9
10
11
12
13
14
read [选项][变量名]
选项:
-a 后跟一个变量,该变量会被认为是个数组,然后给其赋值,默认是以空格为分割符。
-p: “提示信息”:在等待read输入时,输出提示信息
-t: 秒数:read命令会一直等待用户输入,使用此选项可以指定等待时间
-n: 数字:read命令只接受指定的字符数,就会执行
-s: 隐藏输入的数据,适用于机密信息的输入
-d: 后面跟一个标志符,其实只有其后的第一个字符有用,作为结束的标志。
-e: 在输入的时候可以使用命令补全功能。
变量名:
变量名可以自定义,如果不指定变量名,会把输入保存为默认变量REPLY.
如果只提供了一个变量名,则整个输入行赋予该变量.
如果提供了一个以上的变量名,则输入行分为若干字,一个接一个地赋予各个变量,而命令行上的最后一个变量取得剩余的所有字

例子如下:

read.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash

read -t 30 -p "Please input your name: " name
#提示“请输入姓名”并等待30 秒,把用户的输入保存入变量name 中
echo "your name is $name"
#看看变量“$name”中是否保存了你的输入

read -s -t 30 -p "Please enter your age: " age
#提示“请输入年龄”并等待30秒,把用户的输入保存入变量age中
#年龄是隐私,所以我们用“-s”选项隐藏输入
echo -e "\n"
#调整输出格式,如果不输出换行,一会的年龄输出不会换行
echo "your age is $age"

read -n 1 -t 30 -p "Please select your gender[M/F]:" gender
#提示“请选择性别”并等待30秒,把用户的输入保存入变量gender
#使用“-n1”选项只接收一个输入字符就会执行(都不用输入回车)
echo -e "\n"
echo "Sex is $gender"

bash read.sh #运行脚本
Please input your name:zhangsan
your name is zhangsan
Please input your age:

your age is 19
Please input your sex:男

Sex is 男

SHELL运算符

在shell中,运算符和其他编程脚本语言一样,常见的有算数运算符、关系运算符、逻辑运算符、字符串运算符、文件测试运算符等

算数运算符

原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
例如,两个数相加(注意使用的是反引号 ` 而不是单引号 ‘)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[root@192 treeman]# b=`expr 3 + 4`
[root@192 treeman]# echo $b
7
#注意
#表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。
#完整的表达式要被 ` ` 包含,注意这个字符不是常用的单引号,在 Esc 键下边

若a=10,b=20
|加法 |expr $a + $b 结果为 30。
|减法 |expr $a - $b 结果为 -10。
*| 乘法 |expr $a \* $b 结果为 200。
/ |除法 |expr $b / $a 结果为 2。
% |取余| expr $b % $a 结果为 0。
= |赋值| a=$b 将把变量 b 的值赋给 a。
==| 相等。用于比较两个数字,相同则返回 true(真)。| [ $a == $b ] 返回 false(假)。
!= |不相等。用于比较两个数字,不相同则返回 true。 |[ $a != $b ] 返回 true。

关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20

运算符 单词 说明 举例
-eq equal 检测两个数是否相等,相等返回 true。 [ $a -eq $b ] 返回 false。
-ne not equal 检测两个数是否相等,不相等返回 true。 [ $a -ne $b ] 返回 true。
-gt great than 检测左边的数是否大于右边的,如果是,则返回 true。 [ $a -gt $b ] 返回 false。
-lt less than 检测左边的数是否小于右边的,如果是,则返回 true。 [ $a -lt $b ] 返回 true。
-ge great than or equal 检测左边的数是否大于等于右边的,如果是,则返回 true。 [ $a -ge $b ] 返回 false
-le less than or equal 检测左边的数是否小于等于右边的,如果是,则返回 true。 [ $a -le $b ] 返回 true
demo.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
#接受用户的输入
read -p '请输入需要查询的用户名:' username

#获取指定用户名在passwd文件中出现的次数
count=$(cat /etc/passwd | grep ^$username | wc -l)
#count=`cat /etc/passwd | grep ^$username | wc -l`

#判断出现的次数,如果次数=0则用户不存在,反之存在
if [ $count == 0 ]
then
echo '用户不存在'
else
echo '用户存在'
fi
##注意变量赋值时,=左右两边不需要空格,否则会被当成命令

逻辑运算符

下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20

或运算:一个为真即为真,全部为假才是假
与运算:一个为假即为假,全部为真才是真

运算符 说明 举例
! 非运算,表达式为 true 则返回 false,否则返回 true。 [ ! false ] 返回 true
-o 或(或者)运算,有一个表达式为 true 则返回 true。 [ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a 与(并且)运算,两个表达式都为 true 才返回 true。 [ $a -lt 20 -a $b -gt 100 ] 返回 false

字符串运算符

下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”

运算符 说明 举例
= 检测两个字符串是否相等,相等返回 true。 [ $a = $b ] 返回 false。
!= 检测两个字符串是否相等,不相等返回 true。 [ $a != $b ] 返回 true
-z 检测字符串长度是否为0,为0返回 true。 [ -z $a ] 返回 false。
-n 检测字符串长度是否为0,不为0返回 true。 [ -n $a ] 返回 true
str 检测字符串是否为空,不为空返回 true。 [ $a ] 返回 true

文件测试运算符(重点)

文件测试运算符用于检测 Unix/Linux 文件的各种属性

操作符 说明 举例
-b file 检测文件是否是块设备文件,如果是,则返回 true。 [ -b $file ] 返回 false
-c file 检测文件是否是字符设备文件,如果是,则返回 true。 [ -c $file ] 返回 false。
-d file 检测文件是否是目录,如果是,则返回 true。 [ -d $file ] 返回 false
-f file 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。 [ -f $file ] 返回 true。
-g file 检测文件是否设置了 SGID 位,如果是,则返回 true。 [ -g $file ] 返回 false
-k file 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。 [ -k $file ] 返回 false。
-p file 检测文件是否是有名管道,如果是,则返回 true。 [ -p $file ] 返回 false
-u file 检测文件是否设置了 SUID 位,如果是,则返回 true。 [ -u $file ] 返回 false。
-r file 检测文件是否可读,如果是,则返回 true。 [ -r $file ] 返回 true
-w file 检测文件是否可写,如果是,则返回 true。 [ -w $file ] 返回 true。
-x file 检测文件是否可执行,如果是,则返回 true。 [ -x $file ] 返回 true
-s file 检测文件是否为空(文件大小是否大于0),不为空返回 true。 [ -s $file ] 返回 true。
-e file 检测文件(包括目录)是否存在,如果是,则返回 true。 [ -e $file ] 返回 true

注意:权限几个判断,如果只有一个部分符合,则认为是有权限的

流程控制

if条件判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
if [ 条件判断式 ]
then
程序
fi

###########################
if [ 条件判断式 ]
then
条件成立时,执行的程序
else
条件不成立时,执行的另一个程序
fi
##########################
if [ 条件判断式1 ]
then
当条件判断式1成立时,执行程序1
elif [ 条件判断式2 ]
then
当条件判断式2成立时,执行程序2
…省略更多条件…
else
当所有条件都不成立时,最后执行此程序
fi


nmap端口扫描
1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost ~]$ nmap -sT 域名或IP
选项:
-s 扫描
-T 扫描所有开启的TCP端口


#知道了nmap命令的用法,我们在脚本中使用的命令就是为了截取http的状态

#就证明apache启动正常,否则证明apache启动错误。来看看脚本中命令的结果:
[root@localhost ~]$ nmap -sT 192.168.4.210 | grep tcp | grep http | awk ' fprint $2}’
#扫描指定计算机,提取包含tcp 的行,在提取包含httpd 的行,截取第二列open
#把截取的值赋予变量port

多分支case条件语句

case语句和if…elif…else语句一样都是多分支条件语句,不过和if多分支条件语句不同的是,case语句只能判断一种条件关系,而if语句可以判断多种条件关系。

语法:

case $变量名 in
“值1”)
如果变量的值等于值1,则执行程序1
;;
“值2”)
如果变量的值等于值2,则执行程序2
::
…省略其他分支…
*)
如果变量的值都不是以上的值,则执行此程序
;;
esac

if-case.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/bash
read -p "请输入一个字符,并按Enter确认:" KEY
case "$KEY" in
[a-z]|[A-Z])
echo "您输入的是字母"
;;

[0-9])
echo "您输入的是数字"
;;

*)
echo "您输入的是其他字符"
;;
esac

  • case语句,会取出变量中的值,然后与语句体中的值逐一比较。如果数值符合,则执行对应的程序,如果数值不符,则依次比较下一个值。如果所有的值都不符合,则执行 “*)” (*代表所有其他值)中的程序。
  • case语句以“case”开头,以“esac”结尾
  • 每一个分支程序之后要通过“;;”双分号结尾,代表该程序段结束(千万不要忘记,每次写case语句,都不要忘记双分号

for循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
for 变量 in 值1 值2 值3 …(可以是一个文件等)
do
程序
done

这种语法中for循环的次数,取决于in后面值的个数(空格分隔),有几个值就循环几次,并且每次循环都把值赋予变量。
也就是说,假设in后面有三个值,for会循环三次,第一次循环会把值1赋予变量,第二次循环会把值2赋予变量,以此类推。

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

语法二中需要注意:
初始值:在循环开始时,需要给某个变量赋予初始值,如i=1;

循环控制条件:用于指定变量循环的次数,如i<=100,则只要i的值小于等于100,循环就会继续;

变量变化:每次循环之后,变量该如何变化,如i=i+1。代表每次循环之后,变量i的值都加1。

#################################

while循环

对while循环来讲,只要条件判断式成立,循环就会一直继续,直到条件判断式不成立,循环才会停止。

语法:

while [ 条件判断式 ]
do
程序
done

addnum.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash
PRICE=$(expr $RANDOM % 1000)
TIMES=0

echo "商品的价格为0-999之间,猜猜看是多少?"
while true
do
read -p "请输入您猜的价格:" INT
let TIMES++

if [ $INT -eq $PRICE ] ; then
echo "恭喜您猜对了,实际价格是 $PRICE"
echo "您总共猜了 $TIMES 次"
exit 0
elif [ $INT -gt $PRICE ] ; then
echo "太高了"
else
echo "太低了"
fi
done

until循环

和while循环相反,until循环时只要条件判断式不成立则进行循环,并执行循环程序。一旦循环条件成立,则终止循环。

语法:

until [ 条件判断式 ]
do
程序
done

until.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
#从1加到100

i=1
s=0
#t给变量i和变量s赋值

until [ $i -gt 100 ]
#循环直到变量i的值大于100,就停止循环
do
s=$(( $s+$i ))
i=$(( $i+1 ))
done
echo "The sum is: $s"

function函数

function 函数名 () {
程序逻辑
}

关键字function可省略不写

function.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/bash
#接收用户输入的数字,然后从1加到这个数字

function sum () {
#定义函数sum
s=0
for (( i=0; i<=$num;i=i+1 ))
#循环直到i大于$1为止。$1是函数sum 的第一个参数
#在函数中也可以使用位置参数变量,不过这里的$1指的是函数的第一个参数
do
s=$(( $i+$s ))
done
echo "The sum of 1+2+3...+$1 is :$s"
#输出1加到$1的和
}

read -p "Please input a number: " -t 30 num
#接收用户输入的数字,并把值赋予变量num
y=$(echo $num | sed 's/[0-9]//g')
#把变量num的值替换为空,并赋予变量y

if [ -z "$y"]
#判断变量y是否为空,以确定变量num中是否为数字
then
sum $num
#调用sum函数,并把变量num的值作为第一个参数传递给sum函数
else
echo "Error!! Please input a number!"
#如果变量num 的值不是数字,则输出报错信息
fi

exit语句

系统是有exit命令的,用于退出当前用户的登录状态。可是在Shell脚本中,exit语句是用来退出当前脚本的。也就是说,在Shell脚本中,只要碰到了exit语句,后续的程序就不再执行,而直接退出脚本

语法: exit [返回值]

exit.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash
#演示exit的作用

read -p "Please input a number: " -t 30 num
#接收用户的输入,并把输入赋予变量num
y=$ (echo $num | sed 's/[0-9]//g')
#如果变量num 的值是数字,则把num的值替换为空,否则不替换
#把替换之后的值赋予变量y
[ -n "$y" ] && echo "Error! Please input a number!" && exit 18
#判断变量y的值如果不为空,输出报错信息,退出脚本,退出返回值为18
echo "The number is: $num"
#如果没有退出,则打印变量num中的数字

break语句

当程序执行到break语句时,会结束整个当前循环。而continue 语句也是结束循环的语句,不过continue 语句单次当前循环,而下次循环会继续

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost ~]$ vi sh/break.sh
#!/bin/bash
#演示break 跳出循环

for (( i=1;i<=10; i=i+1 ))
#循环十次
do
if ["$i" -eq 4 ]
#如果变量i的值等于4
then
break
#退出整个循环
fi
echo $i
#输出变量i的值
done

continue语句

continue也是结束流程控制的语句。如果在循环中,continue语句只会结束单次当前循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost ~]$ vi sh/break.sh
#!/bin/bash
#演示continue

for (( i=1;i<=10;i=i+1 ))
#循环十次
do
if ["$i" -eq 4 ]
#如果变量i的值等于4
then
continue
#退出换成continue
fi
echo $i
#输出变量i的值
done

字符截取、替换和处理命令

正则表达式

元字符 描述 示例
\ 转义符,将特殊字符进行转义,忽略其特殊意义 a.b匹配a.b,但不能匹配ajb,.被转义为特殊意义
^ 匹配行首,awk中,^则是匹配字符串的开始 ^tux匹配以tux开头的行
$ 匹配行尾,awk中,$则是匹配字符串的结尾 tux$匹配以tux结尾的行
. 匹配除换行符\n之外的任意单个字符 ab.匹配abc或abd,不可匹配abcd或abde,只能匹配单字符
[ ] 匹配包含在[字符]之中的任意一个字符 coo[kl]可以匹配cook或cool
[^] 匹配[^字符]之外的任意一个字符 123[^45]不可以匹配1234或1235,1236、1237都可以
[-] 匹配[]中指定范围内的任意一个字符,要写成递增 [0-9]可以匹配1、2或3等其中任意一个数字
? 匹配之前的项1次或者0次 colou?r可以匹配color或者colour,不能匹配colouu
* 匹配之前的项0次或者多次 co*l匹配cl、col、cool、coool等
+ 匹配之前的项1次或者多次 sa-6+匹配sa-6、sa-666,不能匹配sa-
{n} 匹配之前的项n次,n是可以为0的正整数 [0-9]{3}匹配任意一个三位数,可以扩展为[0-9][0-9][0-9]
{n,m} 指定之前的项至少匹配n次,最多匹配m次,n<=m [0-9]{2,5}匹配从两位数到五位数之间的任意一个数字
| 交替匹配左右两项 ab(c|d)匹配abc或abd
() 匹配表达式,创建一个用于匹配的子串 ma(tri)?匹配max或maxtrix

cut 列提取命令

1
2
3
4
5
6
7
8
9
[root@localhost ~]$ cut [选项] 文件名
选项:
-f 列号: 提取第几列
-d 分隔符: 按照指定分隔符分割列
-n 取消分割多字节字符
-c 字符范围: 不依赖分隔符来区分列,而是通过字符范围(行首为0)来进行字段提取。“n-”表示从第n个字符到行尾;“n-m”从第n个字符到第m个字符;“一m”表示从第1个字符到第m个字符。
--complement 补足被选择的字节、字符或字段
--out-delimiter 指定输出内容是的字段分割符

cut命令的默认分隔符是制表符,也就是“tab”键,不过对空格符可是支持的不怎么好啊。我们先建立一个测试文件,然后看看cut命令的作用吧

grep

grep (global search regular expression(RE) and print out the line,全面搜索正则表达式并把行打印出来)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。用于过滤/搜索的特定字符。可使用正则表达式能配合多种命令使用,使用上十分灵活

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
-a --text  # 不要忽略二进制数据。
-A <显示行数> --after-context=<显示行数> # 除了显示符合范本样式的那一行之外,并显示该行之后的内容。
-b --byte-offset # 在显示符合范本样式的那一行之外,并显示该行之前的内容。
-B<显示行数> --before-context=<显示行数> # 除了显示符合样式的那一行之外,并显示该行之前的内容。
-c --count # 计算符合范本样式的列数。
-C<显示行数> --context=<显示行数>或-<显示行数> # 除了显示符合范本样式的那一列之外,并显示该列之前后的内容。
-d<进行动作> --directories=<动作> # 当指定要查找的是目录而非文件时,必须使用这项参数,否则grep命令将回报信息并停止动作。
-e<范本样式> --regexp=<范本样式> # 指定字符串作为查找文件内容的范本样式。
-E --extended-regexp # 将范本样式为延伸的普通表示法来使用,意味着使用能使用扩展正则表达式。
-f<范本文件> --file=<规则文件> # 指定范本文件,其内容有一个或多个范本样式,让grep查找符合范本条件的文件内容,格式为每一列的范本样式。
-F --fixed-regexp # 将范本样式视为固定字符串的列表。
-G --basic-regexp # 将范本样式视为普通的表示法来使用。
-h --no-filename # 在显示符合范本样式的那一列之前,不标示该列所属的文件名称。
-H --with-filename # 在显示符合范本样式的那一列之前,标示该列的文件名称。
-i --ignore-case # 忽略字符大小写的差别。
-l --file-with-matches # 列出文件内容符合指定的范本样式的文件名称。
-L --files-without-match # 列出文件内容不符合指定的范本样式的文件名称。
-n --line-number # 在显示符合范本样式的那一列之前,标示出该列的编号。
-P --perl-regexp # PATTERN 是一个 Perl 正则表达式
-q --quiet或--silent # 不显示任何信息。
-R/-r --recursive # 此参数的效果和指定“-d recurse”参数相同。
-s --no-messages # 不显示错误信息。
-v --revert-match # 反转查找。
-V --version # 显示版本信息。
-w --word-regexp # 只显示全字符合的列。
-x --line-regexp # 只显示全列符合的列。
-y # 此参数效果跟“-i”相同。
-o # 只输出文件中匹配到的部分。
-m <num> --max-count=<num> # 找到num行结果后停止查找,用来限制匹配行数

##########################################
#规则表达式

^ # 锚定行的开始 如:'^grep'匹配所有以grep开头的行。
$ # 锚定行的结束 如:'grep$' 匹配所有以grep结尾的行。
. # 匹配一个非换行符的字符 如:'gr.p'匹配gr后接一个任意字符,然后是p。
* # 匹配零个或多个先前字符 如:'*grep'匹配所有一个或多个空格后紧跟grep的行。
.* # 一起用代表任意字符。
[] # 匹配一个指定范围内的字符,如'[Gg]rep'匹配Grep和grep。
[^] # 匹配一个不在指定范围内的字符,如:'[^A-FH-Z]rep'匹配不包含A-R和T-Z的一个字母开头,紧跟rep的行。
(..) # 标记匹配字符,如'(love)',love被标记为1。
< # 锚定单词的开始,如:'<grep'匹配包含以grep开头的单词的行。
> # 锚定单词的结束,如'grep>'匹配包含以grep结尾的单词的行。
x{m} # 重复字符x,m次,如:'0{5}'匹配包含5个o的行。
x{m,} # 重复字符x,至少m次,如:'o{5,}'匹配至少有5个o的行。
x{m,n} # 重复字符x,至少m次,不多于n次,如:'o{5,10}'匹配5--10个o的行。
\w # 匹配文字和数字字符,也就是[A-Za-z0-9],如:'G\w*p'匹配以G后跟零个或多个文字或数字字符,然后是p。
\W # \w的反置形式,匹配一个或多个非单词字符,如点号句号等。
\b # 单词锁定符,如: '\bgrep\b'只匹配grep。

#在文件中搜索一个单词,命令会返回一个包含 “match_pattern” 的文本行
grep match_pattern file_name
grep "match_pattern" file_name
#在多个文件中查找
grep "match_pattern" file_1 file_2 file_3 ...
#输出除之外的所有行 -v 选项
grep -v "match_pattern" file_name
#标记匹配颜色 –color=auto 选项
grep "match_pattern" file_name --color=auto

awk 编程

AWK 是一种处理文本文件的语言,是一个强大的文本分析工具

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[root@localhost ~]$ printf ‘输出类型输出格式’ 输出内容

输出类型:
%c: ASCII字符.显示相对应参数的第一个字符
%-ns: 输出字符串,减号“-”表示左对齐(默认右对齐),n是数字指代输出几个字符,几个参数就写几个%-ns
%-ni: 输出整数,n是数字指代输出几个数字
%f: 输出小数点右边的位数
%m.nf: 输出浮点数,m和n是数字,指代输出的整数位数和小数位数。如%8.2f代表共输出8位数,其中2位是小数,6位是整数。

输出格式:
\a: 输出警告声音
\b: 输出退格键,也就是Backspace键
\f: 清除屏幕
\n: 换行
\r: 回车,也就是Enter键
\t: 水平输出退格键,也就是Tab 键
\v: 垂直输出退格键,也就是Tab 键


[root@localhost ~]$ vi student.txt
ID Name php Linux MySQL Average
1 AAA 66 66 66 66
2 BBB 77 77 77 77
3 CCC 88 88 88 88

#printf格式输出文件
[root@localhost ~]$ printf '%s\t %s\t %s\t %s\t %s\t %s\t \n’ $(cat student.txt)
#%s分别对应后面的参数,6列就写6个
ID Name php Linux MySQL Average
1 AAA 66 66 66 66
2 BBB 77 77 77 77
3 CCC 88 88 88 88

如果不想把成绩当成字符串输出,而是按照整型和浮点型输出,则要这样
[root@localhost ~]$ printf '%i\t %s\t %i\t %i\t %i\t %8.2f\t \n’ \ $(cat student.txt | grep -v Name)

awk 基本使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[root@localhost ~]$ awk‘条件1{动作1} 条件2{动作2}…’ 文件名
条件(Pattern):
一般使用关系表达式作为条件。这些关系表达式非常多,例如:
x > 10 判断变量x是否大于10
x == y 判断变量x是否等于变量y
A ~ B 判断字符串A中是否包含能匹配B表达式的子字符串
A !~ B 判断字符串A中是否不包含能匹配B表达式的子字符串

动作(Action) :
格式化输出
流程控制语句

常用参数:

-F 指定输入时用到的字段分隔符
-v 自定义变量
-f 从脚本中读取awk命令
-m 对val值设置内在限制

[root@localhost ~]$ df -h | awk '{print $1 "\t" $3}'
#截取df命令的第一列和第三列


awk 的条件

条件的类型 条件 说明
awk保留字 BEGIN 在awk程序一开始时,尚未读取任何数据之前执行。BEGIN后的动作只在程序开始时执行一次
awk保留字 END 在awk程序处理完所有数据,即将结束时执行。END后的动作只在程序结束时执行一次
关系运算符 > 大于
关系运算符 < 小于
关系运算符 >= 大于等于
关系运算符 <= 小于等于
关系运算符 == 等于。用于判断两个值是否相等,如果是给变量赋值,请使用“”号
关系运算符 != 不等于
关系运算符 A~B 判断字符串A中是否包含能匹配B表达式的子字符串
关系运算符 A!~B 判断字符串A中是否不包含能匹配B表达式的子字符串
正则表达式 /正则/ 如果在”//“中可以写入字符,也可以支持正则表达式

BEGIN

BEGIN是awk的保留字,是一种特殊的条件类型。BEGIN的执行时机是“在 awk程序一开始时,尚未读取任何数据之前执行”。一旦BEGIN后的动作执行一次,当awk开始从文件中读入数据,BEGIN的条件就不再成立,所以BEGIN定义的动作只能被执行一次

1
2
3
4
5
6
[root@localhost ~]$ awk 'BEGIN{printf "This is a transcript \n" } {printf $2 "\t" $6 "\n"}’ student.txt
#awk命令只要检测不到完整的单引号不会执行,所以这个命令的换行不用加入“|”,就是一行命令
#这里定义了两个动作
#第一个动作使用BEGIN条件,所以会在读入文件数据前打印“这是一张成绩单”(只会执行一次)
#第二个动作会打印文件的第二字段和第六字段

END

END也是awk保留字,不过刚好和BEGIN相反。END是在awk程序处理完所有数据,即将结束时执行。END后的动作只在程序结束时执行一次

1
2
3
[root@localhost ~]$ awk 'END{printf "The End \n"} {printf $2 "\t" $6 "\n"}’ student.txt
#在输出结尾输入“The End”,这并不是文档本身的内容,而且只会执行一次

关系运算符

举几个例子看看关系运算符。假设我想看看平均成绩大于等于87分的学员是谁,就可以这样输入命令

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost ~]$ cat student.txt | grep -v Name | awk '$6 >= 87 {printf $2 "\n"}'
#使用cat输出文件内容,用grep取反包含“Name”的行
#判断第六字段(平均成绩)大于等于87分的行,如果判断式成立,则打第六列(学员名$2

[root@localhost ~]$ awk '$2 ~ /Sc/ {printf $6 "\n"}' student.txt
#如果第二字段中输入包含有“Sc”字符,则打印第六字段数据
85.66
#这里要注意在awk中,使用“//”包含的字符串,awk命令才会查找。也就是说字符串必须用“//”包含,awk命令才能正确识别
[root@localhost ~]$ awk '/Liming/ {print}’student.txt
#打印Liming的成绩
[root@localhost ~]$ df -h | awk '/sda[O-9]/ {printf $1 "\t" $5 "\n"}’
#查询包含有sda数字的行,并打印第一字段和第五字段

加入了条件之后,只有条件成立动作才会执行,如果条件不满足,则动作则不运行。通过这个实验,大家可以发现,虽然awk是列提取命令,但是也要按行来读入的。这个命令的执行过程是这样的:

1)如果有BEGIN条件,则先执行BEGIN定义的动作。
2)如果没有BEGIN条件,则读入第一行,把第一行的数据依次赋予$0、$1、$2等变量。其中$0代表此行的整体数据,$1代表第一字段,$2代表第二字段。
3)依据条件类型判断动作是否执行。如果条件符合,则执行动作,否则读入下一行数据。如果没有条件,则每行都执行动作。
4)读入下一行数据,重复执行以上步骤。

awk 内置变量

awk内置变量 作用
$0 代表目前awk所读入的整行数据。我们已知awk是一行一行读入数据的,$0就代表当前读入行的整行数据。
$n 代表目前读入行的第n个字段。比如,$1表示第1个字段(列),$2表示第2个字段(列),如此类推
NF 当前行拥有的字段(列)总数
NR 当前awk所处理的行,是总数据的第几行。
FS 用户定义分隔符。awk的默认分隔符是任何空格,如果想要使用其他分隔符(如“:”),就需要FS变量定义。
ARGC 命令行参数个数。
ARGV 命令行参数数组。
FNR 当前文件中的当前记录数(对输入文件起始为1
OFMT 数值的输出格式(默认为%.6g)。
OFS 输出字段的分隔符(默认为空格)。
ORS 输出记录分隔符(默认为换行符)。
RS 输入记录分隔符(默认为换行符)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
1、打印文件的第一列(域) :
awk '{print $1}' filename

2、打印文件的前两列(域) :
awk '{print $1,$2}' filename

3、打印完第一列,然后打印第二列 :
awk '{print $1 $2}' filename

4、打印文本文件的总行数 :
awk 'END{print NR}' filename

5、打印文本第一行 :
awk 'NR==1{print}' filename

6、打印文本第二行第一列 :
sed -n "2, 1p" filename | awk 'print $1'



1. 获取第一列
ps -aux | grep watchdog | awk '{print $1}'

2. 获取第一列,第二列,第三列
ps -aux | grep watchdog | awk '{print $1, $2, $3}'

3. 获取第一行的第一列,第二列,第三列
ps -aux | grep watchdog | awk 'NR==1{print $1, $2, $3}'

4. 获取行数NR
df -h | awk 'END{print NR}'

5. 获取列数NF(这里是获取最后一行的列数,注意每行的列数可能是不同的)
ps -aux | grep watchdog | awk 'END{print NF}'

6. 获取最后一列
ps -aux | grep watchdog | awk '{print $NF}'

7. 对文件进行操作
awk '{print $1}' fileName

8. 指定分隔符(这里以:分割)
ps -aux | grep watchdog |awk -F':' '{print $1}'

9. 超出范围不报错
ps -aux | grep watchdog | awk '{print $100}'

[root@localhost ~]$ cat /etc/passwd | grep "/bin/bash" | awk 'BEGIN {FS=":"} {printf $1 "\t" $3 "\n"}’

#查询可以登录的用户的用户名和UID


#############################
[root@localhost ~]$ awk 'NR==2 {php1=$3}
NR==3 {php2=$3}
NR==4 {php3=$3;totle=phpl+php2+php3;print "totle php is " totle}’ student.txt
#统计PHIP成绩的总分
我们解释下这个命令。“NR==2 {iphp1=$3}” (条件是NR==2,动作是php1=$3) 这句话是指如果输入数据是第二行(第一行是标题行),就把第二行的第三字段的值赋予变量“php1”。
“NR==3 {php2=$3}" 这句话是指如果输入数据是第三行,就把第三行的第三字段的值赋予变量“php2”。“NR==4 {php3=$3;totle=phpl+php2+php3;print “totle php is " totle}”(“NR==4”是条件,后面(中的都是动作)这句话是指如果输入数据是第四行﹐就把第四行的第三字段的值赋予变量"php3”;然后定义变量totle的值是“php1+php2+php3”;然后输出“totle php is”关键字,后面加变量totle的值。

在awk编程中,因为命令语句非常长,在输入格式时需要注意以下内容:

  • 多个条件 {动作} 可以用空格分割,也可以用回车分割
  • 在一个动作中,如果需要执行多个命令,需要用 “;” 分割,或用回车分割
  • 在awk中,变量的赋值与调用都不需要加入“$”符
  • 条件中判断两个值是否相同,请使用 “==”,以便和变量赋值进行区分
1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost ~]$ awk '{if (NR>=2) {if ($4>60) printf $2 "is a good man!\n"}}’ student.txt
#程序中有两个if判断,第一个判断行号大于2,第二个判断Linux成绩大于90分
Liming is a good man !
Sc is a good man !
假设如果Linux成绩大于90,就是一个好男人
其实在 awk中 if判断语句,完全可以直接利用awk自带的条件来取代,刚刚的脚本可以改写成这样
[root@localhost ~]$ awk ’NR>=2 {test=$4}
test>90 {printf $2 "is a good man! \n"}’ student.txt
#先判断行号如果大于2,就把第四字段赋予变量test
#在判断如果test的值大于90分,就打印好男人
Liming is a good man!
Sc is a good man!

awk 函数

awk编程也允许在编程时使用函数,我们讲讲awk的自定义函数。awk函数的定义方法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
function 函数名(参数列表){
函数体
}

[root@localhost ~]$ awk 'function test(a,b) { printf a "\t" b "\n"}
#定义函数test,包含两个参数,函数体的内容是输出这两个参数的值
{ test($2,$6) } ' student.txt
#调用函数test,并向两个参数传递值。
Name Average
AAA 87.66
BBB 85.66
CCC 91.66

awk 中调用脚本

对于小的单行程序来说,将脚本作为命令行自变量传递给awk是非常简单的,而对于多行程序就比较难处理。当程序是多行的时候,使用外部脚本是很适合的。首先在外部文件中写好脚本,然后可以使用awk的-f选项,使其读入脚本并且执行。
例如,我们可以先编写一个awk脚本

1
2
3
4
5
6
7
8
[root@localhost ~]$ vi pass.awk
BEGIN {FS=":"}
{ print $1 "\t" $3}

[root@localhost ~]$ awk -f pass.awk /etc/passwd
root 0bin 1daemon 2adm 3lp 4sync 5shutdown 6halt 7mail 8operator 11games 12ftp 14nobody 99systemd-network 192dbus 81polkitd 999tss 59sshd 74postfix 89chrony 998treeman 1000
[root@192 treeman]#

sed 文本选取、替换、删除、新增的命令

sed主要是用来将数据进行选取、替换、删除、新增的命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
[root@localhost ~]$ sed [选项] ‘[动作]’ 文件名

选项:
-n: 一般sed命令会把所有数据都输出到屏幕,如果加入此选择,则只会把经过sed命令处理的行输出到屏幕。
-e: 允许对输入数据应用多条sed命令编辑。
-f 脚本文件名: 从sed脚本中读入sed操作。和awk命令的-f非常类似。
-r: 在sed中支持扩展正则表达式。
-i: 用sed的修改结果直接修改读取数据的文件,而不是由屏幕输出

动作:
num a \: 追加,在当前行后添加一行或多行。添加多行时,除最后一行外,每行末尾需要用“\”代表数据未完结。num表示第几行
c \: 行替换,用c后面的字符串替换原数据行,替换多行时,除最后一行外,每行末尾需用“”代表数据未完结。
num i \: 插入,在当期行前插入一行或多行。插入多行时,除最后一行外,每行末尾需要用“”代表数据未完结。num表示第几行
d ; 删除,删除指定的行。
p : 打印,输出指定的行。
s : 字串替换,用一个字符串替换另外一个字符串。格式为“行范围s/"旧字串/新字串/g”(和vim中的替换格式类似)。
####################################################
[root@localhost ~]$ sed '2p' student.txt
ID Name php Linux MySQL Average
1 AAA 66 66 66 66
2 BBB 77 77 77 77
3 CCC 88 88 88 88
指定输出某行,使用-n选项
[root@192 treeman]# sed -n "2p" student.txt
1 AAA 66 66 66 66

[root@192 treeman]# sed "2,4d" student.txt
ID Name php Linux MySQL Average
#删除第二行到第四行数据
[root@192 treeman]# sed "2a hello" student.txt
ID Name php Linux MySQL Average
1 AAA 66 66 66 66
hello
2 BBB 77 77 77 77
3 CCC 88 88 88 88
#在第二行后加入 hello
[root@192 treeman]# sed "2i world" student.txt
ID Name php Linux MySQL Average
world
1 AAA 66 66 66 66
2 BBB 77 77 77 77
3 CCC 88 88 88 88
#在第二行前插入两行数据
[root@192 treeman]#
[root@192 treeman]# sed -n '2i hello world' student.txt
#只查看sed命令操作的数据
如果是想追加或插入多行数据,除最后一行外,每行的末尾都要加入“\”代表数据未完结。再来看看“-n”选项的作用

“-n”只查看sed命令操作的数据,而不是查看所有数据。
再来看看如何实现行数据替换,假设AAA的成绩太好了,我实在是不想看到他的成绩刺激我,那就可以使用"c"动作
[root@192 treeman]# cat student.txt | sed '2c not found'
ID Name php Linux MySQL Average
not found
2 BBB 77 77 77 77
3 CCC 88 88 88 88
sed命令默认情况是不会修改文件内容的,如果我确定需要让 sed命令直接处理文件的内容,可以使用“-i”选项。不过要小心啊,这样非常容易误操作,在操作系统文件时请小心谨慎。
[root@192 treeman]# sed -i '2a hello world' student.txt
[root@192 treeman]# cat student.txt
ID Name php Linux MySQL Average
1 AAA 66 66 66 66
hello world
2 BBB 77 77 77 77
3 CCC 88 88 88 88

“c”动作是进行整行替换的,如果仅仅想替换行中的部分数据,就要使用“s”动作了。g 使得 sed 对文件中所有符合的字符串都被替换, 修改后内容会到标准输出,不会修改原文件
[root@localhost ~]$ sed 's/旧字串/新字串/g' 文件名
[root@localhost ~]$ sed '行范围s/旧字串/新字串/g' 文件名
[root@localhost ~]$ sed '3s/74/99/g' student.txt
[root@192 treeman]# sed '4s/77/90/g' student.txt
ID Name php Linux MySQL Average
1 AAA 66 66 66 66
hello world
2 BBB 90 90 90 90
3 CCC 88 88 88 88
#在第三行中,把74换成99
#如果我想把AAA老师的成绩注释掉,让他不再生效
#这里使用正则表达式,“^”代表行首
[root@192 treeman]# sed '2s/^/#/g' student.txt
ID Name php Linux MySQL Average
#1 AAA 66 66 66 66
hello world
2 BBB 77 77 77 77
3 CCC 88 88 88 88
#“-e”选项可以同时执行多个sed动作,当然如果只是执行一个动作也可以使用“-e”选项,但是这时没有什么意义。还要注意,多个动作之间要用“;”号或回车分割
[root@192 treeman]# sed -e 's/AAA//g;s/BBB//g' student.txt
ID Name php Linux MySQL Average
1 66 66 66 66
hello world
2 77 77 77 77
3 CCC 88 88 88 88

sort 排序命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
[root@localhost~]$ sort [选项] 文件名
选项:

-f: 忽略大小写
-b: 忽略每行前面的空白部分
-n: 以数值型进行排序,默认使用字符串型排序
-r: 反向排序
-u: 删除重复行。就是uniq命令
-t: 指定分隔符,默认是分隔符是制表符
-k n[,m]: ―按照指定的字段范围排序。从第n字段开始,m字段结束(默认到行尾)

####################################
[root@localhost~]$ sort /etc/passwd
#排序用户信息文件
[root@localhost~]$ sort -r /etc/passwd
#反向排序
#如果想要指定排序的字段,需要使用“-t”选项指定分隔符,并使用“-k”选项指定字段号。加入我想要按照UID字段排序/etc/passwd文件
[root@localhost~]$ sort -t ":" -k 3,3 /etc/passwd
#指定分隔符是“:”,用第三字段开头,第三字段结尾排序,就是只用第三字段排序

#因为sort默认是按照字符排序,前面用户的UID的第一个字符都是1,所以这么排序。要想按照数字排序,请使用“-n”选项
[root@localhost~]$ sort -n -t ":" -k 3,3 /etc/passwd
#当然“-k”选项可以直接使用“-k 3”,代表从第三字段到行尾都排序(第一个字符先排序,如果一致,第二个字符再排序,直到行尾)

uniq 取消重复行

1
2
3
4
5
6
7
8
9
10
[root@localhost~]$ uniq [选项] 文件名
选项:
-i:忽略大小写
[root@192 treeman]# cat student.txt | uniq -i
ID Name php Linux MySQL Average
1 AAA 66 66 66 66
hello world
2 BBB 77 77 77 77
3 CCC 88 88 88 88

wc 统计命令

1
2
3
4
5
6
7
8
9
10
11
12
13
[root@localhost~]$ wc [选项] 文件名
选项:
-l:只统计行数
-w:只统计单词数
-m:只统计字符数

[root@192 treeman]# cat student.txt | wc -l
6
[root@192 treeman]# cat student.txt | wc -w
28
[root@192 treeman]# cat student.txt | wc -m
228

脚本

练习-内存使用监控脚本

内存监控脚本需要的命令free -m

1
2
3
4
5
6
7
8
9
1.获取内存命令
[root@localhost ~]# free -m
total used free shared buff/cache available
Mem: 972 236 500 7 235 590
Swap: 2047 0 2047
[root@localhost ~]#
2.获取需要的available(可用内存)的参数值
[root@localhost ~]# free -m | awk 'NR==2 {print $NF}'
587

vim menMonitor.sh

menMonitor.sh
1
2
3
4
5
6
7
8
9
10
#! /bin/bash
mem=`free -m |awk 'NR==2 {print $NF}'`

warr="current available Mem is $mem"

if [ "$mem" -lt "600" ]; then
echo "$warr" | tee /tmp/messages.txt
mail -s "`date +%F-%T` $warr" treeman@00.com </tmp/messages.txt
echo "the available is not enough"
fi

加入定时

1
2
3
4
5
#加入定时任务:每分钟执行一次
crontab -e
#分/1 时 日 周 月 执行程序 脚本位置 后台& 输出到dev/null就是默认不输出
*/1 * * * * /bin/bash /script/free.sh & >/dev/null
crontab -l 查看定时任务

评论