该系列是基于牛客Shell题库,针对具体题目进行查漏补缺,学习相应的命令。
刷题链接:牛客题霸-Shell篇。
该系列文章都放到专栏下,专栏链接为:《专栏:Shell》。欢迎关注专栏~
本文知识预告:
- 本文首先复习了字符串拼接,
grep
、sort
和awk
命令的相关的用法; - 然后,给出了四种题目的解决方案,基本算是前面题目都用到过的知识点。
题目:SHELL22 处理文本
有一个文本文件nowcoder.txt,假设内容格式如下:
111:13443
222:13211
111:13643
333:12341
222:12123
现在需要编写一个shell脚本,按照以下的格式输出:
[111]
13443
13643
[222]
13211
12123
[333]
12341
相关命令学习
Shell中的字符串拼接
字符串拼接的方式很简单,直接相连即可,比如:
wz@lucky:~$ strs="1+1="
wz@lucky:~$ strs=$strs"2"
wz@lucky:~$ echo $strs
1+1=2
另外,如果有变量,可以把字符串变量添加进{}
,并且需要把$
放到外面,直接相连就行了,如下例子:
wz@lucky:~$ name='aha'
wz@lucky:~$ strs="hello"
wz@lucky:~$ new_str=${strs}" "${name}
wz@lucky:~$ echo $new_str
hello aha
grep
:强大的文本搜索工具
grep
来自于英文词组“global search regular expression and print out the line”的缩写,意思是用于全面搜索的正则表达式,并将结果输出。人们通常会将grep
命令与正则表达式搭配使用,参数作为搜索过程中的补充或对输出结果的筛选,命令模式十分灵活。
与之容易混淆的是egrep
命令和fgrep
命令。如果把grep
命令当作是标准搜索命令,那么egrep
则是扩展搜索命令,等价于“grep -E
”命令,支持扩展的正则表达式。而fgrep
则是快速搜索命令,等价于“grep -F
”命令,不支持正则表达式,直接按照字符串内容进行匹配。
语法格式: grep [参数] 文件
常用参数:
参数 | 功能 |
---|---|
-i | 忽略大小写 |
-c | 只输出匹配行的数量 |
-l | 只列出符合匹配的文件名,不列出具体的匹配行 |
-n | 列出所有的匹配行,显示行号 |
-h | 查询多文件时不显示文件名 |
-s | 不显示不存在、没有匹配文本的错误信息 |
-v | 显示不包含匹配文本的所有行 |
-w | 匹配整词 |
-x | 匹配整行 |
-r | 递归搜索 |
-q | 禁止输出任何结果,已退出状态表示搜索是否成功 |
-b | 打印匹配行距文件头部的偏移量,以字节为单位 |
-o | 与-b 结合使用,打印匹配的词据文件头部的偏移量,以字节为单位 |
-F | 匹配固定字符串的内容 |
-E | 支持扩展的正则表达式 |
awk
:文本和数据进行处理的编程语言
awk
命令来自于三位创始人”Alfred Aho,Peter Weinberger, Brian Kernighan “的姓氏缩写,其功能是用于对文本和数据进行处理的编程语言。使用awk
命令可以让用户自定义函数或正则表达式对文本内容进行高效管理,与sed
、grep
并称为Linux系统中的文本三剑客。
语法格式:awk 参数 文件
常用参数:
参数 | 功能 |
---|---|
-F | 指定输入时用到的字段分隔符 |
-v | 自定义变量 |
-f | 从脚本中读取awk 命令 |
-m | 对val 值设置内在限制 |
常用的awk
内置变量:
awk
语法由一系列条件和动作组成,在花括号内可以有多个动作,多个动作之间用分号分隔,在多个条件和动作之间可以有若干空格,也可以没有。
变量名称 | 说明 |
---|---|
FILENAME | 当前输入文档的文件名 |
FNR | 当前输入文档的当前行号,尤其当多个输入文档时有用 |
FS | 设置字段分隔符,默认为空格或制表符 |
NF | 当前记录(行)的字段(列)个数 |
NR | 输入数据流的当前记录数(行号) |
OFS | 输出字段分隔符,默认为空格 |
ORS | 输出记录分隔符,默认为换行符 |
RS | 输入记录分隔符,默认为换行符 |
awk
是一种处理文本文件的编程语言,文件的每行数据都被称为记录,默认以空格或制表符为分隔符,每条记录被分成若干字段(列),awk
每次从文件中读取一条记录。
sort
:对文件内容进行排序
sort
命令的功能是对文件内容进行排序。有时文本中的内容顺序不正确,一行行地手动修改实在太麻烦了。此时使用sort
命令就再合适不过了,它能够对文本内容进行再次排序。
语法格式:sort [参数] 文件
常用参数:
-b | 忽略每行前面开始出的空格字符 |
---|---|
-c | 检查文件是否已经按照顺序排序 |
-d | 除字母、数字及空格字符外,忽略其他字符 |
-f | 将小写字母视为大写字母 |
-i | 除040至176之间的ASCII字符外,忽略其他字符 |
-m | 将几个排序号的文件进行合并 |
-M | 将前面3个字母依照月份的缩写进行排序 |
-n | 依照数值的大小排序 |
-o <输出文件> | 将排序后的结果存入制定的文件 |
-r | 以相反的顺序来排序 |
-t <分隔字符> | 指定排序时所用的栏位分隔字符 |
-k | 指定需要排序的栏位 |
-s | 通过禁用最后的比较来稳定排序 |
cut
:按列提取文件内容
cut
命令的功能是用于按列提取文件内容。常用的grep
命令仅能对关键词进行按行提取过滤,而cut
命令则是可以根据指定的关键词信息,针对特定的列内容进行过滤。
语法格式:cut [参数] 文件
常用参数:
-c | 以字符为单位进行分割 |
---|---|
-b | 以字节为单位进行分割 |
-d | 自定义分隔符,默认为制表符”TAB” |
-f | 显示指定字段的内容 |
-n | 取消分割多字节字符 |
--complement | 补足被选择的字节、字符或字段 |
--out-delimiter | 指定输出内容是的字段分割符 |
参考实例
- 以冒号为间隔符,仅提取指定文件中第一列的内容:
lucky@DESKTOP-VQ8KID4:~$ cut -d : -f 1 /etc/passwd
root
daemon
bin
...
sshd
landscape
pollinate
lucky
- 仅提取指定文件中每行的前4个字符:
lucky@DESKTOP-VQ8KID4:~$ head /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
lucky@DESKTOP-VQ8KID4:~$ cut -c 1-4 /etc/passwd
root
daem
bin:
sys:
sync
...
- 按照指定要求分割:比如获取后缀名
lucky@DESKTOP-VQ8KID4:~$ ls | cut -d "." -f2
txt
txt
py
txt
txt
sh
md
sh
txt
py
sh
题目解决方案
方法一:awk
+字符串拼接
awk -F ":" '{
a[$1] = a[$1] $2 "\n"
}
END {for (i in a){
printf("[%s]\n%s",i,a[i])
}
}' nowcoder.txt
下面的写法也可:
awk -F ":" '{
res[$1] = (res[$1] == "" ? $2 : (res[$1] "\n" $2))
}
END {for(k in res){
print "["k"]"
print res[k]
}
}' nowcoder.txt
方法二:cut
+sort
+grep
+awk
for val in $(cat nowcoder.txt | cut -d ":" -f 1 | sort | uniq); do
echo "[$val]"
cat nowcoder.txt | grep $val | awk -F ":" '{print $2}'
done < nowcoder.txt
方法三:sort
+awk
cat nowcoder.txt | sort -t ":" -k 1,1 -sb | awk -F ':' 'BEGIN{t=0}{if($1 != t){print "["$1"]";t=$1;print $2}else{print $2}}'
方法四:for
+grep
将分号前面的先去重找出来,以此为条件,找出相应的数字即可。
number01=$(cat nowcoder.txt | awk -F':' '{print $1}' | sort | uniq -c | awk '{print $2}')
for i in $number01; do
number02=$(grep "${i}" nowcoder.txt | awk -F':' '{print $2}')
echo "[${i}]"
echo -e "${number02}"
done