Bash 慢还是我做错了什么?

问题描述 投票:0回答:2

最近我需要查看我的 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 真的这么慢吗?还是有办法加快我的代码速度?

python bash performance shell
2个回答
0
投票

随着问题的最新更新,评论似乎没有足够的意义,迭代建议......

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
目录中获取实际文件名的样本会有所帮助。


0
投票

除了评论中的咆哮之外 - 请记住,例如,当您在 Bash 中执行

sed ...
时,您正在运行整个子进程 - 一个操作系统支持的子进程,有很多开销 - 而在 Python 中,函数调用只会运行几个操作码。这同样适用于
ls
echo
以及几乎所有“命令”,但少数是内置命令。

精通 Bash 的人通常会知道如何避免这些陷阱,只是不要在循环中调用子进程。因此,评论中的人们告诉您“永远不要在脚本中使用

ls
”。

另一方面,像 Python 这样的语言具有标准语法,可以在单个进程中运行所有内容,如果需要在外部进程中运行某些内容,这是非常明确的 - 函数调用所占用的操作系统资源比单一进程少 1000 倍。外部进程正在运行。

© www.soinside.com 2019 - 2024. All rights reserved.