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脚本编程去运行,如图所示:

Shell企业编程基础实战_第1张图片

Shell、用户及Kernel位置关系

Shell企业编程基础实战_第2张图片

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

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

  • Bourne Again Shell(/bin/bash)

  • 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]

示例:

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 </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 "" > /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内容为:

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次

Shell企业编程基础实战_第3张图片

  1. |(竖线)功能实践

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

1570782322885

  1. () 功能实践

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

1570782405525

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

1570782420917

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

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

Shell企业编程基础实战_第4张图片

  1. \< \>边界符锚定

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

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

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

\

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