Sed 脚本在处​​理大文件时崩溃

Sed 脚本在处​​理大文件时崩溃

我有一个 shell 脚本,它本质上是一个带有一些检查的 sed 脚本。该脚本的目标是将文件头转换为。

&FCI
NORB=280,
NELEC=78,
MS2=0,
UHF=.FALSE.,
ORBSYM=1,1,1,1,1,1,1,1,<...>
&END
  1.48971678130072078261E+01   1   1   1   1
 -1.91501428271686324756E+00   1   1   2   1
  4.38796949990802698238E+00   1   1   2   2

&FCI NORB=280, NELEC=78, MS2=0, UHF=.FALSE., 
ORBSYM=1,1,1,1,1,1,1,1,<...>
ISYM=1,
/
  1.48971678130072078261E+01   1   1   1   1
 -1.91501428271686324756E+00   1   1   2   1
  4.38796949990802698238E+00   1   1   2   2

这是脚本:

#!/bin/bash

# $1 : FCIDUMP file to convert from "new format" to "old format"

if [ ${#} -ne 1 ]
then
  echo "Syntaxis: fcidump_new2old FCIDUMPFILE" 1>$2
  exit 1
fi

if egrep '&FCI ([a-zA-Z2 ]*=[0-9 ]*,){2,}' ${1} > /dev/null
then
  echo "The provided file is already in old FCIDUMP format." 1>&2
  exit 2
fi

sed '
1,20 {
   :a; N; $!ba
   s/\(=[^,]*,\)\n/\1 /g
   s/\(&FCI\)\n/\1 /
   s/ORBSYM/\n&/g
   s/&END/ISYM=1,\n\//
}' -i "${1}"

exit 0

该脚本适用于“小”文件,但现在我遇到一个约 9 GB 的文件,脚本崩溃并显示“非常清晰的错误消息”:

script.sh: line 24: 406089 Killed                  sed '
1,20 {
   :a; N; $!ba
   s/\(=[^,]*,\)\n/\1 /g
   s/\(&FCI\)\n/\1 /
   s/ORBSYM/\n&/g
   s/&END/ISYM=1,\n\//
}' -i "${1}"

我怎样才能让这个 sed 脚本真正只查看标题并能够处理如此大的文件?顺便说一下,丑陋的硬编码“20”在那里,因为我不知道更好的办法。

额外信息:

  • 尝试了一些方法后,我发现生成了奇怪的文件:sedexG4Lg、sedQ5olGZ、sedXVma1Y、sed21enyi、sednzenBn、sedqCeeey sedzIWMUi。除了 sednzenBn 之外,其他文件都是空的,sednzenBn 类似于输入文件,但只有一半。

  • 丢弃 -i 标志并将输出重定向到另一个文件将产生一个空文件。

答案1

常规方法

  • 您可以将每个文件拆分为一个文件头和另一个带有数据行的文件
  • 然后,您可以使用当前的 sed 命令轻松地单独编辑标题
  • 最后,您可以将标题和文件与数据行连接起来。

管理大文件的轻量级工具

测试

  • 我使用您的标题和一个包含 1080000000 个编号行(大小为 19 Gib)的文件进行了测试,总共 1080000007 行,并且它有效,输出文件(包含 1080000004 行)在我的旧 hp xw8400 工作站中用 5 分钟写入(包括输入启动 shellscript 的命令)。

    $ ls -lh --time-style=full-iso huge*
    -rw-r--r-- 1 sudodus sudodus 19G 2018-12-15 19:50:45.278328120 +0100 huge.in
    -rw-r--r-- 1 sudodus sudodus 19G 2018-12-15 19:55:46.808798456 +0100 huge.out
    
  • 大型写入操作发生在 SSD 上的系统分区和 HDD 上的数据分区之间。

Shell脚本

您需要在文件系统中留出足够的可用空间来/tmp存放巨大的临时“数据”文件,根据您最初的问题,该空间需要超过 9 GB。

$ LANG=C df -h /tmp
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda1       106G   32G   69G  32% /

这看起来可能是一种尴尬的做法,但它适用于大文件,且不会使工具崩溃。也许您必须将临时“数据”文件存储在其他地方,例如外部驱动器(但速度可能会更慢)。

#!/bin/bash

# $1 : FCIDUMP file to convert from "new format" to "old format"

if [ $# -ne 2 ]
then
  echo "Syntaxis: $0 fcidumpfile oldstylefile " 1>&2
  echo "Example:  $0 file.in file.out" 1>&2
  exit 1
fi

if [ "$1" == "$2" ]
then
  echo "The names of the input file and output file must differ"
  exit 2
exit
fi

endheader="$(grep -m 1 -n '&END' "$1" | cut -d: -f1)"
if [ "$endheader" == "" ]
then
  echo "Bad input file: the end marker of the header was not found"
  exit 3
fi
#echo "endheader=$endheader"

< "$1" head -n "$endheader" > /tmp/header
#cat /tmp/header

if egrep '&FCI ([a-zA-Z2 ]*=[0-9 ]*,){2,}' /tmp/header  > /dev/null
then
  echo "The provided file is already in old FCIDUMP format." 1>&2
  exit 4
fi

# run sed inline on /tmp/header 
sed '
{
:a; N; $!ba
s/\(=[^,]*,\)\n/\1 /g
s/\(&FCI\)\n/\1 /
s/ORBSYM/\n&/g
s/&END/ISYM=1,\n\//
}' -i /tmp/header 

if [ $? -ne 0 ]
then
  echo "Failed to convert the header format in /tmp/header"
  exit 5
fi

< "$1" tail -n +$(($endheader+1)) > /tmp/tailer

if [ $? -ne 0 ]
then
  echo "Failed to create the 'data' file /tmp/tailer"
  exit 6
fi

#echo "---"
#cat /tmp/tailer
#echo "---"

cat /tmp/header /tmp/tailer > "$2"

exit 0

答案2

sed可能不是最好的工具,请调查一下perl。但是,你可以将问题重新表述为:

  1. 从巨型数据文件中提取旧标题,并将其保存为其自己的文件。

  2. 调整提取出的旧标头,使其成为新标头。

  3. 将巨型数据文件中的旧标题替换为新标题。

    endheader="$(grep -m 1 -n '&END' "$1" | cut -d: -f1)"
    head -n "$endheader" >/tmp/header
    trap "/bin/rm -f /tmp/header" EXIT
    # do the sed stuff to /tmp/header, I assume it does what you want 
    sed '
    {
    :a; N; $!ba
    s/\(=[^,]*,\)\n/\1 /g
    s/\(&FCI\)\n/\1 /
    s/ORBSYM/\n&/g
    s/&END/ISYM=1,\n\//
    }' -i /tmp/header 
    
    # Then combine the new header with the rest of the giant data file,
    # using `ed` (see `man ed;info Ed`) and here-document
    ed "$1" <<EndOfEd
    1,${endheader}d
    :0r /tmp/header
    :wq
    EndOfEd
    

相关内容