之前由于运维需求,需要对一个大日志文件按照日期进行划分,将每天的日志写入一个单独的文件中。
刚开始接到这个需求后,我浏览了一遍日志文件,发现里面只有11月17号到11月22号的日志,天数不多,可以尝试手动划分。但一看行数,好几万行,这得拖到什么时候,手指都按麻了。。。
于是我有了一个最初的思路:将文件放到linux系统中,利用cat+grep命令匹配行并输出到指定文件中。由于涉及的天数很少,我很快完成了操作;
1)命令行:cat+grep
# 11月17号的日志
cat message.log | grep 'Nov 17' > Nov_17.log
# 后续日志命令类似
...
那么很自然地,我思考到:如果大日志文件中,涉及的天数很多呢,难道有几十上百天,我就要敲几十上百次命令吗?显然不可能,于是想到了使用脚本自动操作
2)脚本:逐行read追加写
#!/bin/bash# 设置日志文件路径
log_file="messages.log"
# 设置输出目录
output_dir="logs"# 创建输出目录
mkdir -p "$output_dir"# 保存当前日期
current_date=""# 读取日志文件的每一行
while IFS= read -r line
do# 获取日期部分(前两个单词)date=$(echo "$line" | awk '{print $1, $2}')# 检查日期是否改变if [ "$date" != "$current_date" ]; then# 更新当前日期current_date="$date"# 将日期格式化为文件名中可接受的格式(替换空格为下划线)file_name=$(echo "$date" | tr ' ' '_').log# 创建新的日志文件touch "$output_dir/$file_name"fi# 将日志行追加到对应的文件中echo "$line" >> "$output_dir/$file_name"
done < "$log_file"
完成这一步后,我特地找了一个很大的日志文件(40w行)进行测试,发现过程很漫长,足足用了十几分钟,这可太慢了,想来是每次read进行IO操作浪费了不少时间,那么换个思路,将文件内容全部存入数组中,每次遍历去数组中读取内容,在必要的时候一次性将匹配的行全部写入目标文件。
3)脚本:写入数组进行操作,减少IO次数,匹配行号一次性写入
#!/bin/bash# 设置日志文件路径
log_file="$1"
# 设置输出目录
output_dir="logs"
# 将文件写入数组中
mapfile -t file_arr < $log_file
# 开始行
start_line=1
# 结束行
end_line=1
# 创建输出目录
mkdir -p "$output_dir"
#保存当前日期
current_date=""# 读取日志文件的每一行
for ((i=0; i<${#file_arr[@]}; i++)); doecho 当前时间 $(date | awk '{print $4}')# 获取日期部分(前两个单词)date=$(echo ${file_arr[i]} | awk '{print $1, $2}')if [ "$date" != "$current_date" ]; thenif [ "$current_date" != "" ]; then # 将日期格式化为文件名中可接受的格式(替换空格为下划线)file_name=$(echo "$current_date" | tr ' ' '_').log# 更新结束行end_line=$i# 将上一个日期的结果写入文件中awk -v start=$start_line -v end=$end_line 'NR >= start && NR <= end' $log_file > ${output_dir}/${file_name}# 更新起始行start_line=$((i+1))ficurrent_date=$datefiif [ $((i+1)) == ${#file_arr[@]} ]; then# 最后一个日期的处理file_name=$(echo "$current_date" | tr ' ' '_').logawk -v start=$start_line -v end=$((i+1)) 'NR >= start && NR <= end' $log_file > ${output_dir}/${file_name}fi
done
我信心满满地进行测试,但等了许久都不见完成。干脆每次循环输出一下当前时间,看看到底怎么回事。这一操作就跑了足足一个多小时,仔细观察终端的输出,发现程序每秒只执行了几十次循环,也不知道为啥如此慢。
我去咨询了一下GPT,说shell是解释型语言,执行效率相比编译型语言还是差一个档次。我用Java编写了同样的代码逻辑,并且是逐行输入,这下仅仅用了几秒钟,就完成了!不愧是高效的编程语言。
4) Java
public class Main {private static String[] fileArr;public static void main(String[] args) throws IOException {String fileDir = "src/test/resources";File file = new File(fileDir+"/messages.log");BufferedReader br = new BufferedReader(new FileReader(file));List<String> fileList = new ArrayList<>();String line;while ((line = br.readLine()) != null) {fileList.add(line);}fileArr = fileList.toArray(new String[0]);System.out.println(fileArr.length);String curDate = "";int startLine = 0;int endLine = 0;for (int i = 0; i < fileArr.length; i++) {String date = fileArr[i].split(" ")[0] + fileArr[i].split(" ")[1];if (!curDate.equals(date)) {if (!"".equals(curDate)) {endLine = i;String fileName = fileDir+"/"+curDate+".log";writeToFile(startLine, endLine, fileName);startLine = i + 1;}// 更新日期curDate = date;}// 最后的日期处理if (i == fileArr.length - 1) {String fileName = fileDir+"/"+curDate+".log";writeToFile(startLine, i, fileName);}}}private static void writeToFile(int startLine, int endLine, String fileName) throws IOException {BufferedWriter bw = new BufferedWriter(new FileWriter(fileName));for (int i = startLine; i <= endLine; i++) {bw.write(fileArr[i]);if (i == endLine){System.out.println(fileArr[i]);}bw.newLine();bw.flush();}}
}