根据模式将一个文件拆分成多个文件

20

我有一个二进制文件,我使用hexdump和一些awk和sed命令将其转换为常规文件。输出文件看起来像这样 -

$cat temp
3d3d01f87347545002f1d5b2be4ee4d700010100018000cc57e5820000000000000000000
000000087d3f513000000000000000000000000000000000001001001010f000000000026 
58783100b354c52658783100b43d3d0000ad6413400103231665f301010b9130194899f2f
fffffffffff02007c00dc015800a040402802f1d5b2b8ca5674504f433031000000000004
6363070000000000000000000000000065450000b4fb6b4000393d3d1116cdcc57e58287d
3f55285a1084b

这个临时文件有一些不太常见的标记(3d3d),它们代表着新的二进制记录的开始。我需要根据这些标记将文件分割成多个部分。

我的期望输出是基于这些标记分割后的多个文件(文件数量与临时文件中的标记数相同)。

因此,我的输出应该类似于这样 -

$cat temp1
3d3d01f87347545002f1d5b2be4ee4d700010100018000cc57e582000000000000000
0000000000087d3f513000000000000000000000000000000000001001001010f00000000
002658783100b354c52658783100b4

$cat temp2
3d3d0000ad6413400103231665f301010b9130194899f2ffffffffffff02007c00dc0
15800a040402802f1d5b2b8ca5674504f4330310000000000046363070000000000000000
000000000065450000b4fb6b400039

$cat temp3
3d3d1116cdcc57e58287d3f55285a1084b
5个回答

21

awk 中的变量 RS 很适合这种情况,它允许你定义记录分隔符。因此,你只需要将每个记录保存在自己的临时文件中即可。最简单的版本如下:

cat temp |
  awk -v RS="3d3d" '{ print $0 > "temp" NR }' 

示例文本以引人注目的3d3d开头,因此temp1将是一个空文件。此外,引人注目的内容本身不会出现在临时文件的开头,正如问题中所示。最后,如果有大量记录,您可能会遇到打开文件的系统限制。一些小的复杂情况将使它更接近您想要的结果并使其更安全:

cat temp |
  awk -v RS="3d3d" 'NR > 1 { print RS $0 > "temp" (NR-1); close("temp" (NR-1)) }' 

1
嗯,你不需要使用 cat。如果输入只有一行,你只会得到第一条记录。输出也会缺少原始的 RSecho '3d3dsomething3d3danything' | awk 'BEGIN {RS="3d3d"} {print}' 只会输出 something - Zsolt Botykai
1
或者我错了。你的解决方案唯一的问题是输出中缺少 RS。(还有无用的使用 cat。) - Zsolt Botykai
2
@ZsoltBotykai,正如我们所讨论的那样,RS出现在输出中。而cat并不是无用的:它提供了数据生成和处理之间的逻辑分离。因此,cat temp代表在awk阶段之前进行的任何转换,同时避免在已经很长的awk命令行中再添加更多内容。 - Michael J. Barber
1
你是对的,抱歉关于RS部分的问题。关于cat,你可能想阅读这篇文章:http://partmaps.org/era/unix/award.html和这篇文章:http://en.wikipedia.org/wiki/Cat_(Unix)#Useless_use_of_cat - Zsolt Botykai
4
非常清楚,但不确定它是否对恰当的修辞表达有任何相关说法。你可能想阅读其他观点,比如《经典Shell脚本编程》(Robbins和Beebe,2005年)中的观点。 - Michael J. Barber
显示剩余2条评论

16
#!/usr/bin/perl

undef $/;
$_ = <>;
$n = 0;

for $match (split(/(?=3d3d)/)) {
      open(O, '>temp' . ++$n);
      print O $match;
      close(O);
}

谢谢,这个很好用。我可以在运行解析器代码之前,在我的解析器脚本中调用这个脚本,以便它可以在所有临时文件上运行。 - jaypal singh
有没有关于学习Perl的书籍推荐?我是UNIX新手,最近开始学习bash、sed和awk。 - jaypal singh
用法:将其复制到新文件split.pl中,然后使其可执行并运行:./split.pl yourdata.txt - Nicolas Raoul
@rob-mayoff,你能帮我处理这个问题吗:http://stackoverflow.com/questions/42671047/split-big-file-in-unix-based-on-size-and-pattern - Newbie

