最近我需要查看我的 Minecraft 服务器中的日志。我需要解压几个压缩日志文件并将它们折叠成一个巨大的日志。然后我会使用 grep 来寻找有用的信息(但这就是我决定这样做的方式)。
所以我决定编写一个 bash 脚本:
dir="/home/minecraft/spigot-1.20.2/logs"
logs=$(ls $dir)
start=$SECONDS
sum=""
for log in $logs
do
s=$SECONDS
log_date=$(echo $log | cut -b 1-10)
unzipped_log="$(zcat -f $dir/$log | sed -e "s/\[Async Chat Thread - #[0-9]\+\/INFO]: //" -e "s/^/$log_date /g")"$'\n'
while read -r line
do
echo "$line"
done <<< "$unzipped_log"
echo The file $log $(du -h $dir/$log) processed in $(($SECONDS - $s))
done
s=$SECONDS
last="$(cat $dir/latest.log | sed -e "s/\[Async Chat Thread - #[0-9]\+\/INFO]: //")"$'\n'
date_today=$(date +%Y-%m-%d)
while read -r line
do
echo "$date_today $line"
done <<< "$last"
echo The file latest.log processed in $(($SECONDS - $s))
echo Script worked in $(($SECONDS-$start)) seconds
启动后
source sum_logs.sh | tac > all_logs.txt
计时为60秒。
然后我用Python重写了代码:
import os, gzip, time, re
from datetime import datetime
dir = "/home/minecraft/spigot-1.20.2/logs"
start = time.time()
time.sleep(1)
logs = os.listdir(dir)
for log in logs:
log_path = os.path.join(dir, log)
log_date = log[:10]
try:
with gzip.open(log_path, 'rt') as f:
for line in f.readlines():
line = re.sub(r'\[Async Chat Thread - #\d+\/INFO]: ', '', line)
print(log_date, line.replace('\n', ''))
except:
with open(log_path, 'r') as f:
for line in f.readlines():
line = re.sub(r'\[Async Chat Thread - #\d+\/INFO]: ', '', line)
print(log_date, line.replace('\n', ''))
elapsed = round(time.time() - start)
print(f'Script worked in {elapsed} seconds')
计时为9秒。
我知道 bash 比 Python 慢。但我不知道差异是 6 倍。 bash 真的这么慢吗?还是有办法加快我的代码速度?
随着问题的最新更新,评论似乎没有足够的意义,迭代建议......
unzipped_log="$(zcat -f $dir/$log | sed -e "s/\[Async Chat Thread - #[0-9]\+\/INFO]: //" -e "s/^/$log_date /g")"
while read -r line
do
echo "$line"
done <<< "$unzipped_log"
无需将所有
zcat ... | sec ...
结果存储在变量中,而是直接输入到 while/read
循环:
while read -r line
do
echo "$line"
done < <(zcat -f $dir/$log | sed -e "s/\[Async Chat Thread - #[0-9]\+\/INFO]: //" -e "s/^/$log_date /g")"
但是由于循环所做的唯一事情就是“读取一行,回显一行,读取一行,回显一行”,我们可以消除循环:
zcat -f $dir/$log | sed -e "s/\[Async Chat Thread - #[0-9]\+\/INFO]: //" -e "s/^/$log_date /g"
可以使用完全相同的代码设计来替换第二个
while/read
循环。
注意: 将
$date_today
移至 sed
脚本中,就像对 $log_date
所做的那样。
logs=$(ls $dir) / for log in $logs
结构可以改进,但在 $dir
目录中获取实际文件名的样本会有所帮助。
除了评论中的咆哮之外 - 请记住,例如,当您在 Bash 中执行
sed ...
时,您正在运行整个子进程 - 一个操作系统支持的子进程,有很多开销 - 而在 Python 中,函数调用只会运行几个操作码。这同样适用于 ls
、echo
以及几乎所有“命令”,但少数是内置命令。
精通 Bash 的人通常会知道如何避免这些陷阱,只是不要在循环中调用子进程。因此,评论中的人们告诉您“永远不要在脚本中使用
ls
”。
另一方面,像 Python 这样的语言具有标准语法,可以在单个进程中运行所有内容,如果需要在外部进程中运行某些内容,这是非常明确的 - 函数调用所占用的操作系统资源比单一进程少 1000 倍。外部进程正在运行。