高效编辑大文件
Posted
技术标签:
【中文标题】高效编辑大文件【英文标题】:Editing large files efficiently 【发布时间】:2021-10-24 11:29:00 【问题描述】:我有一些大型日志文件具有来自 RFC3162 (MMM dd HH:mm:ss) 的旧 syslog 格式日期,我想将其更改为来自 RFC5424 (YYYY-mm-ddTHH:mm:ss) 的新 syslog 格式日期+TMZ)。我创建了以下 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 '%s\n' "$CMD" | awk -F" $i" 'BEGIN ORS=""; print $1')
newdate=$(date +'%Y-%m-%dT%H:%M:%S+02:00' -d "$tmpdate")
printf '%s\n' "$CMD" | sed 's/'"$tmpdate"'/'"$newdate"'/g' >> $j.new
done
mv $j.new $j
done
cd ..
fi
done
但这需要很长时间才能执行,因为我有几百万行的文件(例如,邮件服务器上的日志可以追溯到一年多)。到目前为止,这已经运行了好几天,还有很多行要解析:-)
那么两个问题。
-
为什么这个脚本需要这么长时间才能执行?
有更快的方法吗?使用 GNU utils(sed、awk 等)、bash 或 python 之一。
======== 编辑 =======
以下是旧格式的示例:
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
注意2月和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。
【问题讨论】:
您可能想要for i in "$@"
而不是for i in $1
- 根据定义,$1
只能包含一个项目。
好吧,从技术上讲,未引用的 $1
会进行分词和全局扩展,因此 for i in $1
会工作(或break,具体取决于您的查看)如果您致电 script.sh "dir1 dir2 dir3"
或 script.sh "*"
。是的,但是for i in "$@"; do
或只是for i; do
将是处理多个参数的明智方式。
啊,是的,那是$1
是因为我一次给脚本一个文件。由于 $things 的原因,我必须在笔记本电脑上执行此操作,并且解析一些文件需要很长时间,并且我需要在完成工作后关闭笔记本电脑的电源,然后我在尝试找出更好的方法时这样做了。
【参考方案1】:
您使用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
【讨论】:
感谢您,它运行良好。只是一个更正,sed
从不接触文件,它从stdin
获取管道并将其输出到新文件的末尾(或者它可能是您所指的新文件的附加内容?)
是的,您读取整个文件的次数与来自(useless!) cat
的输入行一样多。相关:***.com/questions/65538947/…【参考方案2】:
为什么这个脚本需要这么长时间才能执行?
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-\2T\3+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-\2T\3+02:00/' */*.2021
这将替换日志行开头的所有日期,在当前目录下的所有日志文件中的一个目录中。日志将就地修改。每个日志的备份都使用后缀.bak
创建。
【讨论】:
-E
选项到 sed
不可移植。 OP 标记了这个linux,所以它可能对他们有用(尽管看到初学者将 Mac 和 Windows 问题错误地标记为 Linux 的情况并不少见,无论出于何种原因),但它不起作用,例如在开箱即用的 Mac 上 - 尽管 sed -r
在那里的工作方式大致相同。
@tripleee -i
也不是。但是现在,我知道的每个sed
实现都支持-E
和-i suffix
。实际上-E
来自BSD,而GNU使用-r
并在后来添加-E
作为同义词(在commit message中,作者甚至声称-E
被添加到POSIX,但我找不到它在那里)。由于 macOS 使用来自 BSD 的sed
,macOS supports -E
and -i suffix
。
这看起来更有希望,不过是一个问题。如果日志的消息部分(即时间戳之后的文本)包含类似something 1 something else
的内容,那么新的正则表达式将取代它,是否有任何危险?
@proxymoxy 不,至少如果每行都以日期开头,则不会。我使用^
(行首)来防止这种情况。 s/^(..) /\1 0/'
只处理每行的前四个字符。
@socowi 太好了,这正是医生所要求的 :-)以上是关于高效编辑大文件的主要内容,如果未能解决你的问题,请参考以下文章