Shell企业编程基础实战

Shell企业编程基础

说到Shell编程,很多从事Linux运维工作的朋友都不陌生,都对Shell有基本的了解,读者可能刚开始接触Shell的时候,有各种想法,感觉编程非常困难,SHELL编程是所有编程语言中最容易上手,最容易学习的编程脚本语言。

本章向读者介绍Shell编程入门、Shell编程变量、If、While、For、Case、Select基本语句案例演练及Shell编程四剑客Find、Grep、Awk、Sed深度剖析等。

SHELL编程入门简介

曾经有人说过,学习Linux不知道Shell编程,那就是不懂Linux,现在细细品味确实是这样。Shell是操作系统的最外层,Shell可以合并编程语言以控制进程和文件,以及启动和控制其它程序。

Shell 通过提示您输入,向操作系统解释该输入,然后处理来自操作系统的任何结果输出,简单来说Shell就是一个用户跟操作系统之间的一个命令解释器。

Shell是用户与Linux操作系统之间沟通的桥梁,用户可以输入命令执行,又可以利用 Shell脚本编程去运行,如图所示:

clip_image002

Shell、用户及Kernel位置关系

clip_image004

Linux Shell种类非常多,常见的SHELL如下:

  • Bourne Shell(/usr/bin/sh或/bin/sh)

  • Bourne Again Shell(/bin/bash)

    扫描二维码关注公众号,回复: 7909801 查看本文章
  • C Shell(/usr/bin/csh)

  • K Shell(/usr/bin/ksh)

  • Shell for Root(/sbin/sh)

不同的Shell语言的语法有所不同,一般不能交换使用,最常用的shell是Bash,也就是Bourne Again Shell。Bash由于易用和免费,在日常工作中被广泛使用,也是大多数Linux操作系统默认的Shell环境。

Shell、Shell编程、Shell脚本、Shell命令之间都有什么区别呢?

简单来说Shell是一个整体的概念,Shell编程与Shell脚本统称为Shell编程,Shell命令是Shell编程底层具体的语句和实现方法。

SHELL脚本及Hello World

要熟练掌握Shell编程语言,需要大量的练习,初学者可以用Shell打印“Hello World”字符,寓意着开始新的启程!

Shell脚本编程需要如下几个事项:

  1. Shell脚本名称命名一般为英文、大写、小写;

  2. 不能使用特殊符号、空格来命名;

  3. Shell脚本后缀以.sh结尾;

  4. 不建议Shell命名为纯数字,一般以脚本功能命名。

  5. Shell脚本内容首行需以#!/bin/bash开头;

  6. Shell脚本中变量名称尽量使用大写字母,字母间不能使用“-”,可以使用“_”;

  7. Shell脚本变量名称不能以数字、特殊符号开头。

如下为第一个Shell编程脚本,脚本名称为:first_shell.sh,代码内容如下:

#!/bin/bash
#This is my First shell
#By author test.net
echo “Hello World ”

First_shell.sh脚本内容详解如下:

#!/bin/bash             #固定格式,定义该脚本所使用的Shell类型;
#This is my First shell     #号表示注释,没有任何的意义,SHELL不会解析它;
#By author test.net #表示脚本创建人,#号表示注解;
echo “Hello World !” #Shell脚本主命令,执行该脚本呈现的内容。

Shell脚本编写完毕,如果运行该脚本,运行用户需要有执行权限,可以使用chmod o+x first_shell.sh赋予可执行权限。然后./first_shell.sh执行即可,还可以直接使用命令执行: /bin/sh first_shell.sh直接运行脚本,不需要执行权限,最终脚本执行显示效果一样。

初学者学习Shell编程,可以将在Shell终端运行的各种命令依次写入到脚本内容中,可以把Shell脚本当成是Shell命令的堆积。

Shell脚本字符串颜色

再介绍下字符串输出颜色,有时候关键地方需要醒目,颜色是最好的方式:

字体颜色

  • 30:黑

  • 31:红

  • 32:绿

  • 33:黄

  • 34:蓝色

  • 35:紫色

  • 36:深绿

  • 37:白色

字体背景颜色

  • 40:黑

  • 41:深红

  • 42:绿

  • 43:黄色

  • 44:蓝色

  • 45:紫色

  • 46:深绿

  • 47:白色

显示方式

  • 0:终端默认设置

  • 1:高亮显示

  • 4:下划线

  • 5:闪烁

  • 7:反白显示

  • 8:隐藏

格式:

