我对大文件和bash. 这是上下文:
我尝试使用以下脚本不起作用。 我的问题是关于这个脚本不起作用,而不是替代解决方案。
while read line; do
new_file=${line:0:10}_file.log
echo "$line" >> $new_file
done < file.log
Run Code Online (Sandbox Code Playgroud)
经过调试,发现问题出在new_file变量上。这个脚本:
while read line; do
new_file=${line:0:10}_file.log
echo $new_file
done < file.log | uniq -c
Run Code Online (Sandbox Code Playgroud)
给出了下面的结果(我把xes放在了数据保密的地方,其他字符是真实的)。注意dh和较短的字符串:
...
27402 2011-xx-x4
27262 2011-xx-x5
22514 2011-xx-x6
17908 2011-xx-x7
...
3227382 2011-xx-x9
4474604 2011-xx-x0
1557680 2011-xx-x1
1 2011-xx-x2
3 2011-xx-x1
...
12 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
1 208--
1 2011-xx-x1
1 2011-xx-dh
1 2011-xx-x1
...
Run Code Online (Sandbox Code Playgroud)
我的文件格式没有问题。该脚本cut -c 1-10 file.log | uniq -c仅提供有效的时间戳。有趣的是,上述输出的一部分变成了cut ... | uniq -c:
3227382 2011-xx-x9
4474604 2011-xx-x0
5722027 2011-xx-x1
Run Code Online (Sandbox Code Playgroud)
我们可以看到,在 uniq count 之后4474604,我的初始脚本失败了。
我是否在 bash 中遇到了我不知道的限制,我是否在 bash 中发现了一个错误(这似乎不太可能),或者我做错了什么?
更新:
问题发生在读取 2G 的文件后。它接缝read和重定向不喜欢大于 2G 的文件。但仍在寻找更准确的解释。
更新2:
它绝对看起来像一个错误。它可以通过以下方式复制:
yes "0123456789abcdefghijklmnopqrs" | head -n 100000000 > file
while read line; do file=${line:0:10}; echo $file; done < file | uniq -c
Run Code Online (Sandbox Code Playgroud)
但这作为一种解决方法效果很好(我发现它很有用cat):
cat file | while read line; do file=${line:0:10}; echo $file; done | uniq -c
Run Code Online (Sandbox Code Playgroud)
一个错误已提交给 GNU 和 Debian。受影响的版本是bashDebian Squeeze 6.0.2 和 6.0.4 上的4.1.5。
echo ${BASH_VERSINFO[@]}
4 1 5 1 release x86_64-pc-linux-gnu
Run Code Online (Sandbox Code Playgroud)
更新3:
感谢 Andreas Schwab 对我的错误报告做出快速反应,这个补丁可以解决这种不当行为。受影响的文件lib/sh/zread.c正如吉尔斯早先指出的那样:
diff --git a/lib/sh/zread.c b/lib/sh/zread.c index 0fd1199..3731a41 100644
--- a/lib/sh/zread.c
+++ b/lib/sh/zread.c @@ -161,7 +161,7 @@ zsyncfd (fd)
int fd; { off_t off;
- int r;
+ off_t r;
off = lused - lind; r = 0;
Run Code Online (Sandbox Code Playgroud)
该r变量用于保存 的返回值lseek。由于lseek返回文件开头的偏移量,当它超过 2GB 时,int值为负,这会导致测试if (r >= 0)在应该成功的地方失败。
Gil*_*il' 13
您在 bash 中发现了一个错误。这是一个已知的错误,有一个已知的修复程序。
程序将文件中的偏移量表示为某种具有有限大小的整数类型的变量。在过去,每个人都使用int几乎所有的东西,int类型被限制为 32 位,包括符号位,所以它可以存储从 -2147483648 到 2147483647 的值。现在不同的东西有不同的类型名称,包括off_t用于文件中的偏移量。
默认情况下,off_t在 32 位平台上是 32 位类型(最多允许 2GB),在 64 位平台上是 64 位类型(允许最多 8EB)。但是,通常使用 LARGEFILE 选项编译程序,该选项会将类型off_t切换为 64 位宽,并使程序调用合适的函数实现,例如lseek.
看来您正在 32 位平台上运行 bash,并且您的 bash 二进制文件没有使用大文件支持进行编译。现在,当您从常规文件中读取一行时,bash 使用内部缓冲区批量读取字符以提高性能(有关更多详细信息,请参阅 中的源代码builtins/read.def)。当一行完成时,bash 调用lseek将文件偏移量倒回到行尾的位置,以防其他程序关心该文件中的位置。调用lseek发生在 中的zsyncfc函数中lib/sh/zread.c。
我还没有详细阅读源代码,但我推测当绝对偏移量为负时,在过渡点某些事情不会顺利发生。因此,在超过 2GB 标记后,bash 在重新填充缓冲区时最终以错误的偏移量读取。
如果我的结论是错误的,而您的 bash 实际上是在 64 位平台上运行或编译时支持大文件,那绝对是一个错误。请将其报告给您的发行版或上游。
无论如何,shell 都不是处理如此大文件的正确工具。它会很慢。如果可能,请使用 sed,否则使用 awk。