Perl比bash快吗?

Perl比bash快吗?,perl,bash,optimization,comparison,performance,Perl,Bash,Optimization,Comparison,Performance,我有一个bash脚本,它在两个时间戳之间剪切日志文件的一部分,但是由于文件的大小,运行它需要相当长的时间 如果我要用Perl重写脚本,我能实现显著的速度提升吗?或者我必须使用类似C的语言来实现这一点吗 #!/bin/bash if [ $# -ne 3 ]; then echo "USAGE $0 <logfile(s)> <from date (epoch)> <to date (epoch)>" exit 1 fi LOGFILES=$1 FR

我有一个bash脚本,它在两个时间戳之间剪切日志文件的一部分,但是由于文件的大小,运行它需要相当长的时间

如果我要用Perl重写脚本,我能实现显著的速度提升吗?或者我必须使用类似C的语言来实现这一点吗

#!/bin/bash

if [ $# -ne 3 ]; then
  echo "USAGE $0 <logfile(s)> <from date (epoch)> <to date (epoch)>"
  exit 1
fi

LOGFILES=$1
FROM=$2
TO=$3
rm -f /tmp/getlogs??????
TEMP=`mktemp /tmp/getlogsXXXXXX`

## LOGS NEED TO BE LISTED CHRONOLOGICALLY
ls -lnt $LOGFILES|awk '{print $8}' > $TEMP
LOGFILES=`tac $TEMP`
cp /dev/null $TEMP

findEntry() {
  RETURN=0
  dt=$1
  fil=$2
  ln1=$3
  ln2=$4
  t1=`tail -n+$ln1 $fil|head -n1|cut -c1-15`
  dt1=`date -d "$t1" +%s`
  t2=`tail -n+$ln2 $fil|head -n1|cut -c1-15`
  dt2=`date -d "$t2" +%s`
  if [ $dt -ge $dt2 ]; then
    mid=$dt2
  else
    mid=$(( (($ln2-$ln1)*($dt-$dt1)/($dt2-$dt1))+$ln1 ))
  fi
  t3=`tail -n+$mid $fil|head -n1|cut -c1-15`
  dt3=`date -d "$t3" +%s`
  # finished
  if [ $dt -eq $dt3 ]; then
    # FOUND IT (scroll back to the first match)
    while [ $dt -eq $dt3 ]; do
      mid=$(( $mid-1 ))
      t3=`tail -n+$mid $fil|head -n1|cut -c1-15`
      dt3=`date -d "$t3" +%s`
    done
    RETURN=$(( $mid+1 ))
    return
  fi
  if [ $(( $mid-1 )) -eq $ln1 ] || [ $(( $ln2-1)) -eq $mid ]; then
    # FOUND NEAR IT
    RETURN=$mid
    return
  fi
  # not finished yet
  if [ $dt -lt $dt3 ]; then
    # too high
    findEntry $dt $fil $ln1 $mid
  else
    if [ $dt -ge $dt3 ]; then
      # too low
      findEntry $dt $fil $mid $ln2
    fi
  fi
}

# Check timestamps on logfiles
LOGS=""
for LOG in $LOGFILES; do
  filetime=`ls -ln $LOG|awk '{print $6,$7}'`
  timestamp=`date -d "$filetime" +%s`
  if [ $timestamp -ge $FROM ]; then
    LOGS="$LOGS $LOG"
  fi
done

# Check first and last dates in LOGS to refine further
for LOG in $LOGS; do
    if [ ${LOG%.gz} != $LOG ]; then
      gunzip -c $LOG > $TEMP
    else
      cp $LOG $TEMP
    fi
    t=`head -n1 $TEMP|cut -c1-15`
    FIRST=`date -d "$t" +%s`
    t=`tail -n1 $TEMP|cut -c1-15`
    LAST=`date -d "$t" +%s`
    if [ $TO -lt $FIRST ] || [ $FROM -gt $LAST ]; then
      # This file is entirely out of range
      cp /dev/null $TEMP
    else
      if [ $FROM -le $FIRST ]; then
        if [ $TO -ge $LAST ]; then
          # Entire file is within range
          cat $TEMP
        else
          # Last part of file is out of range
          STARTLINENUMBER=1
          ENDLINENUMBER=`wc -l<$TEMP`
          findEntry $TO $TEMP $STARTLINENUMBER $ENDLINENUMBER
          head -n$RETURN $TEMP
        fi
      else
        if [ $TO -ge $LAST ]; then
          # First part of file is out of range
          STARTLINENUMBER=1
          ENDLINENUMBER=`wc -l<$TEMP`
          findEntry $FROM $TEMP $STARTLINENUMBER $ENDLINENUMBER
          tail -n+$RETURN $TEMP
        else
          # range is entirely within this logfile
          STARTLINENUMBER=1
          ENDLINENUMBER=`wc -l<$TEMP`
          findEntry $FROM $TEMP $STARTLINENUMBER $ENDLINENUMBER
          n1=$RETURN
          findEntry $TO $TEMP $STARTLINENUMBER $ENDLINENUMBER
          n2=$RETURN
          tail -n+$n1 $TEMP|head -n$(( $n2-$n1 ))
        fi
      fi
    fi
done
rm -f /tmp/getlogs??????
#/bin/bash
如果[$#-ne 3];然后
回显“使用率$0”
出口1
fi
日志文件=$1
FROM=2美元
至=3美元
rm-f/tmp/getlogs??????
TEMP=`mktemp/tmp/getlogsxxx`
##日志需要按时间顺序列出
ls-lnt$LOGFILES | awk'{print$8}'>$TEMP
日志文件=`tac$TEMP`
cp/dev/null$TEMP
findEntry(){
返回=0
dt=1美元
费尔=2美元
ln1=3美元
ln2=4美元
t1=`tail-n+ln1$fil | head-n1 | cut-c1-15`
dt1=`date-d“$t1”+%s`
t2=`tail-n+ln2$fil | head-n1 | cut-c1-15`
dt2=`date-d“$t2”+%s`
如果[$dt-ge$dt2];则
mid=$dt2
其他的
mid=$(($ln2-$ln1)*($dt-$dt1)/($dt2-$dt1))+ln1))
fi
t3=尾部-n+中部$fil |头部-n1 |切割-c1-15`
dt3=`date-d“$t3”+%s`
#完成
如果[$dt-等式$dt3];则
#找到它(滚动回第一个匹配项)
而[$dt-等式$dt3];则
中期=$($mid-1))
t3=尾部-n+中部$fil |头部-n1 |切割-c1-15`
dt3=`date-d“$t3”+%s`
完成
返回=$($mid+1))
返回
fi
如果[$($mid-1))-eq$ln1]|【$($ln2-1))-eq$mid];则
#在附近找到的
返回=$mid
返回
fi
#还没完成
如果[$dt-lt$dt3];则
#太高
财政部$dt$fil$ln1$mid
其他的
如果[$dt-ge$dt3];则
#太低
财政部$dt$fil$mid$ln2
fi
fi
}
#检查日志文件上的时间戳
LOGS=“”
用于登录$LOGFILES;做
filetime=`ls-ln$LOG | awk'{print$6,$7}'`
时间戳=`date-d“$filetime”+%s`
如果[$timestamp-ge$FROM];然后
LOGS=“$LOGS$LOG”
fi
完成
#检查日志中的第一个和最后一个日期以进一步细化
用于登录$LOGS;做
如果[${LOG%.gz}!=$LOG];然后
gunzip-c$LOG>$TEMP
其他的
cp$LOG$TEMP
fi
t=`head-n1$TEMP | cut-c1-15`
FIRST=`date-d“$t”+%s`
t=`tail-n1$TEMP | cut-c1-15`
LAST=`date-d“$t”+%s`
如果[$TO-lt$FIRST]| |[$FROM-gt$LAST];然后
#此文件完全超出范围
cp/dev/null$TEMP
其他的
如果[$FROM-le$FIRST];然后
如果[$TO-ge$LAST];然后
#整个文件在范围内
cat$TEMP
其他的
#文件的最后一部分超出范围
数字=1

