1.函数
1.1 定义
简单来讲,所谓函数就是把完成特定功能,并且多次使用的一组命令或者语句封装在一个固定的结构中,这个结构我们就叫做函数。从本质上讲,函数是将一个函数名与某个代码块进行映射。也就是说,用户在定义了函数之后,就可以通过函数名来调用其所对应的一组代码。
使用shell函数优势
1、把相同的程序段定义为函数,可以减少整个程序段代码量,提升开发效率。
2、增加程序段可读性、易读性,提升管理效率。
3、可以实现程序功能模块化,使得程序具备通用性(可移植性)。
1.2 语法
function 函数名() {
指令
return
}
简化写法1:
function 函数名 {
指令
return
}
简化写法2:
函数名() {
指令
return
}
1.3 函数的调用
基本语法:
函数名称 参数1 参数2 …
Shell的函数参数的语法比较特殊。实际上,Shell将脚本参数和函数参数做了统一地处理。也就是说,Shell采用了相同的方法来处理脚本的参数和函数参数。
(1)调用函数:直接执行函数名即可。
函数名
(2)带参数的函数执行方法
函数名 参数
与Shell脚本一样,用户可以在Shell函数中使用位置变量来获取参数值。例如,$0 表示脚本名称,$# 来获取函数的参数个数,$1 表示第1个参数,$2 表示第2个参数等,以此类推。另外,用户还可以通过系统变量 $@ 和 $* 获取所有参数的值。
[root@openEuler ~]# cat func01.sh
#!/bin/bash
func() {
echo "the function has $# parameters"
echo "all parameters are $*"
echo "all parameters are $@"
echo "the script name is $0"
echo "the first parameter is $1"
}
func hello world
[root@openEuler ~]# bash func01.sh
the function has 2 parameters
all parameters are hello world
all parameters are hello world
the script name is func01.sh
the first parameter is hello
1.4 函数的返回值
1)在函数内用 return 退出函数并返回函数的值,在函数外用echo $?获取返回值 注意:返回值的范围只能在0~255,超过部分需除以256取余
2)在函数内用echo输出值,在函数体外可用 变量=$(函数名) 获取函数的返回值
#示例1
1.第一种方法
sum1() {
sum=$[$1 + $2]
echo $s
}
read -p "输入第一个数" first
read -p "输入第二个数" second
sum1 $first $second
2.第二种方法
sum() {
s=$[$1 + $2] ///$1 $2 传递位置参数
echo $s
}
sum $1 $2 ///$1 $2 代表执行脚本函数时命令后面跟的两个参数位置参数
#示例2:获取字符串的长度
[root@openEuler ~]# cat length.sh
#!/bin/bash
length() {
str=$1
result=0
if [ "$1" != "" ]; then
result=${#str}
fi
echo "$result"
}
len=$(length "abc123")
echo "the string's length is $len"
[root@openEuler ~]# bash length.sh
the string's length is 6
1.5 函数的案例
1.5.1 案例1
[root@openEuler ~]# cat fun1.sh
#!/bin/bash
#定义一个将十进制转换为二进制的函数
decimal_to_bin() {
NUM=$1
for i in {1..8}
do
SUM=$[NUM % 2]$SUM
let NUM/=2
done
echo $SUM
}
#定义一个分割IP的函数
split_ip() {
IP=$1
for i in {1..4}
do
num=${IP%%.*} #取十进制最左边的十进制,如192
IP=${IP#*.} #取十进制从第二个开始到最右边,如168.72.150
BIN=$(decimal_to_bin num)
echo -n "$BIN."
done
}
read -p "Please enter a valid IP address: " INIP
res=$(split_ip $INIP)
echo ${res%.*} #用于删除二进制最右边的点,%.*代表从字符串的右边开始删除,删到第一个点为止
1.5.2 案例2
写一个脚本,判定192.168.33.120-192.168.33.130之间的主机哪些在线。
要求:
1、使用函数来实现一台主机的判定过程;
2、在主程序中来调用此函数判定指定范围内的所有主机的在线情况。
方法一:直接使用函数实现(无参数,无返回值)
[root@openEuler ~]# cat online01.sh
#!/bin/bash
online() {
for i in {120..130}; do
if ping -c 1 192.168.33.$i &>/dev/null
then
echo "192.168.33.$i is up"
else
echo "192.168.33.$i is unknown"
fi
done
}
online
方法二:使用函数传参(有参数,无返回值)
[root@openEuler ~]# cat online02.sh
#!/bin/bash
online() {
if ping -c 1 $1 &>/dev/null
then
echo "$1 is up"
else
echo "$1 is unknown"
fi
}
for i in {120..130}
do
online 192.168.33.$i
done
方法三:使用函数返回值判断(有参数,有返回值)
[root@openEuler ~]# cat online03.sh
#!/bin/bash
online() {
if ping -c 1 $1 &>/dev/null
then
return 0
else
return 1
fi
}
for i in {120..130}
do
online 192.168.33.$i
if [ $? -eq 0 ];then
echo "192.168.33.$i is up"
else
echo "192.168.33.$i is unknown"
fi
done
1.5.3 案例3:
写一个脚本,使用函数完成
1、函数能够接受一个参数,参数为用户名;
判断一个用户是否存在
如果存在,就返回此用户的shell和UID;并返回正常状态值;
如果不存在,就说此用户不存在;并返回错误状态值;
2、在主程序中调用函数;
[root@openEuler ~]# cat userms.sh
#!/bin/bash
user() {
if id $1 &>/dev/null
then
echo "`grep ^$1 /etc/passwd | cut -d: -f3,7`"
return 0
else
echo "$1 does not exist"
return 1
fi
}
read -p "please input username:" username
until [ "$username" = "quit" -o "$username" = "exit" -o "$username" = "q" ]
do
user $username
if [ $? == 0 ]; then
read -p "please input again:" username
else
read -p "no $username, Please input again:" username
fi
done
[root@openEuler ~]# bash userms.sh
please input username:redhat
1000:/bin/bash
please input again:root
0:/bin/bash
please input again:dakuang
dakuang does not exist
no dakuang,Please input again:hehe
hehe does not exist
no hehe,Please input again:quit
1.6 函数变量作用域
注意:默认情况下,除了与函数参数关联的特殊变量之外,其他所有的变量都有全局的有效范围。另外,在函数内部,如果没有使用local关键字进行修饰,那么函数中的变量也是全局变量。
示例1:函数的变量是全局变量
[root@openEuler ~]# cat global.sh
#!/bin/bash
var="hello world"
func() {
var="orange"
echo $var
var2="hello"
}
echo "$var"
func
echo "$var"
echo "$var2"
[root@openEuler ~]# bash global.sh
hello world
orange
orange
hello
示例2:函数的变量使用local指定为局部变量
[root@openEuler ~]# cat global02.sh
#!/bin/bash
var="hello world"
func() {
local var="orange"
echo $var
local var2="hello"
}
echo "$var"
func
echo "$var"
echo "$var2"
[root@openEuler ~]# bash global02.sh
hello world
orange
hello world
1.7 递归函数
Linux的Shell也支持函数的递归调用。也就是说,函数可以直接或者间接地调用自身。在函数的递归调用中,函数既是调用者,又是被调用者。
递归函数的调用过程就是反复地调用其自身,每调用一次就进入新的一层。
示例1:根据用户输入的数值计算该数的阶乘
[root@openEuler ~]# cat fact.sh
#!/bin/bash
fact() {
if [$1 -eq 1]; then
result=1
elif [ $1 -gt 1]; then
local m=$[$1 -1]
fact $m
let "result=$1 * $?"
else
echo "The input value is invalid"
fi
return $result
# 或
local n="$1"
if [ "$n" -eq 0 ]; then
result=1
else
let "m=n-1"
fact "$m"
let "result=$n * $?"
fi
return $result
}
fact "$1"
echo "Factorial of $1 is $?"
[root@openEuler ~]# bash fact.sh 0
Factorial of 0 is 1
[root@openEuler ~]# bash fact.sh 5
Factorial of 5 is 120
示例2:使用函数递归目录/var/log,如果是文件直接输出文件名,如果是目录,则输出目录名且输出此目录下的所有目录和文件名
[root@openEuler ~]# vim fact2.sh
#!/bin/bash
listf () {
for f in $(ls $1)
do
if [ -d "$1/$f" ]; then
echo "$2 directory $f" # $2 第一次调用为一个空格,第二次调用为两个空格
listf "$1/$f" " $2"
else
echo "$2 file $f"
fi
done
}
[root@openEuler ~]# cd /var/log
[root@openEuler log]# mkdir -p aa/bb/cc
[root@openEuler log]# cd aa
[root@openEuler aa]# touch a.txt
[root@openEuler aa]# cd bb/cc
[root@openEuler cc]# touch c.txt
[root@openEuler ~]# listf "/var/log" $()
示例3:通过脚本输出环境变量PATH所包含的所有目录以及其中的子目录和所有不可执行文件
[root@openEuler ~]# vim fact3.sh
#!/bin/bash
#定义一个遍历PATH环境变量的函数
list_path_variable() {
IFSB=$IFS
IFS=$IFS':'
for F in $PATH
do
echo "$F"
done
IFS=$IFSB
}
#定义递归函数
listf() {
for F in $(ls $1)
do
if [ -d "$1/$F" ]; then
echo "$2$F"
listf "$1/$F" " $2"
else
if [ ! -x "$1/$F" ]; then
echo "$2$F"
fi
fi
done
}
folder=$(list_path_variable)
for path in $folder
do
echo $path
listf "$path" " "
done
[root@openEuler ~]# bash fact3.sh
1.8 函数库文件
为了方便地重用这些功能,可以创建一些可重用的函数。这些函数可以单独地放在函数库文件中。
函数库文件定义:
创建一个函数库文件的过程非常类似于编写一个Shell脚本。脚本与库文件之间的唯一区别在于函数库文件通常只包括函数,而脚本中则可以既包括函数和变量的定义,又包括可执行的代码。此处所说的可执行代码,是指位于函数外部的代码,当脚本被载入后,这些代码会立即被执行,毋需另外调用。
函数库文件的调用:
当库文件定义好之后,用户就可以在程序中载入库文件,并且调用其中的函数。在Shell中,载入库文件的命令为.,即一个圆点,其语法如下:
. ./filename
或者
source filename
其中,参数filename表示库文件的名称,必须是一个合法的文件名。库文件可以使用相对路径,也可以使用绝对路径。另外,圆点命令和库文件名之间有一个空格。
1.创建函数库文件
[root@openEuler ~]# cat function_library.sh
#!/bin/bash
addition() {
echo $[$1 + $2]
}
subtraction() {
echo $[$1 -$2]
}
multiplication() {
echo $[$1 * $2]
}
division() {
if [ $2 -eq 0 ]; then
echo 'Divisor cannot be 0'
else
echo $[$1 / $2]
fi
}
factorial() {
if [ $1 -eq 1 ]; then
echo 1
elif [ $1 -gt 1 ]; then
local tmp=$[$1 -1]
local res=$(factorial $tmp)
echo $[$1 * res]
else
echo "Please enter an integer greater than or equal to 1!"
fi
}
2. 在脚本中加载函数库文件并使用
[root@openEuler ~]# cat clr_test.sh
#!/bin/bash
#加载函数库文件
source /root/function_library.sh
v1=10
v2=5
r1=$(addition $v1 $v2)
r2=$(subtraction $v1 $v2)
r3=$(multiplication $v1 $v2)
r4=$(division $v1 $v2)
r5=$(factorial $v1)
r6=$(factorial $v2)
echo "$v1+$v2=$r1"
echo "$v1-$v2=$r2"
echo "$v1*$v2=$r3"
echo "$v1/$v2=$r4"
echo "$v1 factorial is $r5"
echo "$v2 factorial is $r6"
2.文本搜索工具----grep
2.1 通配符
通配符是由shell处理的, 它只会出现在命令的“参数”里。当shell在“参数”中遇到了通配符时,shell会将其当作路径或文件名去在磁盘上搜寻可能的匹配:若符合要求的匹配存在,则进行代换(路径扩展);否则就将该通配符作为一个普通字符传递给“命令”,然后再由命令进行处理。总之,通配符实际上就是一种shell实现的路径扩展功能。在通配符被处理后, shell会先完成该命令的重组,然后再继续处理重组后的命令,直至执行该命令。
通配符只是用来查找文件名和目录名
2.1.1 SHELL中的通配符
通配符 | 说明 |
---|---|
* | 匹配任意长度的任意字符,可以是0个 |
? | 匹配任意单个字符,必须是1个 |
[ ] | 匹配指定字符范围内的任意单个字符 |
[a-z,A-Z,0-9] | 匹配所有数字字母,可以不加逗号 |
[a-z] | 表示a-z,A-Y,表示a,A,b,B.....a。不包含Z |
[A-Z] | 表示A,b,B.....z,Z。不包含a |
[a-Z] | 表示所有大小写字母 |
[:upper:] | 所有大写字母 |
[:lower:] | 所有小写字母 |
[:alpha:] | 所有字母,大小写 |
[:digit:] | 所有数字 |
[:alnum:] | 所有数字+字母 |
[:blank:] | 水平空白字符 |
[:space:] | 水平或垂直空白字符 |
[:punct:] | 标点符号 |
[:print:] | 可打印字符 |
[:cntrl:] | 控制(非打印)字符 |
[:graph:] | 图形字符 |
[:xdigit:] | 十六进制字符 |
[^] | 匹配指定字符范围外的任意单个字符 |
[^0-9] | 相当于[^[:digit:]] |
[^a-z] | 表示Z + 其他 |
[^A-Z] | 表示a + 其他 |
2.1.2 通配符实例
1、显示所有menu0开头的文件
[root@openEuler ~]# ls menu0*
menu01.sh menu02.sh menu03.sh menu04.sh
2、显示所有m开头后面只有单个字符的文件
[root@openEuler ~]# ls m?
m1 m2 m3
3、显示所有m或者n开头的文件
[root@openEuler ~]# ls [mn]*
m1 m2 m3 menu01.sh menu02.sh menu03.sh menu04.sh n71 n72 n73
4、显示所有以字母开头的文件
[root@openEuler ~]# ls [a-Z]*
BC1 BC2 BC3 m1 m2 m3 menu01.sh menu02.sh menu03.sh menu04.sh n71 n72 n73
5、显示所有数字开头的文件
[root@openEuler ~]# ls [0-9]*
12a 12b 12c 12d
6、查看所有不区分大小写的字母开头的文件
[root@openEuler ~]# ls [[:alpha:]]*
BC1 BC2 BC3 m1 m2 m3 menu01.sh menu02.sh menu03.sh menu04.sh n71 n72 n73
7、查看所有以数字开头的文件
[root@openEuler ~]# ls [[:digit:]]*
12a 12b 12c 12d
8、查看所有以字母或者数字开头的文件
[root@openEuler ~]# ls [[:alnum:]]*
12a 12b 12c 12d BC1 BC2 BC3 m1 m2 m3 menu01.sh menu02.sh menu03.sh menu04.sh n71 n72 n73
3.正则表达式
3.1 定义
正则表达式是通过一些特殊字符的排列,用以查找、替换、删除一行或多行文字字符串,简单的说,正则表达式就是用在字符串的处理上面的一项表示式。由于正则表达式语法简练,功能强大,得到了许多程序设计语言的支持,包括Java、C++、Perl以及Shell等。
3.2 使用原因
在进行程序设计的过程中,用户会不可避免地遇到处理某些文本的情况。有的时候,用户还需要查找符合某些比较复杂规则的字符串。对于这些情况,如果单纯依靠程序设计语言本身,则往往会使得用户通过复杂的代码来实现。但是,如果使用正则表达式,则会以非常简短的代码来完成。
3.3 使用方式
当一个正则表达式完成之后,并能够保证这个表达式一定是准确的,需要不断地测试才可以确定其正确与否。在不同的环境下,用户需要不同的工具来帮助他完成测试的过程。如果是在Shell命令行中,用户可以使用grep命令来测试。
grep家族有三大成员分别为: grep:支持使用基本正则表达式。 egrep:支持使用扩展正则表达式。 fgrep:不支持使用正则表达式,即所有的正则表达式中的元字符都将作为一般字符,仅仅拥有其字面意义,不再拥有特殊意义。
grep命令的名称来自于全局搜索正则表达式并打印文本行(Global Search Regular Expression and Print out the line)的缩写。它是一个非常古老的UNIX命令,也是一种强大的文本搜索工具。grep命令使用正则表达式来搜索文本,并且把匹配的文本行打印出来。
grep命令根据用户指定的”pattern(过滤条件)“对目标文本逐行进行匹配检查;打印出符合条件的行,即文本搜索工具。注:PATTERN即过滤条件指由文本字符及正则表达式元字符所编写的字符串。
grep命令的基本语法如下:grep [options] pattern [file…]
在上面的语法中,options表示选项,选项列表如下表。pattern表示要匹配的模式,file表示一系列的文件名。grep命令会从一个或者多个文件中搜索满足指定模式的文本行,并且打印出来。模式后面的所有的字符串参数都被看作是文件名。
-n :显示行号
-o :只显示匹配的内容
-q :静默模式,没有任何输出,得用$?来判断执行成功没有,即有没有过滤到想要的内容
-l :如果匹配成功,则只将文件名打印出来,失败则不打印,通常-rl一起用,grep -rl 'root' /etc
-A :如果匹配成功,则将匹配行及其后n行一起打印出来
-B :如果匹配成功,则将匹配行及其前n行一起打印出来
-C :如果匹配成功,则将匹配行及其前后n行一起打印出来
--color:高亮颜色显示匹配到的字符串
-c :如果匹配成功,则将匹配到的行数打印出来
-E :等于egrep,扩展
-i :忽略大小写
-v :取反,不匹配
-w:匹配单词
-r:递归搜索,不仅搜索当前目录,还要搜索其各级子目录
-s:不显示关于不存在或者无法读取文件的错误信息
在进行程序设计的过程中,用户会不可避免地遇到处理某些文本的情况。有的时候,用户还需要查找符合某些比较复杂规则的字符串。对于这些情况,如果单纯依靠程序设计语言本身,则往往会使得用户通过复杂的代码来实现。但是,如果使用正则表达式,则会以非常简短的代码来完成。
正则表达式是通过一些特殊字符的排列,用以查找、替换、删除一行或多行文字字符串,简单的说,正则表达式就是用在字符串的处理上面的一项表示式。由于正则表达式语法简练,功能强大,得到了许多程序设计语言的支持,包括Java、C++、Perl以及Shell等。
3.4 基本正则表达式
基本正则表达式(Basic Regular Expression,BRE),又称为标准正则表达式,是最早制订的正则表达式规范,仅支持最基本的元字符集。基本正则表达式是POSIX规范制订的两种正则表达式语法标准之一,另外一种语法标准称为扩展正则表达式。
元字符 | 说明 |
---|---|
^ | 以某个字符开头 |
$ | 以某个字符结尾 |
. | 匹配任意单字符 |
* | 对前一项进行0次或者多次重复匹配 |
{m,n} | 将前一项字符重复m-n次,{m,},{,n},{m} |
[] | 对方括号内的单字符进行匹配 |
[^] | 不匹配方括号内的单字符 |
^[] | 匹配以某个字符开头的行 |
\ | 转义字符,让一些特殊符号失效 |
() | 定义一个子表达式 |
词首【\<或\b】和词尾锚定【\>或者\b】 | 其后面的任意字符必须作为单词首部出现;其前面的任意字符必须作为单词尾部出现 |
后项引用:后面例子中会用到。
分组:
\(\)
\(ab\)*
后项引用
\1:引用第一个左括号以及与之对应的右括号所包括的所有内容
\2:
\3:
正则表达式字符集
字符 | 说明 |
---|---|
[[:alpha:]] | 匹配任意一个字母,等价于 [A-Za-z] |
[[:alnum:]] | 匹配任意一个字母或者数字,等价于 [A-Za-z0-9] |
[[:digit:]] | 匹配任意一个数字,等价于 0-9 |
[[:lower:]] | 匹配任意一个小写字母,等价于 a-z |
[[:upper:]] | 匹配任意一个大写字母,等价于 A-Z |
[[:space:]] | 匹配任意一个空白符,包括空格、制表符、换行符以及分页符 |
[[:blank:]] | 匹配空格和制表符 |
[[:graph:]] | 匹配任意一个看得见的可打印字符,不包括空白字符 |
[[:print:]] | 匹配任何一个可以打印的字符,包括空白字符,但是不包括控制字符、字符串结束符‘\0’ 、 EOF 文件结束符( -1 ) |
[[:cntrl:]] | 匹配任何一个控制字符,即 ASCII 字符集中的前 32 个字符。例如换行符、制表符等 |
[[:punct:]] | 匹配任何一个标点符号,例如 “[]” 、 “ {}”或者 “,” 等 |
[[:xdigit:]] | 匹配十六进制数字,即 0-9 、 a-f 以及 A-F |
[root@shell shell]# grep ^a /etc/passwd
[root@shell shell]# grep h$ /etc/passwd
[root@shell shell]# grep . /etc/passwd
[root@shell shell]# grep ro* /etc/passwd
[root@shell shell]# grep "ro\{1,2\}" /etc/passwd
[root@shell shell]# grep "ro\{1\}" /etc/passwd
[root@shell shell]# grep [a-zA-Z] /etc/passwd
[root@shell shell]# grep [a-Z] /etc/passwd
[root@shell shell]# grep [0-9] /etc/passwd
[root@shell shell]# grep [0-9a-Z] /etc/passwd 或者grep [[:alnum:]] /etc/passwd
[root@shell shell]# grep [[:space:]] /etc/passwd
[root@shell shell]# grep [^a] /etc/passwd
[root@shell shell]# grep ^[ab] /etc/passwd
[root@shell shell]# grep "\." file
[root@shell shell]# grep "\(root\).*\1" file
[root@shell shell]# grep "ly\>" file
[root@shell shell]# grep "\<lo" file
[root@shell shell]# grep "\<love\>" file
love
[root@shell shell]# grep "\blove\b" file
love
1. ^word 表示搜索以word开头的内容。
[root@openEuler ~]# grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@openEuler ~]# grep ^root /etc/passwd
root:x:0:0:root:/root:/bin/bash
2. word$ 表示搜索以word结尾的内容。
[root@openEuler ~]# grep bash passwd
root:x:0:0:root:/root:/bin/bash
redhat:x:1000:1000:redhat:/home/redhat:/bin/bash
bash:x:33
[root@openEuler ~]# grep bash$ passwd
root:x:0:0:root:/root:/bin/bash
redhat:x:1000:1000:redhat:/home/redhat:/bin/bash
3. ^$ 表示空行,不是空格。
[root@openEuler ~]# grep ^$ passwd
[root@openEuler ~]# grep ^$ passwd -n
45:
49:
4. .代表且只能代表一个任意字符。
[root@openEuler ~]# grep r.t passwd
operator:x:11:0:operator:/root:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
rngd:x:975:974:Random Number Generator Daemon:/var/lib/rngd:/sbin/nologin
rot
[root@openEuler ~]# grep r..t passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
clevis:x:981:981:Clevis Decryption Framework unprivileged user:/var/cache/clevis:/sbin/nologin
root
[root@openEuler ~]# grep r...t passwd
rtkit:x:172:172:RealtimeKit:/proc:/sbin/nologin
unbound:x:994:988:Unbound DNS resolver:/etc/unbound:/sbin/nologin
rooot
5. * 重复0个或多个前面的字符
[root@openEuler ~]# grep r*t passwd
6. [] 匹配字符集合内任意一个字符,如[a-z]
[root@openEuler ~]# grep r[a-z]t passwd
operator:x:11:0:operator:/root:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
rngd:x:975:974:Random Number Generator Daemon:/var/lib/rngd:/sbin/nologin
rot
[root@openEuler ~]# grep r[a-z]*t passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
rtkit:x:172:172:RealtimeKit:/proc:/sbin/nologin
libstoragemgmt:x:995:989:daemon account for libstoragemgmt:/var/run/lsm:/sbin/nologin
setroubleshoot:x:991:984::/var/lib/setroubleshoot:/sbin/nologin
clevis:x:981:981:Clevis Decryption Framework unprivileged user:/var/cache/clevis:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
rngd:x:975:974:Random Number Generator Daemon:/var/lib/rngd:/sbin/nologin
redhat:x:1000:1000:redhat:/home/redhat:/bin/bash
rt
rot
root
rooot
7. [^abc]在中括号里表示非,不包含a或b或c
[root@openEuler ~]# grep r[^a-z]*t passwd
rtkit:x:172:172:RealtimeKit:/proc:/sbin/nologin
rt
r.t
r..t
r...t
8. {n,m} 匹配n到m次,前一个字符。
{n,} 至少N次,多了不限。 {n} n次 {,m} 至多m次,少了不限。
注意:grep要将{}转义,{},egrep不需要转义
[root@openEuler ~]# grep 'r[^a-z]\{3,\}' passwd
[root@openEuler ~]# grep -E 'r[^a-z]{3,}' passwd
通配符和正则表达式比较
(1)通配符和正则表达式看起来有点像,不能混淆。可以简单的理解为通配符只有*,?,[],{}这4种,而正则表达式复杂多了。
(2)在通配符和正则表达式中有其不一样的地方,在通配符中可以匹配任意的0个或多个字符,而在正则表达式中他是重复之前的一个或者多个字符,不能独立使用的。比如通配符可以用来匹配任意字符,而正则表达式中的""不行,他只匹配任意长度的前面的字符(如果想匹配任意字符,那么就需要用.
符号,通配符中的 *
= 正则表达式中的.*
。
(3)使用场景:通配符 是文件名。正则表达式 文本内容
(4) 使用命令 通配符 find rm ls cp 由shell解析; 正则表达式 vi grep sed awk
3.5 扩展正则表达式
扩展正则表达式(Extended Regular Expression,ERE)支持比基本正则表达式更多的元字符,但是扩展正则表达式对有些基本正则表达式所支持的元字符并不支持。前面介绍的元字符“^”、“$”、“.”、“*”、“[]”以及“[^]”这6个元字符在扩展正则表达式都得到了支持,并且其意义和用法都完全相同,不再重复介绍。接下来重点介绍一下在扩展正则表达式中新增加的一些元字符。
元字符 | 说明 |
---|---|
? | 将前一项字符进行0次或者1次的重复匹配 |
+ | 将前一项进行1次或者多次的重复匹配 |
(|) | 匹配|符号左边或者右边的字符 |
[root@shell shell]# grep -E ro? /etc/passwd
[root@shell shell]# grep -E ro+ /etc/passwd
[root@shell shell]# grep -E "(root|adm|ssh)" /etc/passwd
案例
1、显示/etc/passwd文件中以bash结尾的行;
[root@openEuler ~]# grep bash$ /etc/passwd
root:x:0:0:root:/root:/bin/bash
redhat:x:1000:1000:redhat:/home/redhat:/bin/bash
2、找出/etc/passwd文件中的三位或四位数;
[root@openEuler ~]# grep '[[:digit:]]\{3,4\}' /etc/passwd
[root@openEuler ~]# grep -E '[[:digit:]]{3,4}' /etc/passwd
3、找出/etc/grub2.cfg文件中,以至少一个空白字符开头,后面又跟了非空白字符的行;
[root@openEuler ~]# grep "^[[:space:]]\+[^[:space:]]" /etc/grub2.cfg
4、找出"netstat -tan”命令的结果中,以‘LISTEN’后跟0个或多个空白字符结尾的行;
[root@openEuler ~]# netstat -tan | grep "LISTEN[[:space:]]*$"
5、找出"fdisk -l“命令的结果中,取出硬盘路径;
[root@openEuler ~]# fdisk -l | grep Disk | grep /dev/nv | cut -d: -f 1 | cut -d " " -f2
6、找出”ldd /usr/bin/cat“命令的结果中文件路径;
[root@openEuler ~]# ldd /usr/bin/cat | egrep [[:blank:]]\+/[[:graph:]]*
7、找出/proc/meminfo文件中,所有以大写或小写s开头的行
[root@openEuler ~]# grep -i ^S /proc/meminfo
8、显示当前系统上root、centos或spark用户的相关信息;
[root@openEuler ~]# grep -E ^"(root|centos|spark)" /etc/passwd
[root@openEuler ~]# egrep ^"(root|centos|spark)" /etc/passwd
9、echo输出一个绝对路径,使用egrep取出其基名;
[root@openEuler ~]# echo /mnt/sdc/ | grep -E -o "[^/]+/?$" | cut -d "/" -f 1
10、找出ifconfig命令结果中的1-255之间的整数;
[root@openEuler ~]# ifconfig | egrep -w "[1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]"
11. 找出ifconfig命令输出中的所有IP地址
[root@openEuler ~]# ifconfig | egrep -o "[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}"
4.shell编程之sed
4.1 sed工作原理
sed是一种流编辑器,它是文本处理中非常有用的工具,能够完美的配合正则表达式使用,处理时,把当前处理的行存储在临时缓冲区中,称为模式空间,接着用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。接着处理下一行,这样不断重复,直到文件末尾。文件内容并没有改变。
4.2 sed基本语法
sed OPTIONS… [SCRIPT] [INPUTFILE…]
常用的选项:
-n,--quiet,--silent:不输出模式空间中的内容,使用安静模式,在一般sed的用法中,所有来自STDIN的数据一般都会被列出到屏幕上,但如果加上-n参数后,则只有经过sed特殊处理的那一行才会被列出来;
-i:直接编辑原文件,而不是由屏幕输出,默认不对原文件进行操作;
-e:直接在命令行模式上进行sed的动作编辑,多个子命令之间也可以用分号隔开;
sed -e 'command1;command2... filename
或者sed -e 'command1' -e 'command2' ……filename
-r:使用扩展正则表达式;
-f:直接将sed的动作写在一个文件内,-f filename则可以执行filename内的sed动作。
4.3 模式空间中的编辑操作
4.3.1 地址定界
地址定界 | 示例 | 说明 |
---|---|---|
不写地址定界 | 表示对文件所有行进行处理 | |
num1,num2 | 1,3或者1,$ | 对文件的1-3行内容进行处理;如果是$表示文件的最后一行 |
num1,+N | 1,+3 | 对文件的num1行以及以后的N行内容进行处理 |
first~step | 1~2 | 对文件的1,3,5,7,……的行内容进行处理 |
/pattern/ | /^root/,/r/I | 对任何能够被正则表达式匹配到的行进行处理 |
\%pattern% | \%/r% | 可以使用其他的边界符号(例如#),对任何能够被正则表达式匹配到的行进行处理 |
/pattern1/,/pattern2/ | /^root/,/^adm/ | 表示正则表达式1和正则表达式2匹配到的行和两个正则表达式之间的所有行 |
0,/pattern1/或者1,/pattern1/ | 0,/^adm/ | 从第一行开始到能够被正则表达式匹配到的行之间的所有内容 |
4.3.2 常用编辑命令
对文件行内容的编辑命令 | 说明 | 示例 |
---|---|---|
d | 删除匹配到的行 | [root@node13 ~]#sed '1 d' file [root@node13 ~]#sed '/^$/d' /etc/hosts |
p | 打印匹配到的行 | [root@node13 ~]#sed '1p' file |
a 文本内容 | 将文本内容添加到匹配到的行的下一行 | [root@node13 ~]#sed '1 a hello' file |
i 文本内容 | 将文本内容添加到匹配到的行上一行 | [root@node13 ~]#sed '1 i hello' file |
c 文本内容 | 用文本内容替换匹配到的所有行 | [root@node13 ~]#sed '1,3 i hello' file |
s/pattern/replacement/flag | 根据正则表达式进行匹配,将匹配到的内容替换为replacement,flag可以指定g(表示全局替换,默认只替换每行第一个)num1(表示对匹配到的第几个内容进行替换),i(不区分大小写),p(如果成功替换则打印) | [root@node13 ~]#sed '1 s#\<print\>#echo#g' file [root@node13 ~]# sed '1 s/\bprint\b/echo/2' file |
r | 读入文件内容追加到匹配行后面 | [root@node13 ~]#sed '2 r /root/ceshi.sh' file |
R | 读入文件一行内容追加到匹配行后面 | [root@node13 ~]#sed '2 R /root/ceshi.sh' file |
w /dir/filename | 将匹配到的内容另存到/dir/filename中 | [root@node13 ~]# sed '1 w /dir1/file1' file |
sed 's/north/hello/' datafile --替换每行第一个north
sed 's/north/hello/g' datafile --全部替换
sed '1 s/north/hello/g' datafile --替换第一行所有的north
sed '1 s/north/hello/' datafile --替换第一行第一个north
sed '1 s/north/hello/2' datafile --只替换第一行第二个north
巧用替换删除内容(不是删除行)
sed 's/north//' datafile --删除所有行的第一个north
sed 's/north//g' datafile --删除全部的north
sed '1 s/north//2' datafile --删除第一行第二个
sed 's/^.//' datafile --删除每行第一个字符
sed 's/^\(..\)./\1/' datafile --删除第3个字符
sed 's/^\<[a-Z]*[a-Z]\>//' datafile --删除每行第一个单词
特殊符号 | 说明 |
---|---|
! | 对指定行以外的所有行应用命令 |
= | 打印当前行行号 |
~ | “first~step”表示从first行开始,以步长step递增 |
& | 代表匹配到的内容 |
; | 实现一行命令语句可以执行多条sed命令 |
{} | 对单个地址或地址范围执行批量操作 |
+ | 地址范围中用到的符号,做加法运算 |
[root@node13 ~]# sed '1 ! p' file #打印除了第一行以外的行内容
[root@node13 ~]# sed '/echo/ = ' file
[root@node13 ~]# sed '1 s/echo/:&:/' file
[root@node13 ~]# sed '1 s/echo/:&:/; 3 s/bea/aaa/' file
[root@node13 ~]# sed '1,3 {p;=}' file 等同于[root@node13 ~]# sed '1p;2p;3p;1 =;2=;3 =' file
案例
准备数据
cat -n openlab.txt
My name is jock.
I teach linux.
I like play computer game.
My qq is 24523452
My website is http://www.xianoupeng.com
My website is http://www.xianoupeng.com
My website is http://www.xianoupeng.com
My website is http://www.xianoupeng.com
My website is http://www.xianoupeng.com
My website is http://www.xianoupeng.com
1.输出文件第2,3行的内容
sed -n '2,3p' openlab.txt
I teach linux.
I like play computer game.
2. 过滤出含有linux的字符行串
sed -n '/linux/p' openlab.txt
I teach linux.
3.删除含有game的行
sed '/game/p' openlab.txt -n
I like play computer game.
sed '/game/d' openlab.txt
cat -n openlab.txt
My name is jock.
I teach linux.
I like play computer game.
My qq is 24523452
My website is http://www.xianoupeng.com
sed '/game/d' openlab.txt -i
cat -n openlab.txt
My name is jock.
I teach linux.
My qq is 24523452
My website is http://www.xianoupeng.com
4.将文件中的My全部替换为His
(s内置符配合g,代表全局替换,中间的"/"可以替换为"#@/"等)
sed '/s/My/His/g' openlab.txt
5.替换所有My为His,同时QQ号为88888888
sed -e 's/My/His/g' -e 's/24523452/88888888/g' openlab.txt
6.在文件第二行追加内容 a 字符功能,写入到文件,还要添加-i
# 在第二行下插入
sed -i '2a I am useing sed command' openlab.txt
添加多行信息,用换行符“\n”
sed -i "3a i like linux very much.\nand you?" openlab.txt
在每一行下面插入新内容
sed "a --------" openlab.txt
7、在第二行上面插入内容
# 在第二行上插入
sed -i '2i I am boy.' openlab.txt
面试题解析
文件内容如下:
123abc456
456def123
567abc789
789def567
要求输出:
456ABC123
123DEF456
789ABC567
567DEF789
# sed -r 's/([0-9]{3})(.*)([0-9]{3})/\3\2\1/' b.txt | tr -s '[a-z]' '[A-Z]'
456ABC123
123DEF456
789ABC567
567DEF789
5.shell编程之awk
5.1 awk定义
awk是Linux以及UNIX环境中现有的功能最强大的数据处理工具。简单地讲,awk是一种处理文本数据的编程语言。awk的设计使得它非常适合于处理由行和列组成的文本数据。而在Linux或者UNIX环境中,这种类型的数据是非常普遍的。
除此之外,awk 还是一种编程语言环境,它提供了正则表达式的匹配,流程控制,运算符,表达式,变量以及函数等一系列的程序设计语言所具备的特性。它从C语言中获取了一些优秀的思想。awk程序可以读取文本文件,对数据进行排序,对其中的数值执行计算以及生成报表等。
5.2 工作流程
awk命令的基本语法如下:
awk 'pattern { actions }'
在上面的语法中,pattern表示匹配模式,actions表示要执行的操作。以上语法表示,当某个文本行符合pattern指定的匹配规则时,执行actions所执行的操作。在上面的语法中,pattern和actions都是可选的,但是两者必须保证至少有一个。如果省略匹配模式pattern,则表示对所有的文本行执行actions所表示的操作;如果省略actions,则表示将匹配成功的行输出到屏幕。
对于初学者来说,搞清楚awk的工作流程非常重要。只有在掌握了awk的工作流程之后,才有可能用好awk来处理数据。在awk处理数据时,它会反复执行以下4个步骤:
(1)自动从指定的数据文件中读取行文本。
(2)自动更新awk的内置系统变量的值,例如列数变量NF、行数变量NR、行变量$0以及各个列变量$1、$2等等。
(3)依次执行程序中所有的匹配模式及其操作。
(4)当执行完程序中所有的匹配模式及其操作之后,如果数据文件中仍然还有未读取的数据行,则返回到第(1)步,重复执行(1)~(4)的操作。
5.3 基本语法
awk 'BEGIN{ commands } pattern{ commands } END{ commands }' [INPUTFILE…]
5.3.1 awk的输出
(1)print item1,item2,……
- 各项目之间使用逗号隔开,而输出时则以空白字符分隔
- 输出的item可以为字符串或数值、当前记录的字段(如$1)、变量或awk的表达式;数值会先转换为字符串,然后再输出;
- print命令后面的item可以省略,此时其功能相当于print $0, 因此,如果想输出空白行,则需要使用print “ ”;
[root@localhost ~]# awk 'BEGIN { print "line one\nline two\nline three"}'
line one
line two
line three
[root@localhost ~]# awk 'BEGIN{print "This","is","test"}'
This is test
[root@localhost ~]# awk -F: '{print $1,$3}' /etc/passwd | head -n 3
root 0
bin 1
daemon 2
(2)printf("format\n", [arguments])
format是一个用来描述输出格式的字符串,format格式的指示符都以%开头,后跟一个字符,如下:
%c: 显示字符的ASCII码;
%d, %i:十进制整数;
%e, %E:科学计数法显示数值;
%f: 显示浮点数;
%g, %G: 以科学计数法的格式或浮点数的格式显示数值;
%s: 显示字符串;
%u: 无符号整数;
%%: 显示%自身;
- 修饰符:
N: 显示宽度;
-: 左对齐;
+:显示数值符号
- printf语句不会自动打印换行符;\n
- 通常将字符串常量用双引号引起来
- argument为一个参数列表,表示用来显示的数据,可以是变量名等,多个参数之间用逗号隔开。
(3)输出重定向
print items > output-file
print items >> output-file
print items | command
root@localhost ~]# awk -F: '{printf "%-15s %i\n",$1,$3 > "test1" }' /etc/passwd
5.3.3 awk的变量
与其他的程序设计语言一样,awk本身支持变量的相关操作,包括变量的定义和引用,以及参与相关的运算等。此外,还包含了许多内置的系统变量。 变量的作用是用来存储数据。变量由变量名和值两部分组成,其中变量名是用来实现变量值的引用的途径,而变量值则是内存空间中存储的用户数据。 awk的变量名只能包括字母、数字和下划线,并且不能以数字开头。例如abc、a、z以及a123都是合法的变量名,而123abc则是非法的变量名。另外,awk的变量名是区分大小写的,因此,X和x分别表示不同的变量。 在awk中定义变量的方法非常简单,只要给出一个变量名并且赋予适当的值即可。awk中的变量类型分为两种,分别为字符串和数值。但是在定义awk变量时,毋需指定变量类型,awk会根据变量所处的环境自动判断。如果没有指定值,数值类型的变量的缺省值为0,字符串类型的变量的缺省值为空串。 awk提供了许多非常实用的系统变量,例如字段变量、字段数变量以及记录数变量等。
(1)awk内置变量
变量 说明 $0 记录变量,表示当前正在处理的记录 $n 字段变量,其中n为整数,且n大于1。表示第n个字段的值 NF 整数值,表示当前记录(变量$0所代表的记录)的字段数 NR 整数值,表示awk已经读入的记录数;如果有多个文件,这个数目会把处理的多个文件中行统一计数。(显示的是文件的每一行的行号) FNR 与NR不同的是,FNR用于记录正处理的行是当前这一文件中被总共处理的行数; FILENAME 表示正在处理的数据文件的名称 FS 输入字段分隔符,默认值是空格或者制表符,可使用-F指定分隔符 OFS 输出字段分隔符 ,OFS=”#”指定输出分割符为#。 RS 记录分隔符,默认值是换行符 \n ENVIRON 当前shell环境变量及其值的关联数组;
示例:
[root@localhost ~]# echo "this is" > test.txt
[root@localhost ~]# awk 'BEGIN {OFS="#"} {print $1,$2,"a","test"}' test.txt
this#is#a#test
[root@localhost ~]# awk 'BEGIN{print ENVIRON["PATH"]}' /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
(2)用户自定义变量
awk允许用户自定义自己的变量以便在程序代码中使用,变量名命名规则与大多数编程语言相同,只能使用字母、数字和下划线,且不能以数字开头。awk变量名称区分字符大小写。
使用赋值语句进行赋值:
[root@localhost ~]# awk 'BEGIN{test="hello";print test}'
hello
在命令行中使用-v选项赋值:
[root@localhost ~]# awk -v test="hello" 'BEGIN {print test}'
hello
5.3.4 awk的操作符
awk是一种编程语言环境,因此,它也支持常用的运算符以及表达式,例如算术运算、逻辑运算以及关系运算等。
(1)awk支持常用的算术运算,这一点与其他的程序设计语言基本相同。
运算符 | 说明 | 举例 |
---|---|---|
+ | 加法运算 | 1+2表示计算1和2的和 |
- | 减法运算 | 82-2表示计算82和2的差 |
* | 乘法运算 | 2*5表示计算2和5的积 |
/ | 除法运算 | 6/3表示计算6和2的商 |
% | 求模运算 | 5/2表示计算5除以2的余数 |
^ | 指数运算 | 2^3表示计算2的3次方 |
示例:
[root@localhost ~]# awk 'BEGIN{x=2;y=3;print x**y,x^y,x*y,x/y,x+y,x-y,x%y}'
8 8 6 0.666667 5 -1 2
(2)赋值运算符
运算符 | 说明 | 举例 |
---|---|---|
= | 赋值运算 | x=5表示将数值5赋给变量x |
+= | 复合赋值运算,表示将前后两个数值相加后的和赋给前面的变量 | x+=5表示先将x的值与5相加,然后再将和赋给变量x,等价于x=x+5 |
-= | 复合赋值运算,表示将前后两个数值相减后的值赋给前面的变量 | x-=5表示先将x的值减去5,然后再将得到的差赋给变量x,等价于x=x-5 |
*= | 复合赋值运算,表示前后两个数的乘积赋给前面的变量 | 表示先将x的值乘以5,然后再将得到的乘积赋给变量x |
/= | 复合赋值运算,表示前后两个数值的商赋给前面的变量 | 表示先将变量x除以5,再将商赋给变量x |
%= | 复合赋值运算,表示将前面的数值除以后面的数值所得的余数赋给前面的变量 | 将变量x与5相除后的余数赋给变量x |
^= | 复合运算符,表示将前面的数值的后面数值次方赋给前面的变量 | x^=3表示将变量x的3次方赋给变量x |
(3)条件运算符
awk中的条件运算符只有一个,其语法如下:`expression?value1:value2`
这是一个三目运算符,当表达式expression的值为真时,返回值为value1;否则,返回值为value2。
示例:
[root@localhost ~]# cat file
3 6
10 9
3 3
[root@localhost ~]# awk '{max=$1>$2?$1:$2;print NR,"max=",max}' file
1 max= 6
2 max= 10
3 max= 3
(4)逻辑运算符
awk支持3种逻辑运算,分别为逻辑与、逻辑或和逻辑非
运算符 | 说明 | 举例 |
---|---|---|
&& | 逻辑与,当前后两个表达式的值全部为真时,其运算结果才为真 | 1>2&&3>2的值为假 |
|| | 逻辑或,前后两个表达式只要有一个为真,则其运算结果为真。当两个表达式的值都为假时,其运算结果才为假 | 1>2&&3>2的值为真 |
! | 逻辑非,当表达式的值为真时,其运算结果为假;当表达式的值为假时,其运算结果为真 | !(1>2)的值为真 |
(5)关系运算符
运算符 | 说明 | 举例 |
---|---|---|
> | 大于 | 5>2的值为真 |
>= | 大于或者等于 | 8>=8的值为真 |
< | 小于 | 8<12的值为真 |
<= | 小于或者等于 | 4<=7的值为真 |
== | 等于 | 9==9的值为真 |
!= | 不等于 | 1!=3的值为真 |
~ | 匹配运算符 | $1 ~ /^T/表示匹配第一个字段以字符T开头的记录 |
!~ | 不匹配运算符 | $1 !~ /a/表示匹配第一个字段不含有字符a的记录 |
[root@localhost test11]# awk '$1~/^w/ {print}' file
wangmei 70
[root@localhost ~]# awk -F: 'BEGIN {printf "%-10s %-5s %-15s\n","user","uid","shell"} $3==0,$7~"nologin" {printf "%-10s %-5s %-15s\n",$1,$3,$7}' /etc/passwd
user uid shell
root 0 /bin/bash
bin 1 /sbin/nologin
[root@localhost ~]# awk -F: 'BEGIN {printf "%-10s %-5s %-15s\n","user","uid","shell"} $3==0,$7~"nologin" {printf "%-10s %-5s %-15s\n",$1,$3,$7} END{print "-------end---------"}' /etc/passwd
user uid shell
root 0 /bin/bash
bin 1 /sbin/nologin
-------end---------
说明:$3==0,$7~"nologin"表示匹配符合条件1到条件2之间的所有行(成对多次匹配),如果条件2不成立,则一直匹配到文件尾部
[root@localhost ~]# cat /etc/passwd | awk '{FS=":"} $3 < 10 {print $1 "\t" $3}' 查阅/etc/passwd文件第三列小于10以下的数据,并且仅列出账号与第三列。但是这样的写法会导致第一行无法正确显示出来,由于读入第一行的时候,变量$1,$2……默认还是以空格符为分隔的,定义的FS=“:”仅能在第二行后才开始生效。可以使用BEGIN这个关键字来达到从第一行读入。
[root@localhost ~]# cat /etc/passwd | awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t" $3}'
[root@localhost ~]# cat /etc/passwd | awk -F : '$3 < 10 {print $1 "\t" $3}'
说明:$3变量是空值,但是后面与之作比较的是整数,所以$3的值是0,满足条件,所以打印$1的结果,在此打印$3的结果时$3是空串。
(6)其他运算符
awk还支持其他的一些运算符,例如正号+、负号-、自增++以及自减--等,这些运算符的使用方法与其他的语言的使用方法完全相同。
5.3.5 awk的模式
在awk中,匹配模式处于非常重要的地位,它决定着匹配模式后面的操作会影响到哪些文本行。awk中的匹配模式主要包括关系表达式、正则表达式、混合模式,BEGIN模式以及END模式等。
(1)关系表达式
awk提供了许多关系运算符,例如大于>、小于<或者等于==等。awk允许用户使用关系表达式作为匹配模式,当某个文本行满足关系表达式时,将会执行相应的操作。
[root@localhost test11]# cat file
liming 80
wangmei 70
zhangsan 90
lisi 81
[root@localhost test11]# awk '$2 > 80 {print}' file
zhangsan 90
lisi 81
(2)正则表达式
awk支持以正则表达式作为匹配模式,与sed一样,用户需要将正则表达式放在两条斜线之间,其基本语法如下:/regular_expression/
[root@localhost test11]# awk '/^l/{print}' file
liming 80
lisi 81
[root@localhost test11]# awk '/^l|z/{print}' file
liming 80
zhangsan 90
lisi 81
(3)混合模式
awk不仅支持单个的关系表达式或者正则表达式作为模式,还支持使用逻辑运算符&&、||或者!将多个表达式组合起来作为一个模式。其中,&&表示逻辑与,||表示逻辑或,!表示逻辑非。
[root@localhost test11]# awk '/^l/ && $2>80 {print}' file
lisi 81
(4)区间模式
awk还支持一种区间模式,也就是说通过模式可以匹配一段连续的文本行。区间模式的语法如下:
pattern1, pattern2
其中,pattern1和pattern2都是前面所讲的匹配模式,可以是关系表达式,也可以是正则表达式等。当然,也可以是这些模式的混合形式。
[root@localhost test11]# awk '/^l/ && $2>80 {print}' file
lisi 81
(5)BEGIN模式
BEGIN模式是一种特殊的内置模式,其成立的时机为awk程序刚开始执行,但是又尚未读取任何数据之前。因此,该模式所对应的操作仅仅被执行一次,当awk读取数据之后,BEGIN模式便不再成立。所以,用户可以将与数据文件无关,而且在整个程序的生命周期中,只需执行1次的代码放在BEGIN模式对应的操作中。
[root@localhost test11]# cat 1.sh
#!/bin/awk -f
BEGIN {print "hello,world"}
[root@localhost test11]# ./1.sh
hello,world
[root@localhost ~]# awk -F: 'BEGIN {printf "%-15s %-3s %-15s\n","user","uid","shell"} $3==0,$7~"nologin" {printf "%-15s %-3s %-15s\n",$1,$3,$7}' /etc/passwd
(6)END模式
END模式是awk的另外一种特殊模式,该模式成立的时机与BEGIN模式恰好相反,它是在awk命令处理完所有的数据,即将退出程序时成立,在此之前,END模式并不成立。无论数据文件中包含多少行数据,在整个程序的生命周期中,该模式所对应的操作只被执行1次。因此,一般情况下,用户可以将许多善后工作放在END模式对应的操作中。
[root@localhost test11]# cat 2.sh
#! /bin/awk -f
BEGIN {
print "scores report"
print "================================="
}
{ print }
END {
print "================================"
print "printing is over"
}
[root@localhost test11]# ./2.sh file
scores report
=================================
liming 80
wangmei 70
zhangsan 90
lisi 81
================================
printing is over
[root@localhost ~]# awk -F: 'BEGIN {printf "%-15s %-3s %-15s\n","user","uid","shell"} $3==0,$7~"nologin" {printf "%-15s %-3s %-15s\n",$1,$3,$7} END {print "-----End file-----"}' /etc/passwd
5.3.6 awk控制语句
作为一种程序设计语言,awk支持程序的流程控制,例如条件判断、循环以及其他的一些流程控制语句,例如continue、break以及exit等。掌握这些基本的流程控制语句,对于编写出结构良好的程序非常重要。
(1)if语句的功能是根据用户指定的条件来决定执行程序的哪个分支,其语法如下:
if (expression)
{
statement1
statement2
}
else
{
statement3
statement4
}
[root@localhost test11]# cat 3.sh
#! /bin/awk -f
{
if ($2 >= 90) {
print $1,"A"
}
else
{
if($2 >= 80 && $2 < 90)
{
print $1,"B"
}
else
{
print $1,"C"
}
}
}
[root@localhost test11]# cat file
liming 80
wangmei 70
zhangsan 90
lisi 81
[root@localhost test11]# ./3.sh file
liming B
wangmei C
zhangsan A
lisi B
[root@localhost ~]# awk -F: '{if ($1=="root") printf "%-10s %-15s\n", $1, "Admin"; else printf "%-10s %-15s\n",$1, "Common User"}' /etc/passwd | head -n 3
[root@localhost ~]# awk '{if ($1>$2) print NR,"max =",$1;else print NR,"max =",$2}' file
(2)while语句是另外一种常用的循环结构,其语法如下:
写法1:
while (expression)
{
statement1
statement2
……
}
写法2:
do {
statement1
statement2
...
}while (expression)
当表达式expression的值为真时,执行循环体中的statement1以及statement2等语句。如果循环体中只包含一条语句,则可以省略大括号。
[root@localhost ~]# awk 'BEGIN{while(i<=100) {sum+=i;i++}print "sum=",sum}'
sum= 5050
[root@localhost test11]# cat 4.sh
#! /bin/awk -f
BEGIN {
i=0
while (++i <= 9)
{
print i^2
}
}
[root@localhost test11]# ./4.sh
1
4
9
16
25
36
49
64
81
(3)for语句
for(expression1; expression2; expression3)
{
statement1
statement2
...
}
for循环语句通常用在循环次数已知的场合中,其语法如下:
在上面的语法中,表达式expression1通常用来初始化循环变量,表达式expression2通常用来指定循环执行的条件,表达式expression3通常用来改变循环变量的值。当表达式expression2的值为真时,执行循环体中的语句。
[root@localhost ~]# awk 'BEGIN {for(i=1;i<=100;i++){sum+=i;}print "sum=",sum}'
sum= 5050
[root@localhost test11]# cat 6.sh
#! /bin/awk -f
BEGIN {
for(i=1;i<=9;i++)
{
for(j=1;j<=i;j++)
{
if(i*j<10)
{
row=row" "i*j
}
else
{
row=row" "i*j
}
}
print row
row=""
}
}
[root@localhost test11]# ./6.sh
1
2 4
3 6 9
4 8 12 16
5 10 15 20 25
6 12 18 24 30 36
7 14 21 28 35 42 49
8 16 24 32 40 48 56 64
9 18 27 36 45 54 63 72 81
for循环还可以用来遍历数组元素:
语法: for(变量 in 数组){语句}
#统计用户的shell
[root@localhost ~]# awk -F: '$NF!~/^$/{BASH[$NF]++}END{for(A in BASH){printf "%15s:%i\n",A,BASH[A]}}' /etc/passwd
(4)break语句
用户可以通过使用break语句在适当的时机退出for以及while等循环结构,而不必等到循环结构自己退出。
(5)continue语句
continue语句的主要功能是跳过循环结构中该语句后面的尚未执行的语句。break语句与continue语句的功能有着明显的区别,前者是直接退出循环结构,而后者是跳过循环体中尚未执行的语句,重新执行下一次循环。
(6)next语句
next语句的功能与continue语句非常相似,但是next语句并不是用在循环结构中,而是用在整个awk程序中。当awk执行程序时,如果遇到next语句,则提前结束对本行文本的处理,awk会继续读取下一行数据,并且从第一个模式及其操作开始执行。
例如,下面的命令将显示其ID号为奇数的用户:
[root@localhost ~]# awk -F: '{if($3%2==0) next;print $1,$3}' /etc/passwd |head -n 3
bin 1
adm 3
sync 5
(7)exit语句
exit语句的功能是终止awk程序的执行
5.3.7 awk使用数组
(1)定义数组
array[index]
:数组array的索引可以是任意字符串,如果数组某元素事先不存在,那么awk会自动创建此元素并初始化为空串。
(2)使用for循环可以遍历数组元素:for(var in 数组) {语句}
-
var用于引用数组下标
#统计用户的shell
[root@localhost ~]# awk -F: '$NF!~/^$/{BASH[$NF]++}END{for(A in BASH){printf "%15s:%i\n",A,BASH[A]}}' /etc/passwd
[root@localhost ~]# netstat -ant | awk '/^tcp/ {++S[$NF]} END {for(a in S) print
a, S[a]}'
ESTABLISHED 1
LISTEN 10
(3)删除数组变量
delete array[index]
面试题解析
文件内容如下
1.1.1.1 11
1.1.1.1 22
1.1.1.1 33
1.1.1.1 44
2.2.2.2 11
2.2.2.2 22
2.2.2.2 33
2.2.2.2 44
要求使用sed及awk分别将文件输出:
1.1.1.1 11 22 33 44
2.2.2.2 11 22 33 44
# awk '{s[$1]=s[$1]" "$2} END {for (k in s) {print k,s[k]}}' c.txt
1.1.1.1 11 22 33 44
2.2.2.2 11 22 33 44
# sed -r 'N;N;N;s/\n/ /g;s/(.*)(11 )(.*)(22 )(.*)(33 )(.*)(44)/\1\2\4\6\8/' c.txt
1.1.1.1 11 22 33 44
2.2.2.2 11 22 33 44