面试系列之《Linux&Shell》(更新中)

1.用awk命令实现一个词频统计。

假设文件名“data”,文件内容:

A B C D
A C D
D B C C

1.1. python实现

因为不熟悉awk命令,当时用python实现的:

res_dict = {}
with open('./data', 'r', encoding='utf-8') as fp:
    for line in fp:
        for item in line.strip('\n').split(' '):
            res_dict[item] = res_dict.get(item, 0) + 1
print(res_dict)

# {'A': 2, 'B': 2, 'C': 4, 'D': 3}

追问:怎么对这个字典按照出现次数大小降序排序?
正确答案:

sorted(res_dict.items(), key=lambda item: item[1], reverse=True)    # 方式1
sorted(res_dict.items(), key=lambda item: -item[1])     # 方式2

我当时给的答案:

sorted(res_dict, key=lambda k, v: v, reverse=True)

上面代码有两处错误:
1.参数1直接从字典res_dict中获取的只是key,而不是想当然的(key, value)。如果需要获取所有类型为(key, value)的元素,需要调用res_dict.items()。
2.这里的关键字参数key的lambda表达式里参数只能有一个(注意本身python里lambda表达式参数的个数并不限制,可以为任意个,此处应该是sorted方法本身对该参数的限制。),也就是传入的参数为可迭代对象中的单个元素,返回值是根据这个元素所定义的比较规则,默认升序。

关于sorted方法具体可以参考文档:https://docs.python.org/zh-cn/3/howto/sorting.html#key-functions,该方法的返回值为包含所有可迭代对象元素的列表,所以上面如果需要按字典格式输出,可以通过dict()强转一下。

1.2. awk命令实现

# 写法1 从标准输出流读取
cat data | awk 'BEGIN{FS=" "} {for(i=1;i<=NF;i++) freq[$i]++} END{for(word in freq) print word "\t" freq[word]}' | sort -rnk 2

# 写法2 从文件读取
awk 'BEGIN{FS=" "} {for(i=1;i<=NF;i++) freq[$i]++} END{for(word in freq) print word "\t" freq[word]}' data | sort -rnk 2

对于上面命令的解释:

  • BEGIN:后面的动作在文件第一行被读取之前执行,类似于初始化的意思。这里{FS=" "}的含义是设置行分割符为空格。
  • END:最后一行数据处理完之后执行,这里用来循环获取freq里的键值。
  • 中间for循环就是真正对数据处理的方法体,循环中的NF,是awk内置的一个变量,代表正在处理的当前行的总列数。

1)关于awk命令,整个执行流程可以理解为从输出流或文本里从第一行逐行往下读取数据,每行数据按照FS参数指定的分隔符(不指定默认空格)划分为N列,列值保存给变量$1、$2、$3…,然后针对行或划分后的列值根据我们定义的规则选择是否执行某些操作。
该命令的通常使用格式:awk '条件类型1{动作1} 条件类型2{动作2} ...' filename,动作中可以是简单的单语句操作,也可以是复杂的多语句操作,像for循环、while循环等,总之,可以像python和java等高级语言那样自由的编写代码,更多使用方式可以参考官网:
https://www.gnu.org/software/gawk/manual/gawk.html#For-Statement

2)关于sort命令,参数代表的含义:
-r:reverse,逆序输出
-n:numeric-sort,根据字符串对应的数值大小来排序,而非字符串的字典顺序。这里是统计频次。
-k:key,指定用来排序的key,上面用2来指定根据频次排序,2代表的是第2列。

PS:
后来自己实现awk词频统计的过程中遇到了点儿小问题,一直找不到原因,有朋友知道欢迎指出。
当直接在虚拟机中通过vim新建data文件时,统计正常输出结果:

C 4
D 3
B 2
A 2

当在本地(win10)建好数据文件上传到虚拟机中时,统计输出:

C 4
D 2
B 2
A 2
D 1

也就是D本来是3,在后面被拆分成了2+1,开始猜想是换行符的问题,在linux中用了dos2unix转换下,结果还是这样,有懂的朋友欢迎留言。

2.给定两个日期,获取中间的所有日期序列。

例如“2023-01-01”和“2023-01-10”,获取两日期之间的所有日期。

#!/bin/bash

start_date=$1
end_date=$2

echo $start_date
while [ $start_date != $end_date ]
do
  start_date=$(date -d "$start_date +1 day" +"%Y-%m-%d")
  echo $start_date
done 

3.

你可能感兴趣的:(面试题,linux,shell,面试题)