Shell脚本学习笔记
1 Shell基本概述
1.1 Shell脚本的基本格式
Shell脚本文件一般以.sh为后缀命令,格式一般有以下两种:
格式一(推荐):
#!/usr/bin/bash
echo "hello world!"
格式二:
echo "hello world!"
1.2 Shell脚本的执行方式
Shell脚本有三种执行方式,分别如下所示:
第一种: 直接使用bash解析器执行脚本
bash test.sh
第二种: 同样是使用bash解析器执行脚本
sh test.sh
第三种: 给脚本添加执行权限,然后使用./执行脚本
chmod 755 test.sh
./test.sh
对于格式一的脚本: 因为在脚本文件中指定了是shell脚本,所以可以使用以上三种方式来执行,但是脚本文件必须要有执行的权限才能使用第三种执行方式。
对于格式二的脚本: 因为在脚本文件中没有指定是哪种脚本,所以只能使用以上的第一种和第二种方式来执行。
1.3 BashShell脚本的特性
-
自动补全命令或路径:使用tab键自动实例。
-
记录历史命令的功能:使用history命令可以展示历史命令。
history
-
命令使用别名的功能:使用alias为命令指定别名。
# 查看所有的命令的别名 alias # 为命令指定别名(=的前后不能有空格) alias t="echo test" # 取消命令的别名 unalias t
-
常用的快捷键功能:
移动类型:快捷键 功能 ctrl + a 将光标移动到命令行开头处 ctrl + e 将光标移动到命令行结尾处 ctrl + f 将光标向前方移动一个字符 ctrl + b 将光标向后方移动一个字符 ctrl + 方向左键 将光标移动到前一个单词开头 ctrl + 方向右键 将光标移动到后一个单词开头 删除类型:
快捷键 功能 ctrl + u 删除光标前面所有的字符 ctrl + k 删除光标后面所有的字符 ctrl + d 删除光标所在位置的字符 ctrl + w 删除光标前面的一个单词 alt+ d 删除光标后面的一个单词 ctrl + y 撤销前一次 ctrl+u 或 ctrl+k 所删除的字符 ctrl + ? 撤销前一次输入 替换类型:
快捷键 功能 ctrl + t 将光标当前位置的字符与前一个字符交换 alt + t 将光标当前位置的单词和前一个单词交换 alt + u 将光标当前位置的字符变为大小 alt + l 将光标当前位置的字符变为小写 其它类型:
快捷键 功能 ctrl + r 输入单词搜索历史命令 ctrl + p 返回上一次输入的命令 ctrl + l 清屏(相当于clear) -
输入输出重定向:文件描述符 0 是标准输入,1 是标准正确输出,2 是标准错误输出
command > file:将command的标准正确输出以覆盖的方式重定向到file command >> file:将command的标准正确输出以追加的方式重定向到file command 2 > file:将command的标准错误输出以覆盖的方式重定向到file command 2 >> file:将command的标准错误输出以追加的方式重定向到file command < file:将file的内容作为command的标准输入 n >& m:将输出文件m和n合并 n <& m: 将输入文件m和n合并
-
管道 | :将上一次命令的标准输出作为后者命令的输入。
-
命令排序:
操作符 功能 命令1 ; 命令2 命令1和命令2之间没有逻辑关系,无论命令1是否执行成功,都会执行命令2 命令1 && 命令2 命令1执行成功之后才会执行命令2 命令1 || 命令2 命令1执行失败之后才会执行命令2 -
通配符:
配符 功能 * 匹配任意多个字符 ? 匹配任意一个字符 [] 匹配括号中任意一个字符:[0-9]、[a-z]、[A-Z]、[a-Z] {} 集合:touch file{1…9} () 在子shell中执行:(cd /boot; ls) \ 转义字符 -
echo输出不同颜色的内容:
echo -e "\033[30m 黑色内容 \033[0m" echo -e "\033[31m 红色内容 \033[0m" echo -e "\033[32m 绿色内容 \033[0m" echo -e "\033[33m 黄色内容 \033[0m" echo -e "\033[34m 蓝色内容 \033[0m" echo -e "\033[35m 紫色内容 \033[0m" echo -e "\033[36m 天蓝内容 \033[0m" echo -e "\033[37m 的色内容 \033[0m"
-
printf格式化输出文本:printf ‘输出类型输出格式’ 输出内容
输出类型: %ns:输出字符串。n是数字,指代输出几个字符 ; %ni:输出整数。n是数字,指代输出几个数字; %m.nf: 输出浮点数。m和n数字,指代输出的总位数和小数位数。如%8.2f代表共输出8位数,其中2位小数,6位整数; 输出格式: \a: 输出警告声音; \b:输出退格键; \f:清除屏幕; \n:换行; \r:回车,也就是Enter键; \t:水平输出退格键,也就是Tab键; \v:垂直输出退格键,也就是Tab键;
示例:每行有6列,每列都是字符串
printf '%s\t %s\t %s\t %s\t %s\t %s\t\n' $(cat student.txt)
2 Shell脚本变量
2.1 变量常见类型
变量类型 | 变量说明 |
---|---|
自定义变量 | 仅在当前shell中有效 |
系统环境变量 | 在当前shell和子shell中有效 |
位置参数变量 | 用于执行脚本时传递参数 |
预先定义变量 |
2.1.1 自定义变量
- 定义变量:变量名=变量值 (等号的前后不能有空格)
IP="127.0.0.1"
- 引用变量:$变量名 或 ${变量名}
IP="127.0.0.1" echo $IP #输出127.0.0.1 echo ${IP} #输出127.0.0.1 echo $IPTEST #输出为空,因为变量IPTEST不存在 echo ${IP}TEST #输出127.0.0.1TEST
- 查看变量:echo $变量名 或 echo ${变量名}
echo $IP echo ${IP}
- 取消变量:unset 变量名
unset IP
2.1.2 系统环境变量
- 定义变量:export 变量
export IP="127.0.0.1"
- 引用变量:$变量名 或 ${变量名}
export IP="127.0.0.1" echo $IP #输出127.0.0.1 echo ${IP} #输出127.0.0.1 echo $IPTEST #输出为空,因为变量IPTEST不存在 echo ${IP}TEST #输出127.0.0.1TEST
- 查看变量:echo $变量名 或 echo $变量名 或 env | grep 变量名
echo $IP echo ${IP} env | grep IP
- 取消变量:unset 变量名
unset IP
2.1.3 位置参数变量
- 参数变量:$1、$2、$3、$4、$5、$6、$7、$8、$9、${10}
- 示例:
echo.sh脚本内容如下所示:
执行echo.sh脚本时传递三个位置参数:#!/usr/bin/bash echo $1 echo $2 echo $3
输出结果如下所示:bash echo.sh a b c
a b c
2.1.4 预先定义变量
-
预先定义变量如下所示:
量名称 变量含义 $0 脚本文件名称 $* 所有的参数 $@ 所有的参数 $# 参数的个数 $$ 当前进程的PID $! 上一个后台进程的PID $? 上一个命令的返回值(0表示成功) -
示例
echo.sh脚本内容如下所示:#!/usr/bin/bash echo $1 echo $2 echo $3 echo $0 echo $* echo $@ echo $# echo $$
执行echo.sh脚本时传递三个位置参数:
bash echo.sh a b c
输出结果如下所示:
a b c echo.sh a b c a b c 3 13067
2.2 变量赋值方式
2.2.1 显示赋值
- 变量名=变量值
ip=127.0.0.1 date1=`date +%F` # +前面必须有空格,后面不能有空格 date2=$(date +%F) # +前面必须有空格,后面不能有空格 echo $ip # 输出内容为: 127.0.0.1 echo $date1 # 输出内容为: 2019-07-26 echo $date2 # 输出内容为: 2019-07-26
2.2.2 从键盘读取变量值
- 直接一个读取:read 变量名列表
- 带提示信息的读取:read -p “提示信息:” 变量名列表
- 等待一定时间的读取:read -t 5 -p “提示信息:” 变量名列表
- 读指定数目的字符:read -n 2 变量名
read v1 v2 # 读取两个值分别赋值给变量v1和v2 read -p "please input:" v3 v4 # 读取两个值分别赋值给变量v3和v4 read -t 5 -p "please input:" v5 v6 # 等待5秒,如超时,则自动取消读取 read -n 3 v7 # 读取3个字符,读取到3个字符后自动完成读取
2.2.3 变量的强弱引用
- 弱引用:使用双引号引用变量,会解析变量。(变量的解析用双引号)
- 强引用:使用单引号引用变量,不会解析变量。(输出特殊字符用单引号)
IP="127.0.0.1" echo "IP is ${IP}" # 输出的内容为:IP is 127.0.0.1 echo 'IP is ${IP}' # 输出的内容为: IP is ${IP}
2.2.4 两种命令替换
- ``命令替换等价于$()命令替换,命令替换会被先执行
touch `date +%F`_file1.txt # 创建一个名称为2019-07-26_file1.txt的文件 touch $(date +%F)_file2.txt # 创建一个名称为2019-07-26_file2.txt的文件
2.3 变量数值运算
2.3.1 整数运算expr
- 方式一:expr 整数1 op 整数2
- 方式二:expr 变量1 op 变量2
- 说明:op可以为+、-、\*、/、%,且op前后都必须要有空格。
expr 3 + 2 # 输出结果为5 expr 3 - 2 # 输出结果为1 expr 3 \* 2 # 输出结果为6 expr 3 / 2 # 输出结果为1 expr 3 % 2 # 输出结果为1 n1=4 n2=2 expr $n1 + $n2 # 输出结果为6 expr $n1 - $n2 # 输出结果为2 expr $n1 \* $n2 # 输出结果为8 expr $n1 / $n2 # 输出结果为2 expr $n1 % $n2 # 输出结果为0
2.3.2 整数运算$(())
- 方式一:$((整数1 op 整数2))
- 方式二:$((变量1 op 变量2))
- 说明:op可以为+、-、*、/、%等,且op前后可以有空格,也可以没有空格。
echo $((3 + 2)) # 输出结果为5 echo $((3 - 2)) # 输出结果为1 echo $((3 * 2)) # 输出结果为6 echo $((3 / 2)) # 输出结果为1 echo $((3 % 2)) # 输出结果为1 n1=4 n2=2 echo $(($n1 + $n2)) # 输出结果为6 echo $(($n1 - $n2)) # 输出结果为2 echo $(($n1 * $n2)) # 输出结果为8 echo $(($n1 / $n2)) # 输出结果为2 echo $(($n1 % $n2)) # 输出结果为0
2.3.3 整数运算$[]
- 方式一:$[整数1 op 整数2]
- 方式二:$[变量1 op 变量2]
- 说明:op可以为+、-、*、/、%等,且op前后可以有空格,也可以没有空格。
echo $[3 + 2] # 输出结果为5 echo $[3 - 2] # 输出结果为1 echo $[3 * 2] # 输出结果为6 echo $[3 / 2] # 输出结果为1 echo $[3 % 2] # 输出结果为1 n1=4 n2=2 echo $(($n1 + $n2)) # 输出结果为6 echo $(($n1 - $n2)) # 输出结果为2 echo $(($n1 * $n2)) # 输出结果为8 echo $(($n1 / $n2)) # 输出结果为2 echo $(($n1 % $n2)) # 输出结果为0
2.3.4 整数运算let
- 方式一:let 变量=整数1 op 整数2
- 方式二:let 变量=变量1 op 变量2
- 说明:op可以为+、-、*、/、%等,且op前后不能有空格,且=前后也不能有空格。
let v1=3+2 # v1值为5 let v2=3-2 # v2值为1 let v3=3*2 # v3值为6 let v4=3/2 # v4值为1 let v5=3%2 # v5值为1 n1=4 n2=2 let v1=$n1+$n2 # v1值为6 let v2=$n1-$n2 # v2值为2 let v3=$n1*$n2 # v3值为8 let v4=$n1/$n2 # v4值为2 let v5=$n1%$n2 # v5值为0
2.4 变量删除替换
2.4.1 从前往后删除变量内容
-
${变量#*分隔符}:#表示从前面开始匹配,*表示匹配任意多个字符,分隔符可以是单个字符,也可以是个字符串
url=www.baidu.com # 输出变量的值 echo ${url} # 输出结果为:www.baidu.com # 输出变量的长度 echo ${#url} # 输出结果为:13 # 从前开始往后匹配任意多个字符,直到第一个.为止,并将第一个.之前的内容全部删除(最短匹配) echo ${url#*.} # 输出结果为:baidu.com # 从前开始往后匹配任意多个字符,直到最后一个.为止,并将最后一个.之前的内容全部删除(贪婪匹配) echo ${url##*.} # 输出结果为:com
2.4.2 从后往前删除变量内容
-
${变量%分隔符*}:%表示从后面开始匹配,*表示匹配任意多个字符,分隔符可以是单个字符,也可以是个字符串
url=www.baidu.com # 从后开始往前匹配任意多个字符,直到第一个.为止,并将第一个.之后的内容全部删除(最短匹配) echo ${url%.*} # 输出结果为:www.baidu # 从后开始往前匹配任意多个字符,直到最后一个.为止,并将最后一个.之后的内容全部删除(贪婪匹配) echo ${url%%.*} # 输出结果为:www
2.4.3 索引及切片
- 索引:即每个字符的索引位置,从0开始。
- 切片:${变量名:起始索引:结束索引} 或 ${变量名:起始索引}
url=www.baidu.com # 获取索引位置从1到5的内容 echo ${url:1:5} # 输出结果为:ww.ba # 获取索引位置从5到末尾的内容 echo ${url:6} # 输出结果为:idu.com
2.4.4 变量内容替换
- 语法:${变量名/替换前的内容/替换后的内容}
- 说明:/前后不能有空格
url=www.baidu.com # 把baidu替换成google echo ${url/baidu/google} # 输出结果为:www.google.com # 从前往后查找并把查找到的第一个w替换成a(最短匹配) echo ${url/w/a} # 输出结果为:aww.baidu.com # 从前往后查找并把查找到的所有的w替换成a(贪婪匹配) echo ${url//w/a} # 输出结果为:aaa.baidu.com
2.4.5 变量替代
- 方式一:${变量名-新的变量值}
- 说明:如果变量没有值,则会使用新的变量值替代,如果变量已经有值(包括空值),则不会替代。
url=www.baidu.com echo $url # 输出结果为:www.baidu.com # url在有值的情况下进行替换,则不会替换 echo ${url-www.sina.com} # 输出结果为:www.baidu.com # 把url值设置为空 url= # url在空值的情况下进行替换,则不会替换 echo ${url-www.sina.com} # 输出结果为空 # 取消url的值 unset url # url在没有值的情况下进行替换,则会替换 echo ${url-www.sina.com} # 输出结果为:www.sina.com
- 方式二:${变量名:-新的变量值}
- 说明:如果变量没有值(包括空值),则会使用新的变量值替代,如果变量已经有值,则不会替代。
url=www.baidu.com echo $url # 输出结果为:www.baidu.com # url在有值的情况下进行替换,则不会替换 echo ${url:-www.sina.com} # 输出结果为:www.baidu.com # 把url值设置为空 url= # url在空值的情况下进行替换,则会替换 echo ${url:-www.sina.com} # 输出结果为:www.sina.com # 取消url的值 unset url # url在没有值的情况下进行替换,则会替换 echo ${url:-www.taobao.com} # 输出结果为:www.taobao.com
2.4.6 变量自增自减
- 变量自增:i++ 或 ++i
- 变量自减:i-- 或 --i
i=1 echo $((i++)) # 输出结果为:1 echo $((++i)) # 输出结果为:3 echo $((i--)) # 输出结果为:3 echo $((--i)) # 输出结果为:1
3 Shell条件判断
3.1 文件判断
3.1.1 文件判断语法
- 判断语法
判断语法 适用场景 返回结果说明 test 条件表达式 适用于一般情况下的条件判断 如果为真,则返回值为0,否则返回值为1 [条件表达式] 一般用在if语句的条件判断中 [[条件表达式]] 一般用在字符串的条件判断中 - 判断操作符
判断操作符 功能说明 -e dir|file 是否存在文件或目录 -d dir 是否存在目录 -f file 是否存在文件 -r file 是否有读的权限 -w file 是否有写的权限 -x file 是否有执行的权限 -L line 是否存在文件
3.1.2 文件判断示例
-
test示例:
test -d a && mkdir -p a/b # 如果当前目录下存在名称为a的目录,则在a下面创建名称为b的目录 test ! -d a && mkdir -p a # 如果当前目录下不存在名称为a的目录,则在当前目录下创建名称为a的目录 test -d a || mkdir -p a # 如果当前目录下不存在名称为a的目录,则在当前目录下创建名称为a的目录
-
[]示例:
#!/usr/bin/bash # 如果当前目录下不存在名称为a的目录,则在当前目录下创建名称为a的目录 if [ ! -d a ]; then mkdir -p a fi
-
备份综合示例:
#!/usr/bin/bash Mysql_Dump=/usr/bin/mysqldump User=root Password=123456 Data=testdb Backup_Path=/backup/$(date +%F) test -d $Backup_Path || mkdir -p $Backup_Path if [ $? -eq 0 ]; then echo "********Backup is start********" $Mysql_Dump -u$User -p$Password -B $Data > $Back_Path/mysql_db.sql if [ $? -eq 0 ]; then echo "*********Backup is end*********" fi fi
3.2 数值判断
3.2.1 数值判断语法
- 判断语法:[ 整数1 操作符 整数2 ]
- 判断操作符
判断操作符 功能说明 num1 -gt nam2 num1是否大于num2 num1 -lt nam2 num1是否小于num2 num1 -eq nam2 num1是否等于num2 num1 -ne nam2 num1是否不等于num2 num1 -ge nam2 num1是否大于或等于num2 num1 -le nam2 num1是否小于或等于num2
3.2.2 数值判断示例
-
创建用户示例:如果用户不存在才创建
#!/usr/bin/bash read -p "Please input a username: " user id $user &>/dev/null if [ $? -eq 0 ]; then echo "user $user already exists" else useradd $user if [ $? -eq 0 ]; then echo "$user is created." fi fi
-
磁盘使用率示例:如果磁盘/目录使用率超过80%则提示
#!/usr/bin/bash Disk_Used=$(df -h|grep "/$"|awk '{print $5}'|awk -F '%' '{print $1}') if [ $Disk_Used -ge 80 ]; then echo "\033[31m Disk / dir is used: ${Disk_Used}% \033[0m " fi
-
内存使用率示例:如果磁盘/目录使用率超过80%则提示
#!/usr/binbash Mem_Total=$(free -m|grep "^M"|awk '{print $2}') Mem_Used=$(free -m|grep "^M"|awk '{print $3}') Mem_Used_Percent=$((($Mem_Used*100)/$Mem_Total)) if [ $Mem_Used_Percent -ge 70 ]; then echo "\033[31m Memory is used more then: ${Mem_Used_Percent}% \033[0m" fi
3.3 字符串判断
3.3.1 字符串长度判断
- 说明:变量为空或未定义,则长度都为0
url="www.baidu.com" echo ${#url} # 输出字符串的长度。输出结果为13 [ -z "$url" ];echo $? # 判断字符串的长度是否为0。输出结果为1 [ -n "$url" ];echo $? # 判断字符串的长度是否不为0。输出结果为0
3.3.2 字符串内容判断
- 说明:可以用 = 进行判断,也可以用 == 进行判断
username="admin" [ $username = "admin" ];echo $? # 判断字符串是否相等。输出结果为0 [ $username == "admin" ];echo $? # 判断字符串是否相等。输出结果为0 [ $username != "admin" ];echo $? # 判断字符串是否不相等。输出结果为1
3.3.3 多整数条件判断
- 说明:-a(并且),-o(或者),&&(并且),||(或者)
# -a 和 -o 只能用于 [ ] 中,而 && 和 || 只能用于 [[ ]] 中 [ 1 -gt 2 -a 5 -gt 3 ];echo $? # 判断两个条件是否都成立。输出结果为1 [ 1 -gt 2 -o 5 -gt 3 ];echo $? # 判断两个条件中是否存在成立的条件。输出结果为0 [[ 1 -gt 2 && 5 -gt 3 ]];echo $? # 判断两个条件是否都成立。输出结果为1 [[ 1 -gt 2 || 5 -gt 3 ]];echo $? # 判断两个条件中是否存在成立的条件。输出结果为0
3.3.4 正则判断
- 说明:使用 =~ 表明是正则判断,而且只能用 [[ ]] 进行正则判断
username="admin" [[ $username =~ ^a ]];echo $? # 判断变量是否以字母a开头。输出结果为0 [[ $username =~ n$ ]];echo $? # 判断变量是否以字母n结尾。输出结果为0 num=12 [[ $num =~ ^[0-9]+$ ]];echo $? # 判断是否为纯数字。输出结果为0 num=12ab [[ $num =~ ^[0-9]+$ ]];echo $? # 判断是否为纯数字。输出结果为1
3.4 综合示例: 批量创建用户
- 批量创建用户脚本如下:
#!/usr/bin/bash read -p "please input a number:“ num if [[ ! $num =~ ^[0-9]+$ ]]; then echo "not a number error." && exit 1 fi read -p "please input a username prefix: ” prefix if [ -z $prefix ]; then echo "prefix is null." && exit 1 fi for i in $(seq $num); do username=$prefix$i useradd $username &>/dev/null echo "123"|passwd --stdin $username &>/dev/null if [ $? -eq 0 ]; then echo "$username is created." fi done
4 Shell流程控制
4.1 if 流程控制
4.1.1 if 流程控制语法
- 说明:可以根据需求选择单分支、双分支、多分支的流程控制。
if [ 条件表达式 ]; then 命令语句序列 elif [ 条件表达式 ]; then 命令语句序列 else 命令语句序列 fi
4.1.2 if 流程控制示例
- 数字分类
#!/usr/bin/bash read -p "please input a number:" num if [ $num -gt 0 ]; then echo "Positive number" elif [ $num -eq 0 ]; then echo "Zero" else echo "Negative number" fi
4.2 case 流程控制
4.2.1 case 流程控制语法
- 说明:可以根据需求选择单分支、双分支、多分支的流程控制。
case 变量 in 模式1) 命令语句序列;; 模式2) 命令语句序列;; 模式3) 命令语句序列;; *) 命令语句序列;; esac
4.2.2 case 流程控制示例
- 字符串判断
#!/usr/bin/bash read -p "please input a system:" system case $system in Linux) echo "Linux";; Windows) echo "Windows";; *) echo "Useage by input Linux or Windows";; esac
4.3 综合示例: 批量删除用户
- 批量删除用户脚本如下:
#!/user/bin/bash read -p "please input a number:" num if [[ ! $num =~ ^[0-9]+$ ]]; then echo "not a number error." && exit 1 fi read -p "please input a username prefix:" prefix if [ -z $prefix ]; then echo "prefix is null." && exit 1 fi for i in $(seq $num); do username=$prefix$i read -p "delete user ${username}? [y|Y|Yes|n|N|No]" ready case $ready in y|Y|Yes) id $username &>/dev/null if [ $? -eq 0 ]; then userdel $username &>/dev/null echo "delete user ${username} success" else echo "delete user ${username} fail" fi ;; n|N|No) echo "cancel delete user ${username}" ;; *) exit 1 ;; esac done
5 Shell条件循环
5.1 for 条件循环
5.1.1 for 条件循环语法
- 说明:do可以提到与for同一行的位置,变量名在循环体里不一定要引用
for 变量名 in 取值列表 do 循环体 done
5.1.2 for 条件循环示例
- 批量探测主机存活状态
#!/usr/bin/bash for i in {1..10} do # 使用子shell并发执行 { ip=192.168.70.$i ping -c1 -W1 $ip &>/dev/null if [ $? -eq 0 ]; then echo "$ip is start" else echo "$ip is stop" fi }& done # 等待前台结束再执行后面的内容 wait echo "Get Ip Finished."
5.2 while 条件循环
5.2.1 while 条件循环语法
- 说明:do可以提到与for同一行的位置
while 循环条件 do 循环体 done
5.2.2 for 条件循环示例
- 数字分类
#!/usr/bin/bash while read num do if [ $num -gt 0 ]; then echo "postive number" elif [ $num -lt 0 ]; then echo "negative number" elif [ $num -eq 0 ]; then exit 1 fi done
5.3 Shell内置结束命令
内置命令 | 命令功能 |
---|---|
exit | 退出整个程序 |
break | 结束循环 |
continue | 结束本次循环,直接进行下一次循环 |
5.4 综合示例: 读取文件批量创建用户
- 使用 for 循环批量创建用户脚本如下(user.txt中每一行都只有一个用户名和密码,且用户名和密码之间以空格分开):
#!/usr/bin/bash # 指定文件每一行的分隔符为\n,否则会把空格作为分隔符 IFS=$'\n' for user in $(cat user.txt) do username=$(echo $user|awk '{print $1}') password=$(echo $user|awk '{print $2}') id $username &>/dev/null if [ $? -ne 0 ]; then useradd $username &>/dev/null echo $password|passwd --stdin $username &>/dev/null if [ $? -eq 0 ]; then echo "create $username success." else echo "create $username failed." fi else echo "$username already exists." fi done
- 使用 while 循环批量创建用户脚本如下(user.txt中每一行都只有一个用户名和密码,且用户名和密码之间以空格分开):
#!/usr/bin/bash while read user do username=$(echo $user|awk '{print $1}') password=$(echo $user|awk '{print $2}') id $username &>/dev/null if [ $? -ne 0 ]; then useradd $username &>/dev/null echo $password|passwd --stdin $username &>/dev/null if [ $? -eq 0 ]; then echo "create $username success." else echo "create $username failed." fi else echo "$username already exists." fi done<user.txt
6 Shell数组应用
6.1 数组分类
- 数组分类:Shell中的数组分为 普通数组 和 关联数组 两类。
- 变量切片:变量切片不属于数组,但是可以使用索引访问值。例如 username=admin 的变量切片的索引和值如下表:
变量切片示例:值 a d m i n 索引 0 1 2 3 4 username=admin # 获取索引位置从1到2的内容 echo ${username:1:2} # 输出结果为:dm # 获取索引位置从3到末尾的内容 echo ${username:3} # 输出结果为:in
- 普通数组:普通数组只能使用整数作为数组索引。例如 books=(java python shell) 的普通数组的索引和值如下表:
值 java python shell 索引 0 1 2 - 关联数组:关联数组可以使用字符串作为数组索引。例如 user=([name]=admin [age]=18) 的关联数组的索引和值如下表:
值 admin age 索引 name age
6.2 普通数组
6.2.1 普通数组语法
- 普通数组语法:
# 普通数组单个赋值语法 数组名[索引] = 值 # 普通数组一次性赋值语法 数组名=(值1 值2 值3 ...) # 普通数组通过文件来赋值(文件的每一行作为一个元素赋值给数组) 数组名=(`cat 文件名`) # 普通数组访问所有值的语法 ${数组名[*]} # 普通数组访问所有值的语法 ${数组名[@]} # 普通数组访问所有值对应的索引的语法 ${!数组名[@]} # 普通数组统计值的个数 ${#数组名[@]} # 普通数组访问单个值的语法 ${数组名[索引]} # 普通数组从指定位置开始访问所有值的语法 ${数组名[@]:起始索引} # 普通数组从指定位置开始访问指定个数的值的语法 ${数组名[@]:起始索引:取值个数} # 查看所有普通数组的赋值结果 declare -a
6.2.2 普通数组示例
- 普通数组示例:
# 普通数组单个赋值示例 books[0]=java books[1]=python books[2]=linux books[3]=shell books[4]=c # 普通数组一次性赋值示例 books=(java python linux shell c) # 普通数组通过文件来赋值示例 passwds=(`cat /etc/passwd`) echo ${books[*]} # 输出结果为:java python linux shell c echo ${books[@]} # 输出结果为:java python linux shell c echo ${!books[@]} # 输出结果为:0 1 2 3 4 echo ${#books[@]} # 输出结果为:5 echo ${books[1]} # 输出结果为:python echo ${books[@]:2} # 输出结果为:linux shell c echo ${books[@]:1:3} # 输出结果为:python linux shell
6.3 关联数组
6.3.1 关联数组语法
- 关联数组语法:
# 关联数组声明语法 declare -A 数组名 # 关联数组单个赋值语法 数组名[索引名] = 值 # 普通数组一次性赋值语法 数组名=([索引名1]=值1 [索引名2]=值2 [索引名3]=值3 ...) # 关联数组访问所有值的语法 ${数组名[*]} # 关联数组访问所有值的语法 ${数组名[@]} # 关联数组访问所有值对应的索引名的语法 ${!数组名[@]} # 关联数组统计值的个数 ${#数组名[@]} # 关联数组访问单个值的语法 ${数组名[索引名]} # 关联数组从指定位置开始访问所有值的语法(起始索引为数字,且从1开始) ${数组名[@]:起始索引} # 关联数组从指定位置开始访问指定个数的值的语法(起始索引为数字,且从1开始) ${数组名[@]:起始索引:取值个数} # 查看所有关联数组的赋值结果 declare -A
6.3.2 关联数组示例
- 关联数组示例:
# 关联数组声明 declare -A user # 关联数组单个赋值示例 user[name]=admin user[age]=18 user[sex]='male' user[city]='hubei wuhan' # 关联数组一次性赋值示例 user=([name]=admin [age]=18 [sex]='male' [city]='hubei wuhan') echo ${user[*]} # 输出结果为:admin hubei wuhan 18 male echo ${user[@]} # 输出结果为:admin hubei wuhan 18 male echo ${!user[@]} # 输出结果为:name city age sex echo ${#user[@]} # 输出结果为:4 echo ${user[sex]} # 输出结果为:male echo ${user[@]:2} # 输出结果为:hubei wuhan 18 male echo ${user[@]:1:3} # 输出结果为:admin hubei wuhan 18
6.4 数组遍历
6.4.1 数组赋值示例
- 使用 for 循环和文件给数组赋值示例:
#!/usr/bin/bash # 指定文件的换行符为\n,否则会以空格为换行符 IFS=$'\n' # 给数组赋值 for line in `cat /etc/hosts` do hosts[++j]=$line done
- 使用 while 循环和文件给数组赋值示例:
#!/usr/bin/bash # 给数组赋值 while read line do hosts[++j]=$line done </etc/hosts
6.4.2 数组遍历示例
-
根据数组元素的个数进行遍历(不推荐)
#!/usr/bin/bash # 声明关联数组 declare -A user # 对数组进行赋值 user=([name]=admin [age]=18 [sex]='male' [city]='hubei wuhan') # 遍历数组 for i in $(seq ${#user[@]}) do echo 索引是: $i, 索引的值是: ${user[@]:$i:1} done
-
根据数组元素的索引进行遍历(推荐)
#!/usr/bin/bash # 声明关联数组 declare -A user # 对数组进行赋值 user=([name]=admin [age]=18 [sex]='male' [city]='hubei wuhan') # 遍历数组 for i in ${!user[@]} do echo 索引是: $i, 索引的值是: ${user[$i]} done
6.5 综合示例
- 普通数组的赋值和遍历示例:读取 /etc/hosts 的每一行
#!/usr/bin/bash # 给数组赋值 while read line do hosts[++j]=$line done </etc/hosts # 遍历数组 for i in ${!hosts[@]} do echo "$i: ${hosts[i]}" done
- 关联数组的赋值和遍历示例:统计 /etc/passwd 的 shell 数量
#!/usr/bin/bash # 声明关联数组 declare -A passwds # 对数组进行赋值 while read line do type=$(echo $line|awk -F ':' '{print $NF}') let passwds[$type]++ done </etc/passwd # 遍历数组 for i in ${!passwds[@]} do echo 索引是: $i, 索引的值是: ${passwds[$i]} done
7 Shell函数应用
7.1 函数定义
- 函数定义语法:
# 方式一:使用关键字function定义函数 function 函数名() { 函数体 } # 方式二:省略关键字function定义函数 函数名() { 函数体 }
- 函数定义示例:
#!/usr/bin/bash # 使用关键字function定义函数 function hello1() { echo "hello1" } # 省略关键字function定义函数 hello2() { echo "hello2" }
7.2 函数调用
- 函数调用语法:只需要在脚本中写入函数的名称即可实现函数调用,但是函数调用要处于函数定义之后。
- 函数调用示例:
#!/usr/bin/bash function hello1() { echo "hello1" } hello2() { echo "hello2" } # 调用函数hello1 hello1 # 调用函数hello2 hello2
7.3 函数返回值
- 函数返回值语法:在函数体中使用 return 关键字指定返回值,并使用 $? 来获取函数的返回值。返回值只能是整数。
- 函数返回值示例:
#!/usr/bin/bash function hello1() { echo "hello1() executed" # 函数返回值 return 1 } function hello2() { echo "hello2() executed" # 函数返回值 return 2 } # 调用函数hello1 hello1 # 获取函数返回值 echo $?
7.4 函数传参
-
位置参数:在函数中使用位置参数,并且在函数调用时指定位置参数,而在执行脚本时传递参数。
add.sh脚本内容如下:#!/usr/bin/bash function add() { let sum=$1*$2 echo $sum } add $1 $2
add.sh脚本运行如下:
sh add.sh 3 5
add.sh脚本运行打印结果如下:
15
-
指定位置参数的值:执行脚本时,在脚本后面传递的任何参数都不会被接收,而是使用指定位置参数的值。
show.sh脚本内容如下:#!/usr/bin/bash # 指定位置参数的值 set 1 3 5 4 2 function show() { for i in $* do echo $i done } show $*
show.sh脚本第一次运行如下:
sh show.sh
show.sh脚本第一次运行打印结果如下:
1 3 5 4 2
show.sh脚本第二次运行如下:
sh show.sh 1 2 3 4 5
show.sh脚本第二次运行打印结果如下:
1 3 5 4 2
7.5 自定义函数库
- 自定义函数库定义:自定义函数库就是一个单独的脚本文件,其中函数的函数名一般使用 _ 开始,这样方便区分。
- 自定义函数库引入:
#!/usr/bin/bash # 第一种引入方式:使用source关键字 source 函数库文件全路径名称 # 第二种引引入方式:使用 . 符号 . 函数库全路径名称
- 自定义函数库示例:
自定义函数库 math.sh 脚本内容如下:
test_math.sh脚本内容如下:# 自定义的函数 _add function _add() { echo $[$1+$2] } # 自定义的函数 _sub function _sub() { echo $[$1-$2] }
test_math.sh脚本运行如下:#!/usr/bin/bash # 引入自定义函数库 source /home/root/math.sh # 调用自定义函数库中的函数 _add $1 $2 _sub $3 $4
test_math.sh脚本运行打印结果如下:sh test_math.sh 1 3 6 3
4 3
7.6 综合示例
- 使用数组传参示例:求所有参数之和
sum.sh脚本内容如下:
sum.sh脚本运行如下:#!/usr/bin/bash num1=(1 3 5) num2=(2 4 6) num3=(1 2 3 4 5 6) function sum() { sum=0 for i in $* do $sum=$[$sum+$i] done echo "sum=$sum" } # 调用函数时把普通数组num1的所有值作为参数传递给函数 sum ${num1[*]} # 调用函数时把普通数组num2的所有值作为参数传递给函数 sum ${num2[@]} # 调用函数时把普通数组num3的部分值作为参数传递给函数 sum ${num2[@]:1:4}
sum.sh脚本运行打印结果如下:sh sum.sh
sum=9 # 传递的参数是: 1 3 5 sum=12 # 传递的参数是:2 4 5 sum=14 # 传递的参数是:2 3 4 5
- 限制传递的参数的个数:求两个参数之和
add.sh脚本内容如下:
add.sh脚本第一次运行如下:#!/usr/bin/bash # 限制必需传递两个位置参数,否则退出执行 if [ $# -ne 2 ]; then echo "please input two number." exit 1 fi function add() { echo $[$1*$2] } sum=$(add $1 $2) echo "$1 + $2 = $sum"
add.sh脚本第一次运行打印结果如下:sh add.sh
add.sh脚本第二次运行如下:please input two number.
add.sh脚本第二次运行打印结果如下:sh add.sh 2 4 5
add.sh脚本第三次运行如下:please input two number.
add.sh脚本第三次运行打印结果如下:sh add.sh 2 4
2 + 4 = 6
8 正则表达式
8.1 正则表达式基础
- 说明:正则表达式与通配符有着本质的区别。
- 正则表达式元字符含义:
正则表达式 含义 示例 \ 转义符 \{ ^ 匹配行首,awk中,^则是匹配字符串的开头 ^ab $ 匹配行尾,awk中,$则是匹配字符串的结尾 com$ ^$ 表示空行 . 匹配除换行符\n之外的任意单个字符 ab.d [ ] 匹配包含在[ ]之中的任意一个字符 [abc] [^ ] 匹配 [ ]之外的任意一个字符 [^abc] [ - ] 匹配[ ]中指定范围内的任意一个字符 [0-9] [a-z] [A-Z] [a-Z] ? 匹配之前的项0次或者1次 a\? + 匹配之前的项1次或多次 a\+ * 匹配之前的项0次或多次 a* ( ) 匹配表达式,创建一个用于匹配的子串 {n} 匹配之前的项n次,n是非负整数 a{2} {n,} 匹配之前的项最少n次 a{2,} {n,m} 匹配之前的项最少n次,最多m次 a{2,3} | 交替匹配|两边的任意一项 ab(c|d)
8.2 正则表达式示例
- 使用grep和正则表达式对context.txt文件中的内容进行过滤的示例如下:
# 匹配以 m 开头的行 grep "^m" content.txt # 匹配以 m 结尾的行 grep "m$" content.txt # 排除空⾏, 并在每行的行首打印⾏号 grep -vn "^$" content.txt # 匹配任意一个字符,不包括空⾏ grep "." content.txt # 匹配所有,包括空行 grep ".*" content.txt # 匹配单个任意字符 grep "adm.n" content.txt # 匹配以点结尾的行 grep "\.$" content.txt # 匹配8现出了0次或1次的行 grep "8\?" content.txt # 匹配8重复出现了1次或多次的行 grep "8\+" content.txt # 匹配8重复出现了0次或多次的行 grep "8*" content.txt # 匹配包含了abc中任意一个或多个字符的⾏ grep "[abc]" content.txt # 匹配包含除了大写字母I之外的任意字符的⾏ grep "[^I]" content.txt # 匹配包含了数字的行 grep "[0-9]" content.txt # 匹配包含了小写字母的行 grep "[a-z]" content.txt # 匹配包含了8且重复3次的行 grep "8\{3\}" content.txt # 匹配包含了8且重复3次的行,且不用转义符 grep -E "8{3}" content.txt # 匹配包含了8且重复最少1次的行 grep -E "8{1,}" content.txt # 匹配包含了8且重复最少3次最多5次的行 grep -E "8{3,5}" content.txt # 匹配包含了li或le的行 grep -E "l(i|e)" content.txt
9 grep
9.1 grep 基础
- 全称:Gloabal Search Regular Expression and Print out the line(全局搜索正则表达式并打印文本行)。
- 功能:grep是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。
- 语法:
grep [options] pattern [file...]
- 说明:[options]表示选项,pattern表示要匹配的模式(包括目标字符串、变量或者正则表达式),file表示要查询的文件名列表,可以是一个,也可以是多个。
- 选项:
选项 含义 示例 -c 只打印匹配的文本行的行数,不显示匹配的内容 grep -c “is” file -i 匹配时忽略字母的大小写 grep -i “is” file -h 当搜索多个文件时,不显示匹配文件名前缀 grep -h “is” file … -n 列出所有的匹配的文本行,并显示行号 grep -n “is” file -l 只列出含有匹配的文本行的文件的文件名,而不显示具体的匹配内容 grep -l “is” file … -s 不显示关于不存在或者无法读取文件的错误信息 grep -s “is” file … -v 只显示不匹配的文本行 grep -v “^$” file -w 匹配整个单词 grep -w “is” file -x 匹配整个文本行 grep -x “I am admin.” file -o 仅显示匹配到的字符串 grep -o “min” file -r 递归搜索当前目录和子目录 grep -r “is” directory -q 禁止输出任何匹配结果,并以退出码的形式表示是否搜索成功(0表示成功,1表示失败) grep -q “this” file -b 打印匹配的文本行到文件头的偏移量,以字节为单位 grep -b “is” file -A 数字 列出符合条件的行,并列出后续的 n 行 grep -A 6 “am” file -B 数字 列出符合条件的行,并列出前面的 n 行 grep -B 4 “not” file -E 支持扩展正则表达式 grep -E “8{3}” file -P 支持Perl正则表达式 -F 不支持正则表达式,将模式按照字面意思匹配
9.2 grep 示例
- 使用grep和正则表达式对context.txt和readme.txt文件中的内容进行过滤的示例如下:
# 统计content.txt中有多少行包含字符串 "is" grep -c "is" content.txt # 在content.txt中搜索包含字符串 "is" 的行,搜索时忽略大小写 grep -i "is" content.txt # 在content.txt和readme.txt这两个文件中搜索包含字符串 "is" 的行,打印结果时结果前面不显示文件名前缀 grep -h "is" content.txt readme.txt # 在content.txt中搜索包含字符串 "is" 的行,打印结果时在每行的前面打印出该行在content.txt文件中的行号 grep -n "is" content.txt # 在content.txt和readme.txt中搜索包含字符串 "is" 的行,打印结果时只打印包含有搜索内容的文件的文件名 grep -l "is" content.txt readme.txt # 在content.txt和readme.txt中搜索包含字符串 "is" 的行,打印结果时不打印不存在或无法读取的文件的错误信息 grep -s "is" content.txt readme.txt # 在content.txt中搜索除了空行之外的所有行,即只打印非空行 grep -v "^$" content.txt # 在content.txt中搜索包含有单词 is 的行 grep -w "is" content.txt # 在content.txt中搜索内容为 I am admin. 的行 grep -x "I am admin." content.txt # 在content.txt中搜索包含有字符串 "min" 的行,打印结果时只打印匹配到的字符串 “min” grep -o "min" content.txt # 在/home/admin目录下递归的搜索包含有字符串 "is" 的行 grep -r "is" /home/admin # 在content.txt中搜索包含有字符串 "this" 的行,并以退出码的形式返回,如果返回的退出码为0表示找到了,为1表示没有找到 grep -q "this" content.txt # 在content.txt中搜索包含有字符串 "is" 的行,并打印每一个匹配到的字符串 "is" 距文件头的偏移量(以字节为单位) grep -b "is" content.txt # 在content.txt中索引包含有字符串 "am" 的行,打印结果时打印出所有匹配的行,并打印出最后一个匹配行的后6行 grep -A 6 "am" content.txt # 在content.txt中索引包含有字符串 "not" 的行,打印结果时打印出所有匹配的行,并打印出第一个匹配行的前4行 grep -B 4 "not" content.txt # 以支持扩展的正则表达式的形式在content.txt中搜索包含有 888 的行 grep -E "8{3}" content.txt
10 sed
10.1 sed 基础
- 全称:Stream Editor(流编辑器)。
- 功能:sed是非交互式的流编辑器,每次只处理一行内容。处理时,先把当前处理的行存储在模式空间(patten space)中,然后用sed命令处理模式空间中的内容,处理完后再把模式空间中的内容打印到屏幕,接着处理下一行,直到处理完所有数据。
- 语法:
sed [options] script [file...]
- 说明:[options]表示选项,script表示要执行的sed脚本,file表示要查询的文件名列表,可以是一个,也可以是多个。
- 注意:sed命令并不改变原文件内容,只是把处理后的结果打印到屏幕,除非使用选项参数 -i 指定直接修改文件内容。
10.2 sed 选项参数
10.2.1 sed 选项参数说明
- sed 选项参数如下:
选项 含义 -n 不产生命令输出,使用print命令来完成输出 -e 多重编辑;在处理输入时,将script中指定的命令添加到已有的命令中 -f 在处理输入时,将file中指定的命令添加到已有的命令中 -i 直接修改对应文件 -r 支持扩展元字符
10.2.2 sed 选项参数示例
- sed 选项参数示例如下:
# 使用sed命令将输入中的单词 i 替换成单词 I echo "i love shell" | sed 's/i/I/' # 打印结果为: I love shell # 使用-n选项时,不产生命令输出 echo "i love shell" | sed -n 's/i/I/' # 打印结果为:无打印结果 # 不使用-e选项时,如果要执行多个sed命令,则只能管道将前一个sed命令的结果作为下一个sed命令的输入 echo "i love shell" | sed 's/i/I/' | sed 's/love shell/Love Shell/' # 打印结果为: I Love Shell # 使用-e选项时,允许多重编辑 echo "i love shell" | sed -e 's/i/I/' -e 's/love shell/Love Shell/' # 打印结果为: I Love Shell # 使用-e选项时,只需要将多个sed命令写成一个script即可,多个sed命令之间用;分隔,且;之前不能有空格 echo "i love shell" | sed -e 's/i/I/; s/love shell/Love Shell/' # 打印结果为: I Love Shell # 使用-f选项时,使用文件中的sed命令来处理输入。scrpit.sed中的内容如下: # s/i/I # s/love shell/Love Shell/ echo "i love shell" | sed -f script.sed # 打印结果为: I Love Shell # 使用-i选项时,直接对文件中的内容进行修改。content.txt中的内容如下所示: # i love shell sed -i 's/i/I/' content.txt # 上述命令执行完成之后,context.txt中的内容如下所示: # I love shell
10.3 sed 命令参数
- sed命令参数如下:
命令 含义 p 打印行 i 插入行 a 追加行 c 修改行 d 删除行 s 内容替换 ! 反选 r 读文件 w 写文件
10.3.1 打印命令
- 打印命令 p 的语法:
sed [选项参数] '/要匹配的内容/p' file sed [选项参数] '[行号]p' file
- 打印命令 p 的功能:打印出匹配的行的内容
- 打印命令 p 的用法示例如下:
# 打印出content.txt中包含有字符串 "linux" 的行 sed -n '/linux/p' content.txt # 打印出content.txt中的第2行 sed -n '2p' content.txt # 打印出content.txt中的第2到第4行 sed -n '2,4p' content.txt # 打印出content.txt中的最后一行 sed -n '$p' content.txt
10.3.2 插入命令
- 插入命令 i 的语法:
sed [选项参数] '[行号]i\新行内容' file
- 插入命令 i 的功能:在指定行之前插入一个新行,新行的内容为指定的内容。
- 插入命令 i 的说明:如果省略address,则在每一行前面插入新行。必须用 \ 而不是用 / 来分隔。
- 插入命令 i 的示例:
# 在content.txt中的每一行的前面插入一行 "Test Line" sed 'i\Test Line' content.txt # 在content.txt中的第1行前面插入一行 "Test Line" sed '1i\Test Line' content.txt # 在content.txt中的最后1行前面插入一行 "Test Line" sed '$i\Test Line' content.txt # 在content.txt中的第2到第4行的每行前面插入一行 "Test Line" sed '2,4i\Test Line' content.txt
10.3.3 追加命令
- 追加命令 a 的语法:
sed [选项参数] '[行号]a\新行内容' file
- 追加命令 a 的功能:在指定行之后插入一个新行,新行的内容为指定的内容。
- 追加命令 a 的说明:如果省略address,则在每一行后面插入新行。必须用 \ 而不是用 / 来分隔。
- 追加命令 a 的示例:
# 在content.txt中的每一行的后面插入一行 "Test Line" sed 'a\Test Line' content.txt # 在content.txt中的第1行后面插入一行 "Test Line" sed '1a\Test Line' content.txt # 在content.txt中的最后1行后面插入一行 "Test Line" sed '$a\Test Line' content.txt # 在content.txt中的第2到第4行的每行r后面插入一行 "Test Line" sed '2,4a\Test Line' content.txt
10.3.4 修改命令
- 修改命令 c 的语法:
sed [选项参数] '[行号]c\修改后的内容' file sed [选项参数] '/要匹配的内容/c\修改后的内容' file
- 修改命令 c 的功能:修改指定行的内容。
- 修改命令 c 的说明:如果省略address,把每一行都修改为指定内容。第二种语法中要匹配的内容可以正则表达式。
- 修改命令 c 的示例:
# 把content.txt中的每一行都修改为 "Test Line" sed 'c\Test Line' content.txt # 把content.txt中的第1行修改为 "Test Line" sed '1c\Test Line' content.txt # 把content.txt中的最后1行修改为 "Test Line" sed '$c\Test Line' content.txt # 把content.txt中的第2到第4行修改成一行 "Test Line" sed '2,4c\Test Line' content.txt # 把content.txt中包含有字符串 "admin" 的行修改为 "root" sed '/admin/c\root' content.txt # 把content.txt中包含数字的行修改为 "number" sed '/[0-9]/c\number' content.txt
10.3.5 删除命令
- 删除命令 d 的语法:
sed [选项参数] '[行号]d' file sed [选项参数] '/要匹配的内容/d' file
- 删除命令 d 的功能:删除指定行的内容。
- 删除命令 d 的说明:如果省略address,把每一行都删除。第二种语法中要匹配的内容可以正则表达式。
- 删除命令 d 的示例:
# 把content.txt中的每一行都删除 sed 'd' content.txt # 把content.txt中的第1行删除 sed '1d' content.txt # 把content.txt中的最后1行删除 sed '$d' content.txt # 把content.txt中的第2到第4行删除 sed '2,4d' content.txt # 把content.txt中包含有字符串 "admin" 的行删除 sed '/admin/d' content.txt # 把content.txt中包含数字的行删除 sed '/[0-9]/d' content.txt
10.3.6 替换命令
- 替换命令 s 的语法:
sed [选项参数] 's/替换前的内容/替换后的内容/[替换选项]' file
- 替换命令 s 的功能:把每一行中匹配到的第一处的内容替换为指定的内容。
- 替换命令 s 的选项:
选项 含义 数字 把每一行中区配到的第几处的内容替换为指定内容 g 把每一行中匹配到的所有地方的内容全部替换为指定内容 i 匹配时忽略大小写 w file 把替换后的结果写到文件 file 中 - 替换命令 s 的说明:替换前的内容可以正则表达式。
- 替换命令 s 的示例:
# 把content.txt中每一行的第一个字符串 "admin" 替换为字符串 "root" sed 's/admin/root/' content.txt # 把每一行末尾的 . 替换成 ! sed 's/.$/!/' content.txt # 把content.txt中每一行的第二个字符串 "admin" 替换为字符串 "root" sed 's/admin/root/2' content.txt # 把content.txt中每一行的所有字符串 "admin" 全部替换为字符串 "root" sed 's/admin/root/g' content.txt # 把content.txt中每一行的第一个字符串 "admin" 替换为字符串 "root",匹配字符串 "admin" 时忽略大小写 sed 's/admin/root/i' content.txt # 把content.txt中每一行的所有字符串 "admin" 替换为字符串 "root",匹配字符串 "admin" 时忽略大小写 sed 's/admin/root/gi' content.txt # 把content.txt中每一行的第一个字符串 "admin" 替换为字符串 "root",并将替换后的结果写入到文件result.txt中 sed 's/admin/root/w result.txt' content.txt # 遇到特殊字符时,使用 # 作为替换内容分隔符,以免带来困惑或错误。同样可以使用 ! 作为分隔符 # 把content.txt中每一行的第一个字符串 "www.baidu.com" 替换为字符串 "www.zhihu.com" sed 's#http//www.baidu.com#http://www.zhihu.com#' content.txt sed 's!http//www.baidu.com!http://www.zhihu.com!' content.txt
10.3.7 反选命令
- 反选命令 ! 的语法:
sed [选项参数] '行号!命令参数' file
- 反选命令 ! 的功能:取除了指定行之外的所有行。
- 反选命令 ! 的说明:命令参数可以是p、i、a、c、d。
- 反选命令 ! 的示例:
# 把content.txt中第2行之外的所有行打印出来 sed '2!p' content.txt # 在content.txt中除第2行之外的所有行的前面插入新行 "Test Line" sed '2!i\Test Line' content.txt # 在content.txt中除第2行之外的所有行的后面插入新行 "Test Line" sed '2!a\Test Line' content.txt # 把content.txt中除第2行之外的所有行都改成 "Test Line" sed '2!c\Test Line' content.txt # 在content.txt中除第2行之外的所有行都删除 sed '2!d' content.txt
10.3.8 读文件命令
- 读文件命令 r 的语法:
sed [选项参数] '行号r file2' file1 sed [选项参数] ’/要匹配的内容/r file2' file1
- 读文件命令 r 的功能:先处理file1中的内容,处理到指定位置时就把file2中的内容都读取出来,再继续处理file1中的内容。
- 读文件命令 r 的说明:要匹配的内容可以是正则表达式。
- 读文件命令 r 的示例:
结果说明:# 在处理content.txt时,处理完第2行之后再读入addition.txt中的内容 sed '2r addition.txt' content.txt # 在处理content.txt时,处理完之后再读入addition.txt中的内容 sed '$r addition.txt' content.txt # 在处理content.txt时,在处理完每一个包含有字符串 "admin" 的行之后读入addition.txt中的内容 sed '/admin/r addition.txt' content.txt
假如文件addition.txt的内容如下所示:
假如文件content.txt的内容如下所示:addition line 1. addition line 2.
则上述示例中的第一条命令(sed ‘2r addition.txt’ content.txt)执行后的结果则如下所示:content line 1. content line 2. content line 3.
content line 1. content line 2. addition line 1. addition line 2. content line 3.
10.3.9 写文件命令
- 写文件命令 w 的语法:
sed [选项参数] '行号w file2' file1 sed [选项参数] ’/要匹配的内容/w file2' file1
- 写文件命令 w 的功能:把指定内容写入到文件file2中。
- 写文件命令 w 的说明:要匹配的内容可以是正则表达式。
- 写文件命令 w 的示例:
# 把content.txt中第3行的内容写入到addition.txt中 sed '3w addition.txt' content.txt # 把content.txt中第3行到末尾的所有内容写入到addition.txt中 sed '3,$w addition.txt' content.txt # 把content.txt中所有包含字符串 "admin" 的行的内容写入到addition.txt中 sed '/admin/w addition.txt' content.txt
10.4 sed 多行命令
- sed 多行命令的说明:
命令 说明 N 将数据流中的下一行加进来创建一个多行组来处理 D 删除多行组中的一行 P 打印多行组中的一行
10.4.1 单行的 next 命令
-
单行next命令的语法:
sed [选项参数] '/要匹配的内容/{n; 对匹配到的那行的下一行要执行的操作}' file
-
单行next命令的说明:单行next命令用 n,表示把数据流中的下一行移动到sed编辑器的模式空间进行处理,对下一行的处理只能是打印、删除、替换,而不能是插入、追加、修改。
-
单行next命令的解释:一般情况下,sed每次只能处理一行,而不能同时处理多行,而next命令能让sed一次处理多行。
假如content.txt文件的内容如下所示:This is the first line. This is a data line. This is the last line.
如果写一个删除空白行的命令如下所示:
sed '/^$/d' content.txt
执行上述命令则会把所有的空白行都删除,执行的结果如下所示:
This is the header line. This is a data line. This is the last line.
如果只是想删除某一个空白行而不是删除所有的空白行,则上述写的删除空白行的命令就无法满足要求。
如果不知道要删除的那一个空白行的具体行号,而只是知道要删除包含有字符串 “data” 的那一行的下面一行的空白行,则可以使用单行next命令实现,先找到包含有字符串 “data” 的那行,然后删除下一行(空白行),sed单行 next 命令如下所示:
# 匹配包含有字符串 "data" 的行,并删除下一行 sed '/data/{n;d}' content.txt
执行的结果都如下所示:
This is the first line. This is a data line. This is the last line.
-
单行next命令的示例:
# 把包含有字符串 "first" 的行的下一行打印出来 sed -n '/first/{n; p}' content.txt # 把包含有字符串 "first" 的行的下一行删除掉 sed '/first/{n; d}' content.txt # 把包含有字符串 "first" 的行的下一行中的字符串 "data" 替换成字符串 "content" sed '/first/{n; s/data/content/}' content.txt
-
单行next命令的总结:单行next命令是在处理匹配到的行的时候,把下一行移动到工作空间中进行相应的处理。
10.4.2 多行的 next 命令
- 多行next命令的语法:
sed [选项参数] '/要匹配的内容/{N; 对匹配到的那行和下一行合并后的内容要执行的操作}' file
- 多行next命令的说明:多行next命令用 N,表示把数据流中的下一行添加到模式空间中和当前行合并,然后对合并后的内容进行处理,对合并后的内容的处理只能是打印、删除、替换,而不能是插入、追加、修改。
- 多行next命令的解释:
假如content.txt文件的内容如下所示:
可见content.txt文件的第2行和第3行原本是一个完整的句子,却分散到了两行中,如果想把这两行合并为一行就比较麻烦。这时就可以使用sed多行next命令,在找到包含有字符串"data"的那行之后就把下一行合并到当该行的后面,然后用替换命令s将换行符替换成空格。sed多行 next 命令如下所示:This is the first line. This is a data line. This is the last line.
执行的结果如下所示:sed '/data/{N;s/\n/ /}' content.txt
This is the first line. This is a data line. This is the last line.
- 多行next命令的示例:
# 把包含有字符串 "data" 的行和下一行合并,然后打印出来(打印出来后分为两行,因为包含了换行符) sed -n '/data/{N; p}' content.txt # 把包含有字符串 "data" 的行和下一行合并,然后删除掉(两行都会被删除掉) sed '/data/{N; d}' content.txt # 把包含有字符串 "data" 的行的下一行合并,然后把字符串 "data" 替换成字符串 "content"(打印出来后分为两行) sed '/data/{n; s/data/content/}' content.txt
- 多行next命令的总结:多行next命令是在处理匹配到的行的时候,把下一行合并到当前行的后面,然后对合并后的内容进行处理。
10.4.3 多行删除命令
-
多行删除命令的语法:
sed [选项参数] '/要匹配的内容1/{N; /要匹配的内容2/D}' file
-
多行删除命令的功能:根据上下两行的内容进行匹配,如果匹配到了则删除第一行且保留第二行,没有匹配到则不删除。
-
多行删除命令的说明:多行删除命令用 D,需要配合多行next命令 N 一起使用,表示根据 “要匹配的内容1” 匹配到某一行之后,把数据流中的下一行添加到模式空间中和当前行合并,然后在合并后的内容中匹配 “要匹配的内容2”,如果匹配到了,则删除当前行的数据且保留下一行的数据,而不是把合并后的数据全部删除。
-
多行删除命令的解释:
假如content.txt文件的内容如下所示:This is the first line. This is a data line. This is the last line.
可见,content.txt文件的第2和第3行原本是一个完整的句子,却分散到了两行中,如果想根据这两行的内容进行匹配然后删除第2行保留第3行,就比较麻烦。如果先使用多行next命令把这两行合并,然后在合并后的内容中进行匹配,如果匹配到了则进行删除,命令如下所示:
sed '/data/{N;/data\nline/d}' content.txt
执行的结果如下所示:
This is the first line. This is the last line.
可见,先使用多行next命令把这两行合并,然后在合并后的内容中进行匹配,匹配到了再进行删除,则是把合并后的内容全部都删除掉,显示不符合要求。
这时就可以使用sed多行删除命令,在找到包含有字符串"data"的那行之后就把下一行合并到当该行的后面,然后在合并后的内容中匹配字符串 “data\nline”,如果匹配到了则使用多行删除命令删除当前行且保留下一行。sed多行删除命令如下所示:
sed '/data/{N;/data\nline/D}' content.txt
执行的结果如下所示:
This is the first line. line. This is the last line.
-
多行删除命令的总结:多行删除命令结合多行next命令一起使用,先匹配定位到某一行,然后把下一行合并到当前行的后面,然后再在合并后的内容中匹配,如果匹配到了则删除当前行且保留下一行,如果没有匹配到则都不删除。
10.4.4 多行打印命令
-
多行打印命令的语法:
sed [选项参数] '/要匹配的内容1/{N; /要匹配的内容2/P}' file
-
多行打印命令的功能:根据上下两行的内容进行匹配,如果匹配到了则只打印第一行,没有匹配到则不打印。
-
多行打印命令的说明:多行打印命令用 P,需要配合多行next命令 N 一起使用,表示根据 “要匹配的内容1” 匹配到某一行之后,把数据流中的下一行添加到模式空间中和当前行合并,然后在合并后的内容中匹配 “要匹配的内容2”,如果匹配到了,则只打印当前行的数据而不打印下一行的数据。
-
多行打印命令的解释:
假如content.txt文件的内容如下所示:This is the first line. This is a data line. This is the last line.
可见,content.txt文件的第2和第3行原本是一个完整的句子,却分散到了两行中,如果想根据这两行的内容进行匹配然后只打印第2行而不想打印第3行,就比较麻烦。如果先使用多行next命令把这两行合并,然后在合并后的内容中进行匹配,如果匹配到了则进行打印,命令如下所示:
sed -n '/data/{N;/data\nline/p}' content.txt
执行的结果如下所示:
This is a data line.
可见,先使用多行next命令把这两行合并,然后在合并后的内容中进行匹配,匹配到了再进行打印,则是把合并后的内容全部都打印出来,显示不符合要求。
这时就可以使用sed多行打印命令,在找到包含有字符串"data"的那行之后就把下一行合并到当该行的后面,然后在合并后的内容中匹配字符串 “data\nline”,如果匹配到了则使用多行打印命令只打印当前行。sed多行打印命令如下所示:
sed -n '/data/{N;/data\nline/P}' content.txt
执行的结果如下所示:
This is a data
-
多行打印命令的总结:多行打印命令结合多行next命令一起使用,先匹配定位到某一行,然后把下一行合并到当前行的后面,然后再在合并后的内容中匹配,如果匹配到了则只打印当前行,如果没有匹配到则都不打印。
10.5 sed 保持空间
10.5.1 保持空间介绍
- 保持空间的介绍:模式空间(pattern space)是一块活跃的缓冲区,在sed编辑器执行命令时它会保存待处理的文本行,但是它并不是sed编辑器保存文本行的唯一空间。sed编辑器有另外一块称作保持空间(hold space)的缓冲区,在处理模式空间中的某些行时,可以用保持空间来临时保存一些行。
- 保持空间的语法:
sed [选项参数] '命令' file
- 保持空间的命令:
命令 功能 h 把模式空间中的内容复制到保持空间中 H 把模式空间中的内容附加到保持空间中 g 把保持空间中的内容复制到模式空间中 G 把保持空间中的内容附加到模式空间中 x 交换模式空间和保持空间的内容
10.5.2 保持空间示例
- content.txt文件内容:
This is the header line. This is the first data line. This is the second data line. This is the last line.
- 保持空间示例:
# 把content.txt中第1行的内容复制到保持空间,并用保持空间中的内容替换content.txt中的最后一行 # 执行打印结果如下: # This is the header line. # This is the first data line. # This is the second data line. # This is the header line. sed '1h;$g' content.txt # 把content.txt中第1行的内容复制到保持空间, 并把保持空间中的内容追加到content.txt中的最后一行的后面 # 执行打印结果如下: # This is the header line. # This is the first data line. # This is the second data line. # This is the last line. # This is the header line. sed '1h;$G' content.txt # 把content.txt中第1行的内容删除但保留到保持空间,并把保持空间中的内容追加到content.txt中的最后一行的后面 # 执行打印结果如下: # This is the first data line. # This is the second data line. # This is the last line. # This is the header line. sed -r '1{h;d};$G' content.txt # 把content.txt中第1行的内容复制到保持空间,并用保持空间中的内容替换content.txt的第3到最后一行 # 执行打印结果如下: # This is the header line. # This is the first data line. # This is the header line. # This is the header line. sed -r '1h;3,$g' content.txt # 把content.txt中第1行的内容复制到保持空间,第3-4行的内容追加到保持空间,并把保持空间中的内容追加到content.txt中最后一行的后面 # 执行打印结果如下: # This is the header line. # This is the first data line. # This is the second data line. # This is the last line. # This is the header line. # This is the second data line. # This is the last line. sed -r '1h;3,4H;$G' content.txt # 把content.txt中的第2行和第3行交换位置 # 执行打印结果如下: # This is the second data line. # This is the first data line. sed -n '/first/{h;n;p;g;p}' content.txt # 把content.txt中的第2行和第3行重复打印2遍 # 执行打印结果如下: # This is the first data line. # This is the second data line. # This is the first data line. # This is the second data line. sed -n '/first/{h;p;n;p;x;p;x;p}' content.txt
11 awk
11.1 awk 基础
- 全称:linux中的awk实际上是软链接到了gawk,它是awk的GNU版本,所以通常所说的awk实际上是gawk。
- 介绍:awk是一种编程语言而不只是编辑器命令,用于在linux / unix上对文本和数据进行处理,数据可以来自于标准输入、一个或多个文件、或其它命令的输出。awk可以定义变量来保存数据、使用算术和字符串操作符来处理数据、使用结构化编程概念来为数据处理增加处理逻辑、通过提取数据文件中的数据元素并将其重新排列或格式化以生成格式报告。
- 原理:从第一行到最后一行逐行扫描文件,寻找匹配的特定模式的行并在行上进行操作,如果没有指定处理动作则把匹配的行打印到标准输出中,如果没有指定模式则所有的行都会被处理。
- 语法:
awk [options] 'commands' [file] awk [options] -f awk_script_file [file]
- 说明:options表示选项,awk_script_file表示要执行的awk脚本,file表示要查询的文件名列表,可以是一个或多个。commands代码块或awk_script_file脚本文件中内容的格式如下所示:
# BEGIN块 和 END块 可以根据需求省略掉 BEGIN {处理前要执行的操作} {行处理的操作} END {处理后要执行的操作}
- 示例:
# 处理content.txt之前打印 "begin..." # 处理content.txt时,打印每行数据中的第1个数据字段 # 处理content.txt完之后打印 "end..." awk 'BEGIN {print "begin"} {print $1} END {print "end"}' content.txt # 处理content.txt文件时,从script.awk文件中读取awk脚本。该脚本文件中的内容如下所示: # BEGIN {print "begin"} {print $1} END {print "end"} awk -f script.awk content.txt
11.2 awk 选项参数
11.2.1 选项参数说明
- awk 选项参数如下:
选项 含义 -F fs 指定行中划分数据字段的字段分隔符(默认是空格) -v var=value 定义awk程序中的一个变量及其默认值 -mf N 指定要处理的数据文件中的最大字段数 -mr N 指定数据文件中的最大数据行数 -w keyword 指定awk的兼容模式或警告级别
11.2.1 选项参数示例
- awk 选项参数示例如下:
# 以 ':' 作为字段分隔符,并打印第2个字段的内容 echo "This:is:a:line." | awk -F : '{print $2}' # 运行打印结果为:is # 使用 -v 定义变量num,然后在awk脚本中使用变量num awk -v num=10 'BEGIN {print "The num is", num}' # 运行打印结果为:The num is 10
11.3 awk 变量
11.3.1 内建变量
-
数据字段变量:处理文本文件中的数据是awk的特性之一,它会根据字符分隔符把每一行文本分隔成若干个数据字段,然后给每一行中的每个数据字段分配一个变量。数据字段变量用 $n 表示,含义如下表所示:
数据字段变量 含义 $0 代表当前正在处理的文本行 $1 代表文本行中的第1个数据字段 $2 代表文本行中的第2个数据字段 $n 代表文本行中的第n个数据字段 $NF 代表文本行中的最后1个数据字段 数据字段变量示例:
# 打印整行 echo "This is a line." | awk '{print $0}' # 运行打印结果为:This is a line. # 打印第1个数据字段 echo "This is a line." | awk '{print $1}' # 运行打印结果为:This # 打印第2个数据字段 echo "This is a line." | awk '{print $2}' # 运行打印结果为:is # 打印最后1个数据字段 echo "This is a line." | awk '{print $NF}' # 运行打印结果为:line.
-
字段分隔符变量:数据字段是由字段分隔符来划分的,默认情况下,字段分隔符是一个空白字符,也就是空格或者制表符。字段分隔符变量如下表所示:
字段分隔符变量 含义 FS 输入字段分隔符(默认是空格或制表符) RS 输入记录分隔符(默认是换行符) OFS 输出字符分隔符(默认是空格) ORS 输出记录分隔符(默认是换行符) FIELDWIDTHS 由空格分隔的一列数字,定义了每个数据字段确切的宽度(即字符数) 字段分隔符变量示例:
# 输入字段分隔符为 ',' , 输入记录分隔符为 ';' , 输出字段分隔符为 '-' , 输出记录分隔符为 '\n' # content.txt文件中的内容为: # data1,data2;data3,data4; awk 'BEGIN {FS=","; RS=";"; OFS="-"; ORS="\n"} {print $1, $2, $3}' content.txt # 运行打印结果为: # data1-data2 # data3-data4 # 第1至第4个数据字段的宽度分别为3、4、2、5个字符 # data.txt文件的内容为: # 1005.3247596.37 # 115-2.349194.00 # 05810.1298100.1 awk 'BEGIN {FIELDWIDTHS="3 5 2 5"} {print $1,$2,$3,$4}' data.txt # 运行打印结果为: # 100 5.324 75 96.37 # 115 -2.34 91 94.00 # 058 10.12 98 100.1
-
数据变量:除了数据字段变量和字段分隔符变量,awk还提供了其它一些内建变量帮助你了解数据发生了什么变量,并提取shell环境的信息。数据变量如下表所示:
数据变量 含义 ARGC 当前命令行参数个数 ARGIND 当前文件在ARGV中的位置 ARGV 包含命令行参数的数组 CONVFMT 数字的转换格式,默认值为%.6 g ENVIRON 当前shell环境变量及其值组成的关联数组 ERRNO 当读取或关闭输入文件发生错误时的系统错误号 FILENAME 用作awk输入数据的数据文件的文件名 FNR 当前数据文件中的数据行数 IGNORECASE 设成非零值时,忽略awk命令中出现的字符串的字符大小写 NF 数据文件中的字段总数 NR 已处理的输入记录 OFMT 数字的输出格式,默认值是%.6 g RLENGTH 由match函数所匹配的子字符串的长度 RSTART 由match函数所匹配的子字符串的起始位置 数据变量示例:
# 打印出命令行上的参数个数、第1个参数、第2个参数 # 说明:程序脚本不会被当作参数,而awk会被当作参数 awk 'BEGIN {print ARGC, ARGV[0], ARGV[1]}' content.txt # 运行打印结果为: # 2 awk content.txt # 打印环境变量HOME和PATH的值 awk 'BEGIN {print ENVIRON["HOME"]; print ENVIRON["PATH"]}' # 运行打印结果为: # /home/admin # /usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/admin/.local/bin:/home/admin/bin # 打印content.txt中每行的行号、每行中的数据字段总数、每行中的最后一个字段、每行中的内容 # content.txt文件中的内容为: # This is the first line. # This is the second line. # # This is the last line. awk '{print "FNR="FNR, NF, $NF, $0}' content.txt # 运行打印结果为: # FNR=1 5 line. This is the first line. # FNR=2 5 line. This is the second line. # FNR=3 0 # FNR=4 5 line. This is the last line.
11.3.2 自定义变量
- 自定义变量:awk允许在程序代码中自定义变量并使用。自定义变量可以是任意数目的字母、数字、下划线,但不能以数字开头,且变量名区分大小写。
- 在脚本中给自定义变量赋值:在awk脚本中给变量赋值跟在shell脚本中给变量赋值类似。
# 在awk脚本中定义变量message并赋值,然后打印出变量的值 awk 'BEGIN {message="hello world"; print message}' # 运行打印结果为: # hello world
- 在命令行上给变量赋值:用awk命令行来给程序中的变量赋值,这样可以改变变量的值。
在awk命令行中给变量赋值会有一个问题,在你设置了变量后,这个值在awk脚本的 BEGIN 模块中不可用,但可以用 -v 选项参数来解决这个问题:# 在awk命令行中给变量n和变量message赋值 # content.txt文件中的内容为: # This:is:the:first:line. # This:is:the:second:line. # # This:is:the:last:line. awk 'BEGIN {FS=":"} {print $n, message}' n=2 message="hello world" content.txt # 运行打印结果为: # is hello world # is hello world # hello world # is hello world
# 不使用 -v 选项参数时,在awk命令行中给变量设置的值在 BEGIN 模块中不可用 awk 'BEGIN {print "The message is ", message}' message="hello world" # 运行打印结果为: # The message is # 使用 -v 选项参数时,在awk命令行中给变量设置的值在 BEGIN 模块中可以使用 awk -v message="hello world" 'BEGIN {print "The message is", message}' # 运行打印结果为: # The message is hello world
11.4 awk 数组
11.4.1 定义数组
- 数组类型:awk语言提供了关联数组的功能,它的索引值可以是任意文本字符串,每个索引字符串都必须能够唯一地标识出赋给它的数据元素。
- 数组定义和赋值:
数组变量名[索引] = 值
- 数组定义和赋值的示例:
# 定义数组变量user并赋值,然后使用数组变量 awk 'BEGIN {user["name"] = "admin"; user["age"]=25; print user["name"],user["age"]}' # 运行打印结果为: # admin 25
11.4.2 遍历数组
- 数组遍历:for语句会在每次循环时将数组变量的下一个索引值赋给循环变量,然后执行一遍循环体。
for (循环变量 in 数组变量) { 循环体 }
- 数组遍历的示例:
script.awk脚本内容如下:
执行脚本如下:# 定义数组变量user并赋值 BEGIN { # 定义数组变量user并赋值 user["name"]="admin" user["age"]=25 user["sex"]="male" # 遍历数组变量 for (idx in user) { print "Index:", idx, " - Value:", user[idx]; } }
运行打印结果如下:awk -f script.awk
Index: age - Value: 25 Index: sex - Value: male Index: name - Value: admin
11.4.3 删除数组
- 删除索引:从关联数组中删除索引,会从数组中删除关联索引值和相关的数据元素值。
delete 数组变量[索引]
- 删除索引的示例:
script.awd脚本内容如下:
执行脚本如下:# 定义数组变量user并赋值 BEGIN { # 定义数组变量user并赋值 user["name"]="admin" user["age"]=25 user["sex"]="male" # 遍历数组变量 for (idx in user) { print "Index:", idx, " - Value:", user[idx] } # 删除索引 delete user["sex"] print "-----------------" # 遍历数组变量 for (idx in user) { print "Index:", idx, " - Value:", user[idx] } }
运行打印结果如下:awk -f script.awk
Index: age - Value: 25 Index: sex - Value: male Index: name - Value: admin ----------------- Index: age - Value: 25 Index: name - Value: admin
11.5 awk 模式
11.5.1 正则表达式
- 使用语法:正则表达式必须出现在它要控制的程序脚本的左花括号之前。
awk [选项参数] 'BEGIN{} 正则表达式{} END{}' [file]
- 使用示例:
# 在content.txt中匹配以字符串 "This" 开头的行,并打印出匹配到的行中的第1个字段 awk '/^This/{print $1}' content.txt # 在content.txt中匹配不是以字符串 "This" 开头的行,并打印出匹配到的行中的第1个字段 awk '!/^This/{print $1}' content.txt
11.5.2 匹配操作符
- 功能说明:匹配操作符(~)允许将正则表达式限定在记录中的特定数据字段
- 使用语法:
awk [选项参数] 'BEGIN{} 数据字段 ~ 正则表达式{} END{}' [file]
- 使用示例:
# 在content.txt中匹配第1个字段以字符串 "Thi" 开头的行,并打印出匹配到的行 awk '$1 ~ /^Thi/{print $0}' content.txt # 在content.txt中匹配第1个字段不是以字符串 "Thi" 开头的行,并打印出匹配到的行 awk '$1 !~ /^Thi/{print $0}' content.txt
11.5.3 比较表达式
- 使用语法:
awk [选项参数] 'BEGIN{} 比较表达式{} END{}' file
- 比较符号:
比较符号 含义 示例 < 小于 x < y <= 小于或等于 x <= y > 大于 x > y >= 大于或等于 x >= y == 等于 x == y != 不等于 x != y - 使用示例:
# 在content.txt中匹配第4个字段的值为字符串 "first" 的行,并打印出匹配到的行 awk '$4 == "first" {print $0}' content.txt # 在data.txt中匹配第2个字段的值大于10的行,并打印出匹配到的行 awk '$2 > 10 {print $0}' data.txt
11.5.4 运算表达式
- 使用语法:
awk [选项参数] 'BEGIN{} 运算表达式{} END{}' [file]
- 运算符号:可以是 + 、-、*、/、%等运算。
- 使用示例:
# 在data.txt中匹配第2个字段的值加上5后结果大于10的行,并打印出匹配到的行 awk '$2 + 5 > 10 {print $0}' data.txt # 在data.txt中匹配第2个字段的值减去5后结果大于或等于10的行,并打印出匹配到的行 awk '$2 - 5 >= 10 {print $0}' data.txt # 在data.txt中匹配第2个字段的值乘以5后结果小于10的行,并打印出匹配到的行 awk '$2 * 5 < 10 {print $0}' data.txt # 在data.txt中匹配第2个字段的值除以5后结果小于或等于10的行,并打印出匹配到的行 awk '$2 / 5 <= 10 {print $0}' data.txt # 在data.txt中匹配第2个字段的值对5求余后结果等于3的行,并打印出匹配到的行 awk '$2 % 5 == 3 {print $0}' data.txt
11.5.5 复合表达式
- 使用语法:
awk [选项参数] 'BEGIN{} 复合表达式{} END{}' [file]
- 逻辑符号:逻辑与(&&)、逻辑或(||)、逻辑非(!)
- 使用示例:
# 在content.txt中匹配第1个字段以字符串 "Thi" 开头,且第3个字段的值大于10的行,然后打印出第1和第3个字段 awk '$1 ~ /^Thi/ && $3 > 10 {print $1,$3}' content.txt # 在content.txt中匹配第1个字段以字符串 "Thi" 开头,或第3个字段的值大于10的行,然后打印出第1和第3个字段 awk '$1 ~ /^Thi/ || $3 > 10 {print $1,$3}' content.txt
11.6 awk 流程控制
11.6.1 流程控制语法
- 流程控制语法:即可以单行写,也要以分行写。如果执行体只有一条语句,则可以省略掉执行体部分的 { }
if (表达式) {执行体} if (表达式) {执行体} else {执行体} if (表达式) {执行体} else if (表达式) {执行体} else {执行体}
11.6.2 流程控制示例
- 流程控制示例:统计data.txt中第3个字段的值是正数、0、负数的个数
script.awk脚本内容如下:
data.txt内容如下:BEGIN { num1=0 num2=0 num3=0 } { if($3 > 0) num1++ else if($3 ==0) num2++ else if($3 <0) num3++ } END { print "正数的个数:", num1, "; 0的个数:", num2, "; 负数的个数:", num3 }
运行脚本如下:Number is 6 Number is 0 Number is -1 Number is -8 Number is 10
运行打印结果如下:awk -f script.awk data.txt
正数的个数:2;0的个数:1;负数的个数:2
11.7 awk 条件循环
11.7.1 for 循环
- for 循环语法:
for (变量定义; 循环控制; 变量控制) { 循环体 }
- for 循环示例:
# 使用for循环求1到10的和 awk 'BEGIN {sum=0; for(i=1; i<=10; i++) {sum+=i}; print sum}' # 运行打印结果为:55
11.7.2 while 循环
- while 循环语法:
while (循环控制) { 循环体 }
- while 循环示例:
# 使用while循环求1到10的和 awk 'BEGIN {sum=0; i=1; while(i<10) {sum+=i; i++}; print sum}' # 运行打印结果为:55
11.7.3 do-while 循环
- do-while 循环语法:
do { 循环体 } while (循环控制)
- do-while 循环示例:
# 使用do-while循环求1到10的和 awk 'BEGIN {sum=0; i=1; do{sum+=i; i++} while(i<10); print sum}' # 运行打印结果为:55
11.8 awk 格式化打印
11.8.1 格式化打印语法
- 格式化打印语法:格式化打印使用 printf 命令,非格式化打印使用 print 命令。
printf "格式化字符串", 变量1, 变量2, ...
- 格式化字符串语法:
%[控制修饰符]控制选项
- 控制选项说明:
控制选项 含义 c 把一个数字作为ASCII字符显示 d 显示一个整数值 i 显示一个整数值(跟d一样) e 用科学计数法显示一个数 f 显示一个浮点数 g 用科学计数法或符点数显示(选择较短的格式) o 显示一个八进制值 s 显示一个文本字符串 x 显示一个十六进制值 X 显示一个十六进制值,但用大写字母A-F - 控制修饰符:除了控制选项外,还有3种控制修饰符可以用来进一步控制打印。
修改符 含义 width 用于指定输出字段的最小宽度(不足位数则用空格填充,超过宽度则按实际宽度输出) prec 这是一个数字值,用于指定浮点数中小数点后面的位数,或者用于指定文本字符串中显示的最大字符数 - 用于指定输出数据时,是采用左对齐还是右对齐
11.8.2 格式化打印示例
- 格式化打印示例:
# 把97作为ASCII字符打印出来 awk 'BEGIN {x=97; printf "The ASCII is: %c\n", x}' # 运行打印结果为:The ASCII is: a # 显示一个整数 awk 'BEGIN {x=10; printf "The number is: %d\n", x}' # 运行打印结果为:The number is: 10 # 显示一个浮点数 awk 'BEGIN {x=3.1415; printf "The number is: %f\n", x}' # 运行打印结果为:The number is: 3.141500 # 显示一个字符串 awk 'BEGIN {x="admin"; printf "The string is: %s\n", x}' # 运行打印结果为:The string is: admin # 使用科学计数法显示一个数值 awk 'BEGIN {x=100; printf "The number is: %e\n", x}' # 运行打印结果为:The number is: 1.000000e+02 # 指定打印文本字符串时的最大字符数 awk 'BEGIN {x="admin"; printf "The string is: %10s\n", x}' # 运行打印结果为:The string is: admin # 指定打印浮点数时的精度 awk 'BEGIN {x=3.1415; printf "The number is: %8.6f\n", x}' # 运行打印结果为:The number is: 3.141500 # 指定第1个字段打印的宽度为6(默认右对齐),指定第2个字段打印的宽度为6并且左对齐,不指定第3个字段打印的宽度(默认左对齐) # content.txt的内容如下: # admin 123 admin_user # root 12345 root_user awk '{printf "%6s %-6s %s\n", $1, $2, $3}' content.txt # 运行打印结果如下: # admin 123 admin_user # root 12345 root_user
11.9 awk 内建函数
11.9.1 数学函数
-
内置数学函数说明:
函数名称 函数功能 int(x) 求x的整数部分,向0取整 rand() 随机生成一个大于0且小于1的浮点数 sin(x) 求x的正弦,x以弧度为单位 cos(x) 求x的余弦,x以弧度为单位 atan2(x, y) 求x/y的反正切,x和y以弧度为单位 exp(x) 求e的x次幂 log(x) 求x的自然对数 sqrt(x) 求x的平方根 srand(x) 为计算随机数指定一个种子值 -
内置数学函数示例:
# 生成一个0-9的随机整数,并且打印出来 awk 'BEGIN {x=int(10 * rand()); print "The random number is: ", x}' # 求3弧度的正弦 awk 'BEGIN {x=sin(3); print "The result is: ", x}' # 打印结果为:The result is: 0.14112 # 求e的2次幂 awk 'BEGIN {x=exp(2); print "The result is: ", x}' # 打印结果为:The result is: 7.38906 # 求10的自然对数 awk 'BEGIN {x=log(10); print "The result is: ", x}' # 打印结果为:The result is: 2.30259 # 求16的平方根 awk 'BEGIN {x=sqrt(16); print "The result is: ", x}' # 打印结果为:The result is: 4
11.9.2 字符串函数
-
内置字符串函数说明:
函数名称 函数功能 index (s, t) 返回字符串 t 在字符串 s 中首次出现的位置的索引值。如果没找到则返回0。 length ([s]) 返回字符串 s 的长度。如果没有指定 s,则返回 $0 的长度。 match (s, r [, a]) 返回字符串 s 中正则表达式 r 出现位置的索引。如果指定了数组 a,则用数组 a 存储 s 中匹配正则表达式的那部分。 split (s, a [, r]) 将字符串 s 用 FS 字符或正则表达式 r 划分并存放到数组 a 中,并返回字段的总数。 sub (r, s [, t]) 在变量 $0 或目标字符串 t 中查找正则表达式 r 的匹配,如果找到了,就用字符串 s 替换掉第一处匹配。
返回结果为发生了替换的次数。gsub (r, s [, t]) 在变量 $0 或目标字符串 t 中查找正则表达式 r 的匹配,如果找到了,就用字符串 s 替换掉所有的匹配。
返回结果为发生了替换的次数。substr (s, i [, n]) 返回字符串 s 中从索引位置 i 开始的 n 个字符组成的子字符串。如果未指定 n ,则返回 s 剩下的部分。 tolower (s) 将字符串 s 中的所有字符转换成小写。 toupper (s) 将字符串 s 中的所有字符转换成大写。 asort (s [, d]) 将数组 s 按数据元素值排序。索引值会被替换成表示新的排序顺序的连续数字,数据元素值则为原来的数据元素值。
如果指定了数组 d,则排序后的数组会存储在数组 d 中。asorti (s [, d]) 将数组 s 按索引值排序。索引值会被替换成表示新的排序顺序的连续数字,数据元素值则为原来的索引值。
如果指定了数组 d,则排序后的数组会存储在数组 d 中。 -
内置字符串函数示例:
定位函数的示例:# 使用 index 函数获取子字符串 "wo" 在字符串 msg 中首次出现的位置的索引 awk 'BEGIN {msg="hello world"; print index(msg, "wo")}' # 运行打印结果为:7
长度函数的示例:
# 使用 length 函数获取字符串 msg 的长度 awk 'BEGIN {msg="hello world"; print length(msg)}' # 运行打印结果为:11 # 使用 length 函数获取 $0 的长度 echo "hello world" | awk '{print length()}' # 运行打印结果为:11
匹配函数的示例:
# 使用 match 函数获取正则表达式 "o" 在字符串 msg 中首次出现的位置的索引 awk 'BEGIN {msg="hello world"; print match(msg, "o")}' # 运行打印结果为:5 # 使用 match 函数获取正则表达式 "o" 在字符串 msg 中首次出现的位置的索引,并使用数组 a 保存匹配的部分 awk 'BEGIN {msg="hello world"; print match(msg, "o", a); for(i in a) {print "idx:", i, " - val:", a[i]}}' # 运行打印结果如下: # 5 # idx: 0 start - val: 5 # idx: 0 length - val: 1 # idx: 0 - val: o
划分函数的示例:
# 使用 split 函数把字符串 msg 的内容按照FS字符划分并保存到数组 a 中 awk 'BEGIN {msg="hello world"; print split(msg, a); for(i in a) {print "idx:", i, " - val:", a[i]}}' # 运行打印结果如下: # 2 # idx: 1 - val: hello # idx: 2 - val: world # 使用 split 函数把字符串 msg 按照正则表达式 "o" 划分并保存到数组 a 中 awk 'BEGIN {msg="hello world"; print split(msg, a, "o"); for(i in a) {print "idx:", i, " - val:", a[i]}}' # 运行打印结果如下: # 3 # idx: 1 - val: hell # idx: 2 - val: w # idx: 3 - val: rld
替换函数的示例:
# 使用 sub 函数在 $0 中查找正则表达式 "world" 的匹配,如果找到了,则用 "shell" 替换第一处匹配 echo "hello world" | awk '{print sub("world", "shell")}' # 运行打印结果为:1 # 使用 sub 函数在字符串 msg 中查找正则表达式 "world" 的匹配,如果找到了,则用 "shell" 替换第一处匹配 awk 'BEGIN{msg="hello world"; print sub("world", "shell", msg); print msg}' # 运行打印结果如下: # 1 # hello shell # 使用 gsub 函数在 $0 中查找正则表达式 "o" 的匹配,如果找到了,则用 "a" 替换所有的匹配 echo "hello world" | awk '{print gsub("o", "a")}' # 运行打印结果为:2 # 使用 gsub 函数在字符串 msg 中查找正则表达式 "o" 的匹配,如果找到了,则用 "a" 替换所有的匹配 awk 'BEGIN{msg="hello world"; print gsub("o", "a", msg); print msg}' # 运行打印结果如下: # 2 # hella warld
截取函数的示例:
# 使用 substr 函数获取字符串 msg 中从索引值为5开始一直到末尾的部分 awk 'BEGIN{msg="hello world"; print substr(msg, 5)}' # 运行打印结果为:o world # 使用 substr 函数获取字符串 msg 中从索引值为5开始的长度为4的部分 awk 'BEGIN{msg="hello world"; print substr(msg, 5, 4)}' # 运行打印结果为:o wo
大小写转换函数示例:
# 使用 toupper 函数把字符串 msg1转换成大写,使用 tolower 函数把字符串 msg2转换成小写 awk 'BEGIN{msg1="hello"; msg2="WORLD"; print toupper(msg1), tolower(msg2)}' # 运行打印结果如下: # HELLO world
排序函数示例:
# 使用 asort 函数把数组 a 按数据元素值排序,并将排序后的数组存储在数组 b 中 awk 'BEGIN{a["a"]=1; a["e"]=5; a["d"]=4; asort(a, b); for(i in b) {print "idx:", i, " - val:", b[i]}}' # 运行打印结果如下: # idx: 1 - val: 1 # idx: 2 - val: 4 # idx: 3 - val: 5 # 使用 asorti 函数把数组 a按索引值排序,将将排序后的数组存储在数组 b 中 awk 'BEGIN{a["a"]=1; a["e"]=5; a["d"]=4; asorti(a, b); for(i in b) {print "idx:", i, " - val:", b[i]}}' # 运行打印结果如下: # idx: 1 - val: a # idx: 2 - val: d # idx: 3 - val: e
11.9.3 时间函数
-
内置时间函数说明:
函数名称 函数功能 mktime(datespec) 将一个YYYY MM DD HH MM SS [DST]格式指定的日期转换成时间戳值 strftime(format [, timestamp]) 将当前时间的时间戳或timestamp转化为格式化日期 systime() 返回当前时间的时间戳 -
内置时间函数示例:
script.awk脚本的内容如下:BEGIN { date = systime() day = strftime("%A, %B %d, %Y", date) print "date: ", date print "day: ", day }
运行脚本如下:
awk -f script.awk
运行打印结果如下:
date: 1565172614 day: 星期三, 八月 07, 2019
11.10 awk 自定义函数
11.10.1 函数定义
- 函数定义位置:awk 中函数必须定义在所有的模块块之前(包括 BEGIN 模块块)。
awk [选项参数] '函数定义 BEGIN {处理前要执行的操作} {行处理的操作} END {处理后要执行的操作}' [file]
- 函数定义语法:
# 无参函数定义 function 函数名() { 函数体 } # 有参函数定义 function 函数名(变量列表) { 函数体 }
- 函数定义示例:
# 无参函数:生成一个0-9的随机整数 function num() { return int(10 * rand()) } # 有参函数:求两个整数之和 function add(num1, num2) { sum = num1 + num2 printf "%d + %d = %d\n", num1, num2, sum }
11.10.2 函数调用
- 函数调用位置:函数定义后,就可以在程序的各个模块中使用。
- 函数调用语法:
# 无参函数调用 函数名() # 有参函数调用 函数名(变量列表)
- 函数调用示例:
script.awk脚本内容如下:# 生成一个0-9的随机整数 function num() { return int(10 * rand()) } # 求两个整数之和 function add(num1, num2) { sum = num1 + num2 printf "%d + %d = %d\n", num1, num2, sum } BEGIN { num1 = num() num2 = num() add(num1, num2) }
- 脚本运行如下:
awk -f script.awk
- 运行打印结果如下:
2 + 2 = 4
11.10.3 函数返回值
- 函数返回值语法:在函数体中使用 return 关键字指定返回值,调用函数时使用变量接收返回值。
- 函数返回值示例:
function num() { return int(10 * rand()) } BEGIN { # 使用变量接收函数 num() 的返回值 x = num() }
11.10.4 自定义函数库
- 自定义函数库定义:自定义函数库就是一个单独的脚本文件存放所有库函数。
- 自定义函数库使用:
awk [选项参数] -f 库函数文件 'commands' [file] awk [选项参数] -f 库函数文件 -f awk_script_file [file]
- 自定义函数库示例:
自定义函数库 mathlib 脚本内容如下:
script.awk脚本内容如下:# 自定义的函数 num function num() { return int(10 * rand()) } # 自定义的函数 add function add(num1, num2) { sum = num1 + num2 printf "%d + %d = %d\n", num1, num2, sum }
script.awk脚本运行如下:BEGIN { num1 = num() num2 = num() add(num1, num2) }
test_math.sh脚本运行打印结果如下:awk -f mathlib -f script.awk
2 + 2 = 4
11.11 awk 综合示例
- 统计球队的总得分和人均得分:共有2个球队,每个球队有2名队员,每名队员有3项得分,每个球队的每名球员的得分情况按 “姓名,队名,成绩1,成绩2,成绩3” 的格式保存在得分文件score.txt中。
统计所有球队所有队员得分的文件score.txt内容如下:
统计每个球队总得分和人均得分的shell脚本count.sh内容如下:Rich Blum,team1,100,115,95 Barbara Blum,team1,110,115,100 Christine Bresnahan,team2,120,115,118 Tim Bresnahan,team2,125,112,116
运行count.sh脚本如下:#!/usr/bin/bash # 获得第 2 列的队名,并用 uniq 去重 for team in $(awk -F , '{print $2}' score.txt | uniq) do awk -v team=$team ' BEGIN { FS=","; total=0; } { # 如果当前行的内容为当前处理的球队的队员的得分 if($2==team) { # 把该队员的得分累加到当前球队的总得分上 total += $3 + $4 + $5; } } END { # 计算当前球队的人均得分 avg = total / 2; print "Total for", team, "is", total, ", the average is", avg; }' score.txt done
运行打印结果如下:sh count.sh
Total for team1 is 635 , the average is 317.5 Total for team2 is 706 , the average is 353
如果觉得本文对您有帮助,请关注博主的微信公众号,会经常分享一些Java和大数据方面的技术案例!