我对大文件和文件有一个奇怪的问题bash
。这是上下文:
我尝试了以下无效的脚本。我的问题是有关此脚本不起作用,而不是替代解决方案。
while read line; do
new_file=${line:0:10}_file.log
echo "$line" >> $new_file
done < file.log
调试后,我在new_file
变量中发现了问题。该脚本:
while read line; do
new_file=${line:0:10}_file.log
echo $new_file
done < file.log | uniq -c
给出结果如下(我把x
es用来保持数据机密,其他字符是真实的)。请注意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
...
我的文件格式没有问题。该脚本cut -c 1-10 file.log | uniq -c
仅提供有效的时间戳。有趣的是,上述输出的一部分变为cut ... | uniq -c
:
3227382 2011-xx-x9
4474604 2011-xx-x0
5722027 2011-xx-x1
我们可以看到,在uniq count之后4474604
,我的初始脚本失败了。
我是否达到了我不知道的bash限制,是否在bash中发现了bug(似乎不太可能缝制),或者我做错了什么?
更新:
读取文件的2G后会发生此问题。它的接缝read
和重定向不喜欢比2G大的文件。但仍在寻找更精确的解释。
Update2:
绝对看起来像个错误。可以用以下方式复制它:
yes "0123456789abcdefghijklmnopqrs" | head -n 100000000 > file
while read line; do file=${line:0:10}; echo $file; done < file | uniq -c
但这可以作为一种解决方法(它表明我发现对有用cat
):
cat file | while read line; do file=${line:0:10}; echo $file; done | uniq -c
已将一个错误提交给GNU和Debian。受影响的版本是bash
Debian Squeeze 6.0.2和6.0.4上的4.1.5。
echo ${BASH_VERSINFO[@]}
4 1 5 1 release x86_64-pc-linux-gnu
更新3:
感谢Andreas Schwab对我的错误报告迅速做出了反应,这是解决此不良行为的补丁。lib/sh/zread.c
正如Gilles早先指出的那样,受影响的文件:
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;
该r
变量用于保存的返回值lseek
。Aslseek
返回从文件开头开始的偏移量,当偏移量超过2GB时,该int
值为负,这会导致测试if (r >= 0)
在应该成功的地方失败。
您已经在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
。
我没有详细阅读源代码,但是我猜想,当绝对偏移为负数时,过渡点不会顺利进行。因此,bash在超过2GB标记后重新填充缓冲区时,最终会读取错误的偏移量。
如果我的结论是错误的,并且您的bash实际上是在64位平台上运行或使用大文件支持进行编译,则肯定是一个错误。请报告给您的发行商或上游。
无论如何,shell并不是处理此类大文件的正确工具。将会很慢。如果可能,请使用sed,否则请awk。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句