如何分割文件并在每一部分中保持第一行?


73

给定:一个大文本数据文件(例如CSV格式),第一行带有“特殊”行(例如字段名称)。

想要的:与coreutilssplit -l命令等效,但另外的要求是,原始文件的标题行必须出现在每个结果片段的开头。

我猜想会合splithead为之吗?


11
有人应该将其添加为的内置功能似乎是合理的split,不是吗?
丹尼斯·威廉姆森

1
阻止这种情况成为内置的最大因素可能是您通常通过执行来重建拆分文件cat a b c > reconstructed。文件中的多余行表示正常的重建方法不会复制原始文件。
Mark Rushakoff 09年

2
这就是即将推出的(不是unsplit --remove-header实用程序的用途!但认真的说,split如果要使用“重复标题”选项,则仍应默认使用其当前行为。如果确实需要,则只使用标头内容。
丹尼斯·威廉姆森

2
是的,我认为这将是--keep-first N一个不错的选择,split在行和字节模式下都将非常有用
Arkady

1
认为这个好主意-对于拆分文件进行分发而不是重建绝对有用。它是Unix实用程序的那些“这么简单,怎么还不存在”功能之一,以至于我怀疑“负责人”没有因为某种原因而拒绝先前的提议来实现这一确切功能或其他。
Mark Rushakoff 09年

Answers:


62

这是robhruska的脚本清理了一下:

tail -n +2 file.txt | split -l 4 - split_
for file in split_*
do
    head -n 1 file.txt > tmp_file
    cat "$file" >> tmp_file
    mv -f tmp_file "$file"
done

我删除wccutls以及echo在他们不必要的地方。我更改了一些文件名,使它们更有意义。我将其分成多行只是为了使其更易于阅读。

如果想花哨的话,可以使用mktemptempfile创建一个临时文件名,而不使用硬编码的文件名。

编辑

使用GNUsplit可以做到这一点:

split_filter () { { head -n 1 file.txt; cat; } > "$FILE"; }; export -f split_filter; tail -n +2 file.txt | split --lines=4 --filter=split_filter - split_

出于可读性考虑:

split_filter () { { head -n 1 file.txt; cat; } > "$FILE"; }
export -f split_filter
tail -n +2 file.txt | split --lines=4 --filter=split_filter - split_

--filter指定时,split运行用于每个输出文件的命令(在此情况下的函数,其必须导出)并设置变量FILE,在命令的环境,到文件名。

过滤器脚本或函数可以对输出内容甚至文件名进行所需的任何操作。后者的示例可能是输出到可变目录中的固定文件名:> "$FILE/data.dat"例如。


这肯定会起作用。我只是希望有一些像这样的for $part in (split -l 1000 myfile); cat <(head -n1 myfile) $part > myfile.$part; done
单线

那是行不通的,因为split在必要时不会输出stdout
丹尼斯·威廉姆森,

split 可以将文件输出到stdout,(只要我们正在讨论split 该做什么:-)
Arkady

你是对的。那可能很方便。对不起,我看错了你的一线。
丹尼斯·威廉姆森

1
@JohnathanElmore:请注意,GNU实用程序可用于OSX。例如,使用Homebrew
丹尼斯·威廉姆森

15

您可以在GNU coreutils split> = 8.13(2011)中使用新的--filter功能:

tail -n +2 FILE.in | split -l 50 - --filter='sh -c "{ head -n1 FILE.in; cat; } > $FILE"'

2
我喜欢单线版。为了使它更通用,我做了:tail -n +2 FILE.in | split -d --lines 50 - --filter='bash -c "{ head -n1 ${FILE%.*}; cat; } > $FILE"' FILE.in.x
KullDox

12

您可以使用[mg] awk:

awk 'NR==1{
        header=$0; 
        count=1; 
        print header > "x_" count; 
        next 
     } 

     !( (NR-1) % 100){
        count++; 
        print header > "x_" count;
     } 
     {
        print $0 > "x_" count
     }' file

100是每个切片的行数。它不需要临时文件,可以放在一行中。


10

这种单线将大csv分成999条记录,每条的顶部都包含标题(因此999条记录+ 1个标题= 1000行)

cat bigFile.csv | parallel --header : --pipe -N999 'cat >file_{#}.csv'

基于Ole Tange的答案。(关于Ole的回答:您不能在pipepart中使用行数)


请注意,如果我们考虑每个文件中的标题行,则此解决方案中每个较小的文件将具有1000行。
佩蒂·李

这就是为什么我使用999 :)
蒂姆·理查森

1
我不得不brew install parallel在macOS上。奇迹般有效!
Asimov4

7

关于Bash-fu,我是新手,但我能够炮制出这两个命令的怪物。我敢肯定还有更优雅的解决方案。

$> tail -n +2 file.txt | split -l 4
$> for file in `ls xa*`; do echo "`head -1 file.txt`" > tmp; cat $file >> tmp; mv -f tmp $file; done

这是假设您的输入文件是file.txt,您没有使用的prefix参数split,并且您正在的目录中没有其他任何以split的默认xa*输出格式开头的文件。另外,将“ 4”替换为所需的分割线尺寸。


2