\033[1;31;40m   # 1 是显示方式,可选。31 是字体颜色。40m 是字体背景颜色。
\033[0m             # 恢复终端默认颜色,即取消颜色设置。

示例:

#!/bin/bash
# 字体颜色
for i in {31..37}; do
echo -e "\033[$i;40mHello world!\033[0m"
done
# 背景颜色
for i in {41..47}; do
echo -e "\033[47;${i}mHello world!\033[0m"
done
# 显示方式
for i in {1..8}; do
echo -e "\033[$i;31;40mHello world!\033[0m"
done

示例如图:

测试单个案例

[root@localhost ~]# echo -e '\033[31;40mwww.test.net!\033[0m'
www.test.net!

Shell编程之变量详解

Shell是非类型的解释型语言,不像C++、JAVA语言编程时需要事先声明变量,Shell给一个变量赋值,实际上就是定义了变量,在Linux支持的所有shell中,都可以用赋值符号(=)为变量赋值,Shell变量为弱类型,定义变量不需要声明类型,但在使用时需要明确变量的类型,可以使用Declare指定类型。

Declare常见参数有:

+/-  "-"可用来指定变量的属性,"+"为取消变量所设的属性;
-f  仅显示函数;
r  将变量设置为只读;
x  指定的变量会成为环境变量,可供shell以外的程序来使用;
i  指定类型为数值,字符串或运算式。

Shell编程中变量分为三种,分别是系统变量、环境变量和用户变量,Shell变量名在定义时,首个字符必须为字母(a-z,A-Z),不能以数字开头,中间不能有空格,可以使用下划线(_),不能使用(-),也不能使用标点符号等。当脚本中使用某个字符串较频繁并且字符串长度很长时就应该使用变量代替。

例如定义变量A=test.net,定义这样一个变量,A为变量名,test.net是变量的值,变量名有格式规范,变量的值可以随意指定。变量定义完成,如需要引用变量,可以使用$A。

如下脚本var.sh脚本内容如下:

#!/bin/bash
#By author test.net
A=123
echo “Printf variables is $A.”

执行该Shell脚本,结果将会显示:Printf variables is 123。

Shell脚本中的变量其他使用还有很多例如:

  1. 使用条件语句时,常使用变量 if [ $i -gt 1 ]; then ... ; fi。

  2. 引用某个命令的结果时,用变量替代 n=wc -l test.txt。

  3. 写和用户交互的脚本时,变量也是必不可少的,read -p "Input a number: " i; echo $i 如果没写这个i,可以直接使用$REPLY。

  4. 内置变量 $0, $1, $2… $0表示脚本本身,$1 第一个参数,$2 第二个 .... $#表示参数个数。

  5. 数学运算a=2;b=3; c=$(($a+$b))或者$[$a+$b]。

Shell编程之系统变量

Shell常见的变量之一系统变量,主要是用于对参数判断和命令返回值判断时使用,系统变量详解如下:

$0         当前脚本的名称;
$n 当前脚本的第n个参数,n=1,2,…9;
$* 当前脚本的所有参数(不包括程序本身);
$@             传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同
$# 当前脚本的参数个数(不包括程序本身);
$? 命令或程序执行完后的状态,返回0表示执行成功;
$$ 程序本身的PID号。

注意:

$* 和 $@ 的区别是什么?

$* 和 $@ 都表示传递给函数或脚本的所有参数,不被双引号(" ")包含时,都以"$1" "$2" … "$n" 的形式输出所有参数。

但是当它们被双引号(" ")包含时,"$*" 会将所有的参数作为一个整体(强调整体),以"$1 $2 … $n"的形式输出所有参数;即当成一个整体输出,每一个变量参数之间以空格隔开。

"$@" 会将各个参数分开(强调独立),以"$1" "$2" … "$n" 的形式输出所有参数。即每一个变量参数是独立的 。当然也是全部输出。

我们可以在for语句中使用双引号" "看出两个变量的区别,

Shell脚本如下:

#!/bin/bash
#By author test.net test
for i in "$*";do
echo $i
done
echo "================="
for i in "$@";do
echo $i
done

执行测试:

[root@localhost ~]# bash test_num.sh 1 2 3 4 5
1 2 3 4 5
=================
1
2
3
4
5

Shell编程之环境变量

Shell常见的变量之二环境变量,主要是在程序运行时需要设置,环境变量详解如下:

PATH                命令所示路径,以冒号为分割;
HOME 打印用户家目录;
SHELL 显示当前Shell类型;
USER 打印当前用户名;
ID   打印当前用户id信息;
PWD   显示当前所在路径;
TERM 打印当前终端类型;
HOSTNAME     显示当前主机名。

环境变量相关文件:

系统级:

系统级变量文件对所有用户生效。

  • /etc/profile # 系统范围内的环境变量和启动文件。不建议把要做的事情写在这里面,最好创建一个自定义的,放在/etc/profile.d 下

  • /etc/bashrc # 系统范围内的函数和别名

用户级:

用户级变量文件对自己生效,都在自己家目录下。

  • ~/.bashrc # 用户指定别名和函数

  • ~/.bash_logout # 用户退出执行

  • ~/.bash_profile # 用户指定变量和启动程序

  • ~/.bash_history # 用户执行命令历史文件

开启启动脚本顺序:/etc/profile -> /etc/profile.d/*.sh -> ~/.bash_profile -> ~/.bashrc ->

/etc/bashrc

因此,我们可以把写的脚本放到以上文件里执行。

Shell编程之用户变量

Shell常见的变量之三用户变量,用户变量又称为局部变量,主要用在Shell脚本内部或者临时局部使用,系统变量详解如下:

A=test.net                     自定义变量A;
N_SOFT=nginx-1.12.0.tar.gz       自定义变量N_SOFT;
BACK_DIR=/data/backup/           自定义变量BACK_DIR;
IP1=192.168.1.11               自定义变量IP1;
IP2=192.168.1.12               自定义变量IP2。

创建Echo打印菜单Shell脚本,脚本代码如下:

#!/bin/bash
#auto install nginx
#By author test.net
echo -e '\033[32m-----------------------------\033[0m'
FILE=nginx-1.16.0.tar.gz
URL=http://nginx.org/download
PREFIX=/usr/local/nginx/
echo -e "\033[36mPlease Select Install Menu:\033[0m"
echo
echo "1)官方下载nginx文件包."
echo "2)解压nginx源码包."
echo "3)编译安装nginx服务器."
echo "4)启动nginx服务器."
echo -e '\033[32m-----------------------------\033[0m'
sleep 20

Shell编程之标准输入、输出和错误

关于文件描述符(fd)的基本概念:

文件描述符是一个非负整数,在打开现存文件或新建文件时,内核会返回一个文件描述符,读写文件也需要使用文件描述符来访问文件。内核为每个进程维护该进程打开的文件记录表。文件描述符只适于 Unix、Linux 操作系统。

文件描述符列表(标准输入、输出和错误)

系统中共有12个文件描述符,0、1、2分别是标准输入、标准输出、标准错误,3到9是可以被任意使用的。

每一个unix进程,都会拥有三个标准的文件描述符,来对应三种不同的身份。

文件

描述符
描述
映射关系

0 标准输入
键盘
/dev/stdin --> /proc/self/fd/0

1 标准输出
屏幕
/dev/stdin --> /proc/self/fd/1

2 标准错误
屏幕
/dev/stderr --> /proc/self/fd/2

每一个文件描述符会对应一个打开文件,同时不同的文件描述符也可以对应同一个打开文件;同一个文件可以被不同的进程打开,也可以被同一个进程多次打开。

在/proc/PID/fd中,列举了进程PID所拥有的文件描述符,例如

[root@localhost ~]# cat learn_redirect.sh 
#!/bin/bash
source /etc/profile;
# $$表示当前进程的PID
PID=$$
# 查看当前进程的文件描述符指向
ls -l /proc/$PID/fd
echo "-------------------";echo
# 文件描述符1与文件tempfd1进行绑定
( [ -e ./tempfd1 ] || touch ./tempfd1 ) && exec 1<>./tempfd1
# 查看当前进程的文件描述符指向
ls -l /proc/$PID/fd
echo "-------------------";echo;

脚本执行的结果如下:

[root@localhost ~]# cat testfd1 
total 0
lrwx------ 1 root root 64 Sep 14 20:55 0 -> /dev/pts/0
lrwx------ 1 root root 64 Sep 14 20:55 1 -> /root/testfd1
lrwx------ 1 root root 64 Sep 14 20:55 2 -> /dev/pts/0
lr-x------ 1 root root 64 Sep 14 20:55 255 -> /root/test.sh
[root@localhost ~]# sh test.sh
total 0
lrwx------ 1 root root 64 Sep 14 20:55 0 -> /dev/pts/0
lrwx------ 1 root root 64 Sep 14 20:55 1 -> /dev/pts/0
lrwx------ 1 root root 64 Sep 14 20:55 2 -> /dev/pts/0
lr-x------ 1 root root 64 Sep 14 20:55 255 -> /root/test.sh

上述的例子中第9行,将文件描述符1与文件testfile进行了绑定,此后,文件描述符1指向了testfile文件,标准输出被重定向到了文件testfile中。

重定向符号详解
符号  	描述
>			符号左边输出作为右边输入(标准输出)
>>    		符号左边输出追加右边输入
<			符号右边输出作为左边输入(标准输入)
<<			符号右边输出追加左边输入
&			重定向绑定符号

输入和输出可以被重定向符号解释到 shell,shell 命令是从左到右依次执行命令。
重定向输出

1)覆盖输出

一般格式:[n] > file,如果 n 没有指定,默认是 1

示例:

打印结果写到文件:

echo "test" > a.txt

当没有安装 bc 计算器时,错误输出结果写到文件:

echo "1 + 1" |bc 2 > error.log

2)追加重定向输出

一般格式:[n] >> file,如果 n 没有指定,默认是 1

示例:

打印结果追加到文件:

echo "test" >> a.txt

当没有安装 bc 计算器时,错误输出结果追加文件:

echo "1 + 1" |bc 2> error.log
重定向输入

一般格式:[n]<word,如果 n 没有指定,默认是 0

示例:

a.txt 内容作为 grep 输入:

grep "test" --color < a.txt
重定向标准输出和标准错误

1)覆盖重定向标准输出和标准错误

&>file 和>&file 等价于 >file 2>&1
&将标准输出和标准输入绑定到一起,重定向 word 文件。

示例:

当不确定执行对错时都覆盖到文件:

echo "1 + 1" |bc &> error.log

当不确定执行对错时都覆盖到文件:

echo "1 + 1" |bc > error.log 2>&1

2)追加重定向标准输出和标准错误

&>>file 等价于>>file 2>&1

示例:

当不确定执行对错时都追加文件:

echo "1 + 1" |bc &>> error.log

将标准输出和标准输入追加重定向到 delimiter:

<< delimiter
here-document
delimiter

从当前 shell 读取输入源,直到遇到一行只包含 delimiter 终止,内容作为标准输入。

将 eof 标准输入作为 cat 标准输出再写到 a.txt:

# cat << eof
123
abc
eof
123
abc
# cat > a.txt << eof
> 123
> abc
> eof
重定向到空设备

/dev/null 是一个空设备,向它写入的数据都会丢弃,但返回状态是成功的。与其对应的还有一个/dev/zero 设备,提供无限的 0 数据流。

在写 Shell 脚本时我们经常会用到/dev/null 设备,将 stdout、stderr 输出给它,也就是我们不想要这些输出的数据。

通过重定向到/dev/null 忽略输出,比如我们没有安装 bc 计算器,正常会抛出没有发现命令:

echo "1 + 1" |bc >/dev/null 2>&1

这就让标准和错误输出到了空设备。

忽略标准输出:

echo "test" >/dev/null

忽略错误输出:

echo "1 + 1" |bc 2>/dev/null

注意:上个练习提到的2>&1可以这样理解

对于&1 更准确的说应该是文件描述符 1,而1标识标准输出,stdout。

对于2 ,表示标准错误,stderr。

2>&1 的意思就是将标准错误重定向到标准输出。这里标准输出已经重定向到了 /dev/null。那么标准错误也会输出到/dev/null

exec 命令详解

exec 是 bash 的内置命令,shell 的内置命令exec执行命令时,不启用新的shell进程。

source 和. 不启用新的shell,在当前shell中执行,设定的局部变量在执行完命令后仍然有效。

bash 或 sh 或 shell script 执行时,另起一个子shell,其继承父shell的环境变量,其子shell的变量执行完后不影响父shell。exec是用被执行的命令行替换掉当前的shell进程,且exec命令后的其他命令将不再执行。例如在当前shell中执行 exec ls 表示执行ls这条命令来替换当前的shell ,即为执行完后会退出当前shell。

为了避免这个结果的影响,一般将exec命令放到一个shell脚本中,用主脚本调用这个脚本,调用处可以用bash xx.sh(xx.sh为存放exec命令的脚本),这样会为xx.sh建立一个子shell去执行,当执行exec后该子脚本进程就被替换成相应的exec的命令。其中有一个例外:当exec命令对文件描述符操作的时候,就不会替换shell,而是操作完成后还会继续执行后面的命令!

常用格式:exec [-cl] [-a name] [command [arguments]]

如果指定了command,它将用当前的command替换当前的shell, 但是不会产生新的进程,如果有arguments参数,将会作为command的参数。

选项:

-l:将会在传递给command命令的第0个参数前面加上一个dash('-'),有点像在用su的时候(su - username)
-c:将会使command命令在一个空环境中执行
-a:shell会将name作为第0个参数传递给要执行的command命令

exec 语法:

exec命令
作用

exec ls
在shell中执行ls,ls结束后不返回原来的shell中了

exec <>
将file中的内容作为exec的标准输入

exec >file
将file中的内容作为标准写出

exec 3<>
将file读入到fd3中

sort <&3
fd3中读入的内容被分类

exec 4>file
将写入fd4中的内容写入file中

ls >&4
Ls将不会有显示,直接写入fd4中了,上面file中

exec 5<&4
创建fd4的拷贝fd5

exec 3<&-
关闭fd3

举例:

先上我们进如/dev/fd/目录下看一下:

root@localhost #cd /dev/fd
root@localhost #/dev/fd#ls
0  1  2  255

root@localhost #/dev/fd#ls

0 1 2 255

默认会有这四个项:

0是标准输入,默认是键盘。

1是标准输出,默认是屏幕/dev/tty

2是标准错误,默认也是屏幕

255

当我们执行exec 3>/root/test,再去看看/dev/fd,一定多个3,什么意思呢?

也就是又增加了一个设备,这里也可以体会下linux设备即文件的理念。这时候fd3就相当于一个管道了,重定向到fd3中的文件会被写在test中。关闭这个重定向可以用exec 3>&-

read 命令

read 命令从标准输入读取,并把输入的内容复制给变量。

命令格式: read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-pprompt] [-t timeout] [-u fd] [name ...]

-e  在一个交互 shell 中使用 readline 获取行
-r  不允许反斜杠转义任何字符
-s  隐藏输入
-a array  保存为数组,元素以空格分隔
-d delimiter  持续读取直到遇到 delimiter 第一个字符退出
-n nchars  读取 nchars 个字符返回,而不是等到换行符
-p prompt  提示信息
-t timeout  等待超时时间,秒
-u fd  指定文件描述符号码作为输入,默认是 0

示例:

  1. 获取用户输入保存到变量:

[root@localhost ~]# read -p "Please input your name: " VAR
Please input your name: test
[root@localhost ~]# echo $VAR
test
  1. 用户输入保存为数组:

[root@localhost ~]# read -p "Please input your name: " -a ARRAY
Please input your name: 1 2 3 4 5
[root@localhost ~]# echo ${ARRAY[*]}
1 2 3 4 5
[root@localhost ~]# echo ${ARRAY[1]}
2
[root@localhost ~]# echo ${ARRAY[0]}
1
  1. 遇到 e 字符返回:

[root@localhost ~]# read -d e VAR
jf666
e
[root@localhost ~]# echo $VAR
jf666
  1. 从文件作为 read 标准输入:

[root@localhost ~]# cat a.txt
test
[root@localhost ~]# read VAR < a.txt
[root@localhost ~]# echo $VAR
test
  1. while 循环读取每一行作为 read 的标准输入:

[root@localhost ~]# cat a.txt
test
test1
test2
[root@localhost ~]# cat a.txt |while read LINE; do echo $LINE; done
test
test1
test2

分别变量赋值:

[root@localhost ~]# read a b c
1 2 3
[root@localhost ~]# echo $a
1
[root@localhost ~]# echo $b
2
[root@localhost ~]# echo $c
3
[root@localhost ~]# echo 1 2 3 | while read a b c;do echo "$a $b $c"; done
1 2 3
管道符 ”|”

在Unix或类Unix操作系统中,管道是一个由标准输入输出链接起来的进程集合,因此,每一个进程的输出将直接作为下一个进程的输入。

linux管道包含两种

  • 匿名管道(ps aux | grep nginx)

  • 命名管道(mkfifo /tmp/fd1)

管道有一个特点,如果管道中没有数据,那么取管道数据的操作就会滞留,直到管道内进入数据,然后读出后才会终止这一操作;同理,写入管道的操作如果没有读取管道的操作,这一动作就会滞留。

匿名管道

在Unix或类Unix操作系统的命令行中,匿名管道使用ASCII中垂直线|作为匿名管道符,匿名管道的两端是两个普通的,匿名的,打开的文件描述符:一个只读端和一个只写端,这就让其它进程无法连接到该匿名管道。例如:cat file | less

为了执行上面的指令,Shell创建了两个进程来分别执行cat和less。

有一点值得注意的是两个进程都连接到了管道上,这样写入进程cat就将其标准输出(文件描述符为fd 1)连接到了管道的写入端,读取进程less就将其标准输入(文件描述符为fd 0)连接到了管道的读入端。实际上,这两个进程并不知道管道的存在,它们只是从标准文件描述符中读取数据和写入数据。shell必须要完成相关的工作。

命名管道

命名管道简介

命名管道也称FIFO(FIFO,First In First Out),从语义上来讲,FIFO其实与匿名管道类似,但值得注意:

  1. 在文件系统中,FIFO拥有名称,并且是以设备特俗文件的形式存在的;

  2. 任何进程都可以通过FIFO共享数据;

  3. 除非FIFO两端同时有读与写的进程,否则FIFO的数据流通将会阻塞;

  4. 匿名管道是由shell自动创建的,存在于内核中;而FIFO则是由程序创建的(比如mkfifo命令),存在于文件系统中;

  5. 匿名管道是单向的字节流,而FIFO则是双向的字节流;

比如,可以利用FIFO实现单服务器、多客户端的应用程序:利用FIFO实现单服务器多客户端的应用程序

有了上面的知识准备,现在可以开始讲述,linux多进程并发时,如何控制每次并发的进程数。

命名管道特性

  • 如果管道内容为空,则阻塞

  • 如果没有读管道的操作,则阻塞

测试命名管道特性

  1. 如果管道内容为空,则阻塞cat /tmp/fd,管道内容为空则阻塞

10.0.0.7终端1:操作命令
[root@localhost fd]# mkfifo  /tmp/fd1
[root@localhost fd]# cat /tmp/fd1 
10.0.0.7终端2:操作命令
[root@localhost ~]# echo "test" > /tmp/fd1
查看终端1 返回
[root@localhost fd]# cat /tmp/fd1 
test

2.如果没有读管道的操作,则阻塞echo "test" > /tmp/fd1,没有读管道则阻塞

查看10.0.0.7终端1
[root@localhost fd]# echo "test" > /tmp/fd1
查看10.0.0.7终端2
[root@localhost ~]# cat /tmp/fd1 
test

总结:

利用有名管道的上述特性就可以实现一个队列控制了。

举例:一个女士公共厕所总共就10个蹲位,这个蹲位就是队列长度,女厕所门口放着10把药匙,要想上厕所必须拿一把药匙,上完厕所后归还药匙,下一个人就可以拿钥匙进去上厕所了,这样同时来了1千位美女上厕所,那前十个人抢到药匙进去上厕所了,后面的990人需要等一个人出来归还药匙才可以拿到药匙进去上厕所,这样10把药匙就实现了控制1000人上厕所的任务(os中称之为信号量)。

注意:

(1)管道具有存一个读一个,读完一个就少一个,没有则阻塞,放回的可以重复取,这正是队列特性,但是问题是当往管道文件里面放入一段内容,没人取则会阻塞,这样你永远也没办法,往管道里面同时放入10段内容(想当与10把药匙),解决这个问题的关键就是文件描述符了。

(2)mkfifo /tmp/fd1

创建有名管道文件exec 3<>/tmp/fd1,创建文件描述符3关联管道文件,这时候3这个文件描述符就拥有了管道的所有特性,还具有一个管道不具有的特性:无限存不阻塞,无限取不阻塞,而不用关心管道内是否为空,也不用关心是否有内容写入引用文件描述符: &3可以执行n次echo >&3 往管道里放入n把钥匙。

Shell编程之常用Linux系统配置文件解析

常用配置文件详解:

# 查看系统信息
/etc/redhat-release  系统版本

/etc/hosts  主机名与 IP 对应关系

/etc/resolv.conf  DNS 服务器地址

/etc/hostname  主机名

/etc/sysctl.conf  系统参数配置文件

/etc/sudoers  sudo 权限配置

/etc/init.d  	服务启动脚本

/etc/sysconfig/network-scripts      网卡信息配置目录

/etc/rc.d/rc.local   				系统 init 初始化完后执行,不建议将启动服务写在这里面,应创建自己的 systemd 或 udev

/etc/fstab  硬盘自动挂载配置

/etc/crontab 系统级任务计划

/var/spool/cron  用户级任务计划,此目录下以用户名命名对应每个用户的任务计划

/etc/cron.d  描述计算机任务计划

/etc/hosts.allow  TCP 包访问列表

/etc/hosts.deny  TCP 包拒绝列表

/usr/share/doc  各软件的文档

/etc/sshd_config  SSH 服务配置文件

/var/log  系统和应用程序日志目录

/var/spool/mail  邮件目录

# /dev  目录

/dev 目录下存放的是一些设备文件。

/dev/hd[a-t]  IDE 设备

/dev/sd[a-z]  SCSI 设备

/dev/dm-[-9]  LVM 逻辑磁盘

/dev/null  黑洞

/dev/zero  无限 0 数据流

# /proc  目录

/proc 是一个虚拟目录,在 Linux 系统启动后生成的,数据存储在内存中,存放内核运行时的参数、网络信息、进程状态等等。

/proc主目录

/proc/[0-9]+  此目录下数字命名的目录是运行进程信息,目录名为 PID

/proc/meminfo  物理内存、交换空间等信息,free

/proc/loadavg  系统负载

/proc/uptime 系统运行时间

计算系统启动和运行时间:

cat /proc/uptime| awk -F. '{run_days=$1 / 86400;run_hour=($1 % 86400)/3600;run_minute=($1 % 3600)/60;run_second=$1 % 60;printf("系统已运行:%d天%d时%d分%d秒",run_days,run_hour,run_minute,run_second)}'

或 who –b  查看最后一次系统启动的时间

/proc/cpuinfo  CPU 信息

/proc/modules  系统已加载的模块或驱动,lsmod

/proc/mounts  文件系统挂载信息,mount

/proc/swaps  swap 分区信息

/proc/partitions  系统分区信息

/proc/version  内核版本

/proc/stat  CPU 利用率,磁盘,内存页

/proc/devices  可用的设备列表

/proc/net 网络目录

/proc/net 目录存放的是一些网络协议信息。

/proc/net/tcp  TCP 状态连接信息,netstat

/proc/net/udp  UDP 状态连接信息

/proc/net/arp  arp 信息表

/proc/net/dev  网卡流量

/proc/net/snmp  网络传输协议的收发包信息

/proc/net/sockstat  socket 使用情况,比如已使用,正在使用

/proc/net/netstat  网络统计数据,netstat -s

/proc/net/route  路由表

/proc/sys 系统内核目录

这个目录下的文件可被读写,存了大多数内核参数,可以修改改变内核行为。所以修改这些文件要特别小心,修改错误可能导致内核不稳定。

有四个主要的目录:
fs # 文件系统各方面信息,包括配额、文件句柄、inode 和目录项。
kernel # 内核行为的信息
net # 网络配置信息,包括以太网、ipx、ipv4 和 ipv6。
vm # Linux 内核的虚拟内存子系统,通常称为交换空间。

# 内核配置文件
/proc/sys/fs/file-max 		内核分配所有进程最大打开文件句柄数量,可适当增加此值
/proc/sys/fs/file-nr  		只读,第一个值已分配的文件句柄数量,第二个值分配没有使用文件句柄数量,第三个值文件句柄最大数量。

/proc/sys/kernel/ctrl-alt-del 	组合键重启计算机,只为 0 同步缓冲区到磁盘,1 为不同步

/proc/sys/kernel/domainname     配置系统域名

/proc/sys/kernel/exec-shield	配置内核执行保护功能,防止某类型缓冲区溢出***。0 为禁用,1 开启

/proc/sys/kernel/hostname  配置系统主机名

/proc/sys/kernel/osrelease  内核版本号

/proc/sys/kernel/ostype  操作系统类型

/proc/sys/kernel/shmall  设置共享内存的总量,以字节为单位

/proc/sys/kernel/shmmax  设置最大共享内存段

/proc/sys/kernel/shmmni  设置共享内存段最大数量

/proc/sys/kernel/threads-max  设置最大允许线程数量

/proc/sys/kernel/pid_max  设置最大允许创建的 pid 数量

/proc/sys/kernel/version  显示最后一次编译内核时间

/proc/sys/kernel/random/uuid  生成 uuid

/proc/sys/kernel/core_pattern  控制生成 core dump 文件位置和保存格式

/proc/sys/net/core/netdev_max_backlog  设置数据包队列允许最大数量

/proc/sys/net/core/optmem_max  设置 socket 允许最大缓冲区大小

/proc/sys/net/core/somaxconn  每个端口最大监听队列长度

/proc/sys/net/core/rmem_default  设置 socket 接收默认缓冲区大小,单位字节

/proc/sys/net/core/rmem_max  设置 socket 接收最大缓冲区大小

/proc/sys/net/core/wmem_default  设置 socket 发送默认缓冲区大小

/proc/sys/net/core/wmem_max  设置 socket 发送最大缓冲区大小

/proc/sys/net/ipv4/icmp_echo_ignore_all 和 icmp_echo_ignore_broadcasts  设置是否忽略 icmp 响应包和广播包,0 为不忽略,1 为忽略  

/proc/sys/net/ipv4/ip_default_ttl  设置默认生存时间

/proc/sys/net/ipv4/ip_forward       允许系统接口转发数据包,默认 0 为关闭,1 为开启

/proc/sys/net/ipv4/ip_local_port_range   指定使用本地 TCP 或 UDP 端口范围,第一个值最低,第二个值最高

/proc/sys/net/ipv4/tcp_syn_retries  	限制重新发送 syn 尝试建立连接次数

/proc/sys/net/ipv4/tcp_synack_retries  syn ack 确认包尝试次数/proc/sys/net/ipv4/tcp_syncookies      是否启用 syn cookie,0 为关闭,默认 1 为开启


/proc/sys/net/ipv4/tcp_max_tw_buckets  系统保持 TIME_WAIT 最大数量

/proc/sys/net/ipv4/tcp_tw_recycle   是否启用 TIME_WAIT 快速收回,默认 0 为关闭,1 为开启

/proc/sys/net/ipv4/tcp_tw_reuse     是否启用 TIME_WAIT 复用,默认 0 为关闭,1为开启

/proc/sys/net/ipv4/tcp_keepalive_time		TCP 连接保持时间(默认 2 小时),当连接活动,定时器会重新复位。
/proc/sys/vm/swappiness			内核按此值百分比来使用 swap,值越小越不考虑使用物理内存,0 为尽可能不使用 swap

/proc/sys/vm/overcommit_memory		控制内存分配,默认 0 为内核先评估可用内存,如果足够允许申请,否则拒绝,1 为允许分配所有物理内存,2 为允许分配超过物理内存和交换空间总和的内存

/proc/sys/vm/overcommit_ratio		指定物理内存比率,当 overcommit_memory=2时,用户空间进程可使用的内存不超过物理内存*overcommit_ratio+swap

Shell编程语句和实战

Shell编程也叫Shell流程控制语句,流程控制主要是改变程序运行顺序的指令。

If条件语句实战

Linux Shell编程中,if、for、while、case等条件流程控制语句用的非常多,熟练掌握以上流程控制语句及语法的实验,对编写Shel脚本有非常大的益处。

If条件判断语句,通常以if开头,fi结尾。也可加入else或者elif进行多条件的判断,if表达式如下:

if  (表达式) 
语句1
else
语句2
Fi

单分支格式1:if 条件 ; then 语句; fi
双分支格式2:if 条件; then 语句; else 语句; fi
多分支格式3:if …; then … ;elif …; then …; else …; fi

If常见判断逻辑运算符详解:
-f	 					判断文件是否存在 eg: if [ -f filename ];
-d	 					判断目录是否存在 eg: if [ -d dir     ];
-eq						等于,应用于整型比较 equal;
-ne						不等于,应用于整型比较 not equal;
-lt						小于,应用于整型比较 letter;
-gt						大于,应用于整型比较 greater;
-le						小于或等于,应用于整型比较;
-ge 					大于或等于,应用于整型比较;
-a						双方都成立(and) 逻辑表达式 –a 逻辑表达式;
-o						单方成立(or) 逻辑表达式 –o 逻辑表达式;
-z						“字符串”的长度为零则为真
-n                   “字符串”的长度为非零non-zero则为真
||      				单方成立;
&&      				双方都成立表达式。

判断符使用技巧:
1.整数比较使用-lt,-gt,ge等比较运算符,详情参考:整数比较
2.文件测试使用 -d, -f, -x等运算发,详情参考:文件测试
3.逻辑判断使用    &&(且)、||(或)、!(取反)
字符串实用的对比:
1.字符串的比较使用以下三个比较运算符:= 或者(==)、!= 、> 、 <。
2.-z表示后面的值是否为空,为空则返回true,否则返回false。
3.-n表示判断后面的值是否为空,不为空则返回true,为空则返回false。

逻辑判断表达式:
if [ $a -gt $b ]; if [ $a -lt 5 ]; if [ $b -eq 10 ]等 -gt (>); -lt(<); -ge(>=); -le(<=);-eq(==); -ne(!=) 注意到处都是空格
可以使用 && || 结合多个条件
if [ $a -gt 5 ] && [ $a -lt 10 ]; then
if [ $b -gt 5 ] || [ $b -lt 3 ]; then

运算工具( let/expr/bc )
命令	描述	示例
let	赋值并运算,支持++,--	let VAR=(1+2)*3;echo $VAR
x=10;y=5
let x++;echo $x 每次执行一次x加1
let y--;echo $y 每执行一次y-1
let x+=2 每执行一次x加2
let x-=2 没执行一次x减2
expr	乘法符号或者特殊符号需要加分斜杠转义\* 	expr 1 \* 2  运算符两边需要有空格
expr \( 1 + 2 \) \* 2 使用双括号时要进行转义
bc	计算器,支持浮点运算、平方	bc本身就是一个计算器,可以直接输入命令,进入解释器。
echo “1 + 2” |bc 将管道符前面标准输出左右bc的标准输入
echo ‘scale=2;10/3’|bc 用scale保留2位小数点
If语句Shell脚本编程案例:

(1) 比较两个整数大小。

#!/bin/bash
#By author test.net
NUM=100
if  (( $NUM > 4 )) ;then 
echo “The  Num  $NUM  more  than 4.”
else
echo “The  Num  $NUM  less   than 4.”
fi

(2) 判断系统目录是否存在。

#!/bin/bash
#judge DIR or Files
#By author test.net
if  [  !  -d  /data/20140515  -a  !  -d  /tmp/test/  ];then 
mkdir  -p  /data/20140515
fi

(3) if多个条件测试分数判断。

#!/bin/bash
#By author test.net
scores=$1
if  [[ $scores -eq 100 ]]; then
    echo "very good!";
elif [[ $scores -gt 85 ]]; then
    echo "good!";
elif [[ $scores -gt 60 ]]; then
    echo "pass!";
elif [[ $scores -lt 60 ]]; then
    echo "no pass!"
fi

(4) 根据Linux不同发行版本使用不同的命令进行安装软件

#!/bin/bash
if [ -e /etc/redhat-release ]; then
yum install wget -y
elif [ $(cat /etc/issue |cut -d' ' -f1) == "Ubuntu" ]; then
apt-get install wget -y
else
Operating system cannot be found.
exit
fi

SHELL编程括号详解

Shell编程中,尤其是使用if语句时,经常会使用()、(())、[]、[[]]、{}等括号,如下为几种括号简单区别对比:

(  )
用于多个命令组、命令替换、初始化数组,多用于SHELL命令组,例如:JF=(jf1 jf2 jf3),其中括号左右不保留空格;定义变量时增加括号,括号内的变量会失效
(( ))
整数扩展、运算符、重定义变量值,算术运算比较,例如:((i++))、((i<=100)),其中括号左右不保留空格;
[ ]
bash内部命令,[ ]与test是等同的,正则字符范围、引用数组元素编号,不支持+-*/数学运算符,逻辑测试使用-a、-o,通常用于字符串比较、整数比较以及数组索引,其中括号左右要保留空格;
[[ ]]
bash程序语言的关键字,不是一个命令,[[ ]]结构比[ ]结构更加通用,不支持+-*/数学运算符,逻辑测试使用&&、||,通常用于字符串比较、逻辑运算符等,其中括号左右要保留空格;
{}
主要用于命令集合或者范围,例如mkdir  -p  /data/201{7,8}/,其中括号左右不保留空格;

SHELL编程符号详解

Shell编程中,不管是使用变量、编程时,经常会使用$、\、单引号、双引号、反引号等符号,如下为几种符号简单区别对比:

  • 美元符号$,主要是用于引用SHELL编程中变量,例如定义变量JF=www.test.net,引用值,需要用$JF;

  • \反斜杠,主要是用于对特定的字符实现转义,保留原有意义,例如echo “$JF”结果会打印$JF,而不会打印www.test.net;

  • 单引号 (' ') ,单引号,不具有变量置换的功能,所有的任意字符还原为字面意义,实现屏蔽Shell元字符的功能;

  • 双引号(" ") ,双引号,具有变量置换的功能,保留$(使用变量前导符), (转义符), `(反向引号)元字符的功能;

  • 反向引号(),反引号,位于键盘Tab键上面一行的键,用作命令替换(相当于$(...);

LAMP一键自动化安装脚本

通过前面章节对if语句和变量的学习,现基于所学知识,编写一键源码安装LAMP脚本, 编写脚本可以养成先分解脚本的各个功能的习惯,有利于快速写出脚本,写出更高效的脚本。

一键源码安装LAMP脚本,可以拆分为如下功能:

(1) LAMP打印菜单:

  1. 安装apache WEB服务器;

  1. 安装Mysql DB服务器;

  2. 安装PHP 服务器;

  3. 整合LAMP架构

  4. 启动LAMP服务;

(2) Apache服务器安装部署:

Apache官网下载httpd-2.4.37.tar.gz版本,解压,进入安装目录,configure、make 、make install。

(3) Mysql服务器的安装:

Mysql官网下载mysql-5.5.60.tar.gz版本,解压,进入安装目录,configure、make 、make install。

(4) PHP服务器安装:

PHP官网下载php-5.4.31.tar.gz版本,解压,进入安装目录,configure、make 、make install。

一键源码安装LAMP脚本,auto_install_lamp.sh内容如下:

#!/bin/bash
########## function ##########
depend_pkg ()
{
	yum install gcc gcc-c++ make cmake ncurses-devel libxml2-devel \
    perl-devel libcurl-devel libgcrypt libgcrypt-devel libxslt \
    libxslt-devel pcre-devel openssl-devel wget -y
}
cat <<END
        1.[install apache2.4]
        2.[install mysql5.5]
        3.[install php5.4]
END
read -p "Please input number : " NUM
case $NUM in
1)
########## Install Depend Pkg ##########
depend_pkg;
WorkDIR=/usr/local/src
cd $WorkDIR
[ -f "httpd-2.4.37.tar.gz" ] || wget http://mirrors.sohu.com/apache/httpd-2.4.37.tar.gz
ls *.tar.gz |xargs -I file tar xzf file -C $WorkDIR
if [ $? -eq 0 ];then
        yum install apr apr-devel apr-util apr-util-devel -y
else
        echo "------ apr make failed. ------"
    exit 1
fi
########## Install Apache ##########
HTTPDIR=/usr/local/apache2.4
if [ $? -eq 0 ];then
        cd $WorkDIR
        cd httpd-2.4.37
        ./configure -prefix=$HTTPDIR -enable-so -enable-rewrite -enable-modules=all
make && make install
else
        echo "------ apr-util make failed. ------"
        exit 1
fi
if [ $? -eq 0 ];then
        CONF=$HTTPDIR/conf/httpd.conf
        cp $HTTPDIR/bin/apachectl /etc/init.d/httpd
        chmod +x /etc/init.d/httpd
        sed -i "s/#ServerName www.example.com:80/ServerName ${IP}:80/g" $CONF
        sed -i 's/DirectoryIndex index.html/DirectoryIndex index.php index.html/g' $CONF
        sed -i "391 s/^/AddType application\/x-httpd-php .php/" $CONF
	/etc/init.d/httpd start
        IP=`ip address |grep inet|sed -n '3p'|awk '{print $2}'|awk -F/ '{print $1}'`
        Urlcode=`curl -o /dev/null -s -w "%{http_code}" $IP/index.html` 
	[ $Urlcode -eq 200 ] && echo "Apache install success." || echo "Apache install failed."
else
        echo "------ apache make failed. ------"
	exit 1
fi
;;
2)
########## Install Depend Pkg ##########
depend_pkg;
########## Install Mysql ##########
/usr/sbin/groupadd mysql
/usr/sbin/useradd -g mysql -s /sbin/nologin mysql
WorkDIR=/usr/local/src
MYSQLDIR=/usr/local/mysql5.5
cd $WorkDIR
[ -f "mysql-5.5.60.tar.gz" ] || wget https://mirrors.tuna.tsinghua.edu.cn/mysql/downloads/MySQL-5.5/mysql-5.5.60.tar.gz 
tar zxvf mysql-5.5.60.tar.gz
cd mysql-5.5.60
cmake -DCMAKE_INSTALL_PREFIX=$MYSQLDIR \
-DSYSCONFDIR=$MYSQLDIR/etc \
-DMYSQL_DATADIR=$MYSQLDIR/data \
-DDEFAULT_CHARSET=utf8 \
-DDEFAULT_COLLATION=utf8_general_ci
make && make install
if [ $? -eq 0 ];then
	$MYSQLDIR/scripts/mysql_install_db \
--basedir=$MYSQLDIR --datadir=$MYSQLDIR/data/ --user=mysql 1>/dev/null
	mkdir $MYSQLDIR/etc
	cp support-files/my-medium.cnf $MYSQLDIR/etc/my.cnf
	cp support-files/mysql.server /etc/init.d/mysqld
	rm -rf /etc/my.cnf
#	echo "PATH=$PATH:$MYSQLDIR/bin" >> /etc/profile 
#	. /etc/profile
	chmod +x /etc/init.d/mysqld
	chown -R root.mysql $MYSQLDIR
	chown -R mysql.mysql $MYSQLDIR/data/
	/usr/local/mysql5.5/bin/mysqld_safe --user=mysql&
	sleep 10 && /usr/local/mysql5.5/bin/mysqladmin -u root password '123456'
	/usr/local/mysql5.5/bin/mysql -uroot -p'123456' -e "show databases;"
	[ $? -eq 0 ] && echo "MySQL install success." || echo "MySQL install failed."
else
	echo "------mysql cmake failed.------"
	exit 1 
fi
;;
3)
########## Install Depend Pkg ##########
depend_pkg;
########## Install GD ##########
yum install gd freetype freetype-devel libpng libpng-devel zlib zlib-devel libjpeg* -y
########## Install PHP ##########
WorkDIR=/usr/local/src
PHPDIR=/usr/local/php5.4
PHPCONF=$PHPDIR/etc/php.ini
cd $WorkDIR
[ -f "php-5.4.31.tar.gz" ] || wget http://mirrors.sohu.com/php/php-5.4.31.tar.gz
tar zxvf php-5.4.31.tar.gz 
cd php-5.4.31
./configure -prefix=$PHPDIR \
--with-config-file-path=$PHPDIR/etc \
--with-apxs2=/usr/local/apache2.4/bin/apxs \
--with-mysql=/usr/local/mysql5.5 \
--with-mysqli=/usr/local/mysql5.5/bin/mysql_config \
--enable-soap --enable-bcmath --enable-zip --enable-ftp \
--enable-mbstring --with-gd --with-libxml-dir --with-jpeg-dir \
--with-png-dir --with-freetype-dir --with-zlib \
--with-libxml-dir=/usr --with-curl --with-xsl --with-openssl
make && make install
if [ $? -eq 0 ];then
	\cp php.ini-production $PHPCONF
	echo "data.timezone = Asia\Shanghai" >> $PHPCONF
	sed -i 's/upload_max_filesize = 2M/ upload_max_filesize = 50M/g' $PHPCONF
	sed -i 's/display_errors = Off/display_errors = On/g' $PHPCONF
	echo "<?php phpinfo();?>" > /usr/local/apache2.4/htdocs/index.php
	/etc/init.d/httpd restart 
        /etc/init.d/mysqld restart &>/dev/null
        IP=`ip address |grep inet|sed -n '3p'|awk '{print $2}'|awk -F/ '{print $1}
'`
	Urlcode=`curl -o /dev/null -s -w "%{http_code}" $IP/index.php`
	[ $Urlcode -eq 200 ] && echo "PHP install success." || echo "PHP install failed."
    	echo "/usr/local/apache/bin/apachectl start" >> /etc/rc.local
    	chkconfig mysqld on
else
	echo "------ php make failed. ------"
fi
;;
*)
    echo "Please input number 1 2 3."
esac

循环语句For 和While

Shell 循环中分为当直型循环和直到型循环

  1. 当型循环结构:在每次执行循环体前,对条件进行判断,当条件满足时,执行循环体,否则终止循环。

  2. 直到型循环结构:在执行了一次循环体后,对条件进行判断,如果条件不满,就继续执行,知道条件满足终止循环。

Shell编程中循环命令用于特定条件下决定某些语句重复执行的控制方式,有三种常用的循环语句:for、while和until。

while循环和for循环属于“当型循环”,而until属于“直到型循环”。

循环控制符:break和continue控制流程转向。

For循环语句实战

for循环语句主要用于对某个数据域进行循环读取、对文件进行遍历,通常用于需要循环某个文件或者列表。其语法格式以for…do开头,done结尾。

语法格式如下:

For  变量  in  (表达式)
do
	语句1
done

For循环语句Shell脚本编程案例如下:

(1) 循环打印BAT企业官网:

#!/bin/bash
#By author test.net
for  test  in  www.baidu.com www.taobao.com www.qq.com
do
	echo  $test
done

(2) 循环打印1至100数字,seq表示列出数据范围:

#!/bin/bash
#By author test.net
for   i   in  `seq 1 100`
do
	echo  “NUM is $i”
done

(3) For循环求1-100的总和:

#!/bin/bash
#By author test.net
#auto sum 1 100
j=0
for  ((i=1;i<=100;i++))
do
     j=`expr $i + $j`
done
echo $j

(4) 对系统日志文件进行分组打包:

#!/bin/bash
#By author test.net
for   i   in  `find /var/log  -name "*.log"`
do
        tar  -czf  `echo $i|sed s#/#_#g`.tgz  $i
done

(5) For循环批量远程主机文件传输:

#!/bin/bash
#auto scp files for client
#By author test.net
for i in `seq 100 200`
do
scp -r /tmp/test.txt [email protected].$i:/data/webapps/www
done

(6) For循环批量远程主机执行命令:

#!/bin/bash
#auto scp files for client
#By author test.net
for i in `seq 100 200`
do
       ssh -l  root 192.168.1.$i ‘ls /tmp’
done

(7) For循环打印10秒等待提示:

for ((j=0;j<=10;j++))
do 
	 echo  -ne  "\033[32m-\033[0m"
	 sleep 1 
done
echo

(8) 检测多个域名是否可以正常访问

#!/bin/bash 
URL="www.test.net www.sina.com www.jd.com" 
for url in $URL; do 
    HTTP_CODE=$(curl -o /dev/null -s -w %{http_code} http://$url) 
    if [ $HTTP_CODE -eq 200 -o $HTTP_CODE -eq 301 -o $HTTP_CODE -eq 302 ]; then 
        echo "$url OK." 
    else 
        echo "$url NO!" 
    fi 
done
[root@localhost scripts]# bash test.sh
www.baidu.com OK.
www.sina.com OK.
www.jd.com OK.

(9) For循环打印99乘法表

for i in  `seq 9`
do
         for n in `seq 9`
         do 
            [ $n -le $i ] &&  echo  -n  "$i*$n = `echo $(($i*$n))` "
         done 
        echo "  "
done
While循环语句实战

While循环语句也称为前测试循环语句,它的循环重复执行次数,是利用一个条件来控制是否继续重复执行这个语句。

While循环语句与for循环功能类似,主要用于对某个数据域进行循环读取、对文件进行遍历,通常用于需要循环某个文件或者列表,满足循环条件会一直循环,不满足则退出循环,其语法格式以while…do开头,done结尾。

语法格式如下:

while  (表达式)
do
语句1
done

while循环语句之所以命名为前测试循环,是因为它要先判断此循环的条件是否成立,然后才作重复执行的操作。

while循环语句执行过程是:先判断表达式的退出状态,如果退出状态为0,则执行循环体,并且在执行完循环体后,进行下一次循环,否则退出循环执行done后的命令。

为了避免死循环,必须保证在循环体中包含循环出口条件,即存在表达式的退出状态为非0的情况。

While循环语句Shell脚本编程案例如下:

(1) 循环打印BAT企业官网,read指令用于读取行或者读取变量:

v1 版本
#!/bin/bash
#By author test.net
while read line
do 
echo $line
done <test.txt

v2版本让循环的脚本只运行2遍。
#!/bin/bash
#by author test.net
n=1
while ((n<=2));
do
	((n++))
	while read test;
	do
		echo $test;
		sleep 3;
	done < /root/test.txt
done

其中test.txt内容为:

www.baidu.com
www.taobao.com
www.qq.com

(2) While无限每秒输出Hello World:

#!/bin/bash
#By author test.net
while sleep 1
do  
echo -e "\033[32mHello World.\033[0m"
done
# while true表示条件永远为真,因此会一直运行,像死循环一样,但是我们称呼为守护进程。

(3) 循环打印1至100数字,expr用于运算逻辑工具:

#!/bin/bash
#By author test.net
i=0
while ((i<=100))
do
        echo  $i
        i=`expr $i + 1`
done

(4) While循环求1-100的总和:

#!/bin/bash
#By author test.net
#auto sum 1 100
j=0
i=1
while ((i<=100))
do
     j=`expr $i + $j`
     ((i++))
done
echo $j
=================
#!/bin/bash 
i=1
j=0
while [ $i -le 100 ];do
	let j=j+i
	let i=i+1
done 
echo $j

(5) 条件表达式为true,产生死循环,不间断ping主机地址

#!/bin/bash 
#By author test.net
while true; do 
    ping –c 1 www.test.net
done

(6) While循环逐行读取文件:

#!/bin/bash
#By author test.net
while read line
do
    echo  $line;
done  < /etc/hosts

(7) While循环判断输入IP正确性:

#!/bin/bash
#By author test.net
#Check  IP  Address
read -p "Please enter ip Address,example 192.168.0.11 ip": IPADDR
echo $IPADDR|grep -v "[Aa-Zz]"|grep --color -E "([0-9]{1,3}\.){3}[0-9]{1,3}"
while [ $? -ne 0 ]
do
        read -p "Please enter ip Address,example 192.168.0.11 ip": IPADDR
        echo $IPADDR|grep -v "[Aa-Zz]"|grep --color -E "([0-9]{1,3}\.){3}[0-9]{1,3}"
done

(8) 每5秒循环判断/etc/passwd是否被非法修改:

#!/bin/bash
#Check File to change.
#By author test.net
FILES="/etc/passwd"
while true
do
        echo "The Time is `date +%F-%T`"
        OLD=`md5sum $FILES|cut -d" " -f 1`
        sleep 5
        NEW=`md5sum $FILES|cut -d" " -f 1`
        if [[ $OLD != $NEW ]];then
                echo "The $FILES has been modified."
        fi
done

(9) 每10秒循环判断test用户是否登录系统:

#!/bin/bash
#Check File to change. 
#By author test.net
USERS="test"
while true
do
        echo "The Time is `date +%F-%T`"
        sleep 10
        NUM=`who|grep "$USERS"|wc -l`
        if [[ $NUM -ge 1 ]];then
                echo "The $USERS is login in system."
        fi
done

Case选择语句实战

Case选择语句,主要用于对多个选择条件进行匹配输出,与if elif语句结构类似,通常用于脚本传递输入参数,打印出输出结果及内容,其语法格式以Case…in开头,esac结尾。

语法格式如下:

#!/bin/bash
#By author test.net
case  $1  in  
    Pattern1)
    语句1 
    ;;  
    Pattern2)
    语句2
    ;;  
    Pattern3)
    语句3
    ;;  
