Perl比bash快吗?
我有一个bash脚本,它在两个时间戳之间剪切日志文件的一部分,但是由于文件的大小,运行它需要相当长的时间 如果我要用Perl重写脚本,我能实现显著的速度提升吗?或者我必须使用类似C的语言来实现这一点吗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
#!/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解析_