LeetCode上仅有的四道shell编程题解析

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

03输出

Line 10

说明:

1. 如果文件少于十行,你应当输出什么?

2. 至少有三种不同的解法,请尝试尽可能多的方法来解题。

04解析

参考答案:

三种不同的解法

第一种:grep -n "" file.txt | grep -w '10' | cut -d: -f2

第二种:sed -n '10p' file.txt

第三种:awk '{if(NR==10){print $0}}' file.txt

说明当中提到,我们应该考虑文件少于十行的情况,那么我们就要知道文件本身包含的行数,以下给大家提供10种获取文件行数的方式:

[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 ~]# 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

05拓展

tail命令将每个文件的最后10行打印到标准输出。对于多个文件,在每个文件前面加上一个给出文件名的头。如果没有文件,或者文件为-,则读取标准输入。

如何使用tail命令

使用tail命令查看yum.log日志文件,显示最后10行内容,tail默认显示问价你的最后10行内容:

如何显示指定的行数

使用-n命令显示指定的行数,也可以省略字母n,只使用-和数字(数字和-之间没有空格)。

例如:查看自己创建的用户,查看/etc/passwd文件最后两行内容:

如何实时监控文件的更改

如果需要监视文件内容的更改,使用-f选项。这个选项对于监视日志文件非常有用。例如,要显示/var/log/messages文件的最后10行,并监视文件的更新:

想要退出,请按Ctrl+C退出。

查看多个文件

如果提供了多个文件作为tail命令的输入,它将显示每个文件的最后十行。下面例子,使用tail命令显示/etc/passwd和/etc/shadow文件的最后两行内容:

tail命令和其他命令一起使用

例如,要实时监视/var/log/messages文件并显示包含session为129的行,可以使用:

LeetCode 193 有效电话号码

01题目描述

给定一个包含电话号码列表(一行一个电话号码)的文本文件 file.txt,写一个 bash 脚本输出所有有效的电话号码。

你可以假设一个有效的电话号码必须满足以下两种格式: (xxx) xxx-xxxx 或 xxx-xxx-xxxx。(x 表示一个数字)

你也可以假设每行前后没有多余的空格字符。

02文件内容

987-123-4567

123 456 7890

(123) 456-7890

03输出

你的脚本应当输出下列有效的电话号码:

987-123-4567

(123) 456-7890

04解析

这道题目主要考察正则表达式和行匹配工具。

解决此问题,只要写出两种电话号码格式 (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}'

最后匹配出电话号码格式正确的就轻而易举:6种方法

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

grep -P '^([0-9]{3}-|\([0-9]{3}\) )[0-9]{3}-[0-9]{4}$' file.txt

awk '/^([0-9]{3}-|\([0-9]{3}\) )[0-9]{3}-[0-9]{4}$/' file.txt

gawk '/^([0-9]{3}-|\([0-9]{3}\) )[0-9]{3}-[0-9]{4}$/' file.txt

05拓展

温馨提示:这道题主要考察正则表达式和行匹配,可以参考下面的文章学习

SHELL编程正则表达式,这一篇就够了

Shell编程之文本处理工具与bash的特性

LeetCode 192 统计词频

01题目描述

写一个 bash 脚本以统计一个文本文件 words.txt 中每个单词出现的频率。

为了简单起见,你可以假设:

words.txt只包括小写字母和 ' ' 。

每个单词只由小写字母组成。

单词间由一个或多个空格字符分隔。

02文件内容

the day is sunny the the

the sunny is is

03输出

你的脚本应当输出(以词频降序排列):

the 4

is 3

sunny 2

day 1

说明:

不要担心词频相同的单词的排序问题,每个单词出现的频率都是唯一的。

你可以使用一行 Unix pipes 实现吗?

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 文件中每行以间隔符号分割的不同字段

使用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命令来完成这个任务的话很简单,在进行分割的过程中直接用一个关联数组直接保存每一个单词出现的次数,此处我们可以暂时将关联数组理解为一个字典,关键字为单词,值为单词出现的次数(这样理解只是一种通俗的说法)

[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 工具

[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

05拓展

关联数组的更多内容可以阅读:SHELL编程之变量与四则运算

LeetCode 194 转置文件

01题目描述

给定一个文件 file.txt,转置它的内容。你可以假设每行列数相同,并且每个字段由 ' ' 分隔.

02文件内容

name age

alice 21

ryan 30

03输出

name alice ryan

age 21 30

04解析

对于这道题目我们可以简单的理解为将第一列变成第一行,第二列变为第二行,...,第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){
            res[i]=$i
        }
        else{
            res[i]=res[i]" "$i
        }
    }
}END{
    for(j=1;j<=NF;j++){
        print res[j]
    }
}' file.txt

name alice ryan

age 21 30

其中NR表示行号,判断行号是否等于1的目的在于,第一行的内容转置之后为每一行的行首,保证第一行内容在行首就得通过判断得到。

另外给大家提供一种投机取巧的做法,那就是使用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简单。

05拓展

关于awk命令不熟悉的读者朋友,可以参考此文学习:

号称三剑客之首的awk,开始秀!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AllenGd

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值