esac

Case条件语句Shell脚本编程案例如下:

(1) 打印Monitor及Archive选择菜单:

#!/bin/bash
#By author test.net
case $1 in
        monitor)
        echo "monitor"
        ;;
        archive)
        echo "archive"
        ;;
        help )
        echo -e "\033[32mUsage:{$0 monitor | archive |help }\033[0m"
        ;;
        *)
        echo -e "\033[32mUsage:{$0 monitor | archive |help }\033[0m "
esac

(2) 自动修改IP脚本菜单:

#!/bin/bash
#By author test.net
case $i in
                modify_ip)
                change_ip
                ;;
                modify_hosts)
                change_hosts
                ;;
                exit)
                exit
                ;;
                *)
                echo -e "1) modify_ip\n2) modify_ip\n3)exit"  
esac
Select选择语句实战

Select语句一般用于选择,常用于选择菜单的创建,可以配合PS3来做打印菜单的输出信息,其语法格式以select…in do开头,done结尾:

select i in (表达式) 
do
语句
done

Select选择语句Shell脚本编程案例如下:

(1) 打印开源操作系统选择:

#!/bin/bash
#By author test.net
PS3="What you like most of the open source system?"
select i in CentOS RedHat Ubuntu 
do
echo "Your Select System: "$i
done

(2) 打印LAMP选择菜单

#!/bin/bash
#By author test.net
PS3="Please enter you select install menu:"
select i in http php mysql quit
do
case $i in
        http)
        echo Test Httpd.
        ;;
        php)
        echo Test PHP.
        ;;
        mysql)
        echo Test MySQL.
        ;;
        quit)
        echo The System exit.
        exit
esac
done
break 和 和 continue 语句
  • break 是终止循环。

  • continue 是跳出当前循环。

示例 1:在死循环中,满足条件终止循环

#!/bin/bash 
i=0 
while true; do 
    let i++ 
    if [ $i -eq 3 ]; then 
        break 
    fi 
    echo $i 
done
 
# bash test.sh
1
2

上述脚本中首先是使用了while循环来引用let计算器,计算i的值,后面用了 if 判断,并用了 break 语句,如果数字等于3就跳出循环。

示例 2:举例子说明 continue 用法

#!/bin/bash
i=0
while [ $i -lt 5 ]; do
let i++
if [ $i -eq 3 ]; then
continue
fi
echo $i
done
# bash test.sh
1
2
4
5

注上述实验在使用continue 是进行做了一个判断,如果i++ 大于等于到3的时候进行跳出本次循环,继续下次循环。所以打印出来之后没有数字3。

以上2个案例表明continue是用于跳出单此循环而使用,break则是匹配到之后则是跳出整个循环语句,即时没有循环完,也会进行跳出。

Shift 偏移

Shell编程函数实战

Shell允许将一组命令集或语句形成一个可用块,这些块称为Shell函数,Shell函数的用于在于只需定义一次,后期随时使用即可,无需在Shell脚本中添加重复的语句块,其语法格式以function name(){开头,以}结尾。

格式

Shell编程函数默认不能将参数传入()内部,Shell函数参数传递在调用函数名称传递,例如name args1 args2。

function name (){
        command1
command2
        ........
}
name args1 args2
#function关键字可写,也可不写。

引用函数

func [OPTION]

引用函数的时候只需要执行函数名称即可,OPTION是参数,参数可以写多个,和脚本后面的参数是一样的

无参函数

[root@localhost scripts]# cat test.sh
 \#!/bin/bash 
 func() { 
     echo "This is a function." 
 } 
 func
 [root@localhost scripts]# bash test.sh
 This is a function.

Shell函数很简单,函数名后跟双括号,再跟双大括号。通过函数名直接调用,不加小括号,函数命令可以自己定义。

有参函数

[root@localhost scripts]# cat test.sh
#!/bin/bash 
func() { 

    echo "Hello $1" 
} 
func world 
[root@localhost scripts]# bash test.sh
Hello world

通过Shell位置参数给函数传参,参照执行脚本时的$1,$2,$3 等示例。

函数返回值

[root@localhost scripts]# cat test.sh 
#!/bin/bash 
func() { 
   VAR=$((1+1)) 
    return $VAR 
    echo "This is a function." 
} 
func 
echo $?
[root@localhost scripts]# sh test.sh 
2

return在函数中定义状态返回值,返回并终止函数,但返回的只能是0-255的数字,类似于exit。

递归函数

函数也支持递归调用,也就是自己调用自己。

[root@localhost scripts]# cat test.sh 
#!/bin/bash 
test() { 
    echo $1 
    sleep 1 
    test hello 
} 
test
[root@localhost scripts]# bash test.sh

hello
hello
hello
hello
hello
……………

执行会一直在调用本身打印hello,这就形成了闭环。

fork 炸弹

经典的 fork 炸弹就是函数递归调用:

:(){ :|:& };: 或 .(){.|.&};.

这样看起来不好理解,我们更改下格式:

:() {
:|:&
};
:

再易读一点:

test() {
test|test&
};
test

分析下:

:(){ } 定义一个函数,函数名是冒号。

: 调用自身函数

| 管道符

: 再一次递归调用自身函数

:|: 表示每次调用函数":"的时候就会生成两份拷贝。

& 放到后台

; 分号是继续执行下一个命令,可以理解为换行。

: 最后一个冒号是调用函数。

因此不断生成新进程,直到系统资源崩溃,递归函数用的不多。

  1. 创建Apache软件安装函数,给函数Apache_install传递参数1:

#!/bin/bash
#auto install LAMP 
#By author test.net
#Httpd define path variable
H_FILES=httpd-2.2.31.tar.bz2
H_FILES_DIR=httpd-2.2.31
H_URL=http://mirrors.cnnic.cn/apache/httpd/
H_PREFIX=/usr/local/apache2/
function Apache_install()
{
#Install httpd web server 
if [[ "$1" -eq "1" ]];then
	wget -c $H_URL/$H_FILES &&  tar -jxvf $H_FILES && cd $H_FILES_DIR &&./configure --prefix=$H_PREFIX 
	if [ $? -eq 0 ];then
		make && make install
		echo -e "\n\033[32m-----------------------------------------------\033[0m"
		echo -e "\033[32mThe $H_FILES_DIR Server Install Success !\033[0m"
	else
		echo -e "\033[32mThe $H_FILES_DIR Make or Make install ERROR,Please Check......"
		exit 0
	fi
fi
}
Apache_install 1
  1. 创建judge_ip判断IP函数:

#!/bin/bash
#By author test.net
judge_ip(){
        read -p "Please enter ip Address,example 192.168.0.11 ip": IPADDR
        echo $IPADDR|grep -v "[Aa-Zz]"|grep --color -E "([0-9]{1,3}\.){3}[0-9]{1,3}"
}
judge_ip

Shell 正则表达式

什么是正则表达式?

正则表达式在每种语言中都会有,功能就是匹配符合你预期要求的字符串。

为什么要学正则表达式?

在企业工作中,我们每天做的linux运维工作中,时刻都会面对大量带有字符串的文本配置、程序、命令输出及日志文件等,而我们经常会有迫切的需要,从大量的字符串内容中查找符合工作需要的特定的字符串。这就要靠正则表达式。因此,可以说正则表达式就是为过滤字符的需求而生的! 例如:ifconfig的输出取IP,例如:cat /var/log/messages输出等

两个注意事项:

  1. 正则表达式应用非常广泛,存在于各种语言中,例如:php、python、java等。但是我们今天讲的linux系统运维工作中的正则表达式,即linux正则表达式,最常用正则表达式的命令就是grep(egrep)、sed、awk,换句话说linux四剑客剑客要想工作的各高效,那一定离不开正则表达式配合的。

  2. 正则表达式和我们常用的通配符特殊字符是用本质去别的,这一点要注意。通配符例子:ls .log**这里的就是通配符(表示所有),不是正则表达式

Shell 正则表达式分为两种:

  • 基础正则表达式:BRE(basic regular express)

  • 扩展正则表达式:ERE(extend regular express),扩展的表达式有+、?、|和()

下面是一些常用的正则表达式符号

正则表达式
描述

\
转义符,将特殊字符进行转义,忽略其特殊意义

^
匹配行首,awk中,^则是匹配字符串的开始

$
匹配行尾,awk中,$则是匹配字符串的结尾

.
匹配除换行符\n之外的任意单个字符,awk则中可以

[]
匹配包含在[字符]之中的任意一个字符

[^ ]
匹配字符之外的任意一个字符

^[^]
匹配不是中括号内任意一个字符开头的行

[ - ]
匹配[]中指定范围内的任意一个字符,要写成递增

?
匹配之前的项1次或者0次

+
匹配之前的项1次或者多次

*
匹配之前的项0次或者多次

()
匹配表达式,创建一个用于匹配的子串

{ n }
匹配之前的项n次,n是可以为0的正整数

{n,}
之前的项至少需要匹配n次

{n,m}
指定之前的项至少匹配n次,最多匹配m次,n<=m

|
交替匹配|两边的任意一项

<
边界符,匹配字符串开始

>
边界符,匹配字符串结束

基本正则表达式实践

接下来的测试文本如下:

[root@localhost ~]# cat test.log 
%anaconda
pwpolicy root --minlen=6 --minquality=1 --notstrict --nochanges –notempty
pwpolicy user --minlen=6 --minquality=1 --notstrict --nochanges –emptyok
pwpolicy luks --minlen=6 --minquality=1 --notstrict --nochanges --notempty

%end...
=========================

以下实践通过grep 命令增加正则表达式进行匹配练习

  1. ^(尖角号)功能实践

# 匹配首字母为%的行
[root@localhost ~]#  grep -n "^%" test.log 
# -n 参数是显示匹配到的行号。

1570716106386

  1. $(美元符)功能实践

#匹配结尾字符为y的所在行
[root@localhost ~]# grep -n "y$" test.log 

1570716156583

  1. ^$ 功能实践

#匹配所有的空行进行显示
[root@localhost ~]# grep -n "^$" test.log 

1570716182783

  1. (点)功能实践

#匹配任意单个字符进行显示(排除空行)
[root@localhost ~]# grep -n "." test.log 

1570716218056

  1. \(转义符)功能实践

#将特殊字符进行转义,忽略其特殊意义

1570716237463

  1. *(星号)功能实践

#匹配任意的项0次或者多次

1570716255958

  1. .*组合符功能实践

#.* 组合在一起使用可以匹配任意字符串

1570716277103

#匹配任意以o开头的行

1570716339064

  1. [](中括号)功能实践

#匹配到任意包含a-z 字母的字符

1570716370155

  1. [^abc](中括号内取反符)功能实践

#匹配取反字符,将^放在括号内代表取反的意思

1570716393582

扩展正则表达式实践

  1. +(加号)功能实践

#匹配之前+号之前的字符1次或者多次

1570782299517

  1. ?(问号)功能实践

#匹配?号之前的字符1次或者0次

1570782308374

  1. |(竖线)功能实践

#竖线匹配可以进行多个值的选定

1570782322885

  1. () 功能实践

要求:取出包含good或glad的行

1570782405525

#后向引用,后项引用主要用作于匹配分组调换顺序的时候进行使用,\1 表示调用前面第一个括号里面的内容,\2 代表调用第2个括号里面的内容,最多能引用九个括号,也就是说支持从 \1 - \9。

1570782420917

  1. {n,m}匹配次数功能实践

#单独使用一个大括号,可以引用前面的字符一次到多次

1570782435217

  1. \< \>边界符锚定

#边界符可以用固定字符的匹配

\<:其后面的字符必须作为单词的首部出现

\>:其前的字符必须作为单词的尾部出现

\<root \<:root必须作为一行的单词出现

1570782493490

在Shell下使用这些正则表达式处理文本最多的命令有下面几个工具:

命令
描述

grep
默认不支持扩展表达式,加-E选项开启ERE。如果不加-E使用花括号要加转义符{}

egrep
支持基础和扩展表达式

awk
支持egrep所有的正则表达式

sed
默认不支持扩展表达式,加-r选项开启ERE。如果不加-r使用花括号要加转义符{}

Shell编程四剑客之Find

通过如上基础语法的学习,读者对Shell编程有了更近一步的理解,Shell编程不再是简单命令的堆积,而是演变成了各种特殊的语句、各种语法、编程工具、各种命令的集合。

在Shell编程工具中,四剑客工具的使用更加的广泛,Shell编程四剑客包括:find、sed、grep、awk,熟练掌握四剑客会对Shell编程能力极大的提升。

四剑客之Find工具实战,Find工具主要用于操作系统文件、目录的查找,其语法参数格式为:

find   path   -option   [   -print ]   [ -exec   -ok   command ]   { }  \;

其option常用参数详解如下:

-name   filename    		#查找名为filename的文件;
-type    b/d/c/p/l/f		#查是块设备、目录、字符设备、管道、符号链接、普通文件;
-size    n[c]     		    #查长度为n块[或n字节]的文件;
-perm               			#按执行权限来查找;
-user    username   		#按文件属主来查找;
-group   groupname  		#按文件属组来查找;
-mtime    -n +n     		#按文件更改时间来查找文件,-n指n天以内,+n指n天以前;
-atime    -n +n     		#按文件访问时间来查找文件;
-ctime    -n +n     		#按文件创建时间来查找文件;
-mmin     -n +n     			#按文件更改时间来查找文件,-n指n分钟以内,+n指n分钟以前;
-amin     -n +n     			    #按文件访问时间来查找文件;
-cmin     -n +n     			    #按文件创建时间来查找文件;
-nogroup            			    #查无有效属组的文件;
-nouser             			    #查无有效属主的文件;
-newer   f1 !f2     			#找文件,-n指n天以内,+n指n天以前;
-depth              			    #使查找在进入子目录前先行查找完本目录;
-fstype             			    #查更改时间比f1新但比f2旧的文件;
-mount              			    #查文件时不跨越文件系统mount点;
-follow             			    #如果遇到符号链接文件,就跟踪链接所指的文件;
-cpio              				#查位于某一类型文件系统中的文件;
-prune              			   #忽略某个目录;
-maxdepth						         #查找目录级别深度。

(1) Find工具-name参数案列:

find   /data/    -name   "*.txt"     		#查找/data/目录以.txt结尾的文件;
find   /data/    -name   "[A-Z]*"    		#查找/data/目录以大写字母开头的文件;
find   /data/    -name   "test*"     		#查找/data/目录以test开头的文件;

(2) Find工具-type参数案列:

find   /data/    -type d   				#查找/data/目录下的文件夹;
find   /data/    !   -type   d    		#查找/data/目录下的非文件夹;
find   /data/    -type l   				#查找/data/目录下的链接文件。
find  /data/ -type d|xargs chmod 755 -R 	#查目录类型并将权限设置为755;
find  /data/ -type f|xargs chmod 644 -R 	#查文件类型并将权限设置为644;

(3) Find工具-size参数案列:

find   /data/    -size   +1M              #查文件大小大于1Mb的文件;
find   /data/    -size   10M            	#查文件大小为10M的文件;
find   /data/    -size   -1M            	#查文件大小小于1Mb的文件;

(4) Find工具-perm参数案列:

find   /data/    -perm   755   		#查找/data/目录权限为755的文件或者目录;
find   /data/    -perm   -777   		#与-perm 777相同,表示所有权限;
find   /data/    -perm    +644         #文件权限符号644以上; 

(5) Find工具-mtime参数案列:

atime,access time  	文件被读取或者执行的时间;
ctime,change time  	文件状态改变时间;
mtime,modify time  	文件内容被修改的时间;
“-”号代表多少分钟以内或者是多少天以内
“+”号代表多少分钟以前或者是多少天以前
find /data/ -mtime +30 	-name 	"*.log"   #查找30天以前的log文件;
find /data/ -mtime -30 	-name 	"*.txt"  	#查找30天以内的log文件;
find /data/ -mtime 30 	-name  	"*.txt"	#查找第30天的log文件;
find /data/ -mmin  +30	 -name  	"*.log"   #查找30分钟以前修改的log文件;
find /data/ -amin  -30 -name  	"*.txt"  	#查找30分钟以内被访问的log文件;
find /data/ -cmin  30 	-name  	"*.txt"	#查找第30分钟改变的log文件。

(6) Find工具参数综合案列:

#查找/data目录以.log结尾,文件大于10k的文件,同时cp到/tmp目录;
find /data/ -name "*.log"  –type f  -size +10k -exec cp {} /tmp/ \;
#查找/data目录以.txt结尾,文件大于10k的文件,权限为644并删除该文件;
find /data/ -name "*.log"  –type f  -size +10k  -m perm 644 -exec rm –rf {} \;
#查找/data目录以.log结尾,30天以前的文件,大小大于10M并移动到/tmp目录;
find /data/ -name "*.log"  –type f  -mtime +30 –size +10M -exec mv {} /tmp/ \;
find /data/ -atime -1  1天内访问过的文件
find /data/ -ctime -1  1天内状态改变过的文件    
find /data/ -mtime -1  1天内修改过的文件
find /data/ -amin -1  1分钟内访问过的文件
find /data/ -cmin -1  1分钟内状态改变过的文件    
find /data/ -mmin -1  1分钟内修改过的文件

Shell编程四剑客之SED

Sed简介

SED是一个非交互式文本编辑器,它可对文本文件和标准输入进行编辑,标准输入可以来自键盘输入、文本重定向、字符串、变量,甚至来自于管道的文本,与VIM编辑器类似,它一次处理一行内容,Sed可以编辑一个或多个文件,简化对文件的反复操作、编写转换程序等。

Sed命令的原理:在处理文本时把当前处理的行存储在临时缓冲区中,称为“模式空间”(pattern space),紧接着用SED命令处理缓冲区中的内容,处理完成后把缓冲区的内容输出至屏幕或者写入文件。逐行处理直到文件末尾,然而如果打印在屏幕上,实质文件内容并没有改变,除非你使用重定向存储输出或者写入文件。

Sed语法参数

参数格式为:

sed    [-Options]     [‘Commands’]    filename;
sed工具默认处理文本,文本内容输出屏幕已经修改,但是文件内容其实没有修改,需要加-i参数即对文件彻底修改;
-e<script>或--expression=<script>   以选项中指定的script来处理输入的文本文件。
-f<script文件>或--file=<script文件>   以选项中指定的script文件来处理输入的文本文件。
-h                                      或--help 显示帮助。
-n或--quiet或--silent                仅显示script处理后的结果。
-r  使用扩展正则表达式
x                   		    #x为指定行号;
x,y                 		    #指定从x到y的行号范围;
/pattern/           		    #查询包含模式的行;
/pattern/,/pattern/   		 #查询包含两个模式的行;
/pattern/,x         		 #从与pattern的匹配行到x号行之间的行;
x,/pattern/         		  #从x号行到与pattern的匹配行之间的行;
x,y!                		#查询不包括x和y行号的行;
r                			#从另一个文件中读文件;
w                			#将文本写入到一个文件;
y                			#变换字符;
q             			#第一个模式匹配完成后退出;
l                			#显示与八进制ASCII码等价的控制字符;
{}              			#在定位行执行的命令组;
p                			#打印匹配行;
=                			#打印文件行号;
a\              			#在定位行号之后追加文本信息;
i\              			#在定位行号之前插入文本信息;
d                			#删除定位行;
c\              			#用新文本替换定位文本;
s                			#使用替换模式替换相应模式;
&                        #引用已匹配字符串
first~step              步长,每 step 行,从第 first 开始
$                        匹配最后一行
/regexp/                 正则表达式匹配行
number                    只匹配指定行
addr1,addr2              开始匹配 addr1 行开始,直接 addr2 行结束
addr1,+N                 从 addr1 行开始,向后的 N 行
addr1,~N                 从 addr1 行开始,到 N 行结束
Sed实例练习(模式空间)

示例文本:

[root@localhost ~]# cat test.txt 
www.test.net
www.baidu.com
www.taobao.com
www.sina.com
old old old
new new new

(1) 替换test.txt文本中old为new:

sed    's/old/new/g'       test.txt

(2) 打印test.txt文本第一行至第三行:

sed    -n  '1,3p'           test.txt

(3) 打印test.txt文本中第一行与最后一行:

sed    -n '1p;$p'           test.txt

(4) 删除test.txt第一行至第三行、删除匹配行至最后一行:

sed       '1,3d'             test.txt
sed       '/test/,$d'         test.txt

(5) 删除test.txt最后6行及删除最后一行:

for   i  in `seq 1 6`;do  sed  -i   '$d'  test.txt ;done
sed       '$d'               test.txt

(6) 删除test.txt最后一行:

sed       '$d'             test.txt

(7) 在test.txt查找test所在行,并在其下一行添加word字符,a表示在其下一行添加字符串:

sed    '/test/aword'      test.txt

(8) 在test.txt查找test所在行,并在其上一行添加word字符,i表示在其上一行添加字符串:

sed    '/test/i word'       test.txt

(9) 在test.txt查找以test结尾的行尾添加字符串word,$表示结尾标识,&在Sed中表示添加:

sed   's/test$/& word/g'     test.txt

(10) 在test.txt查找www的行,在其行首添加字符串word,^表示起始标识,&在Sed中表示添加:

sed   '/www/s/^/& word/'    test.txt

(11) 多个sed命令组合,使用-e参数:

sed  -e  '/www.jd.com/s/^/&1./'  -e  's/www.jd.com$/&./g'  test.txt

(12) 多个sed命令组合,使用分号“;”分割:

sed  -e  '/www.jd.com/s/^/&1./;s/www.jd.com$/&./g'  test.txt

(13) Sed读取系统变量,变量替换:

TEST=WWW.test.NET
Sed  “s/www.jd.com/$TEST/g” test.txt

(14) 修改Selinux策略enforcing为disabled,查找/SELINUX/行,然后将其行enforcing值改成disabled、!s表示不包括SELINUX行:

sed  -i   '/SELINUX/s/enforcing/disabled/g' /etc/selinux/config
sed  -i   '/SELINUX/!s/enforcing/disabled/g' /etc/selinux/config

(15) 去除空格httpd.conf文件空行或者是#号开头的行

# sed '/^#/d;/^$/d' /etc/httpd/conf/httpd.conf
打印是把匹配的打印出来,删除是把匹配的删除,删除只是不用-n 选项

打印是把匹配的打印出来,删除是把匹配的删除,删除只是不用-n 选项

(16) IP 加单引号

echo '10.10.10.1 10.10.10.2 10.10.10.3' |sed -r 's/[^ ]+/"&"/g'
"10.10.10.1" "10.10.10.2" "10.10.10.3"

(17) 对 1-4 行的 html 进行替换

示例文件:

http://www.baidu.com/index.html

http://www.baidu.com/1.html

http://post.baidu.com/index.html

http://mp3.baidu.com/index.html

http://www.baidu.com/3.html

http://post.baidu.com/2.html
tail  /etc/services | sed '1,4 s/html/txt/'
http://www.baidu.com/index.txt
http://www.baidu.com/1.txt
http://post.baidu.com/index.txt
http://mp3.baidu.com/index.txt
http://www.baidu.com/3.html
http://post.baidu.com/2.html

(18) 分组使用,在第2列后面添加 test

tail /etc/services |sed -r 's/(.*) (.*)(#.*)/\1\2test \3/'
3gpp-cbsp 48049/tcp test # 3GPP Cell Broadcast Service
isnetserv 48128/tcp test # Image Systems Network Services
isnetserv 48128/udp test # Image Systems Network Services
blp5 48129/tcp test # Bloomberg locator
blp5 48129/udp test # Bloomberg locator
com-bardac-dw 48556/tcp test # com-bardac-dw
com-bardac-dw 48556/udp test # com-bardac-dw
iqobject 48619/tcp test # iqobject
iqobject 48619/udp test # iqobject 
第一列是第一个小括号匹配,第二列第二个小括号匹配,第三列一样。将不变的字符串匹配分组,再通过\数字按分组顺序反向引用。
基本正则表达式中支持分组,而在扩展正则表达式中,分组的功能更加强大,也可以说才是真正的分组
():分组,后面可以使用\1 \2 \3...引用前面的括号分组

(19) 处理以下文件内容,将域名取出并进行计数排序,如处理:

http://www.baidu.com/index.html
http://www.baidu.com/1.html
http://post.baidu.com/index.html
http://mp3.baidu.com/index.html
http://www.baidu.com/3.html
http://post.baidu.com/2.html
得到如下结果:
域名的出现的次数 域名
3 www.baidu.com
2 post.baidu.com
1 mp3.baidu.com
[root@localhost shell]# cat file | sed -e ' s/http:\/\///' -e ' s/\/.*//' | sort | uniq -c | sort -rn
3 www.baidu.com
2 post.baidu.com
1 mp3.baidu.com
[root@codfei4 shell]# awk -F/ '{print $3}' file |sort -r|uniq -c|awk '{print $1"\t",$2}'
3 www.baidu.com
2 post.baidu.com
1 mp3.baidu.com

(20) 将协议与端口号位置调换

tail /etc/services |sed -r 's/(.*)(\<[0-9]+\>)\/(tcp|udp)(.*)/\1\3\/\2\4/'
3gpp-cbsp tcp/48049 # 3GPP Cell Broadcast Service
isnetserv tcp/48128 # Image Systems Network Services
isnetserv udp/48128 # Image Systems Network Services
blp5 tcp/48129 # Bloomberg locator
blp5 udp/48129 # Bloomberg locator
com-bardac-dw tcp/48556 # com-bardac-dw
com-bardac-dw udp/48556 # com-bardac-dw
iqobject tcp/48619 # iqobject
iqobject udp/48619 # iqobject
matahari tcp/49000 # Matahari Broker

(21) 字符位置调换

替换 x 字符为大写:
# echo "abc cde xyz" |sed -r 's/(.*)x/\1X/'
abc cde Xyz
456 与 cde 调换:
# echo "abc:cde;123:456" |sed -r 's/([^:]+)(;.*:)([^:]+$)/\3\2\1/'
abc:456;123:cde

(22) 注释匹配行后的多少行

[root@localhost ~]# sed '/5/,+3s/^/@/' 1.txt 
1 
2 
3 
4 
@5 
@6 
@7 
@8 
9 
10

(23) 注释指定多行

[root@localhost ~]# sed -r 's/^3|^5|^7/#&/' 1.txt 
1 
2 
#3 
4
#5 
6 
#7 
8 
9 
10

(24) 去除开头和结尾空格或制表符

echo " 1 2 3 " |sed 's/^[ \t]*//;s/[ \t]*$//'
1 2 3
Sed实例练习(保留空间)

