【问题标题】:Reading 2gb files line by line from Bash script results in "xmalloc:cannot allocate..." error从 Bash 脚本中逐行读取 2gb 文件会导致“xmalloc:cannot allocate...”错误
【发布时间】:2015-01-20 06:20:14
【问题描述】:

在 bash 脚本中打开文件描述符并逐行读取文件时,脚本在处理 70K 行后终止并出现内存分配错误:

xmalloc: 无法分配 11541 字节(已分配 0 字节)

环境: 明威32 Bash:3.1.20(4)-release (i686-pc-msys) 操作系统:Windows 7

输入文件的大小:每个 1.2 GB

脚本如下:

#!/bin/bash
echo Left: $1
echo Right: $2
echo >"$1.diff"
echo >"$2.diff"
exec 4<"$1"
exec 5<"$2"
LINECOUNT=0
while [ $? == 0 ]
do
    exec 0<&4
    read LEFTLINE
    exec 0<&5
    read RIGHTLINE

    if [ $? != 0 ]
    then
        exit -1
    fi

    LINECOUNT=$(($LINECOUNT + 1))
    LINEMOD=$(($LINECOUNT % 1000))
    if [[ $LINEMOD == 0 ]]
    then
        echo Line: $LINECOUNT
    fi

    if [ $LEFTLINE != $RIGHTLINE ]
    then
        echo $LEFTLINE >> "$1.diff"
        echo $RIGHTLINE >> "$2.diff"
        echo Mismatch found
    fi
done

正如我上面所说的,脚本工作了很长时间,处理了大约 70K 行,然后终止。我认为它会终止,因为它耗尽了 32 位进程可以占用的所有内存。

脚本的目的是打开两个相同格式和长度的文件,逐行比较。它创建两个输出文件到其中写出不匹配行的位置。我不得不编写脚本,因为我可以使用的所有比较工具都因“内存不足”错误而崩溃或挂起。当我的脚本也崩溃时,我感到很惊讶。我不得不在 C++ 中重写它以使其工作。现在我试图理解为什么 bash 脚本失败了。理论上它不应该在内存中累积文件内容。相反,它应该一次只读取一行并推进文件指针。我试图了解它为什么会崩溃。也许还有另一种方法可以解决我的问题,您可以推荐我可以将其实现为 bash 脚本。

更新:测试了以下修改。它也崩溃了。

while IFS= read -u4 -r LEFTLINE && IFS= read -u5 -r RIGHTLINE
do
    LINECOUNT=$(($LINECOUNT + 1))
    LINEMOD=$(($LINECOUNT % 1000))

【问题讨论】:

  • 您是否尝试过read -u4 而不是exec 0&lt;&amp;4 语法?所以你可以做while IFS= read -u4 -r LEFTLINE &amp;&amp; IFS= read -u5 -r RIGHTLINE; do echo "$LEFTLINE"; echo "$RIGHTLINE"; done 4&lt;fileA 3&lt;fileB
  • @Mark Setchell 感谢您的有用建议。这样代码就更简洁了。我更新了问题。但是,它仍然因同样的错误而崩溃。
  • @MarkSetchell 实际上是 4gb,但这并不是重点;这可能与 bash 如何读取文件有关。如果有泄漏,那么就有泄漏 - 如果我在 OSX 上输入脚本 /dev/urandom,我也会看到进程的内存持续增长。在特定版本的 bash 中有几篇与内存泄漏相关的在线帖子,所以这可能是问题所在。不幸的是,如果发生泄漏,您可能不得不使用不同的工具而不是 bash。 this looks like it may be related。 OSX 有适合的 bash3.2
  • 小提示:如果您只想清除文件,可以将echo &gt;"$1.diff" 替换为&gt;"$1.diff"。当你比较它们时,你应该在 $LEFTLINE 和 $RIGHTLINE 周围加上双引号 ("),否则当文件中有空格时脚本会失败。
  • 我在一对相同的 20 GB 文件上运行了您的脚本,它只使用了 10 MB 的内存,这并没有增加。重击 4.1.5,x86_64。

标签: windows bash shell mingw32


【解决方案1】:

借助 cmets 人员对问题的宝贵意见,我们找到了解决方案。 Petesh 正确地评论说在以前的 bash 版本中存在一个错误(或许多错误)导致内存泄漏Here 是 Petesh 提供的票的链接。幸运的是,该漏洞已在更新的 bash 版本中得到修复。 所以解决方案是更新 bash。 我使用 bash 版本 4.1.17(9)-release (i686-pc-cygwin) 安装了 cygwin,我的脚本成功完成,只消耗了 1.5 Mb 的内存而没有内存增加。 John Zwinch 还测试了 Bash 4.1.5、x86_64 并确认该版本也修复了该错误。

在解决该问题时,Mark Setchell 和 John Zwinck 建议对脚本进行一些改进。修改并没有解决问题,但使用不同的文件格式使脚本更简单、更可靠。脚本的最终版本如下:

#!/bin/bash
echo Left: $1
echo Right: $2
>"$1.diff"
>"$2.diff"
LINECOUNT=0
while IFS= read -u4 -r LEFTLINE && IFS= read -u5 -r RIGHTLINE
do
    LINECOUNT=$(($LINECOUNT + 1))
    LINEMOD=$(($LINECOUNT % 1000))
    if [[ $LINEMOD == 0 ]]
    then
        echo Line: $LINECOUNT
    fi

    if [ "$LEFTLINE" != "$RIGHTLINE" ]
    then
        echo $LEFTLINE >> "$1.diff"
        echo $RIGHTLINE >> "$2.diff"
        echo Mismatch found
    fi
done 4<"$1" 5<"$2"

【讨论】:

  • 更多您可能喜欢的小改进:LINECOUNT=$(($(LINECOUNT + 1)) 可以缩短为:((LINECOUNT++));和$LINEMOD=$((LINECOUNT % 1000)) 可以缩短为:((LINEMOD = $LINEMOD % 1000))
  • 您还应该将您的答案标记为已接受。 :) (所以它不会出现在“未回答”的问题列表中。)
猜你喜欢
  • 2014-09-03
  • 2011-12-18
  • 1970-01-01
  • 2017-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-03-07
  • 1970-01-01
相关资源
最近更新 更多