5
这可能有效:
# sed 's/3d3d/\n&/2g' temp | split -dl1 - temp
# ls
temp temp00  temp01  temp02
# cat temp00
3d3d01f87347545002f1d5b2be4ee4d700010100018000cc57e5820000000000000000000000000087d3f513000000000000000000000000000000000001001001010f000000000026 58783100b354c52658783100b4
# cat temp01
3d3d0000ad6413400103231665f301010b9130194899f2ffffffffffff02007c00dc015800a040402802f1d5b2b8ca5674504f4330310000000000046363070000000000000000000000000065450000b4fb6b400039
# cat temp02
3d3d1116cdcc57e58287d3f55285a1084b

编辑:

如果源文件中有换行符,您可以先使用 tr -d '\n' <temp 命令将其删除,然后将输出通过上面的 sed 命令进行处理。如果您想要保留它们,则可以:

 sed 's/3d3d/\n&/g;s/^\n\(3d3d\)/\1/' temp |csplit -zf temp - '/^3d3d/' {*}

应该可以解决问题。

只是要注意的是,sed命令中组合标志2g的效果并没有标准化。作者期望GNU sed的行为:对于GNU 'sed',交互定义为:忽略NUMBER之前的匹配,然后从NUMBER开始匹配和替换所有匹配项。 - db-inf

0

Mac OS X的答案

在那个漂亮的awk -v RS="pattern"技巧不起作用的地方。这是我得到的解决方法:

给出这个例子concatted.txt

filename=foo bar
foo bar line1
foo bar line2
filename=baz qux
baz qux line1
baz qux line2

使用此命令(删除注释以防止失败)

# cat: useless use of cat ^__^;
# tr: replace all newlines with delimiter1 (which must not be in concatted.txt) so we have one line of all the next
# sed: replace file start pattern with delimiter2 (which must not be in concatted.txt) so we know where to split out each file
# tr: replace delimiter2 with NULL character since sed can't do it
# xargs: split giant single-line input on NULL character and pass 1 line (= 1 file) at a time to echo into the pipe
# sed: get all but last line (same as head -n -1) because there's an extra since concatted-file.txt ends in a NULL character.
# awk: does a bunch of stuff as the final command. Remember it's getting a single line to work with.
#   {replace all delimiter1s in file with newlines (in place)}
#   {match regex (sets RSTART and RLENGTH) then set filename to regex match (might end at delimiter1). Note in this case the number 9 is the length of "filename=" and the 2 removes the "§" }
#   {write file to filename and close the file (to avoid "too many files open" error)}
cat ../concatted-file.txt \
| tr '\n' '§' \
| sed 's/filename=/∂filename=/g' \
| tr '∂' '\0' \
| xargs -t -0 -n1 echo \
| sed \$d \
| awk '{match($0, /filename=[^§]+§/)} {filename=substr($0, RSTART+9, RLENGTH-9-2)".txt"} {gsub(/§/, "\n", $0)} {print $0 > filename; close(filename)}'

结果会分别生成这两个名为foo bar.txtbaz qux.txt的文件:

filename=foo bar
foo bar line1
foo bar line2



filename=baz qux
baz qux line1
baz qux line2


希望这可以帮到你!


-1

这取决于你的temp文件是否只有一行。但是假设它只有一行,你可以使用以下代码:

sed 's/\(.\)\(3d3d\)/\1#\2/g' FILE | awk -F "#" '{ for (i=1; i++; i<=NF) { print $i > "temp" i } }' 

第一个sed插入一个#作为字段/记录分隔符,然后awk#上拆分并将每个“字段”打印到自己的文件中。

如果输入文件已经在3d3d上拆分,则可以使用以下方法:

awk '/^3d3d/ { i++ } { print > "temp" i }' temp

HTH


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接