Sed之所以能以行为单位进行修改文本或者编辑文本,主要原因是因为它使用了两个空间:一个是活动的“模式空间”,另一个是辅助作用的“保留空间”.

模式空间:可以想象成工程里面的流水线,所有的数据读取过来之后就直接在上面进行工作。

保留空间:可以想象成是仓库,我们在进行数据处理的时候,会把数据读取到保留空间中,作为数据的暂存区域,需要时再进行调出。

SED高级命令可以分为三种功能:

`N、D、P`:处理多行模式空间的问题;
`H、h、G、g、x`:将模式空间的内容放入保留空间以便接下来的编辑;
`:、b、t`:在脚本中实现分支与条件结构(标签)。
n                        #读取下一个输入行,用下一个命令处理新的行;
N                        #将当前读入行的下一行读取到当前的模式空间。
d                        #删除模式空间的所有内容,开始下一个循环
D                        #删除模式空间的第一行,开始下一个循环;
p                        #打印当前模式空间的所有内容;
P                        #打印模式空间的第一行,开始下一个循环
h                        #将模式缓冲区的文本复制到保持缓冲区;
H                        #将模式缓冲区的文本追加到保持缓冲区;
x                        #互换模式缓冲区和保持缓冲区的内容;
g                        #将保持缓冲区的内容复制到模式缓冲区;
G                        #将保持缓冲区的内容追加到模式缓冲区。

1) 在test.txt每行后加入空行,也即每行占永两行空间,每一行后边插入一行空行、两行空行及前三行每行后插入空行:

sed     '/^$/d;G'            test.txt  #删除的是匹配的条件行,留下的是模式空间处理的行,G参数是将保持空间内的行追加到模式空间去(保持空间默认是空的)。
sed     '/^$/d;G;G'     	   test.txt #后面跟2个G 是将G保持空间内的两行追加到模式空间
sed     '/^$/d;1,3G;'    	test.txt #1,3G 是只将追加3行到模式空间

2) 将test.txt偶数行删除及隔两行删除一行:

sed    'n;d'              test.txt #n参数将输入行的下一行显示出来,而后面的d 正好会将显示的行进行删除,测试“sed –n ‘n;p’ file ”
sed    'n;n;d'           test.txt,#2个n参数将输入的的下两行显示出来,正好使用d都删除,形成隔两行删一行的效果。

3) 在test.txt匹配行前一行、后一行插入空行以及同时在匹配前后插入空行:

sed  '/test/{x;p;x;}'      test.txt  #x参数是让模式空间和保持空间互相交换,匹配test时,将模式空间内的数据换成了保持空间内的数据,保持空间默认是空的,所以前一行会变成空行,然后将空行打印出来
sed  '/test/G'                test.txt  #G参数配置上之后将保持空间的内容追加到模式空间去。
sed  '/test/{x;p;x;G;}'   test.txt #综合以上两个参数配置。

4) 在test.txt每行前加入顺序数字序号、加上制表符\t及.符号:

sed = test.txt| sed 'N;s/\n/ /'   # “=”等号打印当前行号码。N参数是配置将当前读入行的下一行读取到当前的模式空间,就是把下一行读到当前的模式空间来,利用s替换换行符,形成数字顺序效果。
sed = test.txt| sed 'N;s/\n/\t/'  #案例同上
sed = test.txt| sed 'N;s/\n/\./'  #案例同上

5) 删除test.txt行前和行尾的任意空格:

sed 's/^[ \t]*//;s/[ \t]*$//' test.txt  #s替换 删除行前

6) 打印test.txt关键词old与new之间的内容:

sed -n '/old/,/new/'p     test.txt   #-n参数可以打印匹配的条件内容行

7) 打印及删除test.txt最后两行:

sed   '$!N;$!D'             test.txt 
N  读取下一行并追加输入到模式空间
D  删除模式空间的第一行,开始下一个循环
#读取1,$!条件满足(不是尾行,#N前加$!表示末尾行不执行N),执行N命令,读取下一行并追加输入到模式空间,得出1\n2。执行$!D,不是最后一行,所以执行D,删除模式空间的第一行,开始下一个循环,模式空间由1\n2成了2。读取第二行,执行 N 命令,此时模式空间是 3\n4,执行 D 命令删除模式空间第一行 3,剩余4,直到执行N读入第5行,$!条件不满足(不是尾行),不执行N命令:继续读入6行,这里模式空间为:5\n6,$!D,因为是最后一行,所以不执行D,控制流到达脚本底部,输出模式空间的内容

sed   'N;$!P;$!D;$d'      test.txt
P  打印模式空间的第一行
N  读取下一行并追加输入到模式空间
D  删除模式空间的第一行,开始下一个循环
d 删除匹配的行
#读取第一行,执行N,此时得出1\n2,P打印从开始到第一个\n的内容,执行$!D,不是最后一行,所以执行D,删除模式空间的第一行,开始下一个循环,模式空间由1\n2成了2,$d 是因为不是末行所以不执行,读取第二行,执行 N 命令,此时模式空间是 3\n4,执行 P 命令显示模式空间第一行 3,执行D,删除模式空间的第一行,剩余4,读取第5行,执行N,将第6行进行读取,执行$!P,因为是最后一行不执行P,执行$!D,因为是最后一行,不执行D,最后执行$d,删除模式空间5/6行。

8) 合并上下两行,也即两行合并:

sed    '$!N;s/\n/ /'          test.txt  #N前加$!表示末尾行不执行N
sed    'N;s/\n/ /'            test.txt
Sed中标签使用(: 、b 和 和 t )

标签可以控制流,实现分支判断。

 :    lable name 定义标签
 b    lable 跳转到指定标签,如果没有标签则到脚本末尾
 t    lable 跳转到指定标签,前提是 s///命令执行成功

1) 将换行符替换成逗号

方法 1:

seq 6 |sed 'N;s/\n/,/'
1,2
3,4
5,6
这种方式并不能满足我们的需求,每次 sed 读取到模式空间再打印是新行,替换\n 也只能对 N 命令,追加后的 1\n2 这样替换。
这时就可以用到标签了:
# seq 6 |sed ':a;N;s/\n/,/;b a'
1,2,3,4,5,6
看看这里的标签使用,:a 是定义的标签名,b a 是跳转到 a 位置。
sed 读取第一行 1,N 命令读取下一行 2,此时模式空间是 1\n2$,执行替换,此时模式空间是1,2$,执行 b 命令再跳转到标签 a 位置继续执行 N 命令,读取下一行 3 追加到模式空间,此时模式空间是 1,2\n3$,再替换,以此类推,不断追加替换,直到最后一行 N 读不到下一行内容退出。

方法 2:

seq 6 |sed ':a;N;$!b a;s/\n/,/g'
1,2,3,4,5,6
先将每行读入到模式空间,最后再执行全局替换。$!是如果是最后一行,则不执行 b a 跳转,最后执行全局替换。
seq 6 |sed ':a;N;b a;s/\n/,/g'
1
2
3
4
5
6
可以看到,不加$!是没有替换,因为循环到 N 命令没有读到行就退出了,后面的替换也就没执行。

2) 每三个数字加个一个逗号

# echo "123456789" |sed -r 's/([0-9]+)([0-9]+{3})/\1,\2/'
123456,789
# echo "123456789" |sed -r ':a;s/([0-9]+)([0-9]+{3})/\1,\2/;t a'
123,456,789
# echo "123456789" |sed -r ':a;s/([0-9]+)([0-9]+{2})/\1,\2/;t a'
1,23,45,67,89
执行第一次时,替换最后一个,跳转后,再对 123456 匹配替换,直到匹配替换不成功,不执行 t 命令。

3) 忽略大小写匹配(I )

# echo -e "a\nA\nb\nc" |sed 's/a/1/Ig'
1
1
b
c

4) 获取总行数(# )

seq 10 |sed -n '$='
Sed 脚本使用编写方法
  1. 从文件读入命令

sed -f sed.sh
sed.sh文件内容:
s/root/yerik/p
s/bash/csh/p
  1. 直接运行脚本 ./sed.sh /etc/passwd

#!/bib/sed -f
s/root/yerik/p
s/bash/csh/p
Sed 扩展练习高级替换

1,删除文件每行的第一个字符。

sed -n 's/^.//gp' /etc/passwd
sed -nr 's/(.)(.*)/\2/p' /etc/passwd
sed -r 's/^.//g' test.txt

2,删除文件每行的第二个字符。

sed -nr 's/(.)(.)(.*)/\1\3/p' /etc/passwd
sed -r 's/(^.)(.)/\1/g' test.txt

3,删除文件每行的最后一个字符。

sed -nr 's/.$//p' /etc/passwd
sed -nr 's/(.*)(.)/\1/p' /etc/passwd
sed -r 's/(.)$//g' test.txt

4,删除文件每行的倒数第二个字符。

sed -nr 's/(.*)(.)(.)/\1\3/p' /etc/passwd
sed -r 's/(.)(.)$/\2/g' test.txt

5,删除文件每行的第二个单词。

sed -nr 's/([^a-Z]*)([a-Z]+)([^a-Z]+)([a-Z]+)(.*)/\1\2\3\5/p' /etc/passwd
sed -r 's/([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]+)(.*)/\1\2\4\5/' test.txt

6,删除文件每行的倒数第二个单词。

sed -nr 's/(.*)([^a-Z]+)([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]*)/\1\2\4\5\6/p' /etc/passwd
sed -r 's/([a-Z]+)([^a-Z])([a-Z]+)$/\2\3/g' test.txt

7,删除文件每行的最后一个单词

sed -nr 's/(.*)([^a-Z]+)([a-Z]+)([^a-Z]*)/\1\2\4/p' /etc/passwd
sed -r 's/([a-Z]+)$//g' test.txt
sed -r 's/(.*)([^a-Z]+)([a-Z]+)/\1\2/' test.txt

8,交换每行的第一个字符和第二个字符。

sed -nr 's/(.)(.)(.*)/\2\1\3/p' /etc/passwd

sed -r 's/(.)(.)/\2\1/' test.txt

9,交换每行的第一个字符和第二个单词

sed -r 's/(^.)([^a-Z]*)([a-Z]+)([^a-Z]+)([a-Z]+)/\5\2\3\4\1/' test.txt

9,交换每行的第一个单词和第二个单词。

sed -nr 's/([^a-Z]*)([a-Z]+)([^a-Z]+)([a-Z]+)(.*)/\1\4\3\2\5/p' /etc/passwd

10,交换每行的第一个单词和最后一个单词。

sed -r 's/([a-Z]+)([^a-Z]+)(.*)([^a-Z]+)([a-Z]+)/\5\2\3\4\1/' test.txt

11,删除一个文件中所有的数字。

sed 's/[0-9]*//g' test.txt

12,删除每行开头的所有空格。

sed -n 's/^\ *//p' /etc/passwd 
sed -nr 's/( *)(.*)/\2/p' test.txt
sed 's/^ *//' test.txt

13,用制表符替换文件中出现的所有空格。

sed -n 's/\ /\t/gp' test.txt
sed -r 's/( +)/\t/g' test.txt
sed 's/ /\t/g' test.txt

14,把所有大写字母用括号()括起来。

sed -nr 's/([A-Z])/(&)/gp' test.txt
sed -n 's/[A-Z]/(&)/gp' test.txt

15,打印每行3次。

sed 'p;p' test.txt
sed -n 'p;p;p'  test.txt

16,隔行删除。

sed -n '1~2p' test.txt
sed '1d;n;d' ww.txt
sed '1~2d' ww.txt 
sed '0~2d' ww.txt

17,把文件从第22行到第33行复制到第44行后面。

sed '1,21h;22h;23,33H;44G' pass
cat -n /etc/passwd | sed '22h;23,33H;44G'

18,把文件从第22行到第33行移动到第44行后面。

sed '22{h;d};23,33{H;d};44G' pass
cat -n /etc/passwd | sed '22{h;d};23,33{H;d};44G'

19,只显示每行的第一个单词。

sed -nr 's/([^a-Z]*)([a-Z]+)([^a-Z]+)(.*)/\2/p' /etc/passwd
sed -r 's/([a-Z]+)(.*)/\1/'  test.txt

20,打印每行的第一个单词和第三个单词。

sed -nr 's/([^a-Z]*)([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]+)([a-Z]+)(.*)/\2--\4/p' /etc/passwd
sed -r 's/([^a-Z]*)([a-Z]+)([^a-Z]+)([a-Z]+)([^a-Z]+)([a-Z]+)(.*)/\2\6/'  test.txt

21,将格式为 mm/yy/dd 的日期格式换成 mm;yy;dd

date +%m/%Y/%d |sed -n 's#/#;#gp'

22, 逆向输出

cat a.txt
ABC
DEF
XYZ
sed '1!G;h;$!d' a.txt
输出样式变成
XYZ
DEF
ABC
Sed 作业练习

1) 把/etc/passwd 复制到/root/test.txt,用sed打印所有行

2) 打印test.txt的3到10行

3) 打印test.txt 中包含 ‘root’ 的行

4) 删除test.txt 的15行以及以后所有行

5) 删除test.txt中包含 ‘bash’ 的行

6) 替换test.txt 中 ‘root’ 为 ‘toor’

7) 替换test.txt中 ‘/sbin/nologin’ 为 ‘/bin/login’

8) 删除test.txt中5到10行中所有的数字

9) 删除test.txt 中所有特殊字符(除了数字以及大小写字母)

10) 把test.txt中第一个单词和最后一个单词调换位置

11) 把test.txt中出现的第一个数字和最后一个单词替换位置

12) 把test.txt 中第一个数字移动到行末尾

13) 在test.txt 20行到末行最前面加 ‘aaa:’

sed习题答案

1.  /bin/cp /etc/passwd  /root/test.txt ;  sed -n '1,$'p test.txt
2.  sed -n '3,10'p test.txt
3.  sed -n '/root/'p test.txt
4.  sed '15,$'d  test.txt
5.  sed '/bash/'d test.txt
6.  sed 's/root/toor/g' test.txt
7.  sed 's#sbin/nologin#bin/login#g' test.txt
8.  sed '5,10s/[0-9]//g' test.txt
9.  sed 's/[^0-9a-zA-Z]//g' test.txt
10.  sed 's/\(^[a-Z]*\)\([^a-Z].*\)\([^a-Z]\)\([a-Z]*$\)/\4\2\3\1/' test.txt
11.  sed 's#\([^0-9][^0-9]*\)\([0-9][0-9]*\)\([^0-9].*\)\([^a-zA-Z]\)\([a-zA-Z][a-zA-Z]*$\)#\1\5\3\4\2#' test.txt
12.  sed 's#\([^0-9][^0-9]*\)\([0-9][0-9]*\)\([^0-9].*$\)#\1\3\2#' test.txt
13.  sed '20,$s/^.*$/aaa:&/' test.txt

Shell编程四剑客之AWK

AWK是一个优良的文本处理工具,LinuxUnix环境中现有的功能最强大的数据处理引擎之一,以Aho、Weinberger、Kernighan三位发明者名字首字母命名为AWK,AWK是一个行级文本高效处理工具,AWK经过改进生成的新的版本有Nawk、Gawk,一般Linux默认为Gawk,Gawk是 AWK的GNU开源免费版本。

AWK基本原理是逐行处理文件中的数据,查找与命令行中所给定内容相匹配的模式,如果发现匹配内容,则进行下一个编程步骤,如果找不到匹配内容,则 继续处理下一行。

awk 处理的工作方式与数据库类似,支持对记录和字段处理,这也是 grep 和 sed 不能实现的。在 awk 中,缺省的情况下将文本文件中的一行视为一个记录,逐行放到内存中处理,而将一行中的某一部分作为记录中的一个字段。用 1,2,3...数字的方式顺序的表示行(记录)中的不同字段。用$后跟数字,引用对应的字段,以逗号分隔,0 表示整个行。

1570787286474

(1) AWK基本语法参数详解:

  • 单引号' '是为了和shell命令区分开;

  • 大括号{ }表示一个命令分组;

  • pattern是一个过滤器,表示匹配pattern条件的行才进行Action处理;

  • action是处理动作,常见动作为Print;

  • 使用#作为注释,pattern和action可以只有其一,但不能两者都没有。

选项

选项                  描述
-f                    program-file  从文件中读取 awk 程序源文件
-F fs                 指定 fs 为输入字段分隔符
-v var=value          变量赋值
--posix               兼容 POSIX 正则表达式
--dump-variables=[file]  把 awk 命令时的全局变量写入文件,默认文件是 awkvars.out
--profile=[file]      格式化 awk 语句到文件,默认是 awkprof.out

(2) Awk 模式

常用模式有:
Pattern                  Description
BEGIN{ }                 给程序赋予初始状态,先执行的工作
END{ }                   程序结束之后执行的一些扫尾工作
/regular expression/      为每个输入记录匹配正则表达式
pattern && pattern        逻辑 and,满足两个模式
pattern || pattern        逻辑 or,满足其中一个模式
! pattern                逻辑 not,不满足模式
pattern1, pattern2        范围模式,匹配所有模式 1 的记录,直到匹配到模式 2

(3) AWK内置变量详解:

- q  FS 分隔符,默认是空格;
- OFS 输出分隔符;
- NR 当前行数,从1开始;
- NF 当前记录字段个数;
- $0 当前记录;
- $1~$n 当前记录第n个字段(列)

(4) AWK内置函数详解:

gsub(r,s):在$0中用s代替r;
index(s,t):返回s中t的第一个位置;
length(s):s的长度;
match(s,r):s是否匹配r;
split(s,a,fs):在fs上将s分成序列a;
substr(s,p):返回s从p开始的子串。

(5) AWK常用操作符,运算符及判断符:

++ --                      增加与减少( 前置或后置);
^ **                       指数( 右结合性);
! + -                   非、一元(unary) 加号、一元减号;
+ - * / %              加、减、乘、除、余数;
< <= == != > >=        数字比较;
&&                      逻辑and;
||                      逻辑or;
= += -= *= /= %= ^= **=       赋值。

(6) AWK与流程控制语句:

if(condition) { } else { };
while { };
do{ }while(condition);
for(init;condition;step){ };
break/continue。

常用AWK工具企业演练案列:

# 模拟测试文件
vim   test.txt
1. www.taobao.com  good
2. www.jf.com      good
3. www.jd.com      good
4. www.baidu.com   good
5. www.sina.com    good
6. www.test.net   good

(1) AWK打印硬盘设备名称,默认以空格为分割:

df    -h|awk  '{print  $1}'

(2) AWK以空格、冒号、\t、分号为分割:

awk  -F '[ :\t;]'  '{print  $1}'            test.txt

(3) AWK以冒号分割,打印第一列,同时将内容追加到/tmp/awk.log下:

awk  -F.  '{print $1 >>"/tmp/awk.log"}'  test.txt

(4) 打印test.txt文件中的第3行至第5行,NR表示打印行号,$0表示文本所有域:

awk 'NR==3,NR==5  {print}'             test.txt
awk 'NR==3,NR==5  {print $0}'          test.txt

(5) 打印test.txt文件中的第3行至第5行的第一列与最后一列:,NF 表示字段的意思

awk 'NR==3,NR==5 {print $1,$NF}'       test.txt

(6) 打印test.txt文件中,长度大于80的行号:

awk   'length($0)>80 {print NR}'        test.txt

(7) AWK引用Shell变量,使用-v或者双引号+单引号即可:

awk -v TEST=hello  '{print TEST,$NF}'      test.txt
TEST="hello";echo| awk  '{print "'${TEST}'";}'

(8) AWK以冒号切割,打印第一列同时只显示前5行:

cat  /etc/passwd|head -5|awk  -F:   '{print $1}'
awk  -F:  'NR>=1&&NR<=5 {print $1}'  /etc/passwd

(9) Awk指定文件test.txt第一列的总和:

cat test.txt |awk '{sum+=$1}END{print sum}'
#sum+=$1 ,是+=$1 表示将$1 列进行相加,赋值给sum变量

(10) AWK NR行号除以2余数为0则跳过该行,继续执行下一行,打印在屏幕: {next} 表示跳过

awk  -F:  'NR%2==0 {next} {print NR,$1}'  /etc/passwd

(11) AWK添加自定义字符:

