如何将一个文本文件拆分为多个文本文件?


16

我有一个名为entry.txt以下内容的文本文件:

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631
[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631
[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

我愿意把它分成三个文本文件:entry1.txtentry2.txtentry3.txt。其内容如下。

entry1.txt

[ entry1 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3633 3634 3636 3690 3691 3693 3766
3767 3769 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5628 5629 5631

entry2.txt

[ entry2 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4526
4527 4529 4583 4584 4586 4773 4774 4776 5153 5154
5156 5628 5629 5631

entry3.txt

[ entry3 ]
1239 1240 1242 1391 1392 1394 1486 1487 1489 1600
1601 1603 1657 1658 1660 2075 2076 2078 2322 2323
2325 2740 2741 2743 3082 3083 3085 3291 3292 3294
3481 3482 3484 3690 3691 3693 3766 3767 3769 4241
4242 4244 4526 4527 4529 4583 4584 4586 4773 4774
4776 5153 5154 5156 5495 5496 5498 5628 5629 5631

换句话说,该[字符表示应开始一个新文件。条目([ entry*],其中*是整数)始终按数字顺序排列,并且是从1到N的连续整数(在我的实际输入文件中,N = 200001)。

有什么方法可以在bash中完成自动文本文件拆分?我的实际输入entry.txt实际上包含200,001个条目。

Answers:


11

这是一个不错的,简单的,怪异的单行代码:

$ gawk '/^\[/{match($0, /^\[ (.+?) \]/, k)} {print >k[1]".txt" }' entry.txt

无论每个条目的行数如何,只要每个条目的标题都像,这将适用于任何文件大小[ blahblah blah blah ]。请注意,在打开之后[和关闭之前的空间]


说明:

awkgawk逐行读取输入文件。读取每一行时,其内容将保存在$0变量中。在这里,我们告诉gawk要匹配方括号内的任何内容,并将其匹配项保存到array中k

因此,每次匹配正则表达式时,即对于文件中的每个标头,k [1]都会具有该行的匹配区域。即,“ entry1”,“ entry2”或“ entry3”或“ entryN”。

最后,我们将每一行打印到一个名为的文件中<whatever value k currently has>.txt,即entry1.txt,entry2.txt ... entryN.txt。

这种方法会快于perl的较大的文件。


+1好。您不需要match输入:/^\[/ { name=$2 }应该足够了。
雷神2012年

谢谢@Thor。您的建议对于所描述的情况是正确的,但它假定条目名称中永远不会有空格。这就是为什么我[ blahblah blah blah ]在答案中使用示例的原因。
terdon

啊,我错过了一些空格分隔的条目。你也可以容纳那些FS,如-F '\\[ | \\]'
雷神

@terdon我真的很喜欢这种简短的解决方案,但是不幸的是,我通常无法将它们归纳为我的需求。你能帮我一下吗?我的文件中有以开头的行#S x,其中x是1、2或3位数字。仅将它们保存到x.dat就足够了。我试过了: gawk '/^#S/{match($0, / [0-9]* /, k)} {print >k[1]".dat" }' myFile.txt还有一些变化。
mikuszefski 2014年

知道gawk '/^#S/{match($0, /^#S (\s+?)([0-9]+)(\s+?)/, k)} {print >k[2]".txt" }' test.txt了。但是,对数组编号的了解不是2很好。
mikuszefski 2014年

17

使用GNU coreutils(非嵌入式Linux,Cygwin)的csplit

csplit -f entry -b '%d.txt' entry.txt '/^\[ .* \]$/' '{*}'

您将得到一个额外的空文件entry0.txt(包含第一个标头之前的部分)。

标准csplit缺少{*}不确定的转发器和-b指定后缀格式的选项,因此在其他系统上,您必须首先计算段数,然后再重命名输出文件。

csplit -f entry -n 9 entry.txt '/^\[ .* \]$/' "{$(egrep -c '^'\[ .* \]$' <entry.txt)}"
for x in entry?????????; do
  y=$((1$x - 1000000000))
  mv "entry$x" "entry$y.txt"
done

我发现csplit有时会有点古怪,但是当我想做这种事情时,它非常有用。
ixtmixilix 2012年

10

在perl中,它可以更简单地完成:

perl -ne 'open(F, ">", ($1).".txt") if /\[ (entry\d+) \]/; print F;' file

9

这是一个简短的awk单线:

awk '/^\[/ {ofn=$2 ".txt"} ofn {print > ofn}' input.txt

这是如何运作的?

  • /^\[/ 匹配以左方括号开头的行,并且
  • {ofn=$2 ".txt"}将变量设置为第二个以空格分隔的单词作为我们的输出文件名。然后,
  • ofn 是一个条件,如果设置了变量,则结果为true(因此将导致忽略第一个标头之前的行)
  • {print > ofn} 将当前行重定向到指定文件。

请注意,如果紧凑使您满意,则可以删除此awk脚本中的所有空格。

还要注意,上面的脚本确实需要节标题在其周围而不是内部有空格。如果您希望能够像[foo]和处理节头[ this that ],那么您将需要更多的代码:

awk '/^\[/ {sub(/^\[ */,""); sub(/ *\] *$/,""); ofn=$0 ".txt"} ofn {print > ofn}' input.txt

这使用awk的sub()功能来去除前导方括号和尾随方括号以及空白。请注意,按照标准的awk行为,这会将空白(字段分隔符)折叠为一个空格(即[ this that ]保存为"this that.txt")。如果在输出文件名中保留原始空格很重要,则可以通过设置FS进行试验。


2

可以从python中的命令行完成,如下所示:

paddy$ python3 -c 'out=0
> with open("entry.txt") as f: 
>   for line in f:
>     if line[0] == "[":
>       if out: out.close()
>       out = open(line.split()[1] + ".txt", "w")
>     else: out.write(line)'

2

这是一种粗略但容易理解的方法:用于grep -l '[ entry ]' FILENAME获取要在[entry]处拆分的行号。使用头部和尾部的组合以获得正确的片段。

就像我说的; 它不是很漂亮,但是很容易理解。


2

使用awk [作为记录分隔符并使用空格作为字段分隔符怎么办?这使我们可以轻松地将数据放入文件中,$0因为他必须将删除的前导[和文件名放回原处$1。然后,我们只需要处理第一条记录为空的特殊情况。这给我们:

awk -v "RS=[" -F " " 'NF != 0 {print "[" $0 > $1}' entry.txt

2

terdon的答案对我有用,但是我需要使用gawk而不是awk。该GAWK手册(搜索“比赛(”)解释说,在比赛的阵列参数()是徒劳无功的延伸。也许这取决于你的Linux安装和你的awk / NAWK / gawk的版本,但我的Ubuntu机器仅GAWK RAN terdon出色的上回答:

$ gawk '{if(match($0, /^\[ (.+?) \]/, k)){name=k[1]}} {print >name".txt" }' entry.txt

1

这是一个perl解决方案。该脚本检测[ entryN ]行并相应地更改输出文件,但是不会验证,解析或处理每个部分中的数据,它只会将输入行打印到输出文件中。

#! /usr/bin/perl 

# default output file is /dev/null - i.e. dump any input before
# the first [ entryN ] line.

$outfile='/dev/null';
open(OUTFILE,">",$outfile) || die "couldn't open $outfile: $!";

while(<>) {
  # uncomment next two lines to optionally remove comments (starting with
  # '#') and skip blank lines.  Also removes leading and trailing
  # whitespace from each line.
  # s/#.*|^\s*|\s*$//g;
  # next if (/^$/)

  # if line begins with '[', extract the filename
  if (m/^\[/) {
    (undef,$outfile,undef) = split ;
    close(OUTFILE);
    open(OUTFILE,">","$outfile.txt") || die "couldn't open $outfile.txt: $!";
  } else {
    print OUTFILE;
  }
}
close(OUTFILE);

1

嗨,我用ruby编写了这个简单的脚本来解决您的问题

#!ruby
# File Name: split.rb

fout = nil

while STDIN.gets
  line = $_
  if line.start_with? '['
    fout.close if fout
    fname = line.split(' ')[1] + '.txt'
    fout = File.new fname,'w'
  end
  fout.write line if fout
end

fout.close if fout

您可以通过以下方式使用它:

ruby split.rb < entry.txt

我已经测试过了,它工作正常。


1

我更喜欢该csplit选项,但是作为替代,这是一个GNU awk解决方案:

parse.awk

BEGIN { 
  RS="\\[ entry[0-9]+ \\]\n"  # Record separator
  ORS=""                      # Reduce whitespace on output
}
NR == 1 { f=RT }              # Entries are of-by-one relative to matched RS
NR  > 1 {
  split(f, a, " ")            # Assuming entries do not have spaces 
  print f  > a[2] ".txt"      # a[2] now holds the bare entry name
  print   >> a[2] ".txt"
  f = RT                      # Remember next entry name
}

像这样运行它:

gawk -f parse.awk entry.txt

1
FWIW,该RT变量似乎是特定于gawk的。使用FreeBSD的awk,此解决方案不适用于我。
ghoti 2015年

@ghoti:对,我应该提到这一点。我已经将其包括在答案中了。谢谢。
2015年
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.