shell脚本基础
Shell 是一个用 C 语言编写的程序,Shell 既是一种命令语言,也是一个编程语言。
shell是一种解释型语言,解释型语言的特征就是有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以运行,其运行时逐行运行;另一种比较常见的编程语言是编译型语言,即需要先完成程序的编译,再来运行。
本文来聊的shell,通指shell脚本编程,发行版本是 Bash(Bourne Again Shell ),这也是目前最常见的shell分支之一,常见于如redhat centos Asianux fedora等Linux系统。
**系统版本:**Centos7.9 mini(含vim) 3.10.0-1160.11.1.el7.x86_64
**shell版本:**bash-4.2.46-34.el7.x86_64
文档修改记录:
版本 | 创建及修订时间 | 编撰人 |
---|---|---|
V.00 | 2021年3月 | systemctl529_Yu |
文档标记的说明:
在本文中:
代码块中,### 表示描述或者备注,直接书写汉字为标题或者下一步的操作。
截图中,以 红色箭头/选框/字体 表示为必然操作或指示,以 蓝色箭头/选框/字体 表示为建议指示或说明。
文本中,以 黄色涂抹 字体表示为重点及需特别注意的事项。
一、第一个shell脚本
shell脚本与其他语言有很大的不同,首先,shell可以说是最好学习的编程语言之一。
而且因为shell脚本的编程目的是为了解决实际应用问题,而非编写面向多人访问的应用程序,所以这也一定程度决定了shell脚本的灵活性和多样性,能解决问题,没有太大性能瓶颈,就够用了,当然也因为此,很多shell脚本,逻辑可能比较混乱(所以要写好注释 ),效率也可能不佳。
再加上shell本身也是一个命令语言,其用到的很多,都是我们每天命名行输入的命令,所以shell脚本对身为运维的我们,上手也容易很多。
好了,接下来开始编写我们的第一个shell脚本
1、编写第一个hello world
编程语言千千万,第一句代码确基本都是 hello world,这也一定象征性的代表了我们开始一门新语言的学习
接下来打开 VIM编辑器:
[root@centos7sp9_node1 ~]# vim no1.sh
#!/bin/bash
echo "Hello World!"
# 在shell中,是单行注释,注释即对代码的说明,或者解释,当在每行最前添加 # ,即此行不会被执行,书写注释,是很重要的一件事,一个脚本如果具备好的注释,对很久之后我们再行修改代码,或者其他人阅读使用我们的代码,都有良好的帮助
#! 是一个约定的标记,它告诉这个脚本需要什么解释器来执行,即使用哪一种 Shell
#!/bin/bash /bin/bash是bash程序的路径
2、执行第一个shell脚本(脚本执行方式)
当我们编写好了脚本,如何执行它:
方法1:通过bash命令执行
[root@centos7sp9_node1 ~]# bash no1.sh
Hello World!
方法2:通过sh命令执行
[root@centos7sp9_node1 ~]# sh no1.sh
Hello World!
在centos中,bash与sh没有明显的区分,sh 是 bash的软链接
sh (Bourne Shell)已经太古老了,基本很少见
[root@centos7sp9_node1 ~]# which sh /usr/bin/sh [root@centos7sp9_node1 ~]# which bash /usr/bin/bash [root@centos7sp9_node1 ~]# ll /usr/bin/sh lrwxrwxrwx. 1 root root 4 1月 5 18:16 /usr/bin/sh -> bash [root@centos7sp9_node1 ~]# ll /usr/bin/bash -rwxr-xr-x. 1 root root 964536 4月 1 2020 /usr/bin/bash
方法3:脚本加执行权限直接执行
[root@centos7sp9_node1 ~]# chmod +x no1.sh
[root@centos7sp9_node1 ~]# ./no1.sh
Hello World!
shell脚本加了执行权限后,执行脚本是不能直接写 # no1.sh的,如果这样写,系统会前往$PATH里找,而PATH里通常不会有我们放置脚本的当前目录。所以使用 # ./no1.sh执行,./ 当前目录,即在当前目录找
二、脚本常用的两个输出命令
因为有几个命令,需要在了解了一些知识后才好理解,所以会跟随进度慢慢聊,本节先说两个输出的命令
1、echo命令
命令格式:
# echo [选项] [要输出的内容]
常用选项:
-n 不换行输出
-e 扩展参数,启用反斜杠转义;若字符串中出现以下字符,则特别加以处理,而不会将它当成一般文字输出:
\a 发出警告声;
\b 删除前一个字符;
\c 最后不加上换行符号;
\f 换行但光标仍旧停留在原来的位置;
\n 换行且光标移至行首;
\r 光标移至行首,但不换行;
\t 插入tab;
\v 与\f相同;
\\ 插入\字符;
\nnn 插入nnn(八进制)所代表的ASCII字符;
例如:
### 常规输出:
[root@centos7sp9_node1 ~]# echo "hello world"
hello world
### -n 不换行:
[root@centos7sp9_node1 ~]# echo -n "hello world"
hello world[root@centos7sp9_node1 ~]#
### -e 开启转义:
### 比如我们想要输出 "\1"
[root@centos7sp9_node1 ~]# echo \1
1
[root@centos7sp9_node1 ~]# echo -e \\1
\1
### 输出一个警告音
[root@centos7sp9_node1 ~]# echo -e "\a"
### 不换行输出
abc[root@centos7sp9_node1 ~]# echo "abc\c"
abc\c
[root@centos7sp9_node1 ~]# echo -e "abc\c"
abc[root@centos7sp9_node1 ~]#
### 再比如输出一个带颜色的文本
[root@centos7sp9_node1 ~]# echo -e "\e[1;31m 1234 \e[0m"
1234
# echo -e “\e[1;31m 1234 \e[0m” “\e[1"是标准格式,代表开始颜色输出,”"\e[0m"代表结束颜色输出 “31m” 是红色, 1234自然就是内容了
字体颜色:30m黑色;31m 红色;32m绿色;33m黄色;34m蓝色;37m白色…
背景颜色:40m黑色;41m 红色;42m绿色;43m黄色;44m蓝色;47m白色…
2、printf命令
命令格式:
# printf [格式控制字符串] [参数列表]
printf的转义序列:
\a 警告字符,通常为ASCII的BEL字符
\b 后退
\c 抑制(不显示)输出结果中任何结尾的换行字符(只在%b格式指示符控制下的参数字符串中有效),而且,任何留在参数里的字符、任何接下来的参数以及任何留在格式字符串中的字符,都被忽略
\f 换页(formfeed)
\n 换行
\r 回车(Carriage return)
\t 水平制表符
\v 垂直制表符
\\ 一个字面上的反斜杠字符
\ddd 表示1到3位数八进制值的字符。仅在格式字符串中有效
\0ddd 表示1到3位的八进制值字符
例如:
### 常规输出(printf输出默认不换行):
[root@centos7sp9_node1 ~]# printf "1234"
1234[root@centos7sp9_node1 ~]# printf "1234\n" # \n 换行
1234
### 格式替代符:
root@centos7sp9_node1 ~]# printf "%-15s %-7s %-5s\n" 姓名 性别 体重
姓名 性别 体重
[root@centos7sp9_node1 ~]# printf "%-15s %-7s %-5.2f\n" systemctl529 man 55.55666
systemctl529 man 55.56
%s 输出一个字符串(%-15s -左对齐,无-右对齐 15宽度15字符,不足空格填充),%d 整型输出,%c 输出一个字符,%f 输出实数(5.2f 保留2位小数)
三、bash的常用操作与功能
1、命令执行的优先级
因为shell命令,存在着像别名,$PATH等概念,所以当命令存在冲突等问题时,会涉及到一个执行优先级的问题
最优先:以路径开头的命令;比如 "# /usr/bin/ps" 直接以路径执行,不必再寻找环境变量等
次优先:alias别名,比如我们在 ".bashrc" 文件中定义的 "alias rm='rm -i'",因为其要比会覆盖bash命令(如果和bash命令冲突),所以优先级较bash命令更高
再次优先级:常规bash命令
最低优先:$PATH环境变量中的命令
我们可以通过执行 echo $PATH,查看到当前的环境变量
[root@centos7sp9_node1 ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
同时可以在 /etc/profile文件中自定义全局的环境变量
2、history历史命令
命令格式:
# history [选项] [历史命令保存文件]
选项:
-w 将当前终端缓存的历史命令写入到历史命令文件中 (默认历史命令文件 ~/.bash_history)
-a 将历史命令缓存区中的命令写入历史命令文件中
-r 将历史命令文件中的命令读入当前历史命令缓存区
-c 清空当前历史命令
例如:
### 当我们在终端输入了一些命令后,实际上是在缓存区的,并没有被保存在历史记录文件中
[root@centos7sp9_node1 ~]# cat .bash_history
[root@centos7sp9_node1 ~]# ls
anaconda-ks.cfg
[root@centos7sp9_node1 ~]# cd
[root@centos7sp9_node1 ~]# cat .bash_history # 可以看到我们执行了命令,但历史记录并没有在文件中存储
### 当我们执行 -w 参数,即可将缓存区中的历史命令保存到历史命令文件中
[root@centos7sp9_node1 ~]# history -w
[root@centos7sp9_node1 ~]# cat .bash_history
cat .bash_history
ls
cd
cat .bash_history
history -w
### 执行 -c 参数,即可清除当前的历史命令
[root@centos7sp9_node1 ~]# history
1 cat .bash_history
2 echo > .bash_history
3 cat .bash_history
4 ls
5 cd
6 cat .bash_history
7 history -w
8 cat .bash_history
9 history
[root@centos7sp9_node1 ~]# history -c
[root@centos7sp9_node1 ~]# history # 可以看到当执行了 -c 参数后,当前历史命令被清除了(但不影响已经保存在历史记录文件中的)
1 history
### 当执行 -r 参数,即可将历史记录文件中的历史记录读取到当前历史记录中
[root@centos7sp9_node1 ~]# history -r
[root@centos7sp9_node1 ~]# history
1 history
2 cat .bash_history
3 history -r
4 cat .bash_history
5 echo > .bash_history
6 cat .bash_history
7 ls
8 cd
9 cat .bash_history
10 history -w
11 history
历史命令文件 bash_history,是存储在对应用户的家目录下的,即 ~/.bash_history
历史命令的调用
历史命令调用的方式有很多,这里单独提3个
1、键盘↑ ↓调用执行过的命令
也就是直接在命令行,按键盘上下箭头就可以调出当前终端本次执行过的命令
2、!命令关键字
在命令行输入 !命令关键字符 即可再次执行最后一条以该关键字开头的命令
例如:
### 当窝在当前命令行执行过两次ls 命令,但是使用的参数不同,那么当我执行 !命令关键字时,再次执行的命令是最后一条以该关键字开头的历史命令
[root@centos7sp9_node1 ~]# history
......
12 ls -al
13 ls -a
14 history
[root@centos7sp9_node1 ~]# !l
ls -a # 可以看到,执行的是最后一条
. anaconda-ks.cfg .bash_logout .bashrc .tcshrc
.. .bash_history .bash_profile .cshrc .viminfo
3、!$
调取上一条命令的最后一个参数
例如:
### 我们执行过一次ls -al命令,那么其参数是 -al
[root@centos7sp9_node1 ~]# ls -al
total 32
dr-xr-x---. 2 root root 151 Feb 22 11:34 .
......
### 然后使用 !$ 来调用上一条命令的最后一个参数
[root@centos7sp9_node1 ~]# ls !$
ls -al
total 32
dr-xr-x---. 2 root root 151 Feb 22 11:34 .
......
3、bash的标准输入输出
执行一个shell会自动打开三个标准文件:
标准输入文件 /dev/stdin通常对应的是键盘;文件描述符号 0
标准输出文件/dev/stdout 和标准错误输出文件/dev/stderr这两个文件通常对应的是屏幕。标准输出文件描述符 1,标准错误输出文件描述符 2
进程将从标准输入文件中得到输入数据,将正常输出到标准输出文件,错误输出写入到标准错误文件中。
4、bash的输出重定向
重定向顾名思义,即不再让输出直接以“标准输出”的方式,输出在显示器,而是指定其输出到某个文件中
输出类型 | 执行表达式 | 描述 |
---|---|---|
标准输出重定向 | # 命令 > 文件 | 覆盖方式将正确输出重定向输出到指定文件 |
标准输出追加重定向 | # 命令 >> 文件 | 追加方式将正确输出重定向输出到指定文件 |
标准错误输出重定向 | # 命令 2> 文件 | 覆盖方式将错误输出重定向输出到指定文件 |
标准错误输出追加重定向 | # 命令 2>> 文件 | 追加方式将错误输出重定向输出到指定文件 |
标准输出及错误输出合并重定向 | # 命令 > 文件 2>&1 # 命令 &> 文件 | 覆盖方式将标准输出及错误输出合并重定向输出到指定文件 |
标准输出及错误输出合并追加重定向 | # 命令 >> 文件 2>&1 # 命令 &>> 文件 | 追加方式将标准输出及错误输出合并重定向输出到指定文件 |
标准输出及错误输出分别追加重定向 | # 命令 > 文件1 2> 文件2 | 追加方式将标准输出及错误输出分别重定向输出到指定文件 |
例如:
标准输出重定向
[root@centos7sp9_node1 ~]# ls > bash01/1.txt
[root@centos7sp9_node1 ~]# cat bash01/1.txt # 可以看到命令结果重定向到了文件中
anaconda-ks.cfg
bash01
但是如果我再执行一次 > ,就会将该文件此前的内容覆盖掉
[root@centos7sp9_node1 ~]# echo "123" > bash01/1.txt
[root@centos7sp9_node1 ~]# cat bash01/1.txt # 可以发现之前的ls命令结果没了
123
标准输出追加重定向
追加顾名思义,即不会覆盖文件原内容
[root@centos7sp9_node1 ~]# cat bash01/1.txt
123
[root@centos7sp9_node1 ~]# echo "abc" >> bash01/1.txt
[root@centos7sp9_node1 ~]# cat bash01/1.txt
123
abc
标准输出及错误输出合并追加重定向
[root@centos7sp9_node1 ~]# echo "123" &>> bash01/2.txt
[root@centos7sp9_node1 ~]# echoasdasd "123" &>> bash01/2.txt
[root@centos7sp9_node1 ~]# cat bash01/2.txt # 可以看到正确的与错误的都保存了
123
-bash: echoasdasd: command not found
标准输出及错误输出分别追加重定向
[root@centos7sp9_node1 ~]# echo "123" >> bash01/3.txt 2>> bash01/4.txt
[root@centos7sp9_node1 ~]# echoasdasdas "123" >> bash01/3.txt 2>> bash01/4.txt
[root@centos7sp9_node1 ~]# cat bash01/3.txt # 标准输出在3.txt
123
[root@centos7sp9_node1 ~]# cat bash01/4.txt # 错误输出在4.txt
-bash: echoasdasdas: command not found
/dev/null 黑洞 输出不想要?可扔进/dev/null中,就会消失掉 ,比如 “# ls &> /dev/null” 这样 ls 的结果就被丢弃了,不会显示
/dev/zero 白洞 白洞与黑洞相反,白洞可以产生无尽大的文件,比如有时候我们使用dd命令创建一个块时 “# dd if=/dev/zero of=test count=100 bs=1M”
5、bash的输入重定向
不同于输出重定向将输出重定向到文件,输入重定向是从文件获取输入
命令格式:
# 命令 < 文件
例如:
### wc命令获取文件行数
[root@centos7sp9_node1 ~]# wc -l /etc/passwd
19 /etc/passwd
[root@centos7sp9_node1 ~]# wc -l < /etc/passwd
19
# 由以上两个不同的效果可以看到,第二条命令不会输出文件名,这是因为添加了 < 后,其仅从标准输入读取内容,也就是我将 /etc/passwd 文件当做了标准输入给 wc -l命令
同时重定向输入与输出
[root@centos7sp9_node1 ~]# wc -l < /etc/passwd > bash01/5.txt [root@centos7sp9_node1 ~]# cat bash01/5.txt 19
6、多命令执行 逻辑与 逻辑或
命令间关联符 | 执行表达式 | 描述 |
---|---|---|
; | 命令1 ; 命令2 | 多个命令顺序执行,这些命令直接无依赖关系 |
&& | 命令1 && 命令2 | 逻辑与,即前一个命令执行正确,命令2才会执行,常见于判断,当前执行一个正确,接着执行什么 |
|| | 命令1 || 命令2 | 逻辑或,即前一个命令执行不正确,命令2才会执行,常见于判断,当前一个执行错误,接着执行什么,那么同时前一个命令执行正确,后一个命令也就不会执行 |
例如:
命令1 ; 命令2
[root@centos7sp9_node1 ~]# ls ; cat bash01/1.txt
anaconda-ks.cfg bash01 # 这是ls的结果
123 # 这是cat bash01/1.txt的结果
abc
命令1 && 命令2
[root@centos7sp9_node1 ~]# systemctl start httpd && systemctl enable httpd # 启动httpd,如果启动httpd正常,就将httpd设置为开机自启
命令1 || 命令2
[root@centos7sp9_node1 ~]# systemctl stop httpd1111 || echo "停止httpd报错" # 停止httpd,如果停止httpd失败,就输出"停止httpd报错"
Failed to stop httpd1111.service: Unit httpd1111.service not loaded.
停止httpd报错
再比如一条命令中,同时具备逻辑与与逻辑或的判断
[root@centos7sp9_node1 ~]# systemctl stop httpd && systemctl disable httpd || echo "停止httpd失败了" # 停止httpd,如果停止成功,就取消httpd开机自启,如果停止失败,就输出 "停止httpd失败了"
搭配输出重定向,去掉标准输出,来演示下
[root@centos7sp9_node1 ~]# ls &> /dev/null && echo yes || echo no yes [root@centos7sp9_node1 ~]# lsssss &> /dev/null && echo yes || echo no no
这里需要注意,写这种即有 && 也有 || 的shell,一定要把 && 放在前,如果 || 在前,&& 在后,那么就会出现这样一个尴尬情况
[root@centos7sp9_node1 ~]# ls || echo no && echo yes anaconda-ks.cfg bash01 test yes # 正确输出时是不是还挺正常的 [root@centos7sp9_node1 ~]# lssssss || echo no && echo yes -bash: lssssss: command not found no # 但是一但执行有问题,那就触发了 || 输出了 no yes # 尴尬的地方来了,&& 发现echo no执行成功了,于是输出了yes。。。
7、常见的通配符
通配符 | 描述 | 例子 |
---|---|---|
? | 匹配一个任意字符 | # find /etc -name passw? 查找/etc/下以passw开头的6位内容 |
* | 匹配 0 个或任意多个任意字符,也就是可以匹配任何内容 | # find /etc -name pass* 查找/etc下pass开头的内容 |
[] | 匹配中括号中任意一个字符。例如,[abc] 代表一定匹配一个字符,或者是 a,或者是 b,或 者是 c | # ls /etc/passw[abcd] ls查看/etc/passw 包含abcd四个字母中其中一个的内容 |
[-] | 匹配中括号中任意一个字符,- 代表一个范围。例如,[a-z] 代表匹配一个小写字母 | # ls /etc/passw[a-z] ls查看/etc/passw开头包含a-z中一个字母的内容 |
[^] | 逻辑非,表示匹配不是中括号内的一个字符。例如,[^0-9] 代表匹配一个不是数字的字符 | # ls /etc/passwd[^0-9] ls查看/etc.passw开头不以数字结尾的内容 |
8、常见的特殊符号
特殊符号 | 描述 |
---|---|
‘’ | 单引号,在单引号中的内容无特殊含义 |
“” | 双引号,在双引号中的内容无特殊含义,但"$"调用变量, "`"引用命令, "\"转义符,不在此列。 |
`` | 等同于$(),即优先执行某个bash命令 |
$() | 等同于``,即优先执行某个bash命令 |
() | 用于一串命令执行时,()中的命令会在子shell中运行 |
{} | 用于一串命令执行时,{}中的命令会在当前shell中运行;也可用于变量变形与替换 |
[] | 用于变量测试 |
# | #开头代表注释 |
$ | 调用变量 |
\ | 转义符 |
四、变量
变量来源于数学,是计算机语言中能储存计算结果或能表示值的抽象概念。变量可以通过变量名访问。
1、变量定义
在定义变量时,变量名无需加$符,直接变量名=值即可,如:
# NAME="node1"
变量名的命名,有一定的格式要求:
变量名只能使用英文字母,数组,下划线组成,同时不可用数字开头
变量名字符间不可有空格
变量名不可用标点
变量名不可与bash命令重合
有效的变量名如:
NAME
name1
name_1
NamE_1_2
无效的变量名如:
~name
name*
1name
另外变量的命名,要尽量的做到见名之意,这样方便我们自己和其他该脚本使用者的阅读
变量定义表达式的格式要求:
shell的变量定义格式,与其他语言有一点不同,即 连接变量与值的=号两侧不能有空格
例如:
[root@centos7sp9_node1 bash01]# NAME= "node2" # 右侧空格
-bash: node2: command not found
[root@centos7sp9_node1 bash01]# NAME = "node2" # 两侧空格
-bash: NAME: command not found
[root@centos7sp9_node1 bash01]# NAME ="node2" # 左侧空格
-bash: NAME: command not found
[root@centos7sp9_node1 bash01]# NAME="node2" # 无空格才正确
1.1 基本变量定义
直接给变量赋予一个值
[root@centos7sp9_node1 bash01]# NAME="node1"
[root@centos7sp9_node1 bash01]# echo $NAME
node1
1.2 将命令结果赋给变量
将命令执行结果赋值给变量,使用前面提到的 `` 或 $() 即可,
例如:
[root@centos7sp9_node1 bash01]# TIME=`date`
[root@centos7sp9_node1 bash01]# echo $TIME
Tue Feb 23 11:14:20 CST 2021
[root@centos7sp9_node1 bash01]# TIME=$(date)
[root@centos7sp9_node1 bash01]# echo $TIME
Tue Feb 23 11:15:11 CST 2021
1.3 变量值的叠加
如果要增加变量的值,可以使用变量值的叠加,格式为:
语法格式:
样式1:# 变量="$变量"要增加的值
样式2:# 变量=${变量}要增加的值
样式1例子:(大家在定义PATH环境变量的时候应该用过这个格式)
[root@centos7sp9_node1 bash01]# NAME="node1"
[root@centos7sp9_node1 bash01]# echo $NAME
node1
[root@centos7sp9_node1 bash01]# NAME="$NAME"node2
[root@centos7sp9_node1 bash01]# echo $NAME
node1node2
样式2例子:
[root@centos7sp9_node1 bash01]# NAME="node1"
[root@centos7sp9_node1 bash01]# echo $NAME
node1
[root@centos7sp9_node1 bash01]# NAME=${NAME}node2
[root@centos7sp9_node1 bash01]# echo $NAME
node1node2
1.4 read 交互式定义变量
可以实现提示信息,并交互输入变量值得效果,具体使用后面会聊到
这里先仅做一个演示
[root@centos7sp9_node1 bash01]# vim Read.sh
read -p "请输入姓名: " NAME
echo $NAME
[root@centos7sp9_node1 bash01]# sh Read.sh
请输入姓名: systemctl529
systemctl529
1.5 declare 声明有类型的变量
shell的变量值,默认是字符串类型,如果要数值运算,要指定变量类型;同时也可以将变量定义为只读,也可以定义数组
基本语法:
# declare [+/-][参数(rxi)] 变量名=值
- 指定变量属性
+ 取消变量属性
常用参数:
-r 设置只读变量
-i 设置整数变量
-a 定义/查看普通数组
-A 定义/查看关联数组
-x 指定的变量会成为环境变量,可供shell以外的程序来使用,效果和export类似
1.5.1 声明只读变量
[root@centos7sp9_node1 bash01]# declare -r NAME_only="centos7sp9_node1"
[root@centos7sp9_node1 bash01]# NAME_only="新的值" # 当我们想覆盖变量值得时候,就会提示这是只读变量
bash: NAME_only: readonly variable
另一种设置只读变量的方法
[root@centos7sp9_node1 bash01]# NAME1="systemctl529"
[root@centos7sp9_node1 bash01]# readonly NAME1
[root@centos7sp9_node1 bash01]# NAME1="node1" # 当我项再次定义该变量,就会提示只读的变量
-bash: NAME: readonly variable
默认情况下,只读变量不可被修改,也不可被取消
1.5.2 声明整数型变量
[root@centos7sp9_node1 bash01]# declare -i A=123
[root@centos7sp9_node1 bash01]# echo $A
123
[root@centos7sp9_node1 bash01]# A=systemctl529 # 当我将一个整数型变量值填充文本值的话,就会异常
[root@centos7sp9_node1 bash01]# echo $A
0
假设我想取消这个整数类型
[root@centos7sp9_node1 bash01]# declare +i A
[root@centos7sp9_node1 bash01]# A=node
[root@centos7sp9_node1 bash01]# echo $A
node
1.5.3 声明数组
数组大概可以先理解为一次给一个”变量“赋予多个想同类型的值;一个数组中有下标(暂时理解为一个变量名),有值,一个下标标记一个值,也就是一个序号代表着一个值
shell数值是一维数组,不支持多维,具体数组相关的,会在后面聊,先这里知道有这种变量定义方式就好
普通数组
先简单演示一个,后面会具体聊,实际数组一般也不会这样声明
[root@centos7sp9_node1 bash01]# declare -a name='([0]="node1" [1]="node2" [2]="node3")'
[root@centos7sp9_node1 bash01]# echo ${name[@]} # @遍历数组内容
node1 node2 node3
1.5.4 将变量导出为环境变量
我这里演示下将一个变量导到环境变量中
# 查看环境变量可以用env命令
[root@centos7sp9_node1 bash01]# env | grep B # 可以看到目前没有 B环境变量
[root@centos7sp9_node1 bash01]# B=systemctl529
[root@centos7sp9_node1 bash01]# declare -x B
[root@centos7sp9_node1 bash01]# env | grep B
B=systemctl529
2、变量调用
变量的调用很简单,$变量名 即可调用,比如我这里用echo 打印出变量内容:
[root@centos7sp9_node1 bash01]# NAME="systemctl_529"
调用
systemctl_529[root@centos7sp9_node1 bash01]# printf "$NAME\n"
systemctl_529
[root@centos7sp9_node1 bash01]# echo $NAME
systemctl_529
3、unset 删除变量
[root@centos7sp9_node1 bash01]# echo $B
systemctl529
[root@centos7sp9_node1 bash01]# unset B
[root@centos7sp9_node1 bash01]# echo $B
4、变量分类
变量通常分为 局部变量(本地变量),环境变量,系统变量与特殊变量
4.1 局部变量(本地变量)
在脚本或命令中定义,仅在当前shell实例中有效,其他shell进程或者子进程中无效
[root@centos7sp9_node1 bash01]# A=123
[root@centos7sp9_node1 bash01]# echo $A
123
[root@centos7sp9_node1 bash01]# su
[root@centos7sp9_node1 bash01]# echo $A
4.2 环境变量
不仅当前进程有效,包括shell启动的子程序,都能访问环境变量
环境变量的设置,分为临时和永久。永久生效可将环境变量写入到 /etc/profile 或者 ~/.bashrc 中
env 命令查看当前用户环境变量,
set 命令查看当前用户所有变量
[root@centos7sp9_node1 bash01]# export B=systemctl529 # ;临时将设置一个环境变量
[root@centos7sp9_node1 bash01]# env | grep ^B
B=systemctl529
再比如增加软件命令的环境变量:
[root@centos7sp9_node1 bash01]# export PATH=/usr/local/mysql/bin:$PATH
[root@centos7sp9_node1 bash01]# echo $PATH
/usr/local/mysql/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
4.3 系统变量与特殊变量
也就是shell本身已经定义好的变量
变量 | 描述 |
---|---|
$0 | 当前执行的程序或者脚本名称 |
$1 $2 $3 …$9 | 脚本后的位置参数,$1是第一个,$2第二个… |
10... {10}... 10...{n} | 扩展位置参数,必须用{}大括号括起来(2位数字以上扩起来) |
$? | 上一条命令执行的返回结果,为0表示正常,非0表示异常 |
$# | 脚本后接的参数个数 |
$* | 将脚本后接的所有参数当做一个整体输出,每个参数之间以空格分隔 |
$@ | 将脚本后接的所有参数输出,但每个参数是独立的 |
$$ | 当前进程进程号 |
$! | 当前终端后台运行的最后一个进程号 |
!$ | 调取最后一条命令的参数 |
例如:
$0
[root@centos7sp9_node1 bash01]# echo $0
bash
$1 $2 $3 ....$9
[root@centos7sp9_node1 bash01]# vim 1.sh
echo "$1 $2 $3"
[root@centos7sp9_node1 bash01]# sh 1.sh a b c
a b c
${10}...${n}
[root@centos7sp9_node1 bash01]# vim 1.sh
echo "$1 $2 ${10}"
[root@centos7sp9_node1 bash01]# sh 1.sh 1 2 3 4 5 6 7 8 9 10
1 2 10
$?
[root@centos7sp9_node1 bash01]# sh 1.sh 1 2 &> /dev/null && echo $?
0
[root@centos7sp9_node1 bash01]# sh 2.sh
sh: 2.sh: No such file or directory
[root@centos7sp9_node1 bash01]# echo $?
127
$#
[root@centos7sp9_node1 bash01]# vim 1.sh
echo "$#"
[root@centos7sp9_node1 bash01]# sh 1.sh a b c
3
$*
[root@centos7sp9_node1 bash01]# vim 1.sh
echo "$*"
[root@centos7sp9_node1 bash01]# sh 1.sh a b c
a b c
$@
[root@centos7sp9_node1 bash01]# vim 1.sh
echo "$@"
[root@centos7sp9_node1 bash01]# sh 1.sh a b c
a b c
$$
[root@centos7sp9_node1 bash01]# echo $$
15715
!$
[root@centos7sp9_node1 bash01]# ls -al
total 8
drwxr-xr-x 2 root root 33 Feb 23 16:17 .
dr-xr-x---. 3 root root 177 Feb 23 16:17 ..
-rw-r--r-- 1 root root 21 Feb 23 16:16 1.sh
-rw-r--r-- 1 root root 44 Feb 23 14:45 Read.sh
[root@centos7sp9_node1 bash01]# ls !$
ls -al
5、定义数组
前面简单提到数组,这里来演示说明
数组中可以存放多个值,是若干数据的集合,其中的每一个数据称为元素,标记元素的序号,叫做索引(下标),shell数组的下标数字也是从0开始计数
shell的数组分为两种:普通数组,关联数组
普通数组:只能使用正整数作为数组索引(元素的下标)
关联数组:可以使用字符串作为数组索引(元素的下标)
5.1 普通数组定义
### 一次只赋一个值
基本语法:
# 数组名[索引下标]=元素(值)
例如:
[root@centos7sp9_node1 bash01]# array[0]=你好
[root@centos7sp9_node1 bash01]# array[1]=世界
### 一次赋多个值
基本语法:
# 数组名=(值1 值2 值3)
例如:
[root@centos7sp9_node1 bash01]# arr1=(你好 世界)
# 将文件内容的每行赋给数组
[root@centos7sp9_node1 bash01]# arr2=(`cat /etc/passwd`)
# 执行结果赋给数组
[root@centos7sp9_node1 bash01]# arr3=(`ls /root`)
# 多种字符类型赋给数组
[root@centos7sp9_node1 bash01]# arr4=(1 2 abc "hello world" "你好 shell" [10]=linux)
5.2 数组读取
基本语法:
# ${数组名[元素下标]}
用法:
${数组名[元素下标N]} # 获取数组里的第N个下标对应的元素
${数组名[*]} # 获取数组里所有元素
${数组名[@]} # 获取数组的所有元素,和*的不同是 *当变量加上""后,会当做一串字符串处理,@加上""后,依然当做数组处理
${#数组名[*]} # 获取数组里所有元素个数(数组长度)
${#数组名[@]} # 获取数组里所有元素个数(数组长度)
${数组名[@]:1:3} # 访问指定的元素,1代表从下标为1的元素开始获取,3代表获取后面几个元素
例子:
### 获取数组里的第N个下标对应的元素
[root@centos7sp9_node1 bash01]# arr1=(1 2 3 abc "hello world")
[root@centos7sp9_node1 bash01]# echo ${arr1[0]}
1
[root@centos7sp9_node1 bash01]# echo ${arr1[4]}
hello world
### 获取数组里所有元素
[root@centos7sp9_node1 bash01]# echo ${arr1[*]}
1 2 3 abc hello world
[root@centos7sp9_node1 bash01]# echo ${arr1[@]}
1 2 3 abc hello world
### 获取数组里所有元素个数
[root@centos7sp9_node1 bash01]# echo ${#arr1[*]}
5
[root@centos7sp9_node1 bash01]# echo ${#arr1[@]}
5
### 访问指定的元素范围
[root@centos7sp9_node1 bash01]# echo ${arr1[@]:0:3}
1 2 3
关于 ${数组名[*]} 和 ${数组名[*]} 的区别:
*当变量加上"“后,会当做一串字符串处理,@加上”"后,依然当做数组处理,
加""后,@可以保持原有参数结构, * 会将原有参数结构打乱
不加""的话,没太大区别
编写个脚本演示: ### 本脚本为变量加""演示 [root@centos7sp9_node1 bash01]# vim arr.sh #!/bin/bash array=(1 2 3) echo '[*]' for i in "${array[*]}";do echo $i done echo "**********************" echo '[@]' for i in "${array[@]}";do echo $i done 执行: [root@centos7sp9_node1 bash01]# sh arr.sh [*] 1 2 3 ********************** [@] 1 2 3 编写个脚本演示: ### 本脚本为变量不加""演示 [root@centos7sp9_node1 bash01]# vim arr.sh #!/bin/bash array=(1 2 3) echo '[*]' for i in ${array[*]};do echo $i done echo "**********************" echo '[@]' for i in ${array[@]};do echo $i done 执行: root@centos7sp9_node1 bash01]# sh arr.sh [*] 1 2 3 ********************** [@] 1 2 3
5.3 关联数组
正如前面所说,关联数值可以使用字符串作为元素的下标。
5.3.1 声明关联数组
[root@centos7sp9_node1 bash01]# declare -A Array1
5.3.2 关联数组赋值
单个赋值
[root@centos7sp9_node1 bash01]# Array1[one]=linux
[root@centos7sp9_node1 bash01]# Array1[two]=centos
[root@centos7sp9_node1 bash01]# Array1[three]=7sp8
一次性赋多个值
[root@centos7sp9_node1 bash01]# Array1=([one]=linux [two]=centos [three]=7sp8)
5.4 关联数组读取
例子:
[root@centos7sp9_node1 bash01]# declare -A|grep Array1
declare -A Array1='([one]="linux" [two]="centos" [three]="7sp8" )'
[root@centos7sp9_node1 bash01]# echo ${Array1[one]}
linux
[root@centos7sp9_node1 bash01]# echo ${Array1[two]}
centos
[root@centos7sp9_node1 bash01]# echo ${Array1[*]}
linux centos 7sp8
[root@centos7sp9_node1 bash01]# echo ${Array1[@]}
linux centos 7sp8
6、特殊变量
6.1 取出目录和文件
[root@centos7sp9_node1 bash01]# dirname /etc/passwd
/etc
[root@centos7sp9_node1 bash01]# basename /etc/passwd
passwd
[root@centos7sp9_node1 bash01]# NAME="/etc/ssh/sshd/sshd.config"
[root@centos7sp9_node1 bash01]# dirname $NAME
/etc/ssh/sshd
[root@centos7sp9_node1 bash01]# basename $NAME
sshd.config
6.2 变量值内容的删除
${#变量名} 获取变量长度
${变量名%.*} 以 . 为分隔符,从右往左去掉一段内容
${变量名%%.*} 以 . 为分隔符,从右往左去掉最大长度内容
${变量名#.*} 以 . 为分隔符,从左往右去掉一段内容
${变量名##.*} 以 . 为分隔符,从左往右去掉最大长度内容
例子:
[root@centos7sp9_node1 bash01]# NAME='www/baidu/com'
[root@centos7sp9_node1 bash01]# echo ${#NAME} # 获取变量内容的长度
13
[root@centos7sp9_node1 bash01]# echo ${NAME%/*} # 以/分割,从右向左删一段
www/baidu
[root@centos7sp9_node1 bash01]# echo ${NAME%%/*} # 以/分割,从右向左删最大(只保留最左一段)
www
[root@centos7sp9_node1 bash01]# echo ${NAME#*/} # 以/分割,从左向右删一段
baidu/com
[root@centos7sp9_node1 bash01]# echo ${NAME##*/} # 以/分割,只保留最右一段
com
6.3 变量内容的替换
/ 替换
// 贪婪替换(匹配的都替换)
例子:
[root@centos7sp9_node1 bash01]# NAME='hello'
[root@centos7sp9_node1 bash01]# echo ${NAME/he/HE}
HEllo
[root@centos7sp9_node1 bash01]# NAME='abcabdabe'
[root@centos7sp9_node1 bash01]# echo ${NAME/ab/12}
12cabdabe
[root@centos7sp9_node1 bash01]# echo ${NAME//ab/12}
12c12d12e
五、字符文本处理
我们在脚本中很多时候需要处理输出结果,文本,或者从中提取某些关键值,比如我想要在脚本中获取服务器ens33网卡的IP地址:
# 写法很多
[root@centos7sp9_node1 bash01]# ip a|grep ens33|grep inet|awk '{print $2}'|awk -F '/' '{print $1}'
192.168.1.201
接下来我们就来聊这些文本处理工具
1、grep行过滤工具
用于查找文件里符合条件的字符串,是以行为过滤
基本语法:
# grep [选项] '关键字' [文件名]
常用选项:
-A <行数>: 显示匹配的一行,并显示匹配行下面多少行
-B <行数>:显示匹配的一行,并显示匹配行上面多少行
-C <行数>: 显示匹配的一行,并显示匹配行前后多少行
-c: 统计匹配到的行数
-e: 指定字符串做为查找文件内容的样式,使用基本正则匹配
-E: 使用扩展正则匹配
-i: 忽略大小写匹配
-l:只列出匹配的文件名
-L:列出不匹配的文件名
-n: 打印行号
-o: 打印匹配关键字
-r: 逐层遍历目录查找,当指定要查找的是目录而非文件时,必须使用这项参数,等同于"-d recurse"
-w: 只显示完全符合关键字的行
-v: 查找不包含匹配关键字的所有行,反向选择
^key: 以关键字开头
key$: 以关键字结尾
^$: 匹配空行
--color=auto:可以将找到的关键词部分加上颜色的显示
例子:
该小节使用/etc/passwd文件作为文件,但还请提前将该文件拷贝,使用拷贝文件作为演示
[root@centos7sp9_node1 bash01]# cp /etc/passwd ./
1.1 -A -B -C 显示匹配行的上下行
-A <行数>: 显示匹配的一行,并显示匹配行下面多少行
-B <行数>:显示匹配的一行,并显示匹配行上面多少行
-C <行数>: 显示匹配的一行,并显示匹配行前后多少行
[root@centos7sp9_node1 bash01]# grep -A 2 mail passwd
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
[root@centos7sp9_node1 bash01]# grep -B 2 mail passwd
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
[root@centos7sp9_node1 bash01]# grep -C 2 mail passwd
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
1.2 -c: 统计匹配到的行数
[root@centos7sp9_node1 bash01]# grep -c root passwd
2
1.3 -i: 忽略大小写匹配
[root@centos7sp9_node1 bash01]# echo 'ROOT:x:0:0:ROOT:/ROOT:/bin/bash' >> passwd
[root@centos7sp9_node1 bash01]# grep root passwd
root:x:0:0:root:/root:/bin/bash
[root@centos7sp9_node1 bash01]# grep -i root passwd
root:x:0:0:root:/root:/bin/bash
ROOT:x:0:0:ROOT:/ROOT:/bin/bash
1.4 -n: 打印行号
[root@centos7sp9_node1 bash01]# grep -n mail passwd
9:mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
1.5 -w 只显示完全符合关键字的行
[root@centos7sp9_node1 bash01]# grep -w root passwd
root:x:0:0:root:/root:/bin/bash
1.6 -o 打印匹配关键字
[root@centos7sp9_node1 bash01]# grep -o root passwd
root
root
root
1.7 -v: 查找不包含匹配关键字的所有行,反向选择
[root@centos7sp9_node1 bash01]# grep -v '/sbin/nologin' passwd | grep -v '/bin/bash'
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
1.8 -r: 逐层遍历目录查找
[root@centos7sp9_node1 bash01]# grep -r mail /root/bash01/
/root/bash01/passwd:mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
1.9 ^key key$ ^$ 匹配开头 结尾 空行
^key: 以关键字开头
keyKaTeX parse error: Expected group after '^' at position 11: : 以关键字结尾 ^̲: 匹配空行
[root@centos7sp9_node1 bash01]# grep ^root passwd # 以root开头的
root:x:0:0:root:/root:/bin/bash
[root@centos7sp9_node1 bash01]# grep halt$ passwd # 以halt结尾的
halt:x:7:0:halt:/sbin:/sbin/halt
[root@centos7sp9_node1 bash01]# grep -v ^$ passwd # 打印不是空行的
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
.......
2、cut 列截取工具
cut用于简单的列的截取
基本语法:
# cut [选项] [参数] [文件]
常用选项:
-b:以字节为单位进行分割,这些字节位置将忽略多字节字符边界,除非指定了 -n
-c:以字符为单位进行分割
-d:自定义分割符,默认制表符为分割符
-f:与-d一起使用,指定打印哪个区域
-n:取消分割多字节字符。仅和 -b 标志一起使用。如果字符的最后一个字节落在由 -b 标志的List 参数指示的范围之内,该字符将被写出;否则,该字符将被排除
例子:
-b 字节截取与 -c 字符截取的区别
[root@centos7sp9_node1 bash01]# vim 1.txt
123
abc
你好
[root@centos7sp9_node1 bash01]# cut -b1 1.txt
1
a
▒
[root@centos7sp9_node1 bash01]# cut -c1 1.txt
1
a
你
[root@centos7sp9_node1 bash01]# cut -b1 passwd |head -2 # 第一个字节
r
b
[root@centos7sp9_node1 bash01]# cut -b1-3 passwd |head -2 # 1-3字节
roo
bin
[root@centos7sp9_node1 bash01]# cut -b1,3,5 passwd |head -2 # 第1,3,5字节
ro:
bnx
[root@centos7sp9_node1 bash01]# cut -d: -f1 passwd |head -2 # 以:为分割符,打印第一列
root
bin
3、sort 排序工具
Linux sort命令用于将文本文件内容加以排序;sort可针对文本文件的内容,以行为单位来排序,排序依据默认是从首字母依次向后以ASCII码比较,升序输出
基本语法:
# sort [选项] [参数] [文件]
选项:
-b 忽略每行前面开始出的空格字符。
-c 检查文件是否已经按照顺序排序。
-d 排序时,处理英文字母、数字及空格字符外,忽略其他的字符。
-f 排序时,将小写字母视为大写字母。
-i 排序时,除了040至176之间的ASCII字符外,忽略其他的字符。
-m 将几个排序好的文件进行合并。
-M 将前面3个字母依照月份的缩写进行排序。
-n 依照数值的大小排序。
-u 意味着是唯一的(unique),输出的结果是去完重了的。
-o<输出文件> 将排序后的结果存入指定的文件。
-r 以相反的顺序来排序(默认是升序)。
-t<分隔字符> 指定排序时所用的栏位分隔字符。
-k 当-t指定了分隔符后,-k指定以第几列为依据进行排序
例子:
将passwd文件按uid数字升序排列
[root@centos7sp9_node1 bash01]# head -1 passwd # 看下passwd文件每行的组成,可见按 : 分割,第三列是UID
root:x:0:0:root:/root:/bin/bash
### 以UID数字升序排序
[root@centos7sp9_node1 bash01]# sort -n -t: -k3 passwd
root:x:0:0:root:/root:/bin/bash
ROOT:x:0:0:ROOT:/ROOT:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
......
### 以UID数字升序排列并去重
[root@centos7sp9_node1 bash01]# sort -nu -t: -k3 passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
......
### 以UID数字降序排序并去重
[root@centos7sp9_node1 bash01]# sort -nru -t: -k3 passwd
test:x:1000:1000:test:/home/test:/bin/bash
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
......
root:x:0:0:root:/root:/bin/bash
排序并将结果重定向到文件
### 按首字母升序排序,并输出到 1.txt文件中
root@centos7sp9_node1 bash01]# sort -o 1.txt passwd
[root@centos7sp9_node1 bash01]# cat 1.txt
adm:x:3:4:adm:/var/adm:/sbin/nologin
apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
......
随机排序
### 新建一个文件,输入以下内容,作为显示文本
[root@centos7sp9_node1 bash01]# vim 2.txt
1
2
3
4
5
6
### 随机排序
[root@centos7sp9_node1 bash01]# sort -R 2.txt
5
3
1
4
6
2
[root@centos7sp9_node1 bash01]# sort -R 2.txt
4
1
3
5
6
2
[root@centos7sp9_node1 bash01]# sort -R 2.txt
4
1
3
2
5
6
4、uniq检查重复工具
uniq 命令用于检查及删除文本文件中==连续==重复出现的行列,一般与 sort 命令结合使用
基本语法:
# uniq [选项] [参数] [文件]
常用选项:
-i 忽略大小写
-c 在每列旁边显示该行重复出现的次数。
-d 仅显示重复出现的行列。
[输入文件] 指定已排序好的文本文件。如果不指定此项,则从标准读取数据;
[输出文件] 指定输出的文件。如果不指定此选项,则将内容显示到标准输出设备(显示终端)。
例子:
### 先来创建个演示文本
[root@centos7sp9_node1 bash01]# vim 3.txt
HELLO
hello
a
a
c
C
### 默认规则去重
[root@centos7sp9_node1 bash01]# uniq 3.txt
HELLO
hello
a
c
C
### 忽略大小写去重
[root@centos7sp9_node1 bash01]# uniq -i 3.txt
HELLO
a
c
### 只显示重复行
[root@centos7sp9_node1 bash01]# uniq -d 3.txt
a
[root@centos7sp9_node1 bash01]# uniq -di 3.txt # 忽略大小写显示重复行
HELLO
a
c
[root@centos7sp9_node1 bash01]# uniq -ic 3.txt # 忽略大小写 显示每行重复的次数
2 HELLO
2 a
2 c
5、tr 字符转换删除工具
tr 命令用于转换或删除文件中的字符。
基本语法1:
# 命令 | tr '字符串1' '字符串2' # 命令执行结果交给tr处理,字符串1用于查询,字符串2用于转换
基本语法2:
# tr '字符串1' '字符串2' < 文件 # tr处理的内容来自文件
常用选项:
-c, --complement:反选设定字符。也就是符合 SET1 的部份不做处理,不符合的剩余部份才进行转换
-d, --delete:删除指令字符
-s, --squeeze-repeats:缩减连续重复的字符成指定的单个字符
-t, --truncate-set1:削减 SET1 指定范围,使之与 SET2 设定长度相等
[:alnum:] :所有字母字符与数字
[:alpha:] :所有字母字符
[:blank:] :所有水平空格
[:cntrl:] :所有控制字符
[:digit:] :所有数字
[:graph:] :所有可打印的字符(不包含空格符)
[:lower:] :所有小写字母
[:print:] :所有可打印的字符(包含空格符)
[:punct:] :所有标点字符
[:space:] :所有水平与垂直空格符
[:upper:] :所有大写字母
[:xdigit:] :所有 16 进位制的数字
[=CHAR=] :所有符合指定的字符(等号里的 CHAR,代表你可自订的字符)
例子:
tr处理结果是对输出生效,不对原文件生效
### 先来截取passwd文件前四行输出到4.txt,将4.txt作为演示文本
[root@centos7sp9_node1 bash01]# head -3 passwd > 4.txt
### 将root替换为xxxx
[root@centos7sp9_node1 bash01]# tr 'root' 'xxxx' < 4.txt
xxxx:x:0:0:xxxx:/xxxx:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nxlxgin
daemxn:x:2:2:daemxn:/sbin:/sbin/nxlxgin
### 将passwd文本中所有小写字母替换为大写
[root@centos7sp9_node1 bash01]# cat 4.txt | tr a-z A-Z
ROOT:X:0:0:ROOT:/ROOT:/BIN/BASH
BIN:X:1:1:BIN:/BIN:/SBIN/NOLOGIN
DAEMON:X:2:2:DAEMON:/SBIN:/SBIN/NOLOGIN
### 删除4.txt所有:和/标点符号
[root@centos7sp9_node1 bash01]# tr -d ':/' < 4.txt
rootx00rootrootbinbash
binx11binbinsbinnologin
daemonx22daemonsbinsbinnologin
### 删除4.txt所有数字
[root@centos7sp9_node1 bash01]# tr -d '0-9' < 4.txt
root:x:::root:/root:/bin/bash
bin:x:::bin:/bin:/sbin/nologin
daemon:x:::daemon:/sbin:/sbin/nologin
### 匹配小写字母和数字并将重复的压缩为一个
[root@centos7sp9_node1 bash01]# echo "1111111" >> 4.txt
[root@centos7sp9_node1 bash01]# echo "aaaaabbbb" >> 4.txt
[root@centos7sp9_node1 bash01]# tr -s '0-9a-z' < 4.txt
rot:x:0:0:rot:/rot:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
1
ab
### 删除所有小写字母
[root@centos7sp9_node1 bash01]# tr -d '[:lower:]' < 4.txt
::0:0::/://
::1:1::/://
::2:2::/://
### 截取出IP 子网位数
[root@centos7sp9_node1 bash01]# ip a | grep ens33$|tr -d 'a-z' | awk '{print $1}'
192.168.1.201/24
6、tee 双向重定向
tee命令用于读取标准输入的数据,将其内容输出到标准输出设备,同时保存成文件
基本语法:
# 命令 | tee [选项] 文件
常用选项:
-a 双向追加重定向(默认是覆盖重定向)
例子:
### 即在标准输出(屏幕)输出,也输出到文件中
[root@centos7sp9_node1 bash01]# cat 4.txt | tee 5.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
1111111
aaaaabbbb
[root@centos7sp9_node1 bash01]# cat 5.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
1111111
aaaaabbbb
### 双向追加重定向
[root@centos7sp9_node1 bash01]# cat 4.txt | tee -a 5.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
1111111
aaaaabbbb
[root@centos7sp9_node1 bash01]# cat 5.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
1111111
aaaaabbbb
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
1111111
aaaaabbbb
7、diff 比较工具
diff以逐行的方式,比较文本文件的异同处。如果指定要比较目录,则diff会比较目录中相同文件名的文件,但不会比较其中子目录。
diff是以如何改变左侧第一个文件才可和右侧第二个文件的形式展示,也就是说,以第二个文件为范本,改第一个文件
基本语法:
# diff [选项] [文件1] [文件2]
常用选项:
-b 不检查空格
-B 不检查空行
-c 显示全部内文,并标出不同之处
-i 不检查大小写
-I 将比较结果交给pr程序分页
-w 忽略所有的空格
--normal 正常格式显示(默认就是)
-c 上下文格式显示
-u 合并格式显示
例子:
7.1 比较文件不同
### 准备两个文件,分别输入以下内容,以备实验
[root@centos7sp9_node1 bash01]# vim 1.txt
111
aaa
bbb
123
[root@centos7sp9_node1 bash01]# vim 2.txt
111
abc
123
bbb
### 默认格式比较显示
[root@centos7sp9_node1 bash01]# diff 1.txt 2.txt
2,3c2 # 左侧文件的第2,3行需要改变(c change改变)才可以和第二个文件的第2行一致
< aaa # < 代表左侧文件的内容(不是要改成什么,而是原内容)
< bbb
--- # --- 分割符
> abc # > 代表右侧文件的内容(不是要改成什么,而是原内容)
4a4 # 左侧文件的第4行需要增加,才可以和右侧文件的第四行相同
> bbb # > 代表右侧文件的内容
### 上下文格式比较显示
[root@centos7sp9_node1 bash01]# diff -c 1.txt 2.txt
*** 1.txt 2021-02-24 15:33:47.269240008 +0800
--- 2.txt 2021-02-24 15:33:28.012238913 +0800 # 前两行主要列出需要比较的文件名和文件的时间错,文件名前面的符号****表示左侧文件1 ----表示右侧文件2
*************** # 分隔符
*** 1,4 **** # ***代表左侧文件1,1,4代表1到4行
111
! aaa # !表示该行需要修改才可以和右侧文件匹配
! bbb # !表示该行需要修改才可以和右侧文件匹配
123
--- 1,4 ---- # ---代表右侧文件,1,4代表1-4行
111
! abc # !表示改行左侧文件需要修改才可以和右侧文件匹配
123
+ bbb # + 表示左侧文件需要增加才可以和右侧文件匹配
### 合并格式比较显示
[root@centos7sp9_node1 bash01]# diff -u 1.txt 2.txt
--- 1.txt 2021-02-24 15:33:47.269240008 +0800
+++ 2.txt 2021-02-24 15:33:28.012238913 +0800 # 前两行主要列出需要比较的文件名和文件的时间错,文件名前面的符号---表示左侧文件1 +++表示右侧文件2
@@ -1,4 +1,4 @@ # -左侧1到4行,+右侧1到4行
111
-aaa # 左侧删掉此行
-bbb # 左侧删掉此行
+abc # 左侧添加此行
123
+bbb # 左侧添加此行
7.3 比较目录不同
[root@centos7sp9_node1 bash01]# mkdir dir1 dir2
[root@centos7sp9_node1 bash01]# touch dir1/{1..3}.txt
[root@centos7sp9_node1 bash01]# touch dir2/{1..5}.sh
[root@centos7sp9_node1 bash01]# echo 123 > dir1/1.txt
[root@centos7sp9_node1 bash01]# echo abc > dir1/1.sh
[root@centos7sp9_node1 bash01]# diff dir1 dir2
diff dir1/1.sh dir2/1.sh
1d0
< abc
Only in dir1: 1.txt
Only in dir2: 2.sh
Only in dir1: 2.txt
Only in dir2: 3.sh
Only in dir1: 3.txt
Only in dir2: 4.sh
Only in dir2: 5.sh
### 只比较目录中文件的不同,不进一步比较文件内容
[root@centos7sp9_node1 bash01]# diff -q dir1 dir2
Files dir1/1.sh and dir2/1.sh differ
Only in dir1: 1.txt
Only in dir2: 2.sh
Only in dir1: 2.txt
Only in dir2: 3.sh
Only in dir1: 3.txt
Only in dir2: 4.sh
Only in dir2: 5.sh
利用paste命令配合diff快速完成文件一致性修改
[root@centos7sp9_node1 bash01]# diff -u 1.txt 2.txt > 1.patch [root@centos7sp9_node1 bash01]# patch 1.txt 1.patch patching file 1.txt [root@centos7sp9_node1 bash01]# diff 1.txt 2.txt
8、sed 非交互流式文本编辑工具
sed工具,是用来处理文件的流编辑器,sed处理文件是一行一行的处理,功能很强大。
sed工具处理文件,在不指定 -i 选项时,其是不会对源文件修改的,默认情况下,sed处理的内容,会直接输出在屏幕。
命令行执行基本语法:
# sed [选项] '动作参数' 文件对象
调用脚本基本语法:
# sed -f 脚本 文件对象
常用选项:
-e 默认是直接在命令行模式上进行sed动作编辑
-f 以动作中指定的script来处理输入的文本文件,将sed的动作写在一个文件内,用–f filename 执行filename内的sed动作
-r 使用扩展正则
-n 取消默认输出(只打印模式匹配的行)
-i 直接修改源文件
常见动作参数:
'p' 打印输出,和-n选项合用
'i' 在指定行之前插入内容
'a' 在指定行之后插入内容
'd' 删除指定行
'c' 更改指定行所有内容(新行换掉旧行)
's' 替换指定行内容(新内容(字符)换掉旧内容)
替换标志:
g 进行全局替换
p 打印行
w 将行写入文件
x 交换暂存缓冲区与模式空间的内容
y 将字符转换为另一字符(不能对正则表达式使用 y 命令)
'r' 从其他文件中读取内容
'w' 将内容另存到某个位置
'&' 保存查找串(用于在替换串中引用)
'=' 输出行号
'!' 取反
'q' 退出
sed 的正则表达式元字符
元字符 | 功 能 | 示 例 | 示例的匹配对象 |
---|---|---|---|
^ | 行首定位符 | /^love/ | 匹配所有以 love 开头的行 |
$ | 行尾定位符 | /love$/ | 匹配所有以 love 结尾的行 |
. | 匹配除换行外的单 个字符 | /l…e/ | 匹配包含字符 l、后跟两个任意 字符、再跟字母 e 的行 |
* | 匹配零个或多个前 导字符 | /*love/ | 匹配在零个或多个空格紧跟着 模式 love 的行 |
[] | 匹配指定字符组内 任一字符 | /[Ll]ove/ | 匹配包含 love 和 Love 的行 |
[^] | 匹配不在指定字符 组内任一字符 | /[^A-KM-Z]ove/ | 匹配包含 ove,但 ove 之前的那 个字符不在 A 至 K 或 M 至 Z 间 的行 |
(…) | 保存已匹配的字符 | ||
& | 保存查找串以便在 替换串中引用 | s/love/**&**/ | 符号&代表查找串。字符串 love 将替换前后各加了两个**的引 用,即 love 变成**love** |
< | 词首定位符 | /<love/ | 匹配包含以 love 开头的单词的 行 |
> | 词尾定位符 | /love>/ | 匹配包含以 love 结尾的单词的 行 |
x{m} | 连续 m 个 x | /o{5}/ | 分别匹配出现连续 5 个字母 o、 至少 5 个连续的 o、或 5~10 个 连续的 o 的行 |
x{m,} | 至少 m 个 x | /o{5,}/ | |
x{m,n} | 至少 m 个 x,但不 超过 n 个 x | /o{5,10}/ |
8.1 对文件实现增删改查
准备一个练习文件
[root@centos7sp9_node1 bash01]# head -4 /etc/passwd > 1.txt
8.1.1 输出文件内容
'p' 打印输出,和-n选项合用
1、 无任何操作输出文件
[root@centos7sp9_node1 bash01]# sed '' 1.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
2、 输出第一行并输出默认输出(模式空间,sed读取文件内容后,会将内容保存到'模式空间'(临时缓存区),默认输出时,会输出出这些内容)
[root@centos7sp9_node1 bash01]# sed '1p' 1.txt
root:x:0:0:root:/root:/bin/bash
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
3、输出第一行,并取消默认输出
[root@centos7sp9_node1 bash01]# sed -n '1p' 1.txt
root:x:0:0:root:/root:/bin/bash
4、输出第1到3行,并取消默认输出
[root@centos7sp9_node1 bash01]# sed -n '1,3p' 1.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
5、输出最后一行,并取消默认输出
[root@centos7sp9_node1 bash01]# sed -n '$p' 1.txt
adm:x:3:4:adm:/var/adm:/sbin/nologin
8.1.2 增加文件内容
'i' 在指定行之前插入内容
'a' 在指定行之后插入内容
1、在第一行之上插入xxxxxxxx
[root@centos7sp9_node1 bash01]# sed '1ixxxxxxx' 1.txt
xxxxxxx
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
2、最最后一行之下插入xxxxxxx
[root@centos7sp9_node1 bash01]# sed '$axxxxxxx' 1.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
xxxxxxx
3、在以root开头的行之上插入备注
[root@centos7sp9_node1 bash01]# sed '/^root/i# 这是超管用户' 1.txt
# 这是超管用户
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
8.1.3 删除文件内容
'd' 删除指定行
1、删除第2行
[root@centos7sp9_node1 bash01]# sed '2d' 1.txt
root:x:0:0:root:/root:/bin/bash
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
2、删除以bash结尾的行
[root@centos7sp9_node1 bash01]# sed '/bash$/d' 1.txt
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
3、删除最后一行
[root@centos7sp9_node1 bash01]# sed '$d' 1.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
8.1.4 修改文件内容
'c' 更改指定行所有内容(新行换掉旧行)
1、将第一行替换为其他内容
[root@centos7sp9_node1 bash01]# sed '1csystemctl529_yu' 1.txt
systemctl529_yu
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
2、将第1到第三行替换为其他内容
[root@centos7sp9_node1 bash01]# sed '1,3csystemctl529_yu' 1.txt
systemctl529_yu # 可见,是将1到3行替换为了一行。
adm:x:3:4:adm:/var/adm:/sbin/nologin
3、将ad开头的行替换为systemctl529_yu
[root@centos7sp9_node1 bash01]# sed '/^ad/csystemctl529_yu' 1.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
systemctl529_yu
's' 替换指定行内容(新内容(字符)换掉旧内容)
替换标志:
g 进行全局替换
p 打印行
w 将行写入文件
x 交换暂存缓冲区与模式空间的内容
y 将字符转换为另一字符(不能对正则表达式使用 y 命令)
1、搜索替换并输出
[root@centos7sp9_node1 bash01]# sed -n 's/root/ROOT/p' 1.txt
ROOT:x:0:0:root:/root:/bin/bash
2、搜索数字并将全局数字都替换为xxxx
[root@centos7sp9_node1 bash01]# sed 's/[0-9]/xxxx/g' 1.txt
root:x:xxxx:xxxx:root:/root:/bin/bash
bin:x:xxxx:xxxx:bin:/bin:/sbin/nologin
daemon:x:xxxx:xxxx:daemon:/sbin:/sbin/nologin
adm:x:xxxx:xxxx:adm:/var/adm:/sbin/nologin
3、搜索为/bin/bash的行,将其替换为/sbin/nologin,并输出
# 分隔符可以自定义,比如这里查询的内容包含/,所以将分隔符替换为#或者@等等
[root@centos7sp9_node1 bash01]# sed -n 's#/bin/bash#/sbin/nologin#p' 1.txt
root:x:0:0:root:/root:/sbin/nologin
4、将第1到第3行注释掉
[root@centos7sp9_node1 bash01]# sed -n '1,3s/^/#/p' 1.txt
#root:x:0:0:root:/root:/bin/bash
#bin:x:1:1:bin:/bin:/sbin/nologin
#daemon:x:2:2:daemon:/sbin:/sbin/nologin
8.1.5 一些其他的操作
'r' 从其他文件中读取内容
'w' 将内容另存到某个位置
'&' 保存查找串(用于在替换串中引用)
'=' 输出行号
'!' 取反
'q' 退出
1、'r' 从其他文件中读取内容
[root@centos7sp9_node1 bash01]# echo '123' > 2.txt
[root@centos7sp9_node1 bash01]# echo '223' >> 2.txt
[root@centos7sp9_node1 bash01]# echo '323' >> 2.txt
# 在第一行下读入/root/bash01/2.txt中的文件
[root@centos7sp9_node1 bash01]# sed '1r /root/bash01/2.txt' 1.txt
root:x:0:0:root:/root:/bin/bash
123
223
323
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
2、'w' 将内容另存到某个位置
# 将匹配的行另存为到2.txt
[root@centos7sp9_node1 bash01]# sed -n '/^root/w /root/bash01/2.txt' 1.txt
[root@centos7sp9_node1 bash01]# cat 2.txt
root:x:0:0:root:/root:/bin/bash # 可见,该重定向是覆盖的
3、'&' 保存查找串(用于在替换串中引用)
# 注释掉1到4行中,所有以小写字母的行
[root@centos7sp9_node1 bash01]# sed -n '1,4s/^[a-z]/#&/p' 1.txt
#root:x:0:0:root:/root:/bin/bash
#bin:x:1:1:bin:/bin:/sbin/nologin
#daemon:x:2:2:daemon:/sbin:/sbin/nologin
#adm:x:3:4:adm:/var/adm:/sbin/nologin
4、'=' 输出行号
# 打印出所有以nologin结尾的行的行号
[root@centos7sp9_node1 bash01]# sed '/nologin$/=' 1.txt
root:x:0:0:root:/root:/bin/bash
2
bin:x:1:1:bin:/bin:/sbin/nologin
3
daemon:x:2:2:daemon:/sbin:/sbin/nologin
4
adm:x:3:4:adm:/var/adm:/sbin/nologin
5、'!' 取反
# 输出除了第一行之外其他的行
[root@centos7sp9_node1 bash01]# sed -n '1!p' 1.txt
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
# 将除了以adm开头的行之外所有行的数字替换为xxxxx
[root@centos7sp9_node1 bash01]# sed '/^adm/!s#[0-9]#******#g' 1.txt
root:x:******:******:root:/root:/bin/bash
bin:x:******:******:bin:/bin:/sbin/nologin
daemon:x:******:******:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
6、'q' 退出
[root@centos7sp9_node1 bash01]# sed '/root/q' 1.txt
root:x:0:0:root:/root:/bin/bash
[root@centos7sp9_node1 bash01]# sed '1q' 1.txt
root:x:0:0:root:/root:/bin/bash
[root@centos7sp9_node1 bash01]# sed '$q' 1.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
[root@centos7sp9_node1 bash01]# sed '2q' 1.txt
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
8.1.7 -e 命令行多重操作
1、在最后一行查找数字替换为***,并且在第一行之前插入注释
[root@centos7sp9_node1 bash01]# sed -e '$s/[0-9]/***/g' -e '1i#这是管理员账户' 1.txt
#这是管理员账户
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:***:***:adm:/var/adm:/sbin/nologin
8.1.8 -i 直接修改源文件
# 比如,直接修改selinux的配置
[root@centos7sp9_node1 bash01]# cat /etc/selinux/config | grep SELINUX=
SELINUX=enforcing
# 修改
[root@centos7sp9_node1 bash01]# sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
[root@centos7sp9_node1 bash01]# cat /etc/selinux/config | grep SELINUX=
SELINUX=disabled
8.2 sed与正则
关于正则,不说太详细了,篇幅过长,如有需要可以参考我之前正则表达式的文档
-r 使用扩展正则
1、过滤掉/etc/ssh/sshd_config中所有的空行与注释
[root@centos7sp9_node1 bash01]# sed -r '/^#|^$/d' /etc/ssh/sshd_config
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
SyslogFacility AUTHPRIV
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication yes
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials no
UsePAM yes
X11Forwarding yes
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
Subsystem sftp /usr/libexec/openssh/sftp-server
2、使用sed过滤出ip地址
[root@centos7sp9_node1 bash01]# ip a|sed -nr '/ens33$/s#^.*inet (.*)/24.*$#\1#gp'
192.168.1.201
3、使用sed过滤出ip 子网 网关
[root@centos7sp9_node1 bash01]# ifconfig ens33|sed -nr '/([0-9]{1,3}\.){3}[0-9]{1,3}/s@[a-z]@ @gp'
192.168.1.201 255.255.255.0 192.168.1.255
8.3 脚本格式使用sed
sed使用脚本处理文件,有几点注意事项:
每行末尾不能出现空格,制表符,或者其他无用的文本等内容
如果一行使用多个命令,要使用 ; 号分隔
不可使用引号包括命令
例子:
[root@centos7sp9_node1 bash01]# vim sed1.sh
#!/bin/sed -f
$d
/^root/s@[0-9]@***@g
1i#这是一个注释
3a#这也是一个注释
p
[root@centos7sp9_node1 bash01]# sed -nf /root/bash01/sed1.sh 1.txt
#这是一个注释
root:x:***:***:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
#这也是一个注释
9、awk 文本处理语言(文本分析工具)
AWK是一种处理文本文件的语言,其具备自身的编程逻辑,因为AWK与Sed的功能过于丰富,有一些平时可能并不会经常使用到的,这里就不做过多的介绍,推荐在学习了sed及awk基础后,百度一些相关的文章,阅读一下留些印象,用时备查。
awk官方手册:http://www.gnu.org/software/gawk/manual/gawk.html
我们常用的awk是GNU的gawk
[root@centos7sp9_node1 bash01]# which awk
/usr/bin/awk
[root@centos7sp9_node1 bash01]# ll /usr/bin/awk
lrwxrwxrwx. 1 root root 4 Jun 2 2020 /usr/bin/awk -> gawk
awk 对我们运维来讲,常常用于处理分析文件数据,而且以为awk本身是有编程逻辑的,所以awk也支持条件判断支持for while循环
9.1 awk的使用方式
和sed相似的是,awk也具备命令行执行以及脚本执行两种方式
命令行基本语法:
# awk [选项] '条件{动作命令}' 文件对象
调用脚本执行基本语法:
# awk -f 脚本 文件对象
常用选项:
-F 定义字段分割符号,不指定时,默认是空格为分割符
-v 定义变量并赋值(方式1:-v varname=value,方式2:在program中定义)
-f 从脚本文件中读取awk命令
awk脚本的两个关键词:
BEGIN{ 这里面放的是执行前的语句 };{这里面放的是处理每一行时要执行的语句};END {这里面放的是处理完所有的行后要执行的语句 }
多个awk语句,以;分隔:
# awk '{awk语句1;awk语句2}'
awk内部变量:
$0 当前处理行的所有记录
$1 $2 ...$n 每行中以指定分割符分隔开的第N个字段
NF 当前记录的列数
$NF 最后一列
FNR/NR 行号
FS 定义间隔符
OFS 定义输出字段分隔符,默认是空格
RS 输入记录分割符,默认是换行
ORS 输出记录分割符,默认是换行
FILENAME 当前输入的文件名
ARGC 命令行参数的个数
ARGV 数组,保存的是命令行所给定的各参数
awk格式化输出:
print 类似echo,其是定义好输出格式的printf
printf 类似echo -n,更加自由化,一切输出格式都需要自己定义
print printf差别的简单演示:
[root@centos7sp9_node1 bash01]# awk -F: '{print $1 $7}' 1.txt root/bin/bash bin/sbin/nologin daemon/sbin/nologin adm/sbin/nologin [root@centos7sp9_node1 bash01]# awk -F: '{printf $1 "\t" $7 "\n"}' 1.txt root /bin/bash bin /sbin/nologin daemon /sbin/nologin adm /sbin/nologin
9.2 基本的使用演示
9.2.1 截取列输出
比如我们截取df 命令的执行结果,截取出盘标与剩余空间
[root@centos7sp9_node1 bash01]# df|awk '{printf $1 "\t" $4 "\n"}'|grep -Ev "Filesystem|tmpfs"
/dev/mapper/vg00-lv_root 18331956
/dev/sda1 318408
再比如,截取出挂载点对应的磁盘使用率
[root@centos7sp9_node1 bash01]# df | grep -Ev "Filesystem|tmpfs"|awk '{printf $NF "\t" $5 "\n"}'|tr -d "%"
/ 11
/boot 38
截取出系统内1分钟 5分钟 15分钟 的CPU利用率
[root@centos7sp9_node1 bash01]# top -n1|grep "%Cpu(s)"|awk -F, '{print $6 $7 $8}'
0.0 hi 0.0 si 0.0 st
进一步截取,将字符放在数值之前
[root@centos7sp9_node1 bash01]# top -n1|grep "%Cpu(s)"|awk -F, '{print $6 $7 $8}'|awk '{printf $3 " " $2 "\t" $5 " " $4 "\t" $7 " " $6 "\n" }'
hi 0.0 si 0.0 st 0.0
9.2.2 BEGIN END
BEGIN 在awk程序一开始,未读取数据之前执行,该动作,只在程序开始时执行一次
END 在awk程序处理完所有数据将结束时执行,该动作,只在程序结束时执行一次
接下来是演示:
先以print输出作为演示,进一步使用,下文继续写
# 比如,我想要截取 1.txt 文件中,用户名以及该用户对应使用的bash,并且要在该结果前,输出一句简介
# 先只输出这两列内容
[root@centos7sp9_node1 bash01]# awk -F: '{print $1 "\t" $NF}' 1.txt
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
# 利用BEGIN添加简介
[root@centos7sp9_node1 bash01]# awk -F: 'BEGIN{print "用户名" "\t" "BASH"} {print $1 "\t" $NF}' 1.txt
用户名 BASH
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
# 在刚才的基础上,利用END输出一个处理结束的提示
[root@centos7sp9_node1 bash01]# awk -F: 'BEGIN{print "用户名" "\t" "BASH"} {print $1 "\t" $NF} END{print "处理结束"}' 1.txt
用户名 BASH
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
处理结束
9.2.3 定义分隔符
默认情况下,awk是以制表符作为分隔,如果需要自定义分隔符,可以使用 FS 的内置变量来实现
需要注意的是,FS定义分隔符,是一个单独的动作,所以需要一个单独的{},而不能直接同{print}动作在一起,具体见以下演示
# 截取出1.txt文件中的用户名及及使用的bash,并以 :分隔
[root@centos7sp9_node1 bash01]# awk '{FS=":"} {print $1 "\t" $NF}' 1.txt
root:x:0:0:root:/root:/bin/bash # 是否奇怪为什么这里第一行是完整的打印出来了,这是因为awk处理数据时,也是一行一行处理,并且是先读取数据。那么如何解决这个问题,就可以使用前面的BEGIN
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
[root@centos7sp9_node1 bash01]# awk 'BEGIN {FS=":"} {print $1 "\t" $NF}' 1.txt
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
adm /sbin/nologin
例子:
查询UID等于0的信息,并打印出其用户名及bash
[root@centos7sp9_node1 bash01]# awk 'BEGIN {FS=":"} $3=="0" {print $1 "\t" $NF}' 1.txt
root /bin/bash
# 关于条件判断,下一小节即是。
9.3 条件判断
awk的判断非常丰富,这里只简单提两个,一个关系运算的判断,一个字符串判断
9.3.1 关系运算
在1.txt文件中,分析出 gid与uid不相等的内容,并输出该用户名及uid gid信息
关系运算符:
< 小于;
<= 小于等于;
> 大于
>= 大于等于
!= 不等于
== 等于
[root@centos7sp9_node1 bash01]# awk -F: '$3 != $4 {print $1 "\t" $3 "\t" $4}' 1.txt
adm 3 4
9.3.2 字符串判断
A ~ B 判断字符串A中是否包含匹配B表达式的子字符串
A !~ B 判断字符串A中是否不包含匹配B表达式的子字符串
比如,我想要判断一下1.txt文件记录的用户信息中,是使用/bin/bash的用户(绝对值匹配的方式)
[root@centos7sp9_node1 bash01]# awk -F: '$NF == "/bin/bash" {print $1}' 1.txt
root
换一种方式:(包含的方式)
# 如果最后一列包含bash,就输出第一列
[root@centos7sp9_node1 bash01]# awk -F: '$NF ~ /bash/ {print $1}' 1.txt root
awk识别字符串,需要使用 // 包含。
9.2.4 输出的自定义
[root@centos7sp9_node1 bash01]# awk 'BEGIN {FS=":"} $3=="0" {print "用户名: "$1 " \t BASH:" $NF}' 1.txt
用户名: root BASH:/bin/bash
10、合并练习
这一节聊到这,咱们回到本节开始的地方,截取网络信息
### 截取IP 子网掩码 网关
[root@centos7sp9_node1 bash01]# ifconfig ens33|grep -w inet|awk '{print $2,$4,$6}'
192.168.1.201 255.255.255.0 192.168.1.255
[root@centos7sp9_node1 bash01]# ifconfig ens33 | grep -w inet |cut -d' ' -f10,13,16
192.168.1.201 255.255.255.0 192.168.1.255
### 分别截取
# IP
[root@centos7sp9_node1 bash01]# ifconfig ens33 |grep -w inet |cut -d' ' -f10
192.168.1.201
[root@centos7sp9_node1 bash01]# ip a|grep ens33 | grep inet|cut -d' ' -f6|awk -F'/' '{print $1}'
192.168.1.201
# 子网掩码
[root@centos7sp9_node1 bash01]# ifconfig ens33|grep netmask|awk '{print $4}'
255.255.255.0
# 网关
[root@centos7sp9_node1 bash01]# ifconfig ens33|grep broadcast|awk '{print $6}'
192.168.1.255
[root@centos7sp9_node1 bash01]# ip a|grep ens33|grep brd|awk '{print $4}'
192.168.1.255
截取系统内所有用户的,用户名.UID.GID.组.bash 按uid降序排列,以空格分隔并同时输出到屏幕和文件中
[root@centos7sp9_node1 bash01]# cut -d':' -f1,3,4,5,7 /etc/passwd | sort -nr -t: -k2 |tr ':' '\t' | tee 1.txt
test 1000 1000 test /bin/bash
polkitd 999 998 User for polkitd /sbin/nologin
systemd-network 192 192 systemd Network Management /sbin/nologin
nobody 99 99 Nobody /sbin/nologin
postfix 89 89 /sbin/nologin
dbus 81 81 System message bus /sbin/nologin
sshd 74 74 Privilege-separated SSH /sbin/nologin
apache 48 48 Apache /sbin/nologin
ftp 14 50 FTP User /sbin/nologin
games 12 100 games /sbin/nologin
operator 11 0 operator /sbin/nologin
mail 8 12 mail /sbin/nologin
halt 7 0 halt /sbin/halt
shutdown 6 0 shutdown /sbin/shutdown
sync 5 0 sync /bin/sync
lp 4 7 lp /sbin/nologin
adm 3 4 adm /sbin/nologin
daemon 2 2 daemon /sbin/nologin
bin 1 1 bin /sbin/nologin
root 0 0 root /bin/bash
[root@centos7sp9_node1 bash01]# cat 1.txt
test 1000 1000 test /bin/bash
polkitd 999 998 User for polkitd /sbin/nologin
systemd-network 192 192 systemd Network Management /sbin/nologin
nobody 99 99 Nobody /sbin/nologin
postfix 89 89 /sbin/nologin
dbus 81 81 System message bus /sbin/nologin
sshd 74 74 Privilege-separated SSH /sbin/nologin
apache 48 48 Apache /sbin/nologin
ftp 14 50 FTP User /sbin/nologin
games 12 100 games /sbin/nologin
operator 11 0 operator /sbin/nologin
mail 8 12 mail /sbin/nologin
halt 7 0 halt /sbin/halt
shutdown 6 0 shutdown /sbin/shutdown
sync 5 0 sync /bin/sync
lp 4 7 lp /sbin/nologin
adm 3 4 adm /sbin/nologin
daemon 2 2 daemon /sbin/nologin
bin 1 1 bin /sbin/nologin
root 0 0 root /bin/bash
根据IP地址给主机配置主机名及/etc/hosts
#!/bin/bash
# 根据IP最后一段内容自动配置主机名
# 比如主机名基础为 atmcpbd***
# 基础主机名
HOSTNAME=atmcpbd
# 截取哪个网卡
ETH=ens33
# 截取系统网卡IP地址
IP=`ip a|grep "$ETH$"|tr -d '[a-z]'|cut -d'/' -f 1|tr -d '[:space:]'`
IPNAME=`echo $IP|cut -d'.' -f4`
# 配置主机名
hostnamectl set-hostname "$HOSTNAME$IPNAME"
# 配置hosts
echo "$IP $HOSTNAME$IPNAME" >> /etc/hosts
根据IP地址给主机配置主机名及/etc/hosts,关闭防火墙 selinux NetworkManager 并配置yum源
#!/bin/bash
# 本脚本用于实现根据IP地址给主机配置主机名及/etc/hosts,关闭防火墙 selinux NetworkManager 并配置yum源
### 根据IP最后一段内容自动配置主机名,比如主机名基础为 atmcpbd***
# 本脚本适用于红帽系列7版本
# 2021年3月
# v.01
# Yu
# 基础主机名
HOSTNAME=atmcpbd
# 截取哪个网卡
ETH=ens33
# yum源url
YUMURL='https://mirrors.tuna.tsinghua.edu.cn/centos/7.9.2009/os/x86_64/'
# 截取系统网卡IP地址
IP=`ip a|grep "$ETH$"|tr -d '[a-z]'|cut -d'/' -f 1|tr -d '[:space:]'`
IPNAME=`echo $IP|cut -d'.' -f4`
# 配置主机名
hostnamectl set-hostname "$HOSTNAME$IPNAME"
# 配置hosts
echo "$IP $HOSTNAME$IPNAME" >> /etc/hosts
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
# 关闭selinux
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
# 配置yum源
cat > /etc/yum.repos.d/centos$IPNAME.repo << end
[centos]
name=centos
baseurl=$YUMURL
enable=1
gpgcheck=0
end
六、shell算数运算
1、算数运算符
运算符 | 描述 | 例子 |
---|---|---|
+ - * / | 加 减 乘 除 | # let a=1+1 && echo $a 2 |
% | 取余 | # let a=13%3 # echo $a 1 |
= | 赋值 | # i=1 && a=$i # echo $a 1 |
== | 判断两个数字是否相等,相等返回true | # a=1 && b=1 # [ $a == $b ] # echo $? 0 |
!= | 判断两个数字是否不相等,不相等返回true | # a=1 && b=2 [ $a != $b ] # echo $? 0 |
2、shell实现基本的数值运算
2.1 expr 命令
[root@centos7sp9_node1 bash01]# a=1
[root@centos7sp9_node1 bash01]# b=2
[root@centos7sp9_node1 bash01]# ab=$(expr $a + $b)
[root@centos7sp9_node1 bash01]# echo $ab
3
注意:$a + $b,+两侧需要有空格
2.2 let命令
let命令效果和expr差不太多,但是let命令要更为灵活,所以平时我们更经常使用let
[root@centos7sp9_node1 bash01]# a=10
[root@centos7sp9_node1 bash01]# b=20
[root@centos7sp9_node1 bash01]# let ab=$a+$b
[root@centos7sp9_node1 bash01]# echo $ab
30
[root@centos7sp9_node1 bash01]# n=1
[root@centos7sp9_node1 bash01]# let n=n+1 # n的值等于n本身再+1
[root@centos7sp9_node1 bash01]# echo $n
2
变种写法:
[root@centos7sp9_node1 bash01]# n=1
[root@centos7sp9_node1 bash01]# let n+=1 # n的值等于n本身再+1
[root@centos7sp9_node1 bash01]# echo $n
2
减法:
[root@centos7sp9_node1 bash01]# a=2
[root@centos7sp9_node1 bash01]# let n-=1
[root@centos7sp9_node1 bash01]# echo $n
1
i++与++i的不同之处:
i++为先赋值再运算,++i为先运算再赋值
演示如下:
[root@centos7sp9_node1 bash01]# i=1
[root@centos7sp9_node1 bash01]# j=1
[root@centos7sp9_node1 bash01]# let a=i++ # 先赋值再运算,即先a=i 再i++(i+1),此时a=原始i值,也就是1 i=i+1也就是2
[root@centos7sp9_node1 bash01]# let b=++j # 先运算再赋值,即先++j(1+j),再将j值赋值给b,此时j=1+j也就是2,b=运算后的j,也是2
[root@centos7sp9_node1 bash01]# echo $a
1
[root@centos7sp9_node1 bash01]# echo $b
2
[root@centos7sp9_node1 bash01]# echo $i
2
[root@centos7sp9_node1 bash01]# echo $j
2
2.3 $(( )) 运算式
因为shell的变量值,默认全是字符串,所以要运算,就要使用 $(())括起来,shell就会明白这要执行运算等操作
[root@centos7sp9_node1 bash01]# echo $((1+1))
2
[root@centos7sp9_node1 bash01]# echo $((2*2))
4
[root@centos7sp9_node1 bash01]# echo $((10/2))
5
[root@centos7sp9_node1 bash01]# echo $((1+3*4/2))
7
[root@centos7sp9_node1 bash01]# vim count1.sh
#!/bin/bash
a=$1
b=$2
c=$3
sum=$(($a+$b+$c))
echo $sum
[root@centos7sp9_node1 bash01]# sh count1.sh 1 2 3
6
2.4 $[ ] 运算式
因为shell的变量值,默认全是字符串,所以要运算,就要使用 $[]括起来,shell就会明白这要执行运算等操作
[root@centos7sp9_node1 bash01]# echo $[2+1]
3
[root@centos7sp9_node1 bash01]# a=1
[root@centos7sp9_node1 bash01]# b=10
[root@centos7sp9_node1 bash01]# echo $[$b-$a]
9
[root@centos7sp9_node1 bash01]# echo $[20/2]
10
[root@centos7sp9_node1 bash01]# echo $[20*2]
40
[root@centos7sp9_node1 bash01]# echo $[(1+3)*4/2]
8
### 数值传参进脚本运算
[root@centos7sp9_node1 bash01]# vim count2.sh
#!/bin/bash
a=$1
b=$2
sum=$[$a*$b+$a-$b]
echo $sum
[root@centos7sp9_node1 bash01]# sh count2.sh 1 2
1
七、条件判断流程控制
条件判断:
方式1:$ test 条件
方式2: [ 条件 ]
方式3:[[ 条件 ]] # [[ ]]支持正则
还有一部分支持 # (( 条件))
1、条件判断符(关键字)
1.1 整数比较运算符
在[]中使用的比较符 | 在 (( )) 和 [[ ]]] 中使用的比较符 | 描述 | 例子(均以a=1 b=2 c=1为变量) |
---|---|---|---|
-eq | == | equal的缩写,表相等 | # [[ $a == $b ]] # echo $? 1 # [ $a -eq $c ] # echo $? 0 # (( $a == $b )) # echo $? 1 |
-ne | != | not equal的缩写,表不相等 | # [ $a -ne $b ] # echo $? 0 # [[ $a != $c ]] # echo $? 1 # (( $a != $b )) # echo $? 0 |
-gt | > | greater thanl的缩写,表大于 | # [ $a -gt $b ] # echo $? 1 # [[ $a > $b ]] # echo KaTeX parse error: Expected 'EOF', got '#' at position 13: ?<br/>1<br/>#̲ ((a>$b)) # echo $? 1 |
-ge | >= | greater equal的缩写,表大于等于 | # [ $a -ge $c ] # echo $? 0 |
-lt | < | greater equal的缩写,表小于 | # [[ $a < $b ]] # echo $? 0 |
-le | <= | greater equal的缩写,表小于等于 | # [ $a -le $c ] # echo KaTeX parse error: Expected 'EOF', got '#' at position 13: ?<br/>0<br/>#̲ ((a<=$c)) # echo $? 0 |
如果英语不好,可以这样记:
e 是等,g是大,l是小 ,t是较于
1.2 逻辑运算符
在[ ]中使用的比较符 | 在 (( )) 和 [[ ]]] 中使用的比较符 | 描述 | 例子 |
---|---|---|---|
-a | && | 逻辑与运算,两个表达式都为true,才返回true | 1等1并且1不等于0,两个判断都满足返回true # [ 1 -eq 1 -a 1 -ne 0 ] # echo $? 0 # ((1==1 && 1!=0)) # echo $? 0 |
-o | || | 逻辑或运算,有一个表达式都为true,则返回true | 1等于1或1不等1,满足一个判断即返回true # [ 1 -eq 1 -o 1 -ne 1 ] ]# echo $? 0 # [ 1 -eq 2 -o 1 -ne 1 ] # echo $? 1 |
假设在一个表达式中,同事书写 && 与 || 用于完成一个判断,比如判断 1是否等于1,当等于1时,则&&打印“相等的”,若失败,则输出“不等的”。那我们在书写该表达式时,是先写 && 还是||
# 先来观察 先写逻辑或再写逻辑与的情况 1、当最先执行的内容结果为true时 [root@centos7sp9_node1 ~]# (( 1 == 1 )) || echo "不等于" && echo "相等的" 相等的 # 根据结果来看,似乎没什么问题。 2、当最先执行的内容结果为false时 [root@centos7sp9_node1 ~]# (( 1 != 1 )) || echo "不等于" && echo "相等的" 不等于 相等的 # 这时候有没有发现一个“意外”出现了,为什么结果明明为假,但是 && 也执行了,这是因为最先执行的数字是否相等的判断结果为假,所以||执行了,但||执行成功后,此时||后表达式的结果为真,故&&也被执行,所以,切记不要这样写。
另外还有 ; 号,其用于分割命令或者表达式,并没有更多的特殊含义。也就是假设两段表达式在同一行书写,那么可以用 ; 号间隔开
1.3 字符串运算符
字符串运算符 | 描述 | 例子 |
---|---|---|
= | 检测两个字符串是否相等,相等则返回true | # [ abc = abc ] # echo $? 0 # [ abc = ac ] # echo $? 1 |
!= | 检测两个字符串是否相等,不相等则返回true | # [ abc != sadas ] # echo $? 0 |
-z | 检测字符串长度是否为0,为0则返回true | # a= # [ -z $a ] # echo $? 0 |
-n | 检测字符串长度是否为0,不为0则返回true | # a=asdfaf # [ -n $a ] # echo $? 0 |
str | 检测字符串是否为null,不为null则返回true | # a=sdsa # [ $a ] # echo $? 0 |
1.4 文件测试运算符
文件测试运算符 | 描述 | 例子 |
---|---|---|
-b | 检测文件是否是块设备文件,如果是,则返回true | # [ -b /dev/sda1 ] # echo $? 0 |
-c | 检测文件是否是字符设备文件,如果是,则返回true | # [ -c /dev/tty ] # echo $? 0 |
-d | 检测文件是否是目录文件,如果是,则返回true | # [ -d /dev ] # echo $? 0 |
-f | 检测文件是否是普通文件(既不是目录也不是设备文件),如果是,则返回true | # [ -f /etc/passwd ] # echo $? 0 |
-g | 检测文件是否设置了SGID位,如果是,则返回true | [ -g $file ] |
-k | 检测文件是否设置了粘着位(stucky Bit),如果是,则返回true | [ -k $file ] |
-p | 检测文件是否具名管道,如果是,则返回true | [ -p $file ] |
-u | 检测文件是否设置了SUID位,如果是,则返回true | [ -u $file ] |
-r | 检测文件是否可读,如果是,则返回true | # [ -r /etc/passwd ] # echo $? 0 |
-w | 检测文件是否可写,如果是,则返回true | [ -w $file ] |
-x | 检测文件是否可执行,如果是,则返回true | [ -x $file ] |
-s | 检测文件是否为不为空(文件大小是否不为0),如果不为0,则返回true | [ -s $file ] |
-e | 检测文件(包括目录)是否存在,如果存在,则返回true | [ -e $file ] |
-a | 检测文件(包括目录)是否存在,如果存在,则返回true | [ -a $file ] |
1.5 文件新旧测试符
以文件修改时间判断
测试符 | 描述 | 例子(均以先创建1.txt,后创建2.txt为演示) |
---|---|---|
-nt | 前者比后者新 | # [ 1.txt -nt 2.txt ] # echo $? 1 |
-ot | 前者比后者旧 | # [ 1.txt -ot 2.txt ] # echo $? 0 |
-ef | 前者与后者是否是同一个文件,或者判断硬连接,是否指向同一个inode | # ln 1.txt 3.txt # [ 1.txt -ef 3.txt ] # echo $? 0 |
2、if 流程控制
2.1 if条件判断
2.1.1 单分支if条件语句基本格式
if条件语句注意点:
if开始fi结尾
[ 条件判断表达式 ] [ … ] 也就是test判断,请不要吝啬空格
then后书写在条件判断成立后要执行的内容,可以直接在判断后;then这样写,也可以另起一行。
基本语法:
if [ 条件判断表达式 ];then
要执行的内容...
fi
或者:
if test 条件;then
要执行的内容...
fi
或者:
[ 条件判断表达式 ] && 要执行的内容
或者:
if (( 条件判断表达式 ));then
要执行的内容...
fi
2.1.2 单分支if条件语句基本使用演示
# 演示判断内存剩余,当剩余不足20%时,输出告警
#!/bin/bash
# 本脚本用于判断内存使用率是否超限
# 2021.3 Yu
Mem=`free | grep Mem|awk '{print $2}'`
Available=`free | grep Mem|awk '{print $NF}'`
Size=20
#Per=`awk 'BEGIN{printf "%.1f%%\n",('$Available'/'$Mem')*100}'`
Per=`printf $(($Available*100/$Mem))`
#echo $Per
if (( $Per <= $Size ));then
echo "可用内存已经不足20%,当前可用为百分之$Per,请留意!"
fi
# 内存压测,以观察脚本在内存不足时,是否会输出提示
[root@centos7sp9_node1 ~]# sh 2.sh
可用内存已经不足20%,当前可用为百分之17,请留意!
2.1.3 双分支if条件语句
基本语法:
if [ 条件判断表达式 ];then
条件成立时要执行的内容...
else
条件不成立时要执行的内容...
fi
# 其他更多写法格式,与单分支if一致
2.1.4 双分支if条件语句基本使用演示
#!/bin/bash
# 本脚本用于备份/var/log/message日志到/backup目录下
# 2021.3 Yu
Date=`date +%y%m%d`
Filename="message$Date.tar.gz"
if [ -d /backup ];then
cd /var/log
tar czf $Filename messages
mv $Filename /backup
else
mkdir /backup
cd /var/log
tar czf $Filename messages
mv $Filename /backup
fi
2.1.5 多分支if条件语句
基本语法:
if [ 条件判断表达式1 ];then
条件1成立时要执行的内容...
elif [ 条件判断表达式2 ];then
条件2成立时要执行的内容...
else
条件均不成立时要执行的内容...
fi
# 其他更多写法格式,与单分支if一致
2.1.6 多分支if条件语句基本使用演示
#!/bin/bash
# 本脚本用于判断用户输入内容是否存在,是否是文件或者目录
# 2021.3 Yu
# 接收用户输入
read -p "请输入要判断的内容:" Name
echo "$Name"
# 判断输入是否为空
if [ -z "$Name" ];then
echo "您似乎没有输入内容,请重新运行输入。"
exit 1 # 退出程序,并将返回值1赋予$?
elif [ ! -e "$Name" ];then
echo "您输入的内容系统内不存在。"
exit 2 # 退出程序,并将返回值2赋予$?
elif [ -f "$Name" ];then
echo "$Name 是一个系统内存在的普通文件"
elif [ -d "$Name" ];then
echo "$Name 是一个系统内存在的目录文件"
else
echo "$Name 系统内存在,但他是非普通文件及非目录文件的其他文件"
fi
[root@centos7sp9_node1 bash01]# sh 4.sh
请输入要判断的内容:/tmp/4.txt
/tmp/4.txt
您输入的内容系统内不存在。
[root@centos7sp9_node1 bash01]# sh 4.sh
请输入要判断的内容:1.sh
1.sh
1.sh 是一个系统内存在的普通文件
2.1.7 多层级嵌套if条件语句
基本语法:
if [ 条件判断表达式1 ];then
条件1成立时要执行的内容...
if [ 条件判断表达式2 ];then
条件2成立时要执行的内容...
fi
elif [ 条件判断表达式3 ];then
条件3成立时要执行的内容...
else
条件均不成立时要执行的内容...
fi
2.1.8 多层级嵌套if条件语句基本使用演示
# 判断设备 CPU空闲率是否高于95%并且内存空闲高于50%,如果是,输出“系统资源非常空闲”,如果cpu空闲率为50-95%并且内存空闲低于50%,则输出“系统目前工作繁忙”,其他情况输出“系统资源不足”
#!/bin/bash
#2021.3 Yu
# 截取CPU当前空闲率
CPU_ID=`vmstat | tail -1|awk '{print $15}'`
# 截取内存当前空闲率
MEM_all=`free | grep Mem|awk '{print $2}'`
MEM_surplus=`free | grep Mem|awk '{print $NF}'`
MEM_ava=`awk 'BEGIN {printf "%.2f\n",'$MEM_surplus'/'$MEM_all'}'|awk -F. '{print $NF}'`
#echo $MEM_ava
#echo $CPU_ID
# 判断CPU空闲率是否低于95%
if (( $CPU_ID >= 95 ));then
# echo "cpu空闲率很高"
if (( $MEM_ava >= 50 ));then
echo "当前系统cpu及内存资源非常空闲"
fi
elif (( $CPU_ID < 95 && $CPU_ID > 50 ));then
# echo "cpu相对繁忙"
if (( $MEM_ava > 30 && $MEM_ava < 50 ));then
echo "当前系统CPU及内存工作繁忙"
fi
else
echo "当前系统资源已经不充足"
fi
3、多分支 case 条件语句
case语句也是一种多分支条件语句,但是case只能判断一种条件,if可以判断多个条件,这是他们的一个不同点。
基本语法:
case $变量名 in
"值1")
如果变量值等于值1,则执行程序1...
;;
"值2")
如果变量值等于值2,则执行程序2...
;;
*)
如果以上都不满足,则执行*对应的程序
;;
esac
3.1 多分支case语句的基本使用演示
#!/bin/bash
# 本脚本用于接收用户输入,完成对应的服务操作
#2021.3 Yu
echo "********************************"
echo "本脚本用于操作httpd服务的启停"
echo "输入 1 或者 start 表示开启httpd"
echo "输入 2 或者 stop 表示停止httpd"
echo "输入 3 或者 enable 添加开机启动"
echo "输入 4 或者 disable 关闭开机启动"
echo "输入 5 或者 status 显示服务状态"
echo "********************************"
read -p "请按照提示输入操作:" KEY
if [ -z $KEY ];then
echo "您似乎没有输入内容,请检查您的输入"
else
case $KEY in
1|start)
systemctl start httpd
;;
2|stop)
systemctl stop httpd
;;
3|enable)
systemctl enable httpd
;;
4|disable)
systemctl disable httpd
;;
5|status)
systemctl status httpd
;;
*)
echo "您的输入为 $KEY ,输入有误,请重试"
;;
esac
fi
八、循环语句
循环,顾名思义,即是按照一定一定条件,重复的执行一些操作。
1、for循环
for循环是固定次数的循环,用于将一些操作执行指定的次数。
1.1 for 循环基本语法
语法1:
for $变量 in 值范围
do
要执行的程序...
done
# 这种语法的循环,循环次数取决于 in 后的值得范围/个数,有多少值,就循环多少次,比如后面有3个值 1 2 3,那就循环3次,如果是{1..10}这种范围,那就是1到10,循环10次
语法2:(类C风格)
for (( 初始值;循环控制条件;变量变化 ))
do
要执行的程序...
done
# 这种语法的注意事项:
# 1.初始值:需要给变量赋予初始值,如 i=1
# 2.循环控制条件:用于指定变量循环的次数,如i<=50,那在i的值不大于50前,循环都会执行,相当于一个范围
# 3.变量变化,每次循环后,变量的值该如何改变,比如i=i+1,即每次循环后,i的值都+1,当i的值增加到循环控制条件限制时,循环停止。
1.2 for 循环基本使用演示
例1:
打印user1-10的用户名出来
#!/bin/bash
# 2021.3 Yu
for i in {1..10}
do
echo "user$i"
done
# 运行结果如下:
[root@centos7sp9_node1 bash02]# sh for1.sh
user1
user2
user3
user4
user5
user6
user7
user8
user9
user10
例2:
每隔3秒统计当前系统进程数,统计5次
#!/bin/bash
#2021.3 Yu
for (( i=1;i<=5;i++ ))
do
ps -ef | wc -l
sleep 3
done
# 运行结果如下:
[root@centos7sp9_node1 bash02]# sh for2.sh
113
113
113
113
113
例3:
使用for循环读取文件内容,并执行对应数量的操作(文件内有多少数量的内容,就循环多少次)
#!/bin/bash
# 2021.3 Yu
ls /var/log/message* > for3.txt
Date=`date +%F`
mkdir /tmp/$Date-message
for i in $( cat for3.txt )
do
X=`echo $i | awk -F/ '{print $NF'}`
cp -a $i /tmp/$Date-message/"$X-$Date.bak"
done
# 执行结果如下:
[root@centos7sp9_node1 bash02]# sh for3.sh
[root@centos7sp9_node1 bash02]# ls /tmp/2021-03-24-message/
messages-20210304-2021-03-24.bak messages-20210318-2021-03-24.bak messages-2021-03-24.bak
messages-20210311-2021-03-24.bak messages-20210321-2021-03-24.bak
例4:
计算1到100中奇数相加的和
#!/bin/bash
# 2021.3 Yu
sum=0
for i in {1..100..2}
do
let sum=$sum+$i
done
echo "1-100奇数和为 $sum"
# 运行结果如下:
[root@centos7sp9_node1 bash02]# sh for4.sh
1-100奇数和为 2500
1.3 循环控制关键字
continue:继续;表示 do done之间continue关键字以下的代码不执行,直接开始下一次循环
break:打断;立刻停止本次循环,执行循环后的代码
exit:退出;立刻跳出程序
演示示例:
1、continue演示
#!/bin/bash
# 2021.3 Yu
for i in {1..5}
do
echo "现在是$i" && continue
echo "观察这句话是否输出"
done
# 执行结果如下:
[root@centos7sp9_node1 bash02]# sh for5.sh
现在是1
现在是2 # 可见,每次都是执行到 continue后,直接开始了下一次循环
现在是3
现在是4
现在是5
2、break演示
#!/bin/bash
# 2021.3 Yu
for i in {1..5}
do
echo "现在是$i" && break
echo "观察这句话是否输出"
done
echo "这是循环外的内容"
# 执行结果如下:
[root@centos7sp9_node1 bash02]# sh for5.sh
现在是1
这是循环外的内容 # 可见遇到break关键字,直接跳出循环,执行了循环外的内容
3、exit演示
#!/bin/bash
# 2021.3 Yu
for i in {1..5}
do
echo "现在是$i" && exit
echo "观察这句话是否输出"
done
echo "这是循环外的内容"
# 执行结果如下:
[root@centos7sp9_node1 bash02]# sh for5.sh
现在是1 # 可见执行了一次遇到 exit,立即停止了程序
1.4 经典循环问题:批量创建用户
#!/bin/bash
# 本脚本用于批量化创建指定数量的用户
# 2021.3 Yu
for i in {1..5}
do
id user$i
if (( $? != 0 ));then
useradd user$i
echo "123456" | passwd --stdin user$i
else
echo "user$i 已存在,跳过该用户"
done
# 执行结果如下:
[root@centos7sp9_node1 bash02]# sh for6.sh
Changing password for user user1.
passwd: all authentication tokens updated successfully.
Changing password for user user2.
passwd: all authentication tokens updated successfully.
Changing password for user user3.
passwd: all authentication tokens updated successfully.
Changing password for user user4.
passwd: all authentication tokens updated successfully.
Changing password for user user5.
passwd: all authentication tokens updated successfully.
[root@centos7sp9_node1 bash02]# sh for6.sh
user1 已存在,跳过该用户
user2 已存在,跳过该用户
user3 已存在,跳过该用户
user4 已存在,跳过该用户
user5 已存在,跳过该用户
2、while循环
while循环,只要条件成立(为真),就会一直循环,条件不成立(为假),就会退出循环
2.1 while 循环基本语法
基本语法:
while [ 条件判断表达式 ]
do
要执行的程序...
done
2.2 while 循环基本使用演示
计算1到100数字相加之和
#!/bin/bash
# 2021.3 Yu
i=1
sum=0
while (( $i <= 100 ))
do
let sum=$sum+$i
let i++ #每次循环,i的值+1,这样当i达到<=100时,循环就会停止
done
echo $sum
# 运行结果如下:
[root@centos7sp9_node1 bash02]# sh while1.sh
5050
计算1到100数字中奇数数字相加之和
#!/bin/bash
# 2021.3 Yu
# FOR循环实现
sum=0
for (( i=1;i<=100;i+=2 ))
do
let sum=$sum+$i
done
echo "这是for循环的结果: $sum"
#################################
#while循环实现
SUM=0
I=1
while (( $I <= 100 ))
do
let SUM=$SUM+$I
let I+=2
done
echo "这是while循环的结果:$SUM"
# 运行结果如下:
[root@centos7sp9_node1 bash02]# sh while2.sh
这是for循环的结果: 2500
这是while循环的结果:2500
写一个死循环,每隔30s ping一次192.168.1.2,当ping失败,就打印告警,并保存记录,ping成功则不记录
#!/bin/bash
# 2021.3 Yu
while true
do
Date=`date +%F%T`
ping -c 1 192.168.1.2 &> /dev/null
if (( $? != 0 ));then
echo "$Date时间与 192.168.1.2 IP 地址失去联系" | tee -a while3.txt
fi
sleep 30
done
# 运行结果演示
[root@centos7sp9_node1 bash02]# sh while3.sh
2021-03-2414:17:54时间与 192.168.1.2 IP 地址失去联系
[root@centos7sp9_node1 bash02]# cat while3.txt
2021-03-2414:17:54时间与 192.168.1.2 IP 地址失去联系
读取文件内容循环
# 准备个文件
[root@centos7sp9_node1 bash02]# vim while5.txt
a b
1
2
3
# 脚本文件
#!/bin/bash
# 2021.3 Yu
echo "第一种方法"
while read i
do
echo $i
done < while5.txt
echo "第二种方法"
cat ./while5.txt | while read i
do
echo $i
done
# 执行结果如下:
[root@centos7sp9_node1 bash02]# sh while5.sh
第一种方法
a b
1
2
3
第二种方法
a b
1
2
3
注意,read i ,read默认是以换行符为分隔,与for i in
cat ./while5.txt
不同,in是以任意空白分隔符为分割
# 如果需要从文本中读取多个值,read 后以空格分隔即可
# 准备个文件
[root@centos7sp9_node1 bash02]# vim while6.txt
xiaoming 15 175
xiaobai 23 181
# 脚本文件
#!/bin/bash
# 2021.3 Yu
while read name age height
do
echo $name $age $height
done < while6.txt
# 执行结果
[root@centos7sp9_node1 bash02]# sh while6.sh
xiaoming 15 175
xiaobai 23 181
2.3 经典循环问题,时间同步
#!/bin/bash
# 本脚本用于周期同步设备时间,适用于具备ntpdate服务的centos7 Asianux7 redhat7等
# 2021.3 Yu
# NTP服务器地址
NTPS=time1.aliyun.com
# 计数初始值
i=0
# 死循环
while true
do
# 同步时间的命令
ntpdate $NTPS &> /dev/null
# 判断时间同步是否成功
if (( $? == 0 ));then
# 当时间同步成功,执行计数操作
let i++
# 当成功超过100次,打印一次结果
if (( $i == 100 ));then
echo "与 $NTPS 的时间同步已经成功 $i 次"
fi
else
# 失败则打印失败,并且将i值设为0,以便重新计数
echo "与 $NTPS 的时间同步失败了,当前是第$i次同步时失败" && i=0
fi
# 每600秒一次
sleep 600
done
# 运行结果如下:
[root@centos7sp9_node1 bash02]# sh while4.sh &
与 time1.aliyun.com 的时间同步已经成功 100 次
3、until 循环
条件为假时进入循环,条件为真就终止循环,与while相反
3.1 until 循环基本语法
基本语法:
until [ 条件判断表达式 ]
do
要执行的程序...
done
3.2 until 循环基本使用演示
因为until使用实在是不太多,所以就简单演示一个。
演示下1-100偶数相加和
#!/bin/bash
# 2021.3 Yu
sum=0
i=2
until (( $i > 100 )) # 重点就是这个判断表达式,为真退出
do
let sum=$sum+$i
let i+=2
done
echo $sum
# 运行结果如下:
[root@centos7sp9_node1 bash02]# sh until1.sh
2550
4、嵌套循环
一个循环体内又包含其他的循环,即为嵌套循环
嵌套循环的顺序也就是先执行外部的循环,然后执行内部的循环,等到内部所有循环执行完成,再开始执行下一次循环
for while until 可以相互嵌套
4.1 嵌套循环基本语法
for i in 值范围
do
for i in 值范围
do
要执行的内容...
done
done
4.2 嵌套循环基本使用演示
4.2.1 for嵌套打印99乘法表
#!/bin/bash
# 2021.3 Yu
for x in {1..9}
do
for y in {1..9}
do
echo -ne "$x * $y = $[ $x * $y ]\t"
done
echo
done
运行结果如下:
4.2.2 while循环打印99乘法表
#!/bin/bash
x=1
while (( $x <= 9 ))
do
y=1
while (( $y <= 9 ))
do
echo -ne "$x * $y = $[ $x * $y ]\t"
let y++
done
echo
let x++
done
运行结果如下:
九、函数
将一组命令等的集合形成一段可用代码,是为shell函数,然后后续想要使用这个代码集合,通过函数名即可调用了
1、函数基本语法
# 定义函数:
函数名(){
命令操作的集合...
}
函数中 return 关键字,用于结束一个函数,return返回值默认是返回函数中最后一个命令的状态码,当然也可以手动的指定(0-256)范围内的数值
当函数中没有写return关键字时,默认函数返回最后一个指令的退出状态值
2、函数基本使用演示
#!/bin/bash
# 2021.3 Yu
# 定义一个函数
Function(){
echo "这是一个函数"
echo "这是一个函数"
}
# 调用这个函数
Function
# 运行结果如下:
[root@centos7sp9_node1 bash02]# sh function1.sh
这是一个函数
这是一个函数
打印一个菜单,并配合case分支执行
#!/bin/bash
# 2021.3 Yu
# 定义菜单函数
Menu(){
cat <<-EOF
1.启动mysql
2.启动nginx
3.启动php-fpm
4.退出
EOF
}
# 循环执行选择
while true
do
Menu
read -p "请输入你要执行的操作对应的数字:" KEY
case $KEY in
1)
systemctl start mysqld
;;
2)
systemctl start nginx
;;
3)
systemctl start php-fpm
;;
4)
exit
;;
*)
echo "输入有误,请从新输入"
;;
esac
done
# 执行结果如下:
[root@centos7sp9_node1 bash02]# sh function2.sh
1.启动mysql
2.启动nginx
3.启动php-fpm
4.退出
请输入你要执行的操作对应的数字:4