ifconfig  eth0|grep "Bcast"|awk '{print "ip_"$2}'
ifconfig |grep "inet"|sed -n '1p'|awk '{print “ip:”$2}'

(12) AWK格式化输出passwd内容,printf打印字符串,%格式化输出分隔符,s表示字符串类型,-12表示12个字符,-6表示6个字符:$NF 表示最后一个字段

awk -F:  '{printf "%-12s %-6s %-8s\n",$1,$2,$NF}'  /etc/passwd

(13) AWK OFS输出格式化\t:

netstat -an|awk '$6 ~ /LISTEN/&&NR>=1&&NR<=10 {print NR,$4,$5,$6}' OFS="\t"
netstat –an 表示查看显示所有连线中的Socket,直接使用ip地址,而不通过域名服务器,awk 命令使用$6匹配 /LISTEN/ ,使用NR 行号匹配前十行,打印出来,打印的字段是NR (行号),匹配第四,第五,第六个字段,最后使用OFS格式化字段,进行空格划分。
netstat -an|awk  '$6 ~ /LISTEN/ {print NR,$0}'

(14) AWK与if组合实战,判断数字比较:

echo 3 2 1 | awk '{ if(($1>$2)||($1>$3)) { print $2} else {print $1} }'
if 判断中数字运算使用小括号。

(15) AWK与数组组合实战,统计passwd文件用户数:

awk -F ':' 'BEGIN {count=0;} {name[count] = $1;count++;}; END{for (i = 0; i < NR; i++) print i, name[i]}'  /etc/passwd
#使用-F 进行分割,BEGIN在awk执行前运行复制,定义name数组,定义count为0,定义数组name为$1参数的赋值;count++实现是count从0开始递增显示,END后面是定义了一个for循环,循环显示数字,print 打印出数字,并调用name数组的。

(16) awk分析Nginx访问日志的状态码404、502等错误信息页面,统计次数大于20的IP地址。

awk '{if ($9~/502|499|500|503|404/) print $1,$9}' /usr/local/nginx/logs/access.log |sort |uniq -c |sort -nr |awk '{if($1>=7) print $2}'

(17) 用/etc/shadow文件中的密文部分替换/etc/passwd中的"x"位置,生成新的/tmp/passwd文件。

awk '{if ($9~/502|499|500|503|404/) print $1,$9}' /usr/local/nginx/logs/access.log |sort |uniq -c |sort -nr |awk '{if($1>=7) print $2}'
awk 'BEGIN{OFS=FS=":"} NR==FNR{a[$1]=$2}NR>FNR{$2=a[$1];print >>"/tmp/passwd"}' /etc/shadow /etc/passwd

(18) Awk统计服务器状态连接数:

awk '{if ($9~/502|499|500|503|404/) print $1,$9}' /usr/local/nginx/logs/access.log |sort |uniq -c |sort -nr |awk '{if($1>=7) print $2}'
awk 'BEGIN{OFS=FS=":"} NR==FNR{a[$1]=$2}NR>FNR{$2=a[$1];print >>"/tmp/passwd"}' /etc/shadow /etc/passwd
netstat -an | awk '/tcp/ {s[$NF]++} END {for(a in s) {print a,s[a]}}'
netstat -an | awk '/tcp/ {print $NF}' | sort | uniq -c

awk模式

常用模式有:

Pattern  Description
BEGIN{ }  给程序赋予初始状态,先执行的工作
END{ }  程序结束之后执行的一些扫尾工作
/regular expression/  为每个输入记录匹配正则表达式
pattern && pattern  逻辑 and,满足两个模式
pattern || pattern  逻辑 or,满足其中一个模式
! pattern  逻辑 not,不满足模式
pattern1, pattern2  范围模式,匹配所有模式 1 的记录,直到匹配到模式 2

1)从文件读取 awk 程序处理文件

# vi test.awk
{print $2}
# tail -n3 /etc/services |awk -f test.awk
48049/tcp
48128/tcp
49000/tcp

2)指定分隔符,打印指定字段

打印第二字段,默认以空格分隔:
# tail -n3 /etc/services |awk '{print $2}'
48049/tcp
48128/tcp
48128/udp
指定冒号为分隔符打印第一字段:
# awk -F ':' '{print $1}' /etc/passwd
root
bin
daemon
adm
lp
sync
......

还可以指定多个分隔符,作为同一个分隔符处理:
# tail -n3 /etc/services |awk -F'[/#]' '{print $3}'
iqobject
iqobject
Matahari Broker
# tail -n3 /etc/services |awk -F'[/#]' '{print $1}'
iqobject 48619
iqobject 48619
matahari 49000
# tail -n3 /etc/services |awk -F'[/#]' '{print $2}'
tcp
udp
tcp
# tail -n3 /etc/services |awk -F'[/#]' '{print $3}'
iqobject
iqobject
Matahari Broker
# tail -n3 /etc/services |awk -F'[ /]+' '{print $2}'
48619
48619
49000
[]元字符的意思是符号其中任意一个字符,也就是说每遇到一个/或#时就分隔一个字段,当用多个分隔符时,就能更方面处理字段了。

3)变量赋值

# awk -v a=123 'BEGIN{print a}'
123
系统变量作为 awk 变量的值:
# a=123
# awk -v a=$a 'BEGIN{print a}'
123
或使用单引号
# awk 'BEGIN{print '$a'}'
123

4)输出 awk 全局变量到文件

# seq 5 |awk --dump-variables '{print $0}'
1
2
3
4
5
# cat awkvars.out
ARGC: number (1)
ARGIND: number (0)
ARGV: array, 1 elements
BINMODE: number (0)
CONVFMT: string ("%.6g")
ERRNO: number (0)
FIELDWIDTHS: string ("")
FILENAME: string ("-")
FNR: number (5)
FS: string (" ")
IGNORECASE: number (0)
LINT: number (0)
NF: number (1)
NR: number (5)
OFMT: string ("%.6g")
OFS: string (" ")
ORS: string ("\n")
RLENGTH: number (0)
RS: string ("\n")
RSTART: number (0)
RT: string ("\n")
SUBSEP: string ("\034")
TEXTDOMAIN: string ("messages")

5)BEGIN 和 END

BEGIN 模式是在处理文件之前执行该操作,常用于修改内置变量、变量赋值和打印输出的页眉或标
题。
例如:打印页眉
# tail /etc/services |awk 'BEGIN{print "Service\t\tPort\t\t\tDescription\n==="}{print
$0}'
Service Port Description
===
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
END 模式是在程序处理完才会执行。
例如:打印页尾
# tail /etc/services |awk '{print $0}END{print "===\nEND......"}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
===
END......
使用awk 打印99乘法表
[root@www-test-net ~]# awk 'BEGIN{for(i=1;i<=9;i++){for(j=1;j<=i;j++){printf i"x"j"="i*j " "}print ""}}'
1x1=1 
2x1=2 2x2=4 
3x1=3 3x2=6 3x3=9 
4x1=4 4x2=8 4x3=12 4x4=16 
5x1=5 5x2=10 5x3=15 5x4=20 5x5=25 
6x1=6 6x2=12 6x3=18 6x4=24 6x5=30 6x6=36 
7x1=7 7x2=14 7x3=21 7x4=28 7x5=35 7x6=42 7x7=49 
8x1=8 8x2=16 8x3=24 8x4=32 8x5=40 8x6=48 8x7=56 8x8=64 
9x1=9 9x2=18 9x3=27 9x4=36 9x5=45 9x6=54 9x7=63 9x8=72 9x9=81

6)格式化输出 awk 命令到文件

# tail /etc/services |awk --profile 'BEGIN{print
"Service\t\tPort\t\t\tDescription\n==="}{print $0}END{print "===\nEND......"}'
Service Port Description
===
nimgtw 48003/udp # Nimbus Gateway
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service Protocol
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
===
END......
# cat awkprof.out
# gawk profile, created Sat Jan 7 19:45:22 test
# BEGIN block(s)
BEGIN {
print "Service\t\tPort\t\t\tDescription\n==="
}
# Rule(s)
{
print $0
}
# END block(s)
END {
print "===\nEND......"
}

7)/re/正则匹配

匹配包含 tcp 的行:

# tail /etc/services |awk '/tcp/{print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
matahari 49000/tcp # Matahari Broker
匹配开头是 blp5 的行:
# tail /etc/services |awk '/^blp5/{print $0}'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
匹配第一个字段是 8 个字符的行:
# tail /etc/services |awk '/^[a-z0-9]{8} /{print $0}'
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker
如果没有匹配到,请查看你的 awk 版本(awk --version)是不是 3,因为 4 才支持{}

8)逻辑 and、or 和 not

匹配记录中包含 blp5 和 tcp 的行:
# tail /etc/services |awk '/blp5/ && /tcp/{print $0}'
blp5 48129/tcp # Bloomberg locator
匹配记录中包含 blp5 或 tcp 的行:
# tail /etc/services |awk '/blp5/ || /tcp/{print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
matahari 49000/tcp # Matahari Broker
不匹配开头是#和空行:
# awk '! /^#/ && ! /^$/{print $0}' /etc/httpd/conf/httpd.conf
或
# awk '! /^#|^$/' /etc/httpd/conf/httpd.conf
或
# awk '/^[^#]|"^$"/' /etc/httpd/conf/httpd.conf

9)匹配范围

# tail /etc/services |awk '/^blp5/,/^com/'
blp5 48129/tcp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
对匹配范围后记录再次处理,例如匹配关键字下一行到最后一行:
# seq 5 |awk '/3/,/^$/{printf /3/?"":$0"\n"}'
4
5
另一种判断真假的方式实现:
# seq 5 |awk '/3/{t=1;next}t'
4
5
1 和 2 都不匹配 3,不执行后面{},执行 t,t 变量还没赋值,为空,空在 awk 中就为假,就不打印
当前行。匹配到 3,执行 t=1,next 跳出,不执行 t。4 也不匹配 3,执行 t,t 的值上次赋值的 1
为真,打印当前行,以此类推。(非 0 的数字都为真,所以 t 可以写任意非 0 数字)
如果想打印匹配行都最后一行,就可以这样了:
# seq 5 |awk '/3/{t=1}t'
3
4
5
内置变量
变量名  描述
FS              输入字段分隔符,默认是空格或制表符
OFS             输出字段分隔符,默认是空格
RS              输入记录分隔符,默认是换行符\n
ORS             输出记录分隔符,默认是换行符\n
NF              统计当前记录中字段个数
NR              统计记录编号,每处理一行记录,编号就会+1
FNR             统计记录编号,每处理一行记录,编号也会+1,与 NR 不同的是,处理第二个
文件时,编号会重新计数。
ARGC            命令行参数数量
ARGV            命令行参数数组序列数组,下标从 0 开始,ARGV[0]是 awk
ARGIND          当前正在处理的文件索引值。第一个文件是 1,第二个文件是 2,以此类推
ENVIRON         当前系统的环境变量
FILENAME        输出当前处理的文件名
IGNORECASE      忽略大小写
SUBSEP          数组中下标的分隔符,默认为"\034"

示例:

1)FS 和 OFS

在程序开始前重新赋值 FS 变量,改变默认分隔符为冒号,与-F 一样。

# awk 'BEGIN{FS=":"}{print $1,$2}' /etc/passwd |head -n5
root x
bin x
daemon x
adm x
lp x
也可以使用-v 来重新赋值这个变量:
# awk -vFS=':' '{print $1,$2}' /etc/passwd |head -n5 # 中间逗号被换成了 OFS 的默
认值
root x
bin x
daemon x
adm x
lp x
由于 OFS 默认以空格分隔,反向引用多个字段分隔的也是空格,如果想指定输出分隔符这样:
# awk 'BEGIN{FS=":";OFS=":"}{print $1,$2}' /etc/passwd |head -n5
root:x
bin:x
daemon:x
adm:x
lp:x
也可以通过字符串拼接实现分隔:
# awk 'BEGIN{FS=":"}{print $1"#"$2}' /etc/passwd |head -n5
root#x
bin#x
daemon#x
adm#x
lp#x

2)RS 和 ORS

RS 默认是\n 分隔每行,如果想指定以某个字符作为分隔符来处理记录:

# echo "www.baidu.com/user/test.html" |awk 'BEGIN{RS="/"}{print $0}'
www.baidu.com
user
test.html
RS 也支持正则,简单演示下:
# seq -f "str%02g" 10 |sed 'n;n;a\-----' |awk 'BEGIN{RS="-+"}{print $1}'
str01
str04
str07
str10
将输出的换行符替换为+号:
# seq 10 |awk 'BEGIN{ORS="+"}{print $0}'
1+2+3+4+5+6+7+8+9+10+
替换某个字符:
# tail -n2 /etc/services |awk 'BEGIN{RS="/";ORS="#"}{print $0}'
iqobject 48619#udp # iqobject
matahari 49000#tcp # Matahari Broker

3)NF

NF 是字段个数。
# echo "a b c d e f" |awk '{print NF}'
6
打印最后一个字段:
# echo "a b c d e f" |awk '{print $NF}'
f
打印倒数第二个字段:
# echo "a b c d e f" |awk '{print $(NF-1)}'
e
排除最后两个字段:
# echo "a b c d e f" |awk '{$NF="";$(NF-1)="";print $0}'
a b c d
排除第一个字段:
# echo "a b c d e f" |awk '{$1="";print $0}'
b c d e f

4)NR 和 FNR

NR 统计记录编号,每处理一行记录,编号就会+1,FNR 不同的是在统计第二个文件时会重新计数。
打印行数:
# tail -n5 /etc/services |awk '{print NR,$0}'
1 com-bardac-dw 48556/tcp # com-bardac-dw
2 com-bardac-dw 48556/udp # com-bardac-dw
3 iqobject 48619/tcp # iqobject
4 iqobject 48619/udp # iqobject
5 matahari 49000/tcp # Matahari Broker
打印总行数:
# tail -n5 /etc/services |awk 'END{print NR}'
5
打印第三行:
# tail -n5 /etc/services |awk 'NR==3'
iqobject 48619/tcp # iqobject
打印第三行第二个字段:
# tail -n5 /etc/services |awk 'NR==3{print $2}'
48619/tcp
打印前三行:
# tail -n5 /etc/services |awk 'NR<=3{print NR,$0}'
1 com-bardac-dw 48556/tcp # com-bardac-dw
2 com-bardac-dw 48556/udp # com-bardac-dw
3 iqobject 48619/tcp # iqobject
看下 NR 和 FNR 的区别:
# cat a
a
b
c
# cat b
c
d
e
# awk '{print NR,FNR,$0}' a b
1 1 a
2 2 b
3 3 c
4 1 c
5 2 d
6 3 e
可以看出 NR 每处理一行就会+1,而 FNR 在处理第二个文件时,编号重新计数。同时也知道 awk 处理
两个文件时,是合并到一起处理。
# awk 'FNR==NR{print $0"1"}FNR!=NR{print $0"2"}' a b
a1
b1
c1
c2
d2
e2
当 FNR==NR 时,说明在处理第一个文件内容,不等于时说明在处理第二个文件内容。
一般 FNR 在处理多个文件时会用到,下面会讲解。

5)ARGC 和 ARGV

ARGC 是命令行参数数量
ARGV 是将命令行参数存到数组,元素由 ARGC 指定,数组下标从 0 开始
# awk 'BEGIN{print ARGC}' 1 2 3
4
# awk 'BEGIN{print ARGV[0]}'
awk
# awk 'BEGIN{print ARGV[1]}' 1 2
1
# awk 'BEGIN{print ARGV[2]}' 1 2
2

6)ARGIND

ARGIND 是当前正在处理的文件索引值,第一个文件是 1,第二个文件是 2,以此类推,从而可以通
过这种方式判断正在处理哪个文件。
# awk '{print ARGIND,$0}' a b
1 a
1 b
1 c
2 c
2 d
2 e
# awk 'ARGIND==1{print "a->"$0}ARGIND==2{print "b->"$0}' a b
a->a
a->b
a->c
b->c
b->d
b->e

7)ENVIRON

ENVIRON 调用系统变量。
# awk 'BEGIN{print ENVIRON["HOME"]}'
/root
如果是设置的环境变量,还需要用 export 导入到系统变量才可以调用:
# awk 'BEGIN{print ENVIRON["a"]}'
# export a
# awk 'BEGIN{print ENVIRON["a"]}'
123

8)FILENAME

FILENAME 是当前处理文件的文件名。
# awk 'FNR==NR{print FILENAME"->"$0}FNR!=NR{print FILENAME"->"$0}' a b
a->a
a->b
a->c
b->c
b->d
b->e

9)忽略大小写

# echo "A a b c" |xargs -n1 |awk 'BEGIN{IGNORECASE=1}/a/'
A
a
等于 1 代表忽略大小写。
操作符
运算符  描述
(....)  分组
$  字段引用
++ --  递增和递减
+ - !  加号,减号,和逻辑否定
* / %  乘,除和取余
+ -  加法,减法
| |&  管道,用于 getline,print 和 printf
< > <= >= != ==  关系运算符
~ !~  正则表达式匹配,否定正则表达式匹配
in  数组成员
&& ||  逻辑 and,逻辑 or
?:  简写条件表达式:
expr1 ? expr2 : expr3
第一个表达式为真,执行 expr2,否则执行 expr3
= += -= *= /= %= ^=  变量赋值运算符

须知:

  • 在 awk 中,有 3种情况表达式为假:数字是 0 ,空字符串和未定义的值。

  • 数值运算,未定义变量初始值为 0。字符运算,未定义变量初始值为空。

举例测试:

# awk 'BEGIN{n=0;if(n)print "true";else print "false"}'
false
# awk 'BEGIN{s="";if(s)print "true";else print "false"}'
false
# awk 'BEGIN{if(s)print "true";else print "false"}'
false

示例:

1)截取整数

# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print +$0}'
123
0
123
# echo "123abc abc123 123abc123" |xargs -n1 | awk '{print -$0}'
-123
0
-123

2)感叹号

打印奇数行:
# seq 6 |awk 'i=!i'
1
3
5
打印偶数行:
# seq 6 |awk '!(i=!i)'
2
4
6
读取第一行:i 是未定义变量,也就是 i=!0,!取反意思。感叹号右边是个布尔值,0 或空字符串为
假,非 0 或非空字符串为真,!0 就是真,因此 i=1,条件为真打印当前记录。
没有 print 为什么会打印呢?因为模式后面没有动作,默认会打印整条记录。
读取第二行:因为上次 i 的值由 0 变成了 1,此时就是 i=!1,条件为假不打印。
读取第三行:上次条件又为假,i 恢复初始值 0,取反,继续打印。以此类推...
可以看出,运算时并没有判断行内容,而是利用布尔值真假判断输出当前行。

3)不匹配某行

# tail /etc/services |awk '!/blp5/{print $0}'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
isnetserv 48128/udp # Image Systems Network Services
com-bardac-dw 48556/tcp # com-bardac-dw
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/tcp # iqobject
iqobject 48619/udp # iqobject
matahari 49000/tcp # Matahari Broker

3)乘法和除法

# seq 5 |awk '{print $0*2}'
2
4
6
8
10
# seq 5 |awk '{print $0%2}'
1
0
1
0
1
打印偶数行:
# seq 5 |awk '$0%2==0{print $0}'
2
4
打印奇数行:
# seq 5 |awk '$0%2!=0{print $0}'
1
3
5

4)管道符使用

# seq 5 |shuf |awk '{print $0|"sort"}'
1
2
3
4
5

5)正则表达式匹配

# seq 5 |awk '$0~3{print $0}'
3
# seq 5 |awk '$0!~3{print $0}'
1
2
4
5
# seq 5 |awk '$0~/[34]/{print $0}'
3
4
# seq 5 |awk '$0!~/[34]/{print $0}'
1
2
5
# seq 5 |awk '$0~/[^34]/{print $0}'
1
2
5

6)判断数组成员

# awk 'BEGIN{a["a"]=123}END{if("a" in a)print "yes"}' </dev/null
yes

7)三目运算符

