【问题标题】:How to split a file and keep the first line in each of the pieces?如何拆分文件并保留每个部分的第一行?
【发布时间】:2010-11-27 13:22:24
【问题描述】:

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

需要:相当于 coreutils split -l 命令,但额外要求原始文件的标题行出现在每个结果片段的开头。

我猜splithead 的一些混合物会起作用吗?

【问题讨论】:

  • 有人应该将其添加为split 的内置功能似乎是合理的,不是吗?
  • 可能反对这成为内置的最大因素是您通常通过执行cat a b c > reconstructed 来重建拆分文件。文件中的多余行意味着正常的重建方法不会重现原始文件。
  • 这就是即将推出的 (not) "unsplit --remove-header" 实用程序的用途!但说真的,split,如果它有一个“repeat-header”选项,仍应默认为其当前行为。如果你真的想要,你只会使用标题。
  • 是的,我认为--keep-first Nsplit 的一个不错的选择,它在行模式和字节模式下都很有用
  • 认为它一个好主意——对于分割文件以进行分发而不是重建绝对非常有用.这是一个如此古老的 Unix 实用程序的“如此简单,怎么还不存在”功能之一,我怀疑“负责人”是否出于某种原因拒绝了以前的提议以执行此确切功能或其他。

标签: linux bash file shell text


【解决方案1】:

这个单行将大 csv 拆分为 999 条记录,保留每条顶部的标题行(因此 999 条记录 + 1 条标题 = 1000 行)

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

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

有关安装并行的一些提示,请参阅 cmets

【讨论】:

  • 请注意,如果我们考虑每个文件中的标题行,那么在此解决方案中每个较小的文件将有 1000 行。
  • 这就是我使用 999 的原因 :)
  • 我必须在 macOS 上brew install parallel。像魅力一样工作!
  • 这太完美了。非常感谢!
  • 与 MacOS 一样,Ubuntu 20.04 也需要安装 parallel 才能工作。请注意,Ubuntu 建议 sudo apt install moreutils # 版本 0.63-1sudo apt install parallel # 版本 20161222-1.1 - 选择后一个建议。第一个建议,moreutils 听起来特别有用,但该软件包中包含的并行版本出错了 (parallel: invalid option -- '-')。第二个建议按预期工作 (details)。
【解决方案2】:

一个简单但可能不那么优雅的方法:预先切断标题,拆分文件,然后用 cat 或任何正在读取它的文件重新加入每个文件的标题。 所以像:

  1. head -n1 file.txt > header.txt
  2. 拆分-l file.txt
  3. cat header.txt f1.txt

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

    • 我喜欢单行版本。只是为了使它对 bash 更通用,我这样做了:tail -n +2 FILE.in | split -d --lines 50 - --filter='bash -c "{ head -n1 ${FILE%.*}; cat; } > $FILE"' FILE.in.x
    【解决方案4】:

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

    csvheader=`head -1 bigfile.csv` 拆分 -d -l10000 bigfile.csv smallfile_ 查找 .|grep 小文件_ | 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' 命令删除多余的标头。

    【讨论】:

      【解决方案5】:

      灵感来自 @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 来查看添加的标题。

      【讨论】:

        【解决方案6】:

        这是 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
        

        我在不需要的地方删除了wccutlsecho。我更改了一些文件名以使它们更有意义。我把它分成多行只是为了更容易阅读。

        如果你想变得花哨,你可以使用mktemptempfile 来创建一个临时文件名,而不是使用硬编码。

        编辑

        使用 GNU 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_
        

        为了可读性而分开:

        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 可以将文件的名称输出到标准输出,不过(只要我们在讨论split 应该做:-)
        • 你是对的。这可能是方便。抱歉,我误读了您的单行字。
        • @JohnathanElmore:请注意,GNU 实用程序可用于 OS X。例如,使用 Homebrew
        【解决方案7】:

        使用 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
        

        【讨论】:

        • "--block 10M" - 节省一天!!
        【解决方案8】:

        我真的很喜欢 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 而不是两行

        【讨论】:

        • 建议:将 awk 脚本更改为简单的:'NR > 1',因为 print 是默认操作。
        • 也就是说,在这种情况下,我怀疑 awk 是否比 tail 快(或至少快得多)。
        • 我也可以在循环之前将标头放在变量中,然后在循环中'echo "$header | ...."
        【解决方案9】:

        我喜欢 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
        

        【讨论】:

        • 我喜欢这个解决方案,但它仅限于两个拆分文件
        • 如果你喜欢它,它有支持它的功能;)它可以很容易地调整到更多文件,但是它不像 split -l 那样灵活
        • “一个班轮” ...pshh
        【解决方案10】:

        你可以使用 [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 是每个切片的行数。 它不需要临时文件,可以放在一行中。

        【讨论】:

          【解决方案11】:

          这是 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”。有关要捕获的更多信号,请参见信号手册页。

          【讨论】:

            【解决方案12】:

            我是 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,您没有使用splitprefix 参数,并且您在一个没有任何其他以@987654325 开头的文件的目录中工作@ 的默认 xa* 输出格式。此外,将“4”替换为您想要的分割线大小。

            【讨论】:

              猜你喜欢
              • 2016-09-20
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-06-22
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多