这是Denis Williamson脚本的更强大的版本。该脚本会创建许多临时文件,如果运行不完整,将它们留在身边会很可惜。因此,让我们添加信号捕获功能(请参阅http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_12_02.html,然后http://tldp.org/LDP/abs/html/debugging.html),删除我们的临时文件;无论如何,这是最佳做法。

trap 'rm split_* tmp_file ; exit 13' SIGINT SIGTERM SIGQUIT 
tail -n +2 file.txt | split -l 4 - split_
for file in split_*
do
    head -n 1 file.txt > tmp_file
    cat $file >> tmp_file
    mv -f tmp_file $file
done

将“ 13”替换为所需的任何返回码。哦,您可能仍然应该使用mktemp(正如某些人已经建议的那样),所以继续从陷阱行中的rm删除'tmp_file“。有关更多信号的信息,请参见信号手册页。


2

我喜欢marco的awk版本,此版本采用了简化的单行格式,您可以在其中轻松地将拆分分数指定为所需的粒度:

awk 'NR==1{print $0 > FILENAME ".split1";  print $0 > FILENAME ".split2";} NR>1{if (NR % 10 > 5) print $0 >> FILENAME ".split1"; else print $0 >> FILENAME ".split2"}' file

我喜欢这种解决方案,但是它仅限于两个拆分文件
2016年

如果你喜欢它有它的特点给予好评;)它可以很容易地调整到多个文件,但是它不够灵活拆分-l
DreamFlasher

“一个班轮” ... pshh
Pandem1c

2

我真的很喜欢Rob和Dennis的版本,以至于我想改进它们。

这是我的版本:

in_file=$1
awk '{if (NR!=1) {print}}' $in_file | split -d -a 5 -l 100000 - $in_file"_" # Get all lines except the first, split into 100,000 line chunks
for file in $in_file"_"*
do
    tmp_file=$(mktemp $in_file.XXXXXX) # Create a safer temp file
    head -n 1 $in_file | cat - $file > $tmp_file # Get header from main file, cat that header with split file contents to temp file
    mv -f $tmp_file $file # Overwrite non-header containing file with header-containing file
done

差异:

  1. in_file是您要拆分维护头的文件参数
  2. 使用awk而不是tail由于awk具有更好的性能
  3. 分成100,000个行文件,而不是4个
  4. 拆分文件名将是带有下划线和数字的输入文件名(最大为99999-从“ -d -a 5”拆分参数开始)
  5. 使用mktemp安全处理临时文件
  6. 使用单行head | cat而不是两行

2

使用GNU并行:

parallel -a bigfile.csv --header : --pipepart 'cat > {#}'

如果您需要在每个部分上运行命令,那么GNU Parallel也可以帮助您做到这一点:

parallel -a bigfile.csv --header : --pipepart my_program_reading_from_stdin
parallel -a bigfile.csv --header : --pipepart --fifo my_program_reading_from_fifo {}
parallel -a bigfile.csv --header : --pipepart --cat my_program_reading_from_a_file {}

如果要将每个CPU内核分为2个部分(例如24个内核= 48个相等大小的部分):

parallel --block -2 -a bigfile.csv --header : --pipepart my_program_reading_from_stdin

如果要分成10 MB的块:

parallel --block 10M -a bigfile.csv --header : --pipepart my_program_reading_from_stdin

2

下面是一个4衬纸,可用于将bigfile.csv拆分为多个较小的文件,并保留csv标头。仅使用内置的Bash命令(head,split,find,grep,xargs和sed),这些命令应可在大多数* nix系统上使用。如果您安装mingw-64 / git-bash,则在Windows上也应该可以使用。

csvheader =`head -1 bigfile.csv`
分割-d -l10000 bigfile.csv smallfile_
查找。| grep smallfile_ | xargs sed -i“ 1s / ^ / $ csvheader \ n /”
sed -i'1d'smallfile_00

逐行说明:

  1. 将标头捕获到名为csvheader的变量
  2. bigfile.csv拆分为多个带有smallfile_前缀的较小文件
  3. 查找所有小文件,然后使用xargssed -i将csvheader插入第一行。请注意,您需要在“双引号”中使用sed才能使用变量。
  4. 现在,第一个名为smallfile_00的文件将在第1行和第2行上具有冗余头(来自原始数据以及步骤3中的sed头插入)。我们可以使用sed -i'1d'命令删除冗余头。

1

受到@Arkady对单线的评论的启发。

  • MYFILE变量只是为了减少样板
  • split不显示文件名,但是该--additional-suffix选项使我们可以轻松控制期望的内容
  • 通过删除中间文件rm $part(假定没有带相同后缀的文件)

MYFILE=mycsv.csv && for part in $(split -n4 --additional-suffix=foo $MYFILE; ls *foo); do cat <(head -n1 $MYFILE) $part > $MYFILE.$part; rm $part; done

证据:

-rw-rw-r--  1 ec2-user ec2-user  32040108 Jun  1 23:18 mycsv.csv.xaafoo
-rw-rw-r--  1 ec2-user ec2-user  32040108 Jun  1 23:18 mycsv.csv.xabfoo
-rw-rw-r--  1 ec2-user ec2-user  32040108 Jun  1 23:18 mycsv.csv.xacfoo
-rw-rw-r--  1 ec2-user ec2-user  32040110 Jun  1 23:18 mycsv.csv.xadfoo

并且当然head -2 *foo会看到添加了标题。


0

一种简单但可能不那么优雅的方法:事先剪掉标题,分割文件,然后使用cat或正在读取文件的任何文件将每个文件的标题重新加入。如下所示:

  1. 头-n1 file.txt> header.txt
  2. 分割-l file.txt
  3. 猫header.txt f1.txt
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.