# awk 'BEGIN{print 1==1?"yes":"no"}' # 三目运算作为一个表达式,里面不允许写 print
yes
# seq 3 |awk '{print $0==2?"yes":"no"}'
no
yes
no
替换换行符为逗号:
\# seq 5 |awk '{print n=(n?n","$0:$0)}'
1
1,2
1,2,3
1,2,3,4
1,2,3,4,5
# seq 5 |awk '{n=(n?n","$0:$0)}END{print n}'
1,2,3,4,5
说明:读取第一行时,n 没有变量,为假输出$0 也就是 1,并赋值变量 n,读取第二行时,n 是 1 为
真,输出 1,2 以此类推,后面会一直为真。
每三行后面添加新一行:
# seq 10 |awk '{print NR%3?$0:$0 "\ntxt"}'
1
2
3
txt
4
5
6
txt
7
8
9
txt
10
在两行合并一行:
# seq 6 |awk '{printf NR%2!=0?$0" ":$0" \n"}'
1 2
3 4
5 6
# seq 6 |awk 'ORS=NR%2?" ":"\n"'
1 2
3 4
5 6
\# seq 6 |awk '{if(NR%2)ORS=" ";else ORS="\n";print}'

8)变量赋值

字段求和:
# seq 5 |awk '{sum+=1}END{print sum}'
5
# seq 5 |awk '{sum+=$0}END{print sum}'
15

流程控制

1 )if 语句

格式:if (condition) statement [ else statement ]
单分支:
# seq 5 |awk '{if($0==3)print $0}'
3
也支持正则匹配判断,一般在写复杂语句时使用:
# echo "123abc#456cde 789aaa#aaabbb " |xargs -n1 |awk -F# '{if($2~/[0-9]/)print $2}'
456cde
# echo "123abc#456cde 789aaa#aaabbb " |xargs -n1 |awk -F# '{if($2!~/[0-9]/)print $2}'
aaabbb
或
# echo "123abc#456cde 789aaa#aaabbb" |xargs -n1 |awk -F# '$2!~/[0-9]/{print $2}'
aaabbb
双分支:
# seq 5 |awk '{if($0==3)print $0;else print "no"}'
no
no
3
no
no
多分支:
# cat file
1 2 3
4 5 6
7 8 9
# awk '{if($1==4){print "1"} else if($2==5){print "2"} else if($3==6){print "3"} else
{print "no"}}' file
no
1
no

2 )while 语句

格式:while (condition) statement
遍历打印所有字段:
# awk '{i=1;while(i<=NF){print $i;i++}}' file
1
2
3
4
5
6
7
8
9
awk 是按行处理的,每次读取一行,并遍历打印每个字段。

3 )for 语句C语言风格

格式:for (expr1; expr2; expr3) statement
遍历打印所有字段:
# cat file
1 2 3
4 5 6
7 8 9
# awk '{for(i=1;i<=NF;i++)print $i}' file
1
2
3
4
5
6
7
8
9
倒叙打印文本:
# awk '{for(i=NF;i>=1;i--)print $i}' file
3
2
1
6
5
4
9
8
7
都换行了,这并不是我们要的结果。怎么改进呢?
# awk '{for(i=NF;i>=1;i--){printf $i" "};print ""}' file # print 本身就会新打印一行
3 2 1
6 5 4
9 8 7
或
# awk '{for(i=NF;i>=1;i--)if(i==1)printf $i"\n";else printf $i" "}' file
3 2 1
6 5 4
9 8 7
在这种情况下,是不是就排除第一行和倒数第一行呢?我们正序打印看下
排除第一行:
# awk '{for(i=2;i<=NF;i++){printf $i" "};print ""}' file
2 3
5 6
8 9
排除第二行:
# awk '{for(i=1;i<=NF-1;i++){printf $i" "};print ""}' file
1 2
4 5
7 8
IP 加单引号:
# echo '10.10.10.1 10.10.10.2 10.10.10.3' |awk '{for(i=1;i<=NF;i++)printf
"\047"$i"\047"}
'10.10.10.1' '10.10.10.2' '10.10.10.3'
\047 是 ASCII 码,可以通过 showkey -a 命令查看。

4 )for 语句遍历数组

格式:for (var in array) statement
# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{for(v in a)print v,a[v]}'
4 str4
5 str5
1 str1
2 str2
3 str3

5 )break 和 和 continue 语句

break 跳过所有循环,continue 跳过当前循环。
# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){break};print i}}'
1
2
# awk 'BEGIN{for(i=1;i<=5;i++){if(i==3){continue};print i}}'
1
2
4
5

6 )删除数组和元素

格式:
delete array[index] 删除数组元素
delete array 删除数组
# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{delete a;for(v in a)print v,a[v]}'
空的…
# seq -f "str%.g" 5 |awk '{a[NR]=$0}END{delete a[3];for(v in a)print v,a[v]}'
4 str4
5 str5
1 str1
2 str2

7 )exit 语句

格式:exit [ expression ]
exit 退出程序,与 shell 的 exit 一样。[ expr ]是 0-255 之间的数字。
# seq 5 |awk '{if($0~/3/)exit (123)}'
# echo $?
123
### 
数组

数组:存储一系列相同类型的元素,键/值方式存储,通过下标(键)来访问值。

awk 中数组称为关联数组,不仅可以使用数字作为下标,还可以使用字符串作为下标。

数组元素的键和值存储在 awk 程序内部的一个表中,该表采用散列算法,因此数组元素是随机排

序。

数组格式:array[index]=value

1)自定义数组

# awk 'BEGIN{a[0]="test";print a[0]}'
test

2)通过 NR 设置记录下标,下标从 1 开始

# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[1]}'
systemd-network
# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[2]}'
zabbix
# tail -n3 /etc/passwd |awk -F: '{a[NR]=$1}END{print a[3]}'
user

3)通过 for 循环遍历数组

# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(v in a)print a[v],v}'
zabbix 4
user 5
admin 1
systemd-bus-proxy 2
systemd-network 3
# tail -n5 /etc/passwd |awk -F: '{a[NR]=$1}END{for(i=1;i<=NR;i++)print a[i],i}'
admin 1
systemd-bus-proxy 2
systemd-network 3
zabbix 4
user 5
上面打印的 i 是数组的下标。
第一种 for 循环的结果是乱序的,刚说过,数组是无序存储。
第二种 for 循环通过下标获取的情况是排序正常。
所以当下标是数字序列时,还是用 for(expr1;expr2;expr3)循环表达式比较好,保持顺序不变。

4)通过++方式作为下标

# tail -n5 /etc/passwd |awk -F: '{a[x++]=$1}END{for(i=0;i<=x-1;i++)print a[i],i}'
admin 0
systemd-bus-proxy 1
systemd-network 2
zabbix 3
user 4
x 被 awk 初始化值是 0,没循环一次+1

5)使用字段作为下标

# tail -n5 /etc/passwd |awk -F: '{a[$1]=$7}END{for(v in a)print a[v],v}'
/sbin/nologin admin
/bin/bash user
/sbin/nologin systemd-network
/sbin/nologin systemd-bus-proxy
/sbin/nologin zabbix

6)统计相同字段出现次数

# tail /etc/services |awk '{a[$1]++}END{for(v in a)print a[v],v}'
2 com-bardac-dw
1 3gpp-cbsp
2 iqobject
1 matahari
2 isnetserv
2 blp5
# tail /etc/services |awk '{a[$1]+=1}END{for(v in a)print a[v],v}'
2 com-bardac-dw
1 3gpp-cbsp
2 iqobject
1 matahari
2 isnetserv
2 blp5
# tail /etc/services |awk '/blp5/{a[$1]++}END{for(v in a)print a[v],v}'
2 blp5
第一个字段作为下标,值被++初始化是 0,每次遇到下标(第一个字段)一样时,对应的值就会被
+1,因此实现了统计出现次数。
想要实现去重的的话就简单了,只要打印下标即可。

7)统计 TCP 连接状态

# netstat -antp |awk '/^tcp/{a[$6]++}END{for(v in a)print a[v],v}'
9 LISTEN
6 ESTABLISHED
6 TIME_WAIT

8)只打印出现次数大于等于 2 的

# tail /etc/services |awk '{a[$1]++}END{for(v in a) if(a[v]>=2){print a[v],v}}'
2 com-bardac-dw
2 iqobject
2 isnetserv
2 blp5

9)去重

只打印重复的行:
# tail /etc/services |awk 'a[$1]++'
isnetserv 48128/udp # Image Systems Network Services
blp5 48129/udp # Bloomberg locator
com-bardac-dw 48556/udp # com-bardac-dw
iqobject 48619/udp # iqobject
不打印重复的行:
# tail /etc/services |awk '!a[$1]++'
3gpp-cbsp 48049/tcp # 3GPP Cell Broadcast Service
isnetserv 48128/tcp # Image Systems Network Services
blp5 48129/tcp # Bloomberg locator
com-bardac-dw 48556/tcp # com-bardac-dw
iqobject 48619/tcp # iqobject
matahari 49000/tcp # Matahari Broker
先明白一个情况,当值是 0 是为假,非 0 整数为真,知道这点就不难理解了。
只打印重复的行说明:当处理第一条记录时,执行了++,初始值是 0 为假,就不打印,如果再遇到
相同的记录,值就会+1,不为 0,则打印。
不打印重复的行说明:当处理第一条记录时,执行了++,初始值是 0 为假,感叹号取反为真,打
印,如果再遇到相同的记录,值就会+1,不为 0 为真,取反为假就不打印。
# tail /etc/services |awk '{if(a[$1]++)print $1}'
isnetserv
blp5
com-bardac-dw
iqobject
使用三目运算:
# tail /etc/services |awk '{print a[$1]++?$1:"no"}'
no
no
isnetserv
no
blp5
no
com-bardac-dw
no
iqobject
no
# tail /etc/services |awk '{if(!a[$1]++)print $1}'
3gpp-cbsp
isnetserv
blp5
com-bardac-dw
iqobject
matahari

10)统计每个相同字段的某字段总数:

# tail /etc/services |awk -F'[ /]+' '{a[$1]+=$2}END{for(v in a)print v, a[v]}'
com-bardac-dw 97112
3gpp-cbsp 48049
iqobject 97238
matahari 49000
isnetserv 96256
blp5 96258

11)多维数组

awk 的多维数组,实际上 awk 并不支持多维数组,而是逻辑上模拟二维数组的访问方式,比如
a[a,b]=1,使用 SUBSEP(默认\034)作为分隔下标字段,存储后是这样 a\034b。
示例:
# awk 'BEGIN{a["x","y"]=123;for(v in a) print v,a[v]}'
xy 123
我们可以重新复制 SUBSEP 变量,改变下标默认分隔符:
# awk 'BEGIN{SUBSEP=":";a["x","y"]=123;for(v in a) print v,a[v]}'
x:y 123
根据指定的字段统计出现次数:
# cat file
A 192.168.1.1 HTTP
B 192.168.1.2 HTTP
B 192.168.1.2 MYSQL
C 192.168.1.1 MYSQL
C 192.168.1.1 MQ
D 192.168.1.4 NGINX
# awk 'BEGIN{SUBSEP="-"}{a[$1,$2]++}END{for(v in a)print a[v],v}' file
1 D-192.168.1.4
1 A-192.168.1.1
2 C-192.168.1.1
2 B-192.168.1.2
内置函数
函数  描述
int(expr)  截断为整数
sqrt(expr)  平方根
rand()  返回一个随机数 N,0 和 1 范围,0 < N < 1
srand([expr])
使用 expr 生成随机数,如果不指定,默认使用当前时间为种子,如果前面有种子则使用生成随机数
asort(a, b)
对数组 a 的值进行排序,把排序后的值存到新的数组 b 中,新排序的数组下标从 1 开始
asorti(a,)  对数组 a 的下标进行排序,同上
sub(r, s [ t])  对输入的记录用 s 替换 r 正则匹配,t 可选针对某字段替换,但只替换第一个字符串
gsub(r, s [, t]) 对输入的记录用 s 替换 r 正则匹配,t 可选针对某字段替换,否则替换所有字符串
gensub(r, s, h [, t])  对输入的记录用 s 替换 r 正则匹配,h 替换指定索引位置
index(s, t)  返回 s 中字符串 t 的索引位置,0 为不存在
length([s])  返回 s 的长度
match(s, r [, a])  测试字符串 s 是否包含匹配 r 的字符串,如果不包含返回 0
split(s, a [, r [,seps] ]) 根据分隔符 seps 将 s 分成数组 a
substr(s, i [, n])  截取字符串 s 从 i 开始到长度 n,如果 n 没指定则是剩余部分
tolower(str)  str 中的所有大写转换成小写
toupper(str)  str 中的所有小写转换成大写
systime()  当前时间戳
strftime([format [,timestamp[, utc-flag]]]) 格式化输出时间,将时间戳转为字符串

示例:

1)int()

截断为整数:
# echo -e "123abc\nabc123\n123abc123" | awk '{print int($0)}'
123
0
123
# awk 'BEGIN{print int(10/3)}'
3

2)sqrt()

获取 9 的平方根:
# awk 'BEGIN{print sqrt(9)}'
3

3)rand()和 srand()

rand()并不是每次运行就是一个随机数,会一直保持一个不变:
# awk 'BEGIN{print rand()}'
0.237788
当执行 srand()函数后,rand()才会发生变化,所以一般在 awk 着两个函数结合生成随机数,但是
也有很大几率生成一样:
# awk 'BEGIN{srand();print rand()}'
0.31687
如果想生成 1-10 的随机数可以这样:
# awk 'BEGIN{srand();print int(rand()*10)}'
4
如果想更完美生成随机数,还得做相应的处理!

4)asort()和 asorti()

排序数组:
# seq -f "str%.g" 5 |awk '{a[x++]=$0}END{s=asort(a,b);for(i=1;i<=s;i++)print
b[i],i}'
str1 1
str2 2
str3 3
str4 4
str5 5
# seq -f "str%.g" 5 |awk '{a[x++]=$0}END{s=asorti(a,b);for(i=1;i<=s;i++)print
b[i],i}'
0 1
1 2
2 3
3 4
4 5
asort 将 a 数组的值放到数组 b,a 下标丢弃,并将数组 b 的总行号赋值给 s,新数组 b 下标从 1 开始,然后遍历。

5)sub()和 gsub()

替换正则匹配的字符串:
# tail /etc/services |awk '/blp5/{sub(/tcp/,"icmp");print $0}'
blp5 48129/icmp # Bloomberg locator
blp5 48129/udp # Bloomberg locator
# tail /etc/services |awk '/blp5/{gsub(/c/,"9");print $0}'
blp5 48129/t9p # Bloomberg lo9ator
blp5 48129/udp # Bloomberg lo9ator
# echo "1 2 2 3 4 5" |awk 'gsub(2,7,$2){print $0}'
1 7 2 3 4 5
# echo "1 2 3 a b c" |awk 'gsub(/[0-9]/, '0'){print $0}'
0 0 0 a b c
在指定行前后加一行:
# seq 5 | awk 'NR==2{sub('/.*/',"txt\n&")}{print}'
1
txt
2
3
4
5
# seq 5 | awk 'NR==2{sub('/.*/',"&\ntxt")}{print}'
1
2
txt
3
4
5

6)index()

获取字段索引起始位置:
# tail -n 5 /etc/services |awk '{print index($2,"tcp")}'
7
0
7
0
7

7)length()

统计字段长度:
# tail -n 5 /etc/services |awk '{print length($2)}'
9
9
9
9
9
统计数组的长度:
# tail -n 5 /etc/services |awk '{a[$1]=$2}END{print length(a)}'
3

8)match

# echo "123abc#456cde 789aaa#234bbb 999aaa#aaabbb" |xargs -n1 |awk '{print
match($0,234)}'
0
8
0
如果记录匹配字符串 234,则返回索引位置,否则返回 0。
那么,我们只想打印包含这个字符串的记录就可以这样:
# echo "123abc#456cde 789aaa#234bbb 999aaa#aaabbb" |xargs -n1 |awk
'{if(match($0,234)!=0)print $0}'
789aaa#234bbb

9)split()

切分记录为数组 a:
# echo -e "123#456#789\nabc#cde#fgh" |awk '{split($0,a);for(v in a)print a[v],v}'
123#456#789 1
abc#cde#fgh 1
以#号切分记录为数据 a:
# echo -e "123#456#789\nabc#cde#fgh" |awk '{split($0,a,"#");for(v in a)print a[v],v}'
123 1
456 2
789 3
abc 1
cde 2
fgh 3

10)substr()

截取字符串索引 4 到最后:
# echo -e "123#456#789\nabc#cde#fgh" |awk '{print
substr($0,4)}'
456#789
#cde#fgh
截取字符串索引 4 到长度 5:
# echo -e "123#456#789\nabc#cde#fgh" |awk '{print substr($0,4,5)}'
#456#
#cde#

11)tolower()和 toupper()

转换小写:
# echo -e "123#456#789\nABC#cde#fgh" |awk '{print tolower($0)}'
123#456#789
abc#cde#fgh
转换大写:
# echo -e "123#456#789\nabc#cde#fgh" |awk '{print toupper($0)}'
123#456#789
ABC#CDE#FGH

12)时间处理

返回当前时间戳:
# awk 'BEGIN{print systime()}'
1483297766
将时间戳转为日期和时间
# echo "1483297766" |awk '{print strftime("%Y-%m-%d %H:%M:%S",$0)}'
test-01-01 14:09:26
I/O 语句
语句 描述
getline  读取下一个输入记录设置给$0
getline var  读取下一个输入记录并赋值给变量 var
command | getline [var]  运行 Shell 命令管道输出到$0 或 var
next  停止当前处理的输入记录后面动作
print  打印当前记录
printf fmt, expr-list  格式化输出
printf fmt, expr-list >file  格式输出和写到文件
system(cmd-line)  执行命令和返回状态
print ... >> file  追加输出到文件
print ... | command  打印输出作为命令输入

示例:

1)getline

# 获取匹配的下一行:
seq 5 |awk '/3/{getline;print}'
4
# seq 5 |awk '/3/{print;getline;print}'
3
4
在匹配的下一行加个星号:
# seq 5 |awk '/3/{getline;sub(".*","&*");print}'
4*
# seq 5 |awk '/3/{print;getline;sub(".*","&*")}{print}'
1
2
3
4*
5

2)getline var

把 a 文件的行追加到 b 文件的行尾:
# cat a
a
b
c
# cat b
1 one
2 two
3 three
# awk '{getline line<"a";print $0,line}' b
1 one a
2 two b
3 three c
把 a 文件的行替换 b 文件的指定字段:
# awk '{getline line<"a";gsub($2,line,$2);print}' b
1 a
2 b
3 c
把 a 文件的行替换 b 文件的对应字段:
# awk '{getline line<"a";gsub("two",line,$2);print}' b
1 one
2 b
3 three

3)command | getline [var]

获取执行 shell 命令后结果的第一行:
# awk 'BEGIN{"seq 5"|getline var;print var}'
1
循环输出执行 shell 命令后的结果:
# awk 'BEGIN{while("seq 5"|getline)print}'
1
2
3
4
5

4)next

不打印匹配行:
# seq 5 |awk '{if($0==3){next}else{print}}'
1
2
4
5
删除指定行:
# seq 5 |awk 'NR==1{next}{print $0}'
2
3
4
5
如果前面动作成功,就遇到 next,后面的动作不再执行,跳过。
或者:
# seq 5 |awk 'NR!=1{print}'
2
3
4
5
把第一行内容放到每行的前面:
# cat a
hello
1 a
2 b
3 c
# awk 'NR==1{s=$0;next}{print s,$0}' a
hello 1 a
hello 2 b
hello 3 c
# awk 'NR==1{s=$0}NF!=1{print s,$0}' a
hello 1 a
hello 2 b
hello 3 c

5)system()

执行 shell 命令判断返回值:
# awk 'BEGIN{if(system("grep root /etc/passwd &>/dev/null")==0)print "yes";else print"no"}'
yes

6)打印结果写到文件

# tail -n5 /etc/services |awk '{print $2 > "a.txt"}'
# cat a.txt
48049/tcp
48128/tcp
48128/udp
48129/tcp
48129/udp

7)管道连接 shell 命令

将结果通过 grep 命令过滤:
# tail -n5 /etc/services |awk '{print $2|"grep tcp"}'
48556/tcp
48619/tcp
49000/tcp
### 
printf 语句

格式化输出,默认打印字符串不换行。

