bash - 让 awk 高效(再次)
问题描述
我有下面的代码,它可以成功运行(感谢@EdMorton),用于解析、清理日志文件(非常大)并输出到更小的文件中。输出文件名是每行的前 2 个字符。但是,如果这两个字符中有特殊字符,则需要将其替换为“_”。这将有助于确保文件名中没有非法字符。
接下来,它检查是否有任何输出文件大于特定大小,如果是,则该文件被第三个字符子分割。
处理 1 GB 的日志(在我的笔记本电脑上)大约需要 10 分钟。这可以更快吗?任何帮助将不胜感激。
示例日志文件
"email1@foo.com:datahere2
email2@foo.com:datahere2
email3@foo.com datahere2
email5@foo.com;dtat'ah'ere2
wrongemailfoo.com
nonascii@row.com;data.is.junk-Œœ
email3@foo.com:datahere2
预期产出
# cat em
email1@foo.com:datahere2
email2@foo.com:datahere2
email3@foo.com:datahere2
email5@foo.com:dtat'ah'ere2
email3@foo.com:datahere2
# cat _leftover
wrongemailfoo.com
nonascii@row.com;data.is.junk-Œœ
代码:
#/usr/bin/env bash
Func_Clean(){
pushd $1 > /dev/null
awk '
{
gsub(/^[ \t"'\'']+|[ \t"'\'']+$/, "")
sub(/[,|;: \t]+/, ":")
if (/^[[:alnum:]_.+-]+@[[:alnum:]_.-]+\.[[:alnum:]]+:/ && /^[\x00-\x7F]*$/) {
print
}
else {
print >> "_leftover"
}
}
' * |
sort -t':' -k1,1 |
awk '
{ curr = tolower(substr($0,1,2)) }
curr != prev {
close(Fpath)
Fpath = gensub(/[^[:alnum:]]/,"_","g",curr)
prev = curr
}
{
print >> Fpath
# print | "gzip -9 -f >> " Fpath # Throws an error
} ' && rm *.txt
find * -type f -prune -size +1000000c \( ! -iname "_leftover" \) |while read FILE; do
awk '
{ curr = tolower(substr($0,1,3)) }
curr != prev {
close(Fpath)
Fpath = gensub(/[^[:alnum:]]/,"_","g",curr)
prev = curr
}
{
print >> Fpath
# print | "gzip -9 -f >> " Fpath # Throws an error
} ' "$FILE" && rm "$FILE"
done
#gzip -9 -f -r . # This would work, but is it effecient?
popd > /dev/null
}
### MAIN - Starting Point ###
BASE_FOLDER="_test2"
for dir in $(find $BASE_FOLDER -type d);
do
if [ $dir != $BASE_FOLDER ]; then
echo $dir
time Func_Clean "$dir"
fi
done
解决方案
Wrt 主题Make awk efficient (again)
- awk 非常高效,您正在寻找使特定 awk 脚本更高效的方法,并使调用 awk 的 shell 脚本更高效。
我看到的唯一明显的性能改进是:
- 改变:
find * -type f -prune -size +1000000c \( ! -iname "_leftover" \) |
while read FILE; do
awk 'script' "$FILE" && rm "$FILE"
done
类似于(未经测试):
readarray -d '' files < <(find . -type f -prune -size +1000000c \( ! -iname "_leftover" \) -print0) &&
awk 'script' "${files[@]}" &&
rm -f "${files[@]}"
所以你总共调用一次 awk 而不是每个文件一次。
对所有目录中的所有文件调用
Func_Clean()
一次,而不是每个目录调用一次。使用GNU 并行或类似方式
Func_Clean()
在所有目录上并行运行。
我看到您正在考虑将输出管道传输gzip
到以节省空间,这很好,但请注意,这会在执行时间方面花费您一些东西(idk 多少)。此外,如果你这样做,那么你需要关闭整个输出管道,因为这是你从 awk 写入的内容,而不仅仅是它末尾的文件,所以你的代码将类似于(未经测试):
{ curr = tolower(substr($0,1,3)) }
curr != prev {
close(Fpath)
Fpath = "gzip -9 -f >> " gensub(/[^[:alnum:]]/,"_","g",curr)
prev = curr
}
{ print | Fpath }
除了上面的建议之外,这并不是为了加快速度find
,它只是清理您问题中的代码以减少冗余和常见错误(UUOC、缺少引号、读取 find 输出的错误方式、不正确使用>>
vs >
, ETC。)。从这样的事情开始(未经测试,假设您确实需要为每个目录分离输出文件):
#/usr/bin/env bash
clean_in() {
awk '
{
gsub(/^[ \t"'\'']+|[ \t"'\'']+$/, "")
sub(/[,|;: \t]+/, ":")
if (/^[[:alnum:]_.+-]+@[[:alnum:]_.-]+\.[[:alnum:]]+:/ && /^[\x00-\x7F]*$/) {
print
}
else {
print > "_leftover"
}
}
' "${@:--}"
}
split_out() {
local n="$1"
shift
awk -v n="$n" '
{ curr = tolower(substr($0,1,n)) }
curr != prev {
close(Fpath)
Fpath = gensub(/[^[:alnum:]]/,"_","g",curr)
prev = curr
}
{ print > Fpath }
' "${@:--}"
}
Func_Clean() {
local dir="$1"
printf '%s\n' "$dir" >&2
pushd "$dir" > /dev/null
clean_in *.txt |
sort -t':' -k1,1 |
split_out 2 &&
rm -f *.txt &&
readarray -d '' big_files < <(find . -type f -prune -size +1000000c \( ! -iname "_leftover" \) -print0) &&
split_out 3 "${big_files[@]}" &&
rm -f "${big_files[@]}"
popd > /dev/null
}
### MAIN - Starting Point ###
base_folder="_test2"
while IFS= read -r dir; do
Func_Clean "$dir"
done < <(find "$base_folder" -mindepth 1 -type d)
如果我是你,我会从那个开始(在任何必要的测试/调试之后)然后寻找提高性能的方法。
推荐阅读
- c# - 如何在单个事务中重用从实体框架返回的 ID
- apache-spark - Spark 数据集的 Avro Schema 与 Scala 案例类
- javascript - 我正在尝试创建一个下载按钮,用于下载该页面上的图像......有一个问题
- r - R_Co-kriging 当感兴趣的变量和辅助变量不在同一位置测量时
- scala - 避免在模式匹配情况下重做不必要的计算
- powershell - 使用 Powershell 脚本搜索 AD 计算机是否存在
- react-native - 使用 react-native-maps 和 use_frameworks 安装 Pod 错误“React/RCTView.h”文件未找到
- python - 在 Python 中循环遍历 JSON 数据
- mongodb - MongoDB Compass 缺少架构选项卡
- python - 求具有两个参数的函数的平均值