将大文件拆分为多个块而无需拆分条目


8

我有一个很大的.msg文件,格式为UIEE格式。

$ wc -l big_db.msg
8726593 big_db.msg

本质上,文件由各种长度的条目组成,如下所示:

UR|1
AA|Condon, Richard
TI|Prizzi's Family
CN|Collectable- Good/Good
MT|FICTION
PU|G.P. Putnam & Sons
DP|1986
ED|First Printing.
BD|Hard Cover
NT|0399132104
KE|MAFIA
KE|FICTION
PR|44.9
XA|4
XB|1
XC|BO
XD|S

UR|10
AA|Gariepy, Henry
TI|Portraits of Perseverance
CN|Good/No Jacket
MT|SOLD
PU|Victor Books
DP|1989
BD|Mass Market Paperback
NT|1989 tpb g 100 meditations from the Book of Job "This book...help you
NT| persevere through the struggles of your life..."
KE|Bible
KE|religion
KE|Job
KE|meditations
PR|28.4
XA|4
XB|5
XC|BO
XD|S

这是两个条目的示例,用空格隔开。我希望将此大文件拆分为较小的文件,而又不将条目拆分为两个文件。

文件中的每个单独条目都由换行符(完全空白的行)分隔。我希望将870万行文件分成15个文件。我了解类似的工具split,但是我不太确定如何分割文件,而只是将其分割在换行符上,因此单个条目不会分解为多个文件。


csplit也存在。
mikeserv

您可以创建临时文件吗?
Braiam 2014年

@Braiam,不确定您的意思,但我认为是。我对文件系统拥有完全访问权限。
user2036066 2014年

他的意思是创建用于该过程的临时文件
polym 2014年

1
为什么我要问15个文件呢?是管前的前缀|(如URAATI)相关文件的数量,即使是同一确切?
polym

Answers:


2

这是一个可行的解决方案:

seq 1 $(((lines=$(wc -l </tmp/file))/16+1)) $lines |
sed 'N;s|\(.*\)\(\n\)\(.*\)|\1d;\1,\3w /tmp/uptoline\3\2\3|;P;$d;D' |
sed -ne :nl -ne '/\n$/!{N;bnl}' -nf - /tmp/file

它的工作原理是允许第一个sed编写第二个sed脚本。第二个sed首先收集所有输入行,直到遇到空白行。然后,将所有输出行写入文件。第sed一个脚本为第二个脚本编写了一个脚本,指示该脚本在何处写入其输出。在我的测试案例中,该脚本如下所示:

1d;1,377w /tmp/uptoline377
377d;377,753w /tmp/uptoline753
753d;753,1129w /tmp/uptoline1129
1129d;1129,1505w /tmp/uptoline1505
1505d;1505,1881w /tmp/uptoline1881
1881d;1881,2257w /tmp/uptoline2257
2257d;2257,2633w /tmp/uptoline2633
2633d;2633,3009w /tmp/uptoline3009
3009d;3009,3385w /tmp/uptoline3385
3385d;3385,3761w /tmp/uptoline3761
3761d;3761,4137w /tmp/uptoline4137
4137d;4137,4513w /tmp/uptoline4513
4513d;4513,4889w /tmp/uptoline4889
4889d;4889,5265w /tmp/uptoline5265
5265d;5265,5641w /tmp/uptoline5641

我这样测试:

printf '%s\nand\nmore\nlines\nhere\n\n' $(seq 1000) >/tmp/file

这为我提供了6000行的文件,如下所示:

<iteration#>
and
more
lines
here
#blank

...重复了1000次

运行上面的脚本后:

set -- /tmp/uptoline*
echo $# total splitfiles
for splitfile do
    echo $splitfile
    wc -l <$splitfile
    tail -n6 $splitfile
done    

输出值

15 total splitfiles
/tmp/uptoline1129
378
188
and
more
lines
here

/tmp/uptoline1505
372
250
and
more
lines
here

/tmp/uptoline1881
378
313
and
more
lines
here

/tmp/uptoline2257
378
376
and
more
lines
here

/tmp/uptoline2633
372
438
and
more
lines
here

/tmp/uptoline3009
378
501
and
more
lines
here

/tmp/uptoline3385
378
564
and
more
lines
here

/tmp/uptoline3761
372
626
and
more
lines
here

/tmp/uptoline377
372
62
and
more
lines
here

/tmp/uptoline4137
378
689
and
more
lines
here

/tmp/uptoline4513
378
752
and
more
lines
here

/tmp/uptoline4889
372
814
and
more
lines
here

/tmp/uptoline5265
378
877
and
more
lines
here

/tmp/uptoline5641
378
940
and
more
lines
here

/tmp/uptoline753
378
125
and
more
lines
here

3

使用以下建议csplit

根据行号拆分

$ csplit file.txt <num lines> "{repetitions}"

假设我有一个包含1000行的文件。

$ seq 1000 > file.txt

$ csplit file.txt 100 "{8}"
288
400
400
400
400
400
400
400
400
405

产生如下文件:

