shell脚本的系统性学习笔记
文章目录
- shell脚本的系统性学习笔记
- 一、入门基础
- 二、脚本基础
- 三、循环体
- 1.for循环
- 基本语法
- 在编写for循环语句脚本时,变量name可以不定义取值范围,也就是没有in和word,语法格式如下。
- 变量name没有定义取值的范围,这个循环语句到底会循环多少次呢?如果变量name没有定义取值范围,则默认取值为$@,也就是所有位置变量的值。这样有几个位置变量,该for循环语句就循环几次。
- 有时候脚本的循环语句需要执行成百上千次,如果每一个值都手动输入,谁也无法接受。Shell支持使用seq或{}自动生成数字序列,并且使用{}还可以自动生成字母序列。for循环语句可以对{}或seq扩展后的数据列表进行循环。
- 还可以使用seq命令生成数字序列,并且可以调用其他变量,但该命令不支持生成字母序列。默认输出序列的分隔符是\n换行符,也可以使用-s选项自定义分隔符。
- c语言风格的for循环
- 2.嵌套循环
- 3.while循环
- 4.IFS
- 5.通过read命令读取文件内容
- 6.untile和select循环
- 7.中断与退出控制
- 四、数组、subshell、函数
- 五、一大波脚本技巧
- 六、sed专项总结
- 七、awk语法
一、入门基础
1.1 脚本的书写格式
脚本文件第一行要求使用shebang(#!)符号指定一个脚本的解释器,如#! /bin/bash、#! /bin/sh、#!/usr/bin/env python等
<<符号后面的关键词可以是任意字符串,但前面使用什么关键词,结束注释时必须使用相同的关键词。如果从<<ABC开始注释,则结束注释信息时也必须使用ABC(字母区分大小写)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cupkwE7P-1660468289831)(C:\Users\liwei\AppData\Roaming\Typora\typora-user-images\image-20220814152816041.png)]
1.2脚本的各种执行方式
1)脚本文件自身没有可执行权限
如果暂时还没有给脚本文件可执行的权限,那么默认脚本是无法直接执行的,但bash或sh这样的解释器,可以将脚本文件作为参数(读取脚本文件中的内容)来执行脚本文件。
2)脚本文件具有可执行权限
脚本文件一旦有了执行权限,就可以使用绝对路径或相对路径执行了。
3)开启子进程执行的方式 bash 绝对路径、相对路径执行 默认开启
4)不开启子进程的执行方式
使用source或.(点)命令来执行脚本文件。
1.3数据的输入与输出
echo 、printf
1.4输入输出重定向
标准输出的文件描述符为1,标准错误输出的文件描述符为2。而标准输入的文件描述符则为0。
如果希望改变输出信息的方向,可以使用>或>>符号将输出信息重定向到文件中。使用1>或1>>可以将标准输出信息重定向到文件(1可以忽略不写,默认值就是1),也可以使用2>或2>>将错误的输出信息重定向到文件。这里使用>符号将输出信息重定向到文件,如果文件不存在,则系统会自动创建该文件,如果文件已经存在,则系统会将该文件的所有内容覆盖(原有数据会丢失!)。而使用>>符号将输出信息重定向到文件,如果文件不存在,则系统会自动创建该文件,如果文件已经存在,则系统会将输出的信息追加到该文件原有信息的末尾。
Linux系统中有一个特殊的设备/dev/null,这是一个黑洞。无论往该文件中写入多少数据,都会被系统吞噬、丢弃。
1.5变量
当需要读取变量值时,需要在变量名前添加一个美元符号“$”;而当变量名与其他非变量名的字符混在一起时,需要使用{}分隔。
hello = 3 # 错误 等会两边不可以有空格
hello1=3 #正确
echo $hello1 #调用变量提前变量的值
常见的系统预设变量
变量名 | 描述 |
---|---|
UID | 当前账户uid |
USER | 当前账户名 |
HOME | home路径 |
LANG | 当前系统语言 |
PATH | 命令所搜路径 |
PWD | 当前路径 |
RANDOM | 随机数 |
$0 | 当前命令的名称 |
$n | 第n个位置参数 |
$? | 上一个命令运行的状态,0成功 1 失败 |
$# | 参数个数 |
$* | 所有参数 |
$$ | 返回当前进程id |
$! | 最后一个进程id |
二、脚本基础
1.条件判断
在Shell中可以使用多种方式进行条件判断,如
1、[[表达式]]
2、[表达式]或
3、test表达式。
使用条件表达式可以测试文件属性,进行字符或数字的比较。需要注意的是,不管使用哪种方式进行条件判断,系统默认都不会有任何输出结果,可以通过echo $?命令,查看上一条命令的退出状态码,或者使用&&和||操作符结合其他命令进行结果的输出操作。
如果需要在一行代码中输入多条命令,在Shell中可以使用;(分号)、&&(与)、||(或)这三个符号将多个命令分隔。其中;(分号)是按顺序执行命令,分号前后的命令可以没有任何逻辑关系。例如,输入“A命令;B命令”,系统会先执行A命令,不管A命令执行结果如何,都会执行B命令。整个命令的退出码以最后一条命令为准,B命令如果执行成功则退出码为0, B命令如果执行失败则退出码为非0。而使用&&(与)符号分隔多条命令时,仅当前一条命令执行成功后,才会执行&&后面的命令。例如,输入“A命令&&B命令”,系统会先执行A命令,如果A命令执行成功则执行B命令,如果A命令执行失败则不执行B命令。而整行命令的退出码取决于两条命令是否同时执行成功,如果A命令执行成功并且B命令执行也成功,则整行命令的退出码为0,而A命令或B命令中的任何一条命令执行失败,则整行命令的退出码为非0。如果使用||(或)符号分隔多条命令,仅当前一条命令不执行或执行失败后才执行后一条命令。
;分号顺序执行
&& 成功才继续执行
|| 不执行或失败才继续执行
2.字符串的判断比较
test a == a # 判断字符串是否相等 测试代码 等会前后一定要有空格 ; 赋值才没空格
echo $?
[$USER == root]
echo $?
[[$USER == root]] && echo y || echo n
[$USER != root] && echo y || echo n
整数的比较运算符
符号 | 含义 |
---|---|
-eq | 等于 |
-ne | 不等于 |
-gt | 大于 |
-ge | 大于等于 |
-lt | 小于 |
-le | 小于等于 |
文件属性判断操作符
操作符 | 功能描述 |
---|---|
-e file | 判断文件或目录是否存在 |
-f file | 判断存在且为普通文件 |
-d file | 判断存在且未目录 |
-r file | 是否有读权限 |
-w file | 是否有写权限 |
-x file | 是否有执行权限 |
-s file-r file | 存在且非空文件是否有读权限 |
-File1 -ef file2w file | |
File1-nt file2-x file | File1 比file2 新 |
File1 -ot file2-s file | file1 比file2 旧 |
# 判断文件是否存在
[-e file.txt] && echo y || echo n
# 判断目录是否存在
[-e /home] && echo y || echo n
3.单分支if语句
对于简单的条件判断,结合&&和||就可以完成大量的脚本。但是当脚本越写越复杂、功能越写越完善时,简单的&&和||就不足以满足需求了。此时,选择使用if语句结合各种判断条件,功能会更加完善和强大。在Shell脚本中if语句有三种格式,分别是单分支if语句、双分支if语句和多分支if语句。下面是单分支if语句的语法格式。
if 条件语句
then
命令序列
fi
if和then可以写同一行用 分号隔开
if 条件语句;then
命令序列
fi
#!/bin/bash
read -p "请输入用户名:" username
read -s -p "请输入密码" password
if [! -z "$username"];then
useradd "$username"
fi
if [! -z "$password"];then
echo "$password" |passwd --stdin "$username"
fi
#!/bin/bash
read -p "user" username
read -s -p "password" password
if [[ ! -z "$username" && ! -z "$password" ]];then #注意此处 要用双[[]] ,注意空格
useradd "$username"
echo "$password" | passwd --stdin "$username"
fi
4.双分支if语句
与单分支if语句的格式一样,then和if可以写在同一行,也可以分开写在不同行。甚至在else和命令序列1中间添加分号将其写在同一行,但很少有人这样写,这将导致代码的可读性非常差。
if 条件测试 ;then
命令序列1
else
命令序列2
fi
#!/bin/bash
if [ ! -z $1 ];then
echo "参数不为空"
else
echo "参数为空"
fi
5.多分枝if语句
if的双分支语法结构仅可以对事务的正确与错误两种情况做出回应,而现实问题往往更复杂,比如数字的大于、小于、等于判断。多分支if语句支持else if(简写elif)子句,可以实现在else中内嵌if的功能。在多分支if语句中elif可以出现多次,实现多次测试判断的效果。首先,来看看多分支if语句的语法格式。
if 条件测试 ;then
命令序列1
elif
命令序列2
elif
命令序列3
…
else
命令序列n
fi
#!/bin/bash
read -p "请输入分数" score
if [[ $score -gt 90 ]];then
echo "优秀"
elif [[ $score -gt 80 ]];then
echo "良好"
elif [[ $score -gt 70 ]];then
echo "中等"
elif [[ $score -ge 60 ]];then
echo "及格"
else
echo "不及格"
fi
6.case语句
case语句语法格式
case word in
模式1 )
命令1;;
模式2)
命令2;;
…
*)
命令n;;
esac
case支持多个条件匹配
case word in
模式1|模式2|模式3)
命令1 ;;
模式4|模式5|模式6)
命令2;;
*)
命令n;;
esac
#!/bin/bash
read -p "输入一个a-f 的字母" key
case $key in
a)
echo "i am a";;
b)
echo "i am b";;
c|d|e|f)
echo "i am in cdef";;
*)
echo "out of range";;
esac
7.实例
#!/bin/bash
if [[ $# -lt 1 ]];then
echo "请输入参数执行脚本时"
exit 1
fi
zifu=$1
case $zifu in
[a-z])
echo "小写字母";;
[A-Z])
echo "大写字母";;
[0-9])
echo "数字";;
*)
echo "OTHERS"
esac
三、循环体
1.for循环
基本语法
for name in []
do
命令序列
done
#!/bin/bash
for i in 1 2 3 4 5
do
echo $i
done
在编写for循环语句脚本时,变量name可以不定义取值范围,也就是没有in和word,语法格式如下。
for name
do
…
done
变量name没有定义取值的范围,这个循环语句到底会循环多少次呢?如果变量name没有定义取值范围,则默认取值为$@,也就是所有位置变量的值。这样有几个位置变量,该for循环语句就循环几次。
#!/bin/bash
for i
do
echo $i
done
执行结果
bash for.sh hell he hi
hell
he
hi
有时候脚本的循环语句需要执行成百上千次,如果每一个值都手动输入,谁也无法接受。Shell支持使用seq或{}自动生成数字序列,并且使用{}还可以自动生成字母序列。for循环语句可以对{}或seq扩展后的数据列表进行循环。
echo {1..3} # 输出1-3
echo {1..10..2} #输出1,3,5,7,9 2的意思是步长
echo {x,y{i,j}{1,2,3},z} #输出 x yi1 yi2 yi3 yj1 yj2 yj3 z
还可以使用seq命令生成数字序列,并且可以调用其他变量,但该命令不支持生成字母序列。默认输出序列的分隔符是\n换行符,也可以使用-s选项自定义分隔符。
seq -s " " 1 5
seq -s " " 2 10 2
c语言风格的for循环
#!/bin/bash
for ((i=1;i<5;i++))
do
echo $i
done
例子
#!/bin/bash
#########################################
#测试1-5000中的闰年
#########################################
for i in {1..50000}
do
if [[ $i%4 -eq 0 && $i%100 -ne 0 || $i%400 -eq 0 ]] ; then
echo "$i : 是闰年"
else
echo "$i : 是平年"
fi
done
#!/bin/bash
#频某个网段的网络连通性
net="192.168.0"
for i in {1..255}
do
ping -c2 -i0.2 -W1 $net.$i &>/dev/null
if [[ $? == 0 ]];then
echo "$net.$i is up"
else
echo "$net.$i is down"
fi
done
#ping 命令详解
-c 发送指定数量的包后停止
-i 发送包间隔秒数
-W 以毫秒为单位设置ping的超时时间
2.嵌套循环
#!/bin/bash
for i in {1..8}
do
for j in {1..8}
do
sum=$i+$j
if [[ $[sum%2] -ne 0 ]];then
echo -ne "\033[41m \033[0m"
else
echo -ne "\033[47m \033[0m"
fi
done
echo
done
#!/bin/bash
# 99乘法口诀打印
for ((i=1;i<=9;i++))
do
for ((j=1;j<=i;j++))
do
echo -n "$i*$j=$[i*j] "
done
echo
done
3.while循环
使用for循环语句可以实现循环次数固定的循环。而使用while循环语句也可以实现循环功能,但while循环的循环次数取决于条件判断的结果。while循环有可能随时结束,也有可能永不结束。
语法
while 条件判断
do
命令序列
done
#!/bin/bash
i=1
sum=0
while [ $i -le 100 ]
do
let sum+=$i
let i++
echo "tmp: $sum"
done
echo $sum
# 注意let的语法 let 命令是 BASH 中用于计算的工具,用于执行一个或多个表达式,变量计算中不需要加上 $ 来表示变量。如果表达式中包含了空格或其他特殊字符,则必须引起来。
4.IFS
5.通过read命令读取文件内容
当定义三个变量并通过键盘输入三个值时,输入的三个值会按照顺序分别被赋值给每个变量。
read key1 key2 key3 #定义变量等待赋值
#结合while循环批量读取数据并通过read命令给变量赋值,基本格式如下
while read line
do
命令
done <文件名
或者
命令|while read line
do
命令
done
6.untile和select循环
untile语法
untile 条件判断
do
命令
done
条件满足时退出循环
select循环
select 变量名 in 值列表
do
命令
done
#!/bin/bash
echo "请选项"
select item in "a" "b" "c" "cc"
do
case $item in
"a")
echo "A";;
"b")
echo "B";;
"c")
echo "C";;
"cc")
echo "CC";;
*)
echo "other";;
esac
done
# 输入选择时选择的是 1,2,3 编号
7.中断与退出控制
continue命令可以结束单次循环,continue命令后面的所有语句不再执行,进而直接跳转到下一次循环。
break后面的所有语句不再执行,并且整个循环提前结束。如果脚本使用了循环的嵌套功能,则break命令后面可以跟数字参数(数字要求大于或等于1),表示对第几层循环执行中断。
中断级别最高的命令exit,该命令会直接结束整个脚本,exit后面也可以跟数字参数,表示脚本的退出状态,如果没有指定数字参数,则脚本的退出状态就是上一个命令的退出状态。
四、数组、subshell、函数
数组
Shell支持一种特殊的变量——数组。数组是一组数据的集合,数组中的每个数据被称为一个数组元素。目前Bash仅支持一维索引数组和关联数组,Bash对数组大小没有限制。
使用*提取数组中的所有元素时会把所有元素视为一个整体,而使用@则将数组所有元素视为若干个体。
arrays=(1 hello 你看)
for i in ${arrays[@]}
do
echo $i
done
for j in ${arrays[*]}
do
echo $j
done
从4.0版本开始Bash为我们提供了一种新的关联数组,使用关联数组,数组的下标可以是任意字符串。关联数组的索引要求具有唯一性,但索引和值可以不一样。定义和调用关联数组的基本语法格式如下。
在这里插入图片描述
函数
与大多数开发语言一样,Shell同样支持函数功能。函数就是给一段代码起一个别名,也就是函数名,定义函数名的规则与定义变量名的规则基本一致,但是函数名允许以数字开头。使用函数可以方便地封装某种特定功能的代码,在调用函数时不需要关心它是如何实现的,只需知道这个函数是做什么的,就可以直接调用它完成某项功能。函数必须先定义,才能被调用。合理地使用函数可以将一个大的工程分割为若干小的功能模块,代码的可读性更好,还可以有效避免代码重复。
方式一:
行数名称(){
代码序列
}
方式二:
function函数名(){
代码
}
方式三:
function{
代码
}
递归函数实例
#!/bin/bash
#斐波那契数列
Fabonaci(){
if [[ $1 -eq 1 || $1 -eq 2 ]];then
echo -n "1 "
else
echo -n "$[$(Fabonaci $[$1-1])+$(Fabonaci $[$1-2])] "
fi
}
for i in {1..10}
do
Fabonaci ${i}
done
echo
五、一大波脚本技巧
八大扩展功能之 花括号
在Shell脚本中可以使用花括号对字符串进行扩展。我们可以在一对花括号中包含一组以分号分隔的字符串或者字符串序列组成一个字符串扩展,注意最终输出的结果以空格分隔。使用该扩展时花括号不可以被引号引用(单引号或双引号),在括号的数量必须是偶数个。
echo {a..z
echo {a..z..2}
echo t{i,o}p # tip top
八大口占功能之 波浪号
波浪号在Shell脚本中默认代表当前用户的家目录,我们也可以在波浪号后面跟一个有效的账户登录名称,可以返回特定账户的家目录。但是,注意账户必须是系统中的有效账户。
~- 表示前一个路径 ~+表示当前路径
Shell八大扩展功能之变量替换
在Shell脚本中我们会频繁地使用 对变量进行扩展替换,变量字符可以放到花括号中,这样可以防止需要扩展的变量字符与其他不需要扩展的字符混淆。如果 对变量进行扩展替换,变量字符可以放到花括号中,这样可以防止需要扩展的变量字符与其他不需要扩展的字符混淆。如果 对变量进行扩展替换,变量字符可以放到花括号中,这样可以防止需要扩展的变量字符与其他不需要扩展的字符混淆。如果后面是位置变量且多于一个数字,必须使用{},如 1 、 1、 1、{11}、${12}。
hi="hello world"
echo $i
echo ${i}
如果变量字符串前面使用感叹号(!),可以实现对变量的间接引用,而不是返回变量本身的值。感叹号必须放在花括号里面,且仅能实现对变量的一层间接引用。
player="DUNCAN"
mvp=$player
echo ${mvp} #直接返回变量的值
echo ${! mvp} #间接引用 返回apayer的值
变量替换操作还可以测试变量是否存在及是否为空,若变量不存在或为空,则可以为变量设置一个默认值。Shell脚本支持多种形式的变量测试与替换功能,变量测试具体语法
十分重要
语法格式 | 描述 |
---|---|
${变量名:-关键字} | 如果变量未定义或者值为空,则返回关键字;否则返回变量值 |
${变量名:=关键字} | 如果变量未定义或者值为空,则将关键字的值赋给变量并返回;否则返回变量的值 |
${变量名:?关键字} | 如果变量未定义或者值为空,则通过标准错误显示包含的关键字信息;否则返回变量的值 |
${变量名:+关键字} | 如果变量未定义或者值为空,则返回空;否则返回关键字 |
Shell八大扩展功能之 命令替换
我们可以通过 (命令)或者 ‘ 命令 ‘ 实现命令替换,推荐使用 (命令)或者`命令`实现命令替换,推荐使用 (命令)或者‘命令‘实现命令替换,推荐使用(命令)这种方式,该方式支持嵌套的命令替换。
Shell八大扩展功能之 算术替换
算术替换扩展的格式为 (()),也可以使用 (()),也可以使用 (()),也可以使用[]的形式,算术扩展支持嵌套。
i=1
echo $((i++))
echo $[i++]
Shell八大扩展功能之进程替换
进程替换的语法格式为:<(命令)或者>(命令)。一旦使用了进程替换功能,系统将会在/dev/fd/目录下创建文件描述符文件,通过该文件描述符将进程的输出结果传递给其他进程。
Shell八大扩展功能之路径替换
除非使用set -f禁用路径替换,否则Bash会在路径和文件名中搜索*、?和[符号,如果找到了这些符号则进行模式匹配的替换,Shell在处理命令时对路径替换后的路径或文件进行处理。如果使用shopt命令时开启了nocaseglob选项,则Bash在进行模式匹配时不区分大小写,默认是区别大小写的。另外,还可以在使用shopt命令时开启extglob选项,可以让Bash支持扩展通配符。shopt命令的-s选项可以开启特定的Shell属性,-u选项可以关闭特定的Shell属性。
六、sed专项总结
sed -i 's?<\\test.ce.version1>?<\\test.ce.version2>?' pom.xml
sed -i 's?<test.ce.version>1.3.M3-RELEASE<\\test.ce.version>?<test.ce.version>1.3.M4-RELEASE<\\test.ce.version>?'
sed的常见命令选项
命令选项 | 描述 |
---|---|
-n | 屏蔽默认输出功能,默认显示到屏幕 |
-r | 支持正则表达式 |
-i | 直接修改源文件 |
-e | 指定需要执行的sed命令 |
-f | 指定需要执行的脚本 |
基本操作命令 | 描述 |
p | 打印当前匹配的数据行 |
l | 打印当前匹配的数据行(显示控制符) |
= | 打印当前读取的数据行 |
a text | 在匹配的数据行后追加文本内容 |
i text | 在匹配的数据行前面插入文本内容 |
d | 删除匹配的数据行整行 |
c text | 将匹配的数据行替换 |
r filename | 从文件读取数据并追加到数据行后面 |
w filename | 将匹配的内容写到指定文件 |
q | 立即退出脚本 |
s | 使用正则表达式,并将匹配的内容替换为指定内容 |
sed的数据定位方法 | 描述 |
number | 定位到n行 |
first~step | 从first行开始步长step |
$ | 匹配最后一行 |
/regexp/ | 使用正则表达式匹配 |
\cregexpc | 使用正则匹配 c为任意字符 |
Addr1 addr2 | 从行号addr1开始 到行号addr2 |
addr +N | 从addr1开始 级后面N行 |
sed 'p' /etc/hosts
127.0.0.1 localhost
127.0.0.1 localhost
# The following lines are desirable for IPv6 capable hosts
# The following lines are desirable for IPv6 capable hosts
::1 localhost ip6-localhost ip6-loopback
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::2 ip6-allrouters
127.0.1.1 localhost.vm localhost
127.0.1.1 localhost.vm localhost
127.0.1.1 hecs-266809 hecs-266809
127.0.1.1 hecs-266809 hecs-266809
当没有指定条件时,默认会匹配所有的数据行,因此/etc/hosts文件有多少行p指令就被执行多少次,sed读取文件的第1行执行p指令将该行内容显示在屏幕上,接着读取文件的第2行继续执行p指令再将该行内容显示在屏幕上。但是,为什么最终每个数据行却打印显示了两次呢?因为哪怕没有p指令,sed也会默认将读取到的所有数据行显示在屏幕上,所以p指令数据行被打印显示了一次,接着sed默认又将读取的数据行再显示了一次,最终每行显示了两次。可以使用-n选项屏蔽sed默认的输出功能。关闭默认的输出功能后,所有的数据行将仅显示一次。
sed -n '2p' /etc/hosts # 只显示文件第二行
sed -n '1,3p' /etc/hosts # 显示第一到第三行
sed -n '1p;3p' /etc/hosts # 显示第一行和第三行
sed -n '3,$p' /etc/hosts # 显示第三行到末尾
sed -n '1~2p' /etc/hosts #显示奇数行 ,既步长为2
sed -n '1,+3p' /etc/hosts #显示第一行开始 级后面的三行数据
sed -n '$p' /etc/hosts # 显示最后一行
sed -n '/root/p' /tmp/password # 显示带有root的行
除了直接使用行号,sed还支持使用正则表达式定位特定的数据行。上面这条命令会读取文件的第1行,使用正则表达式匹配是否包含root,如果包含root则执行p指令,否则不执行任何操作,sed继续读取下一行数据,重复按照条件匹配数据行直到读取文件结束。上面的结果说明/tmp/passwd文件中的第1行和第10行都包含root,其他所有行都没有包含root字符串。
sed -n '/bash$/p' /etc/hosts # 匹配以bash结尾的行并显示
sed -n '/s...:x/p' /etc/hosts #匹配以s开头并以:x结尾并且中间有三个任意字符的行 并展示
sed -n '/[0-9]/p' /etc/hosts # 匹配包含数字的行
sed -n '/^http/p' /etc/hosts # 匹配以http开头的行并显示
默认sed不支持扩展正则,如果希望使用扩展正则匹配数据,可以使用-r参数。
sed -rn '/^(ff|127)/p' /etc/hosts # 正则匹配
sed -rn '\1bash1p' /etc/hosts # 正则匹配 包含bash
sed -rn '\:bash:p' /etc/hosts # 正则匹配 包含bash
sed -rn '/^(ff|127)/=' /etc/hosts # 显示行号
1
5
6
7
8
sed -n '$=' /etc/hosts #显示最后一行的行号
通过a指令添加新的数据行后,虽然在屏幕的输出结果中我们确实看到了添加的新数据,但是查看源文件后就会发现/tmp/hosts并没有实际发生变化,默认sed仅仅是在缓存区中修改了数据并显示在屏幕上,而源文件不会发生变化。如果希望直接修改源文件的话,可以使用-i选项,但是使用该选项修改文件后,万一修改错误,数据将无法被恢复。
生产环境中的最佳实践是先不使用-i选项测试sed指令是否正确,确认无误后再使用-i选项修改源文件,或者对源文件进行备份操作。
下面这条命令会先将文件备份为后缀名称为.bak的文件,再修改源文件的内容,
sed -i.bak '3d' /tmp/hosts #将/tmp/hosts文件的第3行删除,d指令是以行为单位进行删除的指令。
因为sed是逐行处理工具,在r指令的前面没有写匹配条件,则默认会匹配所有数据行,所以虽然只写了一个r指令读取/etc/hostname文件,但是这条指令会被反复执行N次,读取hosts文件的第1行会执行一次r指令,接着读取hosts文件的第2行又会执行一次r指令,依此类推,直到hosts文件读取结束时程序退出。所以我们看到的结果是每一行后面都追加了centos7。如果希望每行后面都追加主机名,可以在r指定前面添加匹配条件。
${#n} #n变量的长度
$n ${n} #是取变量的值
七、awk语法
awk是专门为文本处理设计的编程语言,awk与sed类似都是以数据驱动的行处理软件,通常我们会使用它进行数据扫描、过滤、统计汇总工作,数据可以来自标准输入、管道或者文件。awk在20世纪70年代诞生于贝尔实验室,其名称源于该软件三名开发者的姓氏[插图]。awk有很多版本,在1985—1988年被大量修订与重写并于1988年发布的Gnu awk,是目前应用最广泛的版本,在CentOS7系统中默认使用的就是Gnu awk。
ps -ef |grep xxx.jar |grep -v grep |awk '{print $2}' |xargs kill -9
awk基本语法
awk [选项] ‘条件 动作 条件 动作’ 文件名
1)记录与字段
awk是一种处理文本文件的编程语言,文件的每行数据被称为记录,默认以空格或制表符为分隔符,每条记录会被分成若干字段(列), awk每次从文件中读取一条记录。
2)内置变量
awk语法由一系列条件和动作组成,在花括号内可以有多个动作,在多个动作之间使用分号分隔,在多个条件和动作之间可以有若干空格,也可以没有。awk会逐行扫描以读取文件内容,从第一行到最后一行,寻找与条件匹配的行,并对这些匹配的数据行执行特定的动作。条件可以是正则匹配、数字或字符串比较,动作可以是打印需要过滤的数据或者其他,如果没有指定条件则可以匹配所有数据行,如果没有指定动作则默认为print打印操作。因为awk是逐行处理软件,所以这里的动作默认都隐含着循环,条件被匹配多少次,动作就被执行多少次。
变量名 | 描述 |
---|---|
FILENAME | 文件名 |
FNR | 输入文档的当前行号 |
NR | 输入数据流的当前行 |
$0 | 所有字段 |
$N | 第n个字段 |
NF | 当前行的字段数 |
FS | 字段分隔符 |
OFS | 输出字段分隔符 |
ORS | 输出记录分隔符 |
RS | 输入记录分隔符 |
free
total used free shared buff/cache available
Mem: 2035060 704160 187904 2644 1142996 1132324
Swap: 0 0 0
# ==========================
fred |awk '{print NR}' 每行都打印行号
fred |awk '{print NF}' 打印每行的列数
3)自定义变量
awk可以通过-v(variable)选项设置或者修改变量的值,我们可以使用-v定义新的变量,也可以使用该选项修改内置变量的值。
4)print指令
使用print指令输出特定数据时,我们可以输出变量数据,同时也还可以直接输出常量,如果是字符串常量需要使用双引号括起来,如果是数字常量则可以直接打印。
5)条件匹配
awk支持使用正则进行模糊匹配,也支持字符串和数字的精确匹配,并且支持逻辑与和逻辑或
awk条件判断
awk的语法都在 单引号加大括号里
awk的条件要在()括号里
awk满足条件执行的命令在if()后在加个括号
awk 多条件情况时 最后要执行的放到 END{} 里面
ps -eo user,pid,pcpu,comm |awk '{if ($3>0.1){print}}' #占用cup超过0.1的
ps -eo user,pid,pcpu,rss,comm |awk '{if ($4 > 1024*40){print}}' #占用内存超过4M的
#USER PID %CPU RSS COMMAND
#root 330 0.0 247760 systemd-journal
#root 517 0.0 76804 uniagent
#root 701 0.0 62612 java
#mysql 1474120 0.2 386956 mysqld
#双if分之的案例
ls -l /etc/ |awk '{if ($1 ~/^-/){x++} else{y++}} END {print "普通文件"x,"目录"y}'
#普通文件85 目录113
seq 10 |awk '{if ($1%2==0){print $1 "是偶数";x++} else{print $1 "是奇数";y++}}END{print "偶数"x ,"奇数"y}'
#1是奇数
#2是偶数
#3是奇数
#4是偶数
#5是奇数
#6是偶数
#7是奇数
#8是偶数
#9是奇数
#10是偶数
#偶数5 奇数5
awk函数
内置i/o函数
getline函数可以让awk立刻读取下一行数据(读取下一条记录并复制给$0,并重新设置NF、NR和FNR)。
df -h |awk '{if (NF==1){getline; print $3}; if (NF==6) {print $4}}'
注意:逻辑结束要加分号;
next函数可以停止处理当前的输入记录,立刻读取下一条记录并返回awk程序的第一个模式匹配重新处理数据。getline函数仅仅读取下一条数据,而不会影响后续awk指令的执行。
2)内置数值函数
int(expr)函数为取整函数,仅截取整数部分数值。
rand()函数可以返回0到1之间的随机数N(0<=N<1)。
3)内置字符串函数
index(字符串1,字符串2)函数返回字符串2在字符串1中的位置坐标。
length([s])函数可以统计字符串s的长度,如果不指定字符串s则统计$0的长度。
tolower(str)函数可以将字符串转换为小写。
toupper(str)函数可以将字符串转换为大写。