Bash基础 - Awk 和 Sed

同学做入职在线测试,遇到一道Bash程序题,不太会做所以来问我。我之前对Bash也是一知半解,一边查资料一边摸索给做出来了,中间遇到很多难点,于是想借此机会深入学习Bash的知识,并记录下来,加深记忆。本篇记录一下Awk和Sed相关知识。真题在最后。

Awk

输入

默认输入方式为:

ask SCRIPT INPUT.FILE

但更常用的是使用管道:

echo FILE.or.VARIABLE | awk SCRIPT

分隔符

Awk默认使用空格分隔。因此一个常见的Awk参数是 -F 指定分隔符,比如指定,为分隔符:

echo ${demo} | awk -F ',' '{print NF}'

-F 参数也支持正则表达式,比如指定,:为分隔符:

echo ${demo} | awk -F '[,:]' '{print NF}'

参数脚本

处理简单任务时,一般使用参数脚本,也就是直接在参数中写处理逻辑,以上例子均为参数脚本

比如在测试中的获取JSON对象中的email值:

email=$(echo "${json}" | awk -F '[,:}]' '{for(i=1;i<=NF;i++){if($i~/email/){print $(i+1)}}}')

其中 if 条件内为正则表达式

  • ~表示匹配
  • !~表示不匹配

需要注意的一点是,一般使用单引号' 包裹Awk的参数,避免被Bash先行释义,必要时可以通过拼接字符串来实现比较复杂的逻辑。

文件脚本

1
2
3
4
5
6
7
#!/bin/awk -f
#处理所有行之前
BEGIN {}
#处理每行
{}
#处理所有行之后
END {}

Sed

输入

与Awk一致:

sed PATTERN INPUT.FILE

或者

echo FILE.or.VARIABLE | sed PATTERN

正则模式

's/reg/res/g'

  • s表示替换
  • g表示替换全部,可选
  • reg为模式,与正则基本规则一致
  • res为替换的内容

一些例子:

  • 3s/reg/res/g,替换第三行
  • 3,6s/reg/res/g,替换第三至六行
  • 3s/reg/res/1,替换第三行第一个
  • 3s/reg/res/3g,替换第三行第三个以及之后的

其他

()

  • 用法一
    • 在子bash中运行命令,比如 (cd ~; touch aa.bb)
  • 用法二
    • 生成数组,a=(1 3 5) 或者 a=($(echo $demo | awk '{for(i=1;i<=NF;i++){print $i}}'))

尽量${a}代替$a

  • 边界容易区分,如$ab
  • 有更多功能,${a/reg_old/reg_new}可以正则替换,${a//reg_old/reg_new}可以正则替换全部,

$(cmd)

执行cmd命令的结果,或赋值,等价于`` ,但后者容易看错

(())与$(())

计算表达式,比如((a++))

[] 与 [[]]

[[]][]加强版,等价于test,做条件判真用,比如 if [[ $a == 0 ]]; then echo 'a==0'; fi

真题

You have the following API endpoint:
https://reqres.in/api/users (Description can be found here: https://reqres.in)
Create a Bash script that calls the API. From the result, extract the user object of George Edwards (yes, the user exists). In the user object, change the mail (locally, no need to call any API from here) to edwards.george@reqres.in and store the object to a file.
Do not use temporary files, all needs to happen in memory. You can assume that you can use the typically installed Linux command line tools in your bash script (including sed, jq, tee, grep).
Please provide your Bash-only script.
Bonus task: store all the users within this API including the particular change as mentioned above in a file.

我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#!/bin/bash

cat /dev/null > result.txt

total_pages=$(echo $(curl -s "https://reqres.in/api/users") | awk -F '[,:}]' '{for(i=1;i<=NF;i++){if($i~/'total_pages'/){print $(i+1)}}}')

page=0
data=

while [[ ${page} -le ${total_pages} ]]
do

((page++))

origin=$(curl -s "https://reqres.in/api/users?page=${page}")
data=$(echo "${origin}" | awk -F "[\\\\[\\\\]]" '{for(i=1;i<=NF;i++){if($i~/.*email.*/){print $(i)}}}')
objects=($(echo "${data}" | awk -F "},{|[{}]" '{for(i=1;i<=NF;i++){print $(i)}}'))

for i in "${!objects[@]}";
do
george=$(echo "${objects[$i]}" | awk -F "},{|[{}]" '{for(i=1;i<=NF;i++){if($i~/.*first_name.*George.*last_name.*Edwards.*/){print $(i)}}}')
if [[ -z ${george} ]]; then
echo "{${objects[$i]}}" >> result.txt
else
email=$(echo "${george}" | awk -F '[,:}]' '{for(i=1;i<=NF;i++){if($i~/email/){print $(i+1)}}}')
new=$(echo "${george}" | sed "s/${email}/\\\"edwards.george@reqres.in\\\"/")
echo "{${new}}" >> result.txt
fi
done

done

评论