$ wc -l xx*
  99 xx00
 100 xx01
 100 xx02
 100 xx03
 100 xx04
 100 xx05
 100 xx06
 100 xx07
 100 xx08
 101 xx09
   1 xx10
1001 total

通过提前根据特定文件中的行数预先计算数字,可以避免必须指定重复数的静态限制。

$ lines=100
$ echo $lines 
100

$ rep=$(( ($(wc -l file.txt | cut -d" " -f1) / $lines) -2 ))
$ echo $rep
8

$ csplit file.txt 100 "{$rep}"
288
400
400
400
400
400
400
400
400
405

根据空白行拆分

另一方面,如果您只是想将文件拆分为包含在文件中的空白行,则可以使用以下版本split

$ csplit file2.txt '/^$/' "{*}"

假设我在上面添加了4个空行file.txt,并制作了文件file2.txt。您可以看到它们是手动添加的,如下所示:

$ grep -A1 -B1 "^$" file2.txt
20

21
--
72

73
--
112

113
--
178

179

上面显示了我已经将它们添加到示例文件中的相应数字之间。现在,当我运行csplit命令时:

$ csplit file2.txt '/^$/' "{*}"
51
157
134
265
3290

您可以看到我现在有4个文件已根据空白行进行了拆分:

$ grep -A1 -B1 '^$' xx0*
xx01:
xx01-21
--
xx02:
xx02-73
--
xx03:
xx03-113
--
xx04:
xx04-179

参考文献


我尝试使用此OP进行了编辑,但无法使其正常工作。
user2036066 2014年

该文件未在新的空白行上拆分,这是我一直在努力实现的目标。
user2036066 2014年

@ user2036066-您想将文件拆分为15个文件块,以确保没有在部分行或其他内容上拆分吗?
slm

@ user2036066-等一下,文件中有14-15个完全空白的行要分割?
slm

再次在更多上下文中编辑了操作@slm
user2036066 2014年

3

如果您不关心记录的顺序,则可以执行以下操作:

gawk -vRS= '{printf "%s", $0 RT > "file.out." (NR-1)%15}' file.in

否则,您需要首先获取记录数,以了解每个输出文件中应放入多少记录:

gawk -vRS= -v "n=$(gawk -vRS= 'END {print NR}' file.in)" '
  {printf "%s", $0 RT > "file.out." int((NR-1)*15/n)}' file.in

使用awk分割空白行也是我的第一个想法-+1
godlygeek

什么是file.infile.out
mikeserv

1

如果您只想在行尾进行拆分,则可以使用的-l选项进行拆分split

如果您想在空白行(\n\n)上分割,这就是我在ksh中的处理方法。我还没有测试过,它可能不是理想的,但是可以遵循以下方法:

filenum=0
counter=0
limit=580000

while read LINE
do
  counter=counter+1

  if (( counter >= limit ))
  then
    if [[ $LINE == "" ]]
    then
      filenum=filenum+1
      counter=0
    fi
  fi

  echo $LINE >>big_db$filenum.msg
done <big_db.msg

1
我想可能是我看错了,但是op在问如何继续下去\n\n
mikeserv

那实际上对我没有帮助,因为这仍然会在输入文件中途拆分文件。我需要它,因此文件将仅在空白行上分割。
user2036066 2014年

是的,我读错了,对不起。可能不是最好的方法,我只是将原始文件读入一个循环,其中包含已通过多少行的计数器,一旦您击中了要分割的数字,便开始在下一个输出到新文件空行。
hornj 2014年

立即尝试测试此脚本。
user2036066 2014年

1
我认为OP并不是在问如何拆分\n\n,而是在一行中不拆分。他称换行符为空白行。
polym 2014年


0

如果您不关心记录的顺序,但是特别想获取一定数量的输出文件,那么Stephane的答案就是我想要的方式。但是我有一种感觉,您可能会更关心指定每个输出文件不应超过的大小。这实际上使它变得更容易,因为您可以通读输入文件并收集记录,直到达到该大小为止,然后启动新的输出文件。如果这对您有用,则大多数编程语言都可以使用简短的脚本来处理您的任务。这是awk的实现:

BEGIN {
    RS = "\n\n"
    ORS = "\n\n"
    maxlen = (maxlen == 0 ? 500000 : maxlen)
    oi = 1
}

{
    reclen = length($0) + 2
    if (n + reclen > maxlen) {
        oi++
        n = 0
    }
    n += reclen
    print $0 > FILENAME"."oi
}

将其放在一个文件中,例如program.awk,然后在awk -v maxlen=10000 -f program.awk big_db.msg其中的值maxlen是任何一个文件中所需的最大字节数的情况下运行它。默认将使用500k。

如果要获取一定数量的文件,最简单的方法可能是将输入文件的大小除以所需文件的数量,然后在该数量上加一点以获得maxlen。例如,要从8726593字节中获取15个文件,请除以15得到581773,然后加上一些文件,因此可以给maxlen=590000maxlen=600000。如果要重复执行此操作,则可以配置程序来执行此操作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.