ENDLINENUMBER=`wc-l您几乎肯定会意识到用Perl编写脚本的巨大速度优势,只要在传递第二个时间戳时切断文件读取即可


一般来说,是的;任何复杂的
bash
脚本,除非它真的是一种神奇的魔法,否则在同等的输入和输出方面都可以轻易地被Perl脚本超越。

Perl比bash快得离谱。而且,对于文本操作,使用Perl实际上可以获得比使用C更好的性能,除非您花时间编写复杂的算法。当然,对于简单的东西来说,C是无与伦比的

也就是说,如果您的“bash”脚本没有循环,只是调用其他程序,那么就没有任何好处了。例如,如果脚本看起来像“
catx | grep Y | tr-f3-5 | sort | uniq
”,那么大部分时间都花在cat、grep、tr、sort和uniq上,而不是Bash上

如果脚本中存在任何循环,或者保存了对同一文件的多次读取,则可以获得性能

你说你在一个文件的两个时间戳之间剪切东西。假设您的Bash脚本如下所示:

LINE1=`grep -n TIMESTAMP1 filename | head -1 | cut -d ':' -f 1`
LINE2=`grep -n TIMESTAMP2 filename | head -1 | cut -d ':' -f 1`
tail +$LINE1 filename | head -$(($LINE2-$LINE1))
my $state = 0;
while(<>) {
  exit if /TIMESTAMP2/;
  print $_ if $state == 1;
  $state = 1 if /TIMESTAMP1/;
}
然后您将获得性能,因为您将读取整个文件三次:对于出现“filename”的每个命令,读取一次。在Perl中,您可以执行以下操作:

LINE1=`grep -n TIMESTAMP1 filename | head -1 | cut -d ':' -f 1`
LINE2=`grep -n TIMESTAMP2 filename | head -1 | cut -d ':' -f 1`
tail +$LINE1 filename | head -$(($LINE2-$LINE1))
my $state = 0;
while(<>) {
  exit if /TIMESTAMP2/;
  print $_ if $state == 1;
  $state = 1 if /TIMESTAMP1/;
}
my$state=0;
while(){
退出if/TIMESTAMP2/;
如果$state==1,则打印$;
$state=1如果/TIMESTAMP1/;
}
这将只读取文件一次,并且在读取TIMESTAMP2后也将停止。因为您正在处理多个文件,所以应该使用“last”或“break”而不是“exit”,以便脚本可以继续处理这些文件

无论如何,看到你的脚本,我敢肯定,用Perl重写它会让你受益匪浅。尽管循环处理文件名(其速度将提高,但可能微不足道),但对于不完全在范围内或范围外的每个文件,您可以:

  • 阅读整个文件以计算行数
  • 对文件执行多个尾部
  • 通过“head”或“tail”再次完成文件

  • 此外,头朝下。每次这样做时,都会有一段代码读取该数据。其中一些行会被读10次甚至更多

    我将分析这三种解决方案,并选择在初始启动速度、处理速度和内存使用方面最好的解决方案


    Perl/Python/Ruby之类的东西可能不是绝对最快的,但您可以用这些语言快速开发—比C甚至Bash快得多。

    这取决于Bash脚本的编写方式。如果您没有使用awk来解析日志文件,而是使用bash的while-read循环,那么将其更改为awk将提高速度

    bash
    在动态解释文件时(如果您在运行
    bash
    脚本时修改脚本,您会痛苦地意识到这一点),而不是一次预加载和解析所有文件。是的,如果你在做任何你在
    bash中通常不会做的事情,Perl通常会快得多。

    根据Brent的评论更新了脚本:这一个未经测试

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    my %months = (
        jan => 1, feb => 2,  mar => 3,  apr => 4,
        may => 5, jun => 6,  jul => 7,  aug => 8,
        sep => 9, oct => 10, nov => 11, dec => 12,
    );
    
    while ( my $line = <> ) {
        my $ts = substr $line, 0, 15;
        next if parse_date($ts) lt '0201100543';
        last if parse_date($ts) gt '0715123456';
        print $line;
    }
    
    sub parse_date {
        my ($month, $day, $time) = split ' ', $_[0];
        my ($hour, $min, $sec) = split /:/, $time;
        return sprintf(
            '%2.2d%2.2d%2.2d%2.2d%2.2d',
            $months{lc $month}, $day,
            $hour, $min, $sec,
        );
    }
    
    
    __END__
    
    #/usr/bin/perl
    严格使用;
    使用警告;
    我的%months=(
    1月=>1日,2月=>2日,3月=>3日,4月=>4日,
    5月=>5日,6月=>6日,7月=>7日,8月=>8日,
    九月=>9日,十月=>10日,十一月=>11日,十二月=>12日,
    );
    while(我的$line=){
    my$ts=substr$行,0,15;
    下一个if parse_date($ts)lt'02011000543';
    最后一个if解析_