LeetCode 192
01 题目描述
写一个 bash 脚本以统计一个文本文件 words.txt 中每个单词出现的频率。为了简单起见,你可以假设:
1. words.txt只包括小写字母和 ' ' 。
2. 每个单词只由小写字母组成。
3. 单词间由一个或多个空格字符分隔。
02(words.txt)文件内容
the day is sunny the thethe sunny is is
03 输出(以词频降序排列):
the 4
is 3
sunny 2
day 1
04 解析
对于words.txt文件进行词频统计,首先要做的事情就是把words.txt文件当中的每一个单词分割出来,分割出每一个单词可以使用以下两种方式:
使用awk命令:
[root@localhost ~]# awk '{for(i=1;i<=NF;i++){print $i}}' words.txt
the
day
is
sunny
the
the
the
sunny
is
is
其中NF表示当前记录的字段数(即列数)
$i 文件中每行以间隔符号分割的不同字段
如果对awk命令不熟悉,可以参考之前分享的一篇文章学习:
使用xargs命令:
[root@localhost ~]# cat words.txt | xargs -n1
the
day
is
sunny
the
the
the
sunny
is
is
[root@localhost ~]# cat words.txt | xargs -n2
the day
is sunny
the the
the sunny
is is
xargs命令是用于给其他命令传递参数的一个过滤器,也是组合多个命令的一个工具。
-n选项,指定 输出时每行输出的列数
当我们将words.txt文件中的所有单词都分割出来之后,就可以统计这些单词当中每一个单词出现的次数了。
我们仅考虑使用awk命令来完成这个任务的话很简单,在进行分割的过程中直接用一个关联数组直接保存每一个单词出现的次数,关于关联数组的更多内容可以阅读:SHELL编程之变量与四则运算,此处我们可以暂时将关联数组理解为一个字典,关键字为单词,值为单词出现的次数(这样理解只是一种通俗的说法)
[root@localhost ~]# awk '{for(i=1;i<=NF;i++){asso_array[$i]++;}};END{for(w in asso_array){print w,asso_array[w];}}' words.txt
day 1
sunny 2
the 4
is 3
当然我们也可以在xargs的基础之上使用一些shell小工具来得到每个单词出现的次数。sort 工具及 uniq 工具,这里仅介绍我们解决问题使用的参数,关于小工具(grep、cut、sort、uniq、tee、diff、past、tr)可以参考之前的文章:Shell编程之文本处理工具与bash的特性
[root@localhost ~]# cat words.txt | xargs -n1 | sort
day
is
is
is
sunny
sunny
the
the
the
the
[root@localhost ~]# cat words.txt | xargs -n1 | sort | uniq -c
1 day
3 is
2 sunny
4 the
sort工具用于排序,它将文件的每一行作为一个单位,从首字母向后按照ASCII码值进行比较,默认将他们升序输出。
-r : 降序排列
-n : 以数字排序,默认是按照字符排序的。
uniq用去取出连续的重复行
-c :统计重复行的次数
最后我们仅需要对上面的结果进行排序啦,很简单的使用sort就可以啦!
[root@localhost ~]# cat words.txt | xargs -n1 | sort | uniq -c | sort -rn | awk '{print $2,$1}'
the 4
is 3
sunny 2
day 1
[root@localhost ~]# awk '{for(i=1;i<=NF;i++){asso_array[$i]++;}};END{for(w in asso_array){print w,asso_array[w];}}' words.txt | sort -rn
the 4
sunny 2
is 3
day 1
LeetCode 193
01 题目描述
给定一个包含电话号码列表(一行一个电话号码)的文本文件 file.txt,写一个 bash 脚本输出所有有效的电话号码。
你可以假设一个有效的电话号码必须满足以下两种格式: (xxx) xxx-xxxx 或 xxx-xxx-xxxx。(x 表示一个数字)
你也可以假设每行前后没有多余的空格字符。
02 file.txt文件内容
987-123-4567
123 456 7890
(123) 456-7890
03 你的脚本应当输出下列有效的电话号码:
987-123-4567
(123) 456-7890
04 解析
这道题目主要考察正则表达式和行匹配工具。关于正则表达式的文章可以参看:SHELL编程正则表达式,这一篇就够了,grep 行匹配命令可以参看:Shell编程之文本处理工具与bash的特性。
解决此问题,只要写出两种电话号码格式 (xxx) xxx-xxxx 或 xxx-xxx-xxxx 所对应的正则表达式即可:
(xxx) xxx-xxxx 所对应的正则表达式最笨的写法:
'\([0-9][0-9][0-9]\) [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]'
'[(][0-9][0-9][0-9][)] [0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]'
也就是把每一个x换成数字[0-9],再将给小括号加上反斜杠进行转义或者放进一对中括号内即可。
xxx-xxx-xxxx 则可以表示为:
'[0-9][0-9][0-9]-[0-9][0-9][0-9]-[0-9][0-9][0-9][0-9]'
对上面两种正则表达式做进一步的简化:
'\([0-9]{3}\) [0-9]{3}-[0-9]{4}'
'[(][0-9]{3}[)] [0-9]{3}-[0-9]{4}'
'[0-9]{3}-[0-9]{3}-[0-9]{4}'
此处再给出另外一种电话号码的正则表达式表示方式:
#\d 是基于 Perl 的正则表达式
#(xxx) xxx-xxxx
'[(]\d{3}[)] \d{3}-\d{4}'
'\(\d{3}\) \d{3}-\d{4}'
#xxx-xxx-xxxx
'\d{3}-\d{3}-\d{4}'
有了上面我们总结出来的正则表达式,进行行匹配就简单多了,只需要一个或|就可以将两个正则表达式连在一起:
'\(\d{3}\) \d{3}-\d{4}|\d{3}-\d{3}-\d{4}'
'[(]\d{3}[)] \d{3}-\d{4}|\d{3}-\d{3}-\d{4}'
'\([0-9]{3}\) [0-9]{3}-[0-9]{4}|[0-9]{3}-[0-9]{3}-[0-9]{4}'
使用或运算连接在一起,你如果觉得还不够简化可以再提取出两个正则中相同的部分,则表示为:
'(\(\d{3}\) |\d{3}-)\d{3}-\d{4}'
'([(]\d{3}[)] |\d{3}-)\d{3}-\d{4}'
'(\([0-9]{3}\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}'
最后匹配出电话号码格式正确的就轻而易举:
grep -P '(\(\d{3}\) |\d{3}-)\d{3}-\d{4}' file.txt
grep -P '([(]\d{3}[)] |\d{3}-)\d{3}-\d{4}' file.txt
grep -E '(\([0-9]{3}\) |[0-9]{3}-)[0-9]{3}-[0-9]{4}' file.txt
温馨提示:这道题主要考察正则表达式和行匹配,可以参考下面的文章学习
LeetCode 194
01 问题描述
给定一个文件 file.txt,转置它的内容。你可以假设每行列数相同,并且每个字段由 ' ' 分隔.
02 file.txt文件内容
name age
alice 21
ryan 30
03 输出内容
name alice ryan
age 21 30
对于这道题目我们可以简单的理解为将第一列变成第一行,第二列变为第二行,...,第n列变为第n行。这样一来,问题就简单了,只需要把一列元素串起来,保存起来并输出即可,如何获取一列元素成了我们问题的关键了,awk命令就可以轻松解决这个问题了:
[root@localhost ~]# awk '{for(i=1;i<=NF;i++){print "row[",i,"]="$i}}' file.txt
row[ 1 ]=name
row[ 2 ]=age
row[ 1 ]=alice
row[ 2 ]=21
row[ 1 ]=ryan
row[ 2 ]=30
可以从awk命令对每一行处理后的结果观察到,现在要做的就是把所有row[1]的给串起来,也就是第一列的给串起来,构成第一行,所有的row[2]串起来构成第二行。这个实现起来就简单了,我们只需要加一个判断语句即可:
[root@localhost ~]# awk '{for(i=1;i<=NF;i++){if(NR==1){row[i]=$i} else{row[i]=row[i]" "$i}}};END{for(i=1;i<=NF;i++){print row[i]}}' content.txt
name alice ryan
age 21 30
其中NR表示行号,判断行号是否等于1的目的在于,第一行的内容转置之后为每一行的行首,保证第一行内容在行首就得通过判断得到。
关于awk命令不熟悉的读者朋友,可以参考此文学习:
另外给大家提供一种投机取巧的做法,那就是使用cut命令进行列分割,代码如下:
#!/bin/env bash
column=$(awk '{print NF}' file.txt | uniq)
for((i=1;i<=column;i++))
do
cut -d' ' -f$i file.txt|xargs
done
其中变量column用来保存列数,cut -d' ' -f$i file.txt 表示取出文件当中的一列元素,配合xargs将一列内容转化为一行并输出。是不是超简单,看着比awk简单。
关于cut命令的详细内容请参考:
LeetCode 195
01 题目描述
给定一个文本文件 file.txt,请只打印这个文件中的第十行。
02 文件内容
Line 1
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10 you are so great
03 输出
Line 10 you are so great
说明:
如果文件少于十行,你应当输出什么?
至少有三种不同的解法,请尝试尽可能多的方法来解题。
04 解析
说明当中提到提到,我们应该处理文件少于十行的情况,那么我们就要知道文件本身包含的行数,以下给大家提供6种获取文件行数的方式:
[root@localhost ~]# awk '{print NR}' file.txt | tail -n1
10
[root@localhost ~]# awk 'END{print NR}' file.txt
10
[root@localhost ~]# grep -nc "" file.txt
10
[root@localhost ~]# grep -c "" file.txt
10
[root@localhost ~]# grep -vc "^$" file.txt
10
[root@localhost ~]# grep -n "" file.txt|awk -F: '{print '}|tail -n1 | cut -d: -f1
10
[root@localhost ~]# grep -nc "" file.txt
10
[root@localhost ~]# sed -n "$=" file.txt
10
[root@localhost ~]# wc -l file.txt
10 file.txt
[root@localhost ~]# cat file.txt | wc -l
10
[root@localhost ~]# wc -l file.txt | cut -d' ' -f1
10
知道了行号之后,我们就可以判断文件是否足够十行,足够十行则输出第十行,不足十行则打印文件不足十行,只有多少行即可。
row_num=$(cat file.txt | wc -l)
echo $row_num
if [ $row_num -lt 10 ];then
echo "The number of row is less than 10"
else
awk '{if(NR==10){print $0}}' file.txt
fi
其中打印输出第10行的代码可以替换为:
grep -n "" file.txt | grep -w '10' | cut -d: -f2
sed -n '10p' file.txt
awk '{if(NR==10){print $0}}' file.txt