我有一些大型日志文件,其中包含RFC3162 (MMM dd HH:mm:ss)的旧syslog格式日期,我想将其更改为RFC5424 (YYYY-mm-ddTHH:mm:ss +TMZ)的新syslog格式日期。我已经创建了以下bash脚本:
#!/bin/bash
#Loop over directories
for i in $1
do
echo "Processing directory $i"
if [ -d $i ]
then
cd $i
#Loop over log files inside the directory
for j in *.2021
do
echo "Processing file $j"
#Read line by line and perform transformation on dates and append to new file
cat $j |
while read CMD; do
tmpdate=$(printf '%sn' "$CMD" | awk -F" $i" 'BEGIN {ORS=""}; {print $1}')
newdate=$(date +'%Y-%m-%dT%H:%M:%S+02:00' -d "$tmpdate")
printf '%sn' "$CMD" | sed 's/'"$tmpdate"'/'"$newdate"'/g' >> $j.new
done
mv $j.new $j
done
cd ..
fi
done
但是这需要很长时间来执行,因为我有数百万行的文件(例如,邮件服务器上追溯到一年多前的日志)。到目前为止,这已经运行了几天,仍然有很多行要解析:-)
有两个问题
- 为什么这个脚本需要这么长的时间来执行?
- 有更快的方法来做到这一点吗?使用一个GNU utils (sed, awk等),bash或python。
======== EDIT =======
以下是旧格式的示例:
Feb 1 21:59:44 calendar os-prober: debug: running /usr/lib/os-probes/50mounted-tests on /dev/sda2
Feb 1 21:59:44 calendar 50mounted-tests: debug: /dev/sda2 type not recognised; skipping
Feb 1 21:59:44 calendar os-prober: debug: os detected by /usr/lib/os-probes/50mounted-tests
请注意,在Feb和1之间有2个空格,如果日期是10或更高,则空格仅为1,如
Feb 10 10:39:53 calendar os-prober: debug: running /usr/lib/os-probes/50mounted-tests on /dev/sda2
在新的格式中,它看起来像这样:
2021-02-01T21:59:44+02:00 calendar os-prober: debug: running /usr/lib/os-probes/50mounted-tests on /dev/sda2
2021-02-01T21:59:44+02:00 calendar 50mounted-tests: debug: /dev/sda2 type not recognised; skipping
2021-02-01T21:59:44+02:00 calendar os-prober: debug: os detected by /usr/lib/os-probes/50mounted-tests
TIA。
使用sed
重写整个文件的次数与文件中的行数相同。这是一个庞大的反模式,但不幸的是,这是相当常见的初学者反模式。
创建sed
命令的管道也非常复杂和低效。
当结果将以不同的顺序包含完全相同的信息时,您并不真的需要date
来转换日期格式。试试
awk -vyyyy="$(date +%Y)" 'BEGIN {
split("Jan:Feb:Mar:Apr:May:Jun:Jul:Aug:Sep:Oct:Nov:Dec", _m, ":");
for(i=1; i<=12; ++i) m[_m[i]] = i }
{ printf "%04i-%02i-%02iT%s+02:00 %s",
yyyy, m[$1], $2, $3, substr($0, 17) }' "$j" >"$j.new"
演示:https://ideone.com/VBDqB8
Bash是一种脚本语言,用于运行其他程序。因此,bash本身作为一种语言并不是很快。但如果你反复启动其他进程,情况会更糟。启动一个流程是非常昂贵的。每次执行为什么这个脚本需要这么长的时间来执行?
sed
、awk
、date
,甚至只是$(...)
或... | ...
时,都会启动一个进程。在循环中,这些加起来。
比较time for ((i=0; i<1000; ++i)); do true; done
和time for ((i=0; i<1000; ++i)); do /bin/true; done
。前者使用bash的内置命令,因此不会启动其他进程;它立即结束。后者使用外部程序,因此反复启动一个进程;在我的系统中需要4.5秒。
是否有更快的方法来做到这一点?使用一个GNU utils (sed, awk等),bash或python。
是的。如果你用python重写你的脚本,它会运行得非常快,假设你使用python的内置函数,而不是重复调用sp = subprocess.run(["date", ...], stdout=subprocess.PIPE])
和newDate = sp.stdout
等等:)
当你这样写的时候,你会立即注意到这是无效的。Bash使运行其他程序变得如此容易,以至于您经常忘记幕后完成的所有工作。
但是既然你把你的问题标记为bash,让我们坚持使用脚本解决方案。
MMM
到MM
的转换(例如Jan
到01
)对于sed
来说有点棘手。我们每个月都要换一个。幸运的是,月份总是在开头,所以我们可以把它和日期的其余部分分开替换。
为个位数天数添加前导零,我们使用了一个额外的替换。
sed -i.bak -E -e's/^Jan/01/;s/^Feb/02/;s/^Mar/03/;...'
-e's/^(..) /1 0/'
-e's/^([0-9]+) ?([0-9]+) ([0-9]+:[0-9]+:[0-9]+)/2021-1-2T3+02:00/' */*.2021
第一个表达式可以自动生成:
monthNameToNumber=$(
printf %s\n Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec |
awk '{printf "s/^%s/%02d/;", $0, NR}'
)
sed -i.bak -E -e"$monthNameToNumber"
-e's/^(..) /1 0/'
-e's/^([0-9]+) ?([0-9]+) ([0-9]+:[0-9]+:[0-9]+)/2021-1-2T3+02:00/' */*.2021
这将替换日志行开头的所有日期,在所有日志文件中,当前目录下的一个目录。日志将被就地修改。以.bak
为后缀创建每个日志的备份。