格式:printf [format] arguments
Format 描述
%s  一个字符串
%d,%i  一个小数
%f  一个浮点数
%.ns  输出字符串,n 是输出几个字符
%m.nf  输出浮点数,m 是输出整数位数,n 是输出的小数位数
%x  不带正负号的十六进制,使用 a 至 f 表示 10 到 15
%X  不带正负号的十六进制,使用 A 至 F 表示 10 至 15
%%  输出单个%
%-5s  左对齐,对参数每个字段左对齐,宽度为 5
%-4.2f  左对齐,宽度为 4,保留两位小数
%5s  右对齐,不加横线表示右对齐

示例:

将换行符换成逗号:
# seq 5 |awk '{if($0!=5)printf "%s,",$0;else print $0}'
1,2,3,4,5
小括号中的 5 是最后一个数字。
输出一个字符:
# awk 'BEGIN{printf "%.1s\n","abc"}'
a
保留一个小数点:
# awk 'BEGIN{printf "%.2f\n",10/3}'
3.33
格式化输出:
# awk 'BEGIN{printf "user:%s\tpass:%d\n","abc",123}'
user:abc pass:123
左对齐宽度 10:
# awk 'BEGIN{printf "%-10s %-10s %-10s\n","ID","Name","Passwd"}'
ID Name Passwd
右对齐宽度 10:
# awk 'BEGIN{printf "%10s %10s %10s\n","ID","Name","Passwd"}'
ID Name Passwd
打印表格:
# vi test.awk
BEGIN{
print "+--------------------+--------------------+";
printf "|%-20s|%-20s|\n","Name","Number";
print "+--------------------+--------------------+";
}
# awk -f test.awk
+--------------------+--------------------+
|Name |Number |
+--------------------+--------------------+
格式化输出:
# awk -F: 'BEGIN{printf "UserName\t\tShell\n-----------------------------\n"}{printf
"%-20s %-20s\n",$1,$7}END{print "END...\n"}' /etc/passwd
打印十六进制:
# awk 'BEGIN{printf "%x %X",123,123}'
7b 7B
自定义函数
格式:function name(parameter list) { statements }
示例:
# awk 'function myfunc(a,b){return a+b}BEGIN{print myfunc(1,2)}'
3
常用需求案例

1)两个文件对比

找出 b 文件在 a 文件相同记录:

# seq 1 5 > a

# seq 3 7 > b

方法 1:
\# awk 'FNR==NR{a[$0];next}{if($0 in a)print $0}' a b
3
4
5
# awk 'FNR==NR{a[$0];next}{if($0 in a)print FILENAME,$0}' a b
b 3
b 4
b 5
# awk 'FNR==NR{a[$0]}NR>FNR{if($0 in a)print $0}' a b
3
4
5
# awk 'FNR==NR{a[$0]=1;next}(a[$0]==1)' a b # a[$0]是通过 b 文件每行获取值,如果是 1说明有
# awk 'FNR==NR{a[$0]=1;next}{if(a[$0]==1)print}' a b
3
4
5
方法 2:
# awk 'FILENAME=="a"{a[$0]}FILENAME=="b"{if($0 in a)print $0}' a b
3

5
方法 3:
\# awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]==1' a b
3
4
5

找出 b 文件在 a 文件不同记录:

方法 1:
# awk 'FNR==NR{a[$0];next}!($0 in a)' a b
6
7
# awk 'FNR==NR{a[$0]=1;next}(a[$0]!=1)' a b
# awk 'FNR==NR{a[$0]=1;next}{if(a[$0]!=1)print}' a b
6
7
方法 2:
# awk 'FILENAME=="a"{a[$0]=1}FILENAME=="b" && a[$0]!=1' a b
方法 3:
# awk 'ARGIND==1{a[$0]=1}ARGIND==2 && a[$0]!=1' a b

3)合并两个文件

将 a 文件合并到 b 文件:
# cat a
zhangsan 20
lisi 23
wangwu 29
# cat b
zhangsan man
lisi woman
wangwu man
# awk 'FNR==NR{a[$1]=$0;next}{print a[$1],$2}' a b
zhangsan 20 man
lisi 23 woman
wangwu 29 man
# awk 'FNR==NR{a[$1]=$0}NR>FNR{print a[$1],$2}' a b
zhangsan 20 man
lisi 23 woman
wangwu 29 man
将 a 文件相同 IP 的服务名合并:
# cat a
192.168.1.1: httpd
192.168.1.1: tomcat
192.168.1.2: httpd
192.168.1.2: postfix
192.168.1.3: mysqld
192.168.1.4: httpd
# awk 'BEGIN{FS=":";OFS=":"}{a[$1]=a[$1] $2}END{for(v in a)print v,a[v]}' a
192.168.1.4: httpd
192.168.1.1: httpd tomcat
192.168.1.2: httpd postfix
192.168.1.3: mysqld
说明:数组 a 存储是$1=a[$1] $2,第一个 a[$1]是以第一个字段为下标,值是 a[$1] $2,也就是$1=a[$1] $2,值的 a[$1]是用第一个字段为下标获取对应的值,但第一次数组 a 还没有元素,那么a[$1]是空值,此时数组存储是 192.168.1.1=httpd,再遇到 192.168.1.1 时,a[$1]通过第一字段下标获得上次数组的 httpd,把当前处理的行第二个字段放到上一次同下标的值后面,作为下标192.168.1.1 的新值。此时数组存储是 192.168.1.1=httpd tomcat。每次遇到相同的下标(第一个字段)就会获取上次这个下标对应的值与当前字段并作为此下标的新值。

4)将第一列合并到一行

# cat file
1 2 3
4 5 6
7 8 9
# awk '{for(i=1;i<=NF;i++)a[i]=a[i]$i" "}END{for(v in a)print a[v]}' file
1 4 7
2 5 8
3 6 9
说明:
for 循环是遍历每行的字段,NF 等于 3,循环 3 次。读取第一行时:第一个字段:a[1]=a[1]1" " 值 a[1]还未定义数组,下标也获取不到对应的值,所以为空,因此a[1]=1 。第二个字段:a[2]=a[2]2" " 值 a[2]数组 a 已经定义,但没有 2 这个下标,也获取不到对应的值,为空,因此 a[2]=2 。第三个字段:a[3]=a[3]3" " 值 a[2]与上面一样,为空,a[3]=3 。

读取第二行时:第一个字段:a[1]=a[1]4" " 值 a[2]获取数组 a 的 2 为下标对应的值,上面已经有这个下标了,对应的值是 1,因此 a[1]=1 4

第二个字段:a[2]=a[2]5" " 同上,a[2]=2 5

第三个字段:a[3]=a[3]6" " 同上,a[2]=3 6

读取第三行时处理方式同上,数组最后还是三个下标,分别是 1=1 4 7,2=2 5 8,3=3 6 9。最后for 循环输出所有下标值。

5)字符串拆分,统计出现的次数

字符串拆分:
方法 1:
# echo "hello world" |awk -F '' '{print $1}'
h
# echo "hello" |awk -F '' '{for(i=1;i<=NF;i++)print $i}'
h
e
l
l
o
方法 2:
# echo "hello" |awk '{split($0,a,"''");for(v in a)print a[v]}'
l
o
h
e
l
统计字符串中每个字母出现的次数:
# echo "a.b.c,c.d.e" |awk -F '[.,]' '{for(i=1;i<=NF;i++)a[$i]++}END{for(v in a)print v,a[v]}'
a 1
b 1
c 2
d 1
e 1

6)统计平均成绩

# cat file
job 80
dave 84
tom 75
dave 73
job 72
tom 83
dave 88
# awk '{a[$1]+=$2;b[$1]++}END{for(i in a)print i,a[i]/b[i]}' file
job 76
dave 81.6667
tom 79

7)费用统计

# cat file
zhangsan 8000 1
zhangsan 5000 1
lisi 1000 1
lisi 2000 1
wangwu 1500 1
zhaoliu 6000 1
zhaoliu 2000 1
zhaoliu 3000 1
# awk '{name[$1]++;cost[$1]+=$2;number[$1]+=$3}END{for(v in name)print
v,cost[v],number[v]}' file
zhangsan 5000 1
lisi 3000 2
wangwu 1500 1
zhaoliu 11000 3

8)获取数字字段最大值

# cat file
a b 1
c d 2
e f 3
g h 3
i j 2
获取第三字段最大值:
# awk 'BEGIN{max=0}{if($3>max)max=$3}END{print max}' file
3
打印第三字段最大行:
# awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)print v,a[v],max}' a
g h 3 3 3
e f 3 3 3
c d 2 2 3
a b 1 1 3
i j 2 2 3
# awk 'BEGIN{max=0}{a[$0]=$3;if($3>max)max=$3}END{for(v in a)if(a[v]==max)print v}' a
g h 3
e f 3

9)去除第一行和最后一行

# seq 5 |awk 'NR>2{print s}{s=$0}'
2
3
4
读取第一行,NR=1,不执行 print s,s=1
读取第二行,NR=2,不执行 print s,s=2 (大于为真)
读取第三行,NR=3,执行 print s,此时 s 是上一次 p 赋值内容 2,s=3最后一行,执行 print s,打印倒数第二行,s=最后一行
----------------------------------------------------
获取 Nginx 负载均衡配置端 IP 和端口:
# cat nginx.conf
upstream example-servers1 {
server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s;
}
upstream example-servers2 {
server 127.0.0.1:80 weight=1 max_fails=2 fail_timeout=30s;
server 127.0.0.1:82 backup;
}
# awk '/example-servers1/,/}/{if(NR>2){print s}{s=$2}}' nginx.conf
127.0.0.1:80
# awk '/example-servers1/,/}/{if(i>1)print s;s=$2;i++}' nginx.conf
# awk '/example-servers1/,/}/{if(i>1){print s}{s=$2;i++}}' nginx.conf
127.0.0.1:80
读取第一行,i 初始值为 0,0>1 为假,不执行 print s,x=example-servers1,i=1
读取第二行,i=1,1>1 为假,不执行 print s,s=127.0.0.1:80,i=2
读取第三行,i=2,2>1 为真,执行 print s,此时 s 是上一次 s 赋值内容 127.0.0.1:80,i=3
最后一行,执行 print s,打印倒数第二行,s=最后一行。
这种方式与上面一样,只是用 i++作为计数器。

10)知道上述方式,就可以实现这种需求了,打印匹配行的上一行

# seq 5 |awk '/3/{print s}{s=$0}'
2

Shell编程四剑客之GREP

全面搜索正则表达式(Global search regular expression(RE) ,GREP)是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。

Unix/Linux的grep家族包括grep、egrep和fgrep,其中egrep和fgrep的命令跟grep有细微的区别,egrep是grep的扩展,支持更多的re元字符, fgrep是fixed grep或fast grep简写,它们把所有的字母都看作单词,正则表达式中的元字符表示其自身的字面意义,不再有其他特殊的含义,一般使用比较少。

目前Linux操作系统默认使用GNU版本的grep。它功能更强,可以通过-G、-E、-F命令行选项来使用egrep和fgrep的功能。其语法格式及常用参数详解如下:

grep     -[acinv]    'word'     Filename

Grep常用参数详解如下:

-a 						以文本文件方式搜索;
-c 						计算找到的符合行的次数;
-i 						忽略大小写;
-n 						顺便输出行号;
-v 						反向选择,即显示不包含匹配文本的所有行;
-h 						查询多文件时不显示文件名;
-l 						查询多文件时只输出包含匹配字符的文件名;
-s 						不显示不存在或无匹配文本的错误信息;
-E 						允许使用egrep扩展模式匹配。

学习Grep时,需要了解通配符、正则表达式两个概念,很多读者容易把彼此搞混淆,通配符主要用在Linux的Shell命令中,常用于文件或者文件名称的操作,而正则表达式用于文本内容中的字符串搜索和替换,常用在AWK、GREP、SED、VIM工具中对文本的操作。

通配符类型详解:

*						0个或者多个字符、数字;
?						匹配任意一个字符;
#						表示注解;
|						管道符号;
;						多个命令连续执行;
&						后台运行指令;
!						逻辑运算非;
[ ]						内容范围,匹配括号中内容;
{ }						命令块,多个命令匹配。

正则表达式详解:

*						前一个字符匹配0次或多次;
.						匹配除了换行符以外任意一个字符;
.*						代表任意字符;
^						匹配行首,即以某个字符开头;
$						匹配行尾,即以某个字符结尾;
\(..\) 					标记匹配字符;
[]						匹配中括号里的任意指定字符,但只匹配一个字符;
[^]						匹配除中括号以外的任意一个字符;
\						转义符,取消特殊含义;
\< 						锚定单词的开始;
\> 						锚定单词的结束;
{n}						匹配字符出现n次;
{n,}					匹配字符出现大于等于n次;
{n,m}					匹配字符至少出现n次,最多出现m次;
\w 						匹配文字和数字字符;
\W 						\w的反置形式,匹配一个或多个非单词字符;
\b 						单词锁定符;
\s						匹配任何空白字符;
\d						匹配一个数字字符,等价于[0-9]。

常用GREP工具企业演练案列:

grep  -c "test" 	     test.txt	 统计test字符总行数;
grep  -i "TEST" 	     test.txt	 不区分大小写查找TEST所有的行;
grep  -n "test" 	     test.txt	 打印test的行及行号;
grep  -v "test" 	     test.txt	 不打印test的行;
grep  "test[53]" 	     test.txt	 以字符test开头,接5或者3的行;
grep  "^[^test]" 	     test.txt	 显示输出行首不是test的行;
grep  "[Mm]ay" 	 	 test.txt	 匹配M或m开头的行;
grep  "K…D" 		 test.txt	 匹配K,三个任意字符,紧接D的行;
grep  "[A-Z][9]D"  	 test.txt	 匹配大写字母,紧跟9D的字符行;
grep  "T\{2,\}" 	     test.txt	 打印字符T字符连续出现2次以上的行;
grep  "T\{4,6\}" 	     test.txt	 打印字符T字符连续出现4次及6次的行;
grep  -n "^$" 	 	 test.txt	 打印空行的所在的行号;
grep  -vE "#|^$"       test.txt    不匹配文件中的#和空行;
grep   --color -ra -E "db|config|sql"  *  匹配包含db或者config或者sql的文件;
grep   --color -E "\<([0-9]{1,3}\.){3}([0-9]{1,3})\>"    test.txt 匹配IPV4地址。
grep forest f.txt     #文件查找
grep forest f.txt cpf.txt #多文件查找
grep 'log' /data/ -r -n #目录下查找所有符合关键字的文件
cat f.txt | grep -i shopbase    
grep 'shopbase' /data/ -r -n --include *.{vm,java} #指定文件后缀
grep 'shopbase' /data/ -r -n --exclude *.{vm,java} #反匹配
seq 10 | grep 5 -A 3    #上匹配
seq 10 | grep 5 -B 3    #下匹配
seq 10 | grep 5 -C 3    #上下匹配,平时用这个就妥了
cat f.txt | grep -c 'SHOPBASE'

Shell分析服务器日志,

date常用命令
date  +%Y-%m-%d, date +%y-%m-%d 年月日

date  +%H:%M:%S = date +%T 时间

date +%s  时间戳

date -d @1504620492

date -d "+1day"  一天后

date -d "-1 day"  一天前

date -d "-1 month" 一月前

date -d "-1 min"  一分钟前

date +%w, date +%W 星期
Shell分析服务器日志,连接

参考博文:https://segmentfault.com/a/1190000009745139

Shell数组编程

数组是相同数据类型的元素按一定顺序排列的集合,把有限个类型相同的变量用一个名字命名,然后用编号区分他们变量的集合,这个名称称之为数组名,编号成为下标。Linux Shell编程中常用一维数组。

数组的设计其实了为了处理方便,把具有相同类型的若干变量按有序的形式组织起来的一种形式,以减少重复频繁的单独定义。如图所示:

1570800897302

一维、二维、三维数组

定义数组一般以小括号的方式来定义,数组的值可以随机指定,如下为一维数组的定义、统计、引用和删除操作,:

数组是相同类型的元素按一定顺序排列的集合。

格式:

array=(元素 1 元素 2 元素 3 ...)

用小括号初始化数组,元素之间用空格分隔。

定义方法 1:初始化数组

array=(a b c)

定义方法 2:新建数组并添加元素

array[下标]=元素

定义方法 3:将命令输出作为数组元素

array=($(command))
常用数组配置实战:

(1) 一唯数组定义及创建:

JFTEST=( 
test1
test2
test3 
)
LAMP=(httpd  php  php-devel php-mysql mysql mysql-server)

(2) 数组下标一般从0开始,如下为引用数组的方法:

echo	${JFTEST[0]}    	        引用第一个数组变量,结果打印test1;
echo 	${JFTEST[1]}		    引用第二个数组变量;
echo  	${JFTEST[@]}   		显示该数组所有参数;
echo  	${#JFTEST[@]}  		显示该数组参数个数;
echo    ${#JFTEST[0]}       显示test1字符长度;
echo    ${JFTEST[@]:0}   	打印数组所有的值;
echo    ${JFTEST[@]:1}   	打印从第二个值开始的所有值;
echo    ${JFTEST[@]:0:2} 	打印从第一个值与第二个值;
echo    ${JFTEST[@]:1:2} 	打印从第二个值与第三个值。

(3) 数组替换操作:

JFTEST=( [0]=www1 [1]=www2 [2]=www3 )	      数组赋值;
echo  ${JFTEST[@]/test/test}				      将数组值test替换为test;
NEWJFTEST=`echo  ${JFTEST[@]/test/test}`      将结果赋值新数组。

(4) 数组删除操作:

unset array[0]   	            删除数组第一个值;
unset array[1]             	删除数组第二个值;
unset array                   删除整个数组。

(5) 数组Shell脚本企业案例一,网卡bond绑定脚本:

#!/bin/bash
#Auto Make KVM Virtualization
#Auto config bond scripts
#By author test.net
eth_bond()
{
NETWORK=(
  HWADDR=`ifconfig eth0 |egrep "HWaddr|Bcast" |tr "\n" " "|awk '{print $5,$7,$NF}'|sed -e 's/addr://g' -e 's/Mask://g'|awk '{print $1}'`
  IPADDR=`ifconfig eth0 |egrep "HWaddr|Bcast" |tr "\n" " "|awk '{print $5,$7,$NF}'|sed -e 's/addr://g' -e 's/Mask://g'|awk '{print $2}'`
  NETMASK=`ifconfig eth0 |egrep "HWaddr|Bcast" |tr "\n" " "|awk '{print $5,$7,$NF}'|sed -e 's/addr://g' -e 's/Mask://g'|awk '{print $3}'`
  GATEWAY=`route -n|grep "UG"|awk '{print $2}'`
)
cat >ifcfg-bond0<<EOF
DEVICE=bond0
BOOTPROTO=static
${NETWORK[1]}
${NETWORK[2]}
${NETWORK[3]}
ONBOOT=yes
TYPE=Ethernet
NM_CONTROLLED=no
EOF

(6) 数组Shell脚本企业案例二,定义IPv4值:

#!/bin/bash
#auto Change ip netmask gateway scripts  
#By author test.net
ETHCONF=/etc/sysconfig/network-scripts/ifcfg-eth0
HOSTS=/etc/hosts
NETWORK=/etc/sysconfig/network
DIR=/data/backup/`date +%Y%m%d`
NETMASK=255.255.255.0
echo "----------------------------"
count_ip(){
        count=(`echo  $IPADDR|awk  -F.  '{print $1,$2,$3,$4}'`)
        IP1=${count[0]}
        IP2=${count[1]}
        IP3=${count[2]}
        IP4=${count[3]}
}

(7)遍历数组元素

方法 1:

#!/bin/bash
IP=(192.168.1.1 192.168.1.2 192.168.1.3)
for ((i=0;i<${#IP[*]};i++)); do
echo ${IP[$i]}
done
# bash test.sh
192.168.1.1
192.168.1.2
192.168.1.3

方法 2:

#!/bin/bash
IP=(192.168.1.1 192.168.1.2 192.168.1.3)
for IP in ${IP[*]}; do
echo $IP
done

猜你喜欢

转载自blog.51cto.com/13965278/2451479
今日推荐