【问题标题】:Split delimited file into smaller files by column按列将分隔文件拆分为较小的文件
【发布时间】:2011-07-13 01:23:07
【问题描述】:

我熟悉 linux 中的 split 命令。如果我有一个 100 行长的文件,

split -l 5 myfile.txt

...将 myfile.txt 拆分为 20 个文件,每个文件有 5 行,并将它们写入文件。

我的问题是,我想按列执行此操作。给定一个有 100 列、制表符分隔的文件,是否有类似的命令可以将此文件拆分为 20 个较小的文件,每个文件有 5 列和所有行?

我知道如何使用 cut,但我希望有一个我从未听说过的简单 UNIX 命令可以在不使用 perl 或其他东西包装 cut 的情况下完成此操作。

提前致谢。

【问题讨论】:

  • 顺便说一句,我正在对一个 100GB 的文件、400 万列、11000 行执行此操作。

标签: linux bash unix split cut


【解决方案1】:
#!/bin/bash

(($# == 2)) || { echo -e "\nUsage: $0 <file to split> <# columns in each split>\n\n"; exit; }

infile="$1"

inc=$2
ncol=$(awk 'NR==1{print NF}' "$infile")

((inc < ncol)) || { echo -e "\nSplit size >= number of columns\n\n"; exit; }

for((i=0, start=1, end=$inc; i < ncol/inc + 1; i++, start+=inc, end+=inc)); do
  cut -f$start-$end "$infile" > "${infile}.$i"
done

【讨论】:

  • +0.91(for 参数中的美元符号减去 0.02,infile 周围的花括号减去 0.02,使用 AWK 而不是 read -r -a arr &lt; "$infile"; ncol=${#arr[@]} 之类的东西减去 0.04,@ 也减去 0.02 987654325@ 而不是 printf.) ;)
  • 您好 SiegeX,您的解决方案非常好。干杯!
  • 我们怎样才能保留每个文件的第一列,意思是文件应该是这样的: File_1: col_1 col_2 File_2: col_1 col_3 File_3: col_1 col_4
【解决方案2】:

如果您只需要一个 QAD(Quick & Dirty)解决方案,在我的情况下是一个固定的 8 列;分隔的csv

#!/bin/bash
# delimiter is ;
cut -d';' -f1 "$1" > "${1}.1"
cut -d';' -f2 "$1" > "${1}.2"
cut -d';' -f3 "$1" > "${1}.3"
cut -d';' -f4 "$1" > "${1}.4"
cut -d';' -f5 "$1" > "${1}.5"
cut -d';' -f6 "$1" > "${1}.6"
cut -d';' -f7 "$1" > "${1}.7"
cut -d';' -f8 "$1" > "${1}.8"

【讨论】:

    【解决方案3】:

    感谢您的帮助。我希望有一个类似于 split 的 unix 命令,但我最终通过 SiegeX 的建议用 perl 包装了 cut 命令。

    #!/usr/bin/perl
    
    chomp(my $pwd = `pwd`);
    my $help = "\nUsage: $0 <file to split> <# columns in each split>\n\n";
    die $help if @ARGV!=2;
    
    
    $infile = $ARGV[0];
    chomp($ncol = `head -n 1 $infile | wc -w`);
    
    $start=1;
    $inc = $ARGV[1];
    $end = $start+$inc-1;
    
    die "\nSplit size >= number of columns\n\n" if $inc>=$ncol;
    
    for($i=1 ; $i<$ncol/$inc +1 ; $i++) {
        if ($end>$ncol) {$end=$ncol;}
        `cut -f $start-$end $infile > $infile.$i`;
        $start += $inc;
        $end += $inc;
    }
    

    【讨论】:

      【解决方案4】:
      # do something smarter with output files (& clear on start)
      XIFS="${IFS}"
      IFS=$'\t'
      while read -a LINE; do 
        for (( i=0; i< ${#LINE[@]}; i++ )); do
          echo "${LINE[$i]}" >> /tmp/outfile${i}
        done
      done < infile
      IFS="${XIFS}"
      

      试试上面的...使用文件名'infile'

      注意 IFS 的保存和恢复(有没有人有更好的主意?子外壳?)

      另请注意,如果您第二次运行 - 您会想要删除之前运行的输出...

      【讨论】:

      • 我不认为这个脚本做你认为它做的事。另外,对于您的 IFS 问题,只需使用 while IFS=$'\t' read -a LINE; do
      • 阅读前对 IFS 的好主意!至于其余的 - 它符合我的想法 - 我在发布之前对其进行了测试......我有 3 列用标签分隔,整个文件中有 3 行。它创建了 3 个带有各个列的输出文件 - 试试吧!我也比使用 cut 更喜欢它,因为它更适合大数据
      • nhed:好的,让我换个说法,我不认为这个脚本能满足 OP 的要求 =)。他想要每个文件多列 (5)
      • @Siege:好的,我的立场是正确的,我只是重新阅读了 5 列(我想我可能已经停止阅读 'tab delimited' :)
      【解决方案5】:

      这里有我的解决方案:

      首先是一个输入生成器:

          1 #!/usr/bin/env ruby                                                                                                                                                                                       
          2 #                                                                                                                                                                                                         
          3 def usage(e)                                                                                                                                                                                              
          4   puts "Usage #{__FILE__} <n_rows> <n_cols>"                                                                                                                                                              
          5   exit e                                                                                                                                                                                                  
          6 end                                                                                                                                                                                                       
          7                                                                                                                                                                                                           
          8 usage 1 unless ARGV.size == 2                                                                                                                                                                             
          9                                                                                                                                                                                                           
         10 rows, cols = ARGV.map{|e| e.to_i}                                                                                                                                                                         
         11 (1..rows).each do |l|                                                                                                                                                                                     
         12   (1..cols).each {|c| printf "%s ", c }                                                                                                                                                                   
         13   puts ""                                                                                                                                                                                                 
         14 end 
      

      分割工具:

          1 #!/usr/bin/env ruby                                                                                                                                                                                       
          2 #                                                                                                                                                                                                         
          3                                                                                                                                                                                                           
          4 def usage(e)                                                                                                                                                                                              
          5   puts "Usage #{__FILE__} <column_start> <column_end>"                                                                                                                                                    
          6   exit e                                                                                                                                                                                                  
          7 end                                                                                                                                                                                                       
          8                                                                                                                                                                                                           
          9 usage 1 unless ARGV.size == 2                                                                                                                                                                             
         10                                                                                                                                                                                                           
         11 c_start, c_end = ARGV.map{|e| e.to_i}                                                                                                                                                                     
         12 i = 0                                                                                                                                                                                                     
         13 buffer = []                                                                                                                                                                                               
         14 $stdin.each_line do |l|                                                                                                                                                                                   
         15   i += 1                                                                                                                                                                                                  
         16   buffer << l.split[c_start..c_end].join(" ")                                                                                                                                                             
         17   $stderr.printf "\r%d", i if i % 100000 == 0                                                                                                                                                             
         18 end                                                                                                                                                                                                       
         19 $stderr.puts ""                                                                                                                                                                                           
         20 buffer.each {|l| puts l}
      

      请注意,拆分工具会将 number 的值转储到 stderr 它正在处理中,因此您可以了解运行速度。

      另外,我假设分隔符是一个空格。

      如何运行它的示例:

       $ time ./gen.data.rb 1000 10 | ./split.rb 0 4 > ./out
      

      生成 1000 行,每行 10 列,然后拆分前 5 列。我用时间(1) 测量运行时间。

      我们可以使用一个小的 oneliner 来完成您要求的拆分(按顺序)。这个很 易于在单个节点中并行处理它(检查 bash 构建命令等待)或 将它们发送到集群。

      $ ruby -e '(0..103).each {|i| puts "cat input.txt | ./split.rb #{i-4} #{i} > out.#{i/4}" if i % 4 == 0 && i > 0}' | /bin/bash
      

      基本上会生成:

      cat input.txt | ./split.rb 0 4 > out.1
      cat input.txt | ./split.rb 4 8 > out.2
      cat input.txt | ./split.rb 8 12 > out.3
      cat input.txt | ./split.rb 12 16 > out.4
      cat input.txt | ./split.rb 16 20 > out.5
      cat input.txt | ./split.rb 20 24 > out.6
      cat input.txt | ./split.rb 24 28 > out.7
      cat input.txt | ./split.rb 28 32 > out.8
      cat input.txt | ./split.rb 32 36 > out.9
      cat input.txt | ./split.rb 36 40 > out.10
      cat input.txt | ./split.rb 40 44 > out.11
      cat input.txt | ./split.rb 44 48 > out.12
      cat input.txt | ./split.rb 48 52 > out.13
      cat input.txt | ./split.rb 52 56 > out.14
      cat input.txt | ./split.rb 56 60 > out.15
      cat input.txt | ./split.rb 60 64 > out.16
      cat input.txt | ./split.rb 64 68 > out.17
      cat input.txt | ./split.rb 68 72 > out.18
      cat input.txt | ./split.rb 72 76 > out.19
      cat input.txt | ./split.rb 76 80 > out.20
      cat input.txt | ./split.rb 80 84 > out.21
      cat input.txt | ./split.rb 84 88 > out.22
      cat input.txt | ./split.rb 88 92 > out.23
      cat input.txt | ./split.rb 92 96 > out.24
      cat input.txt | ./split.rb 96 100 > out.25
      

      然后通过管道传输到 bash。

      请注意您并行计算的进程(或作业)的数量,因为它会淹没您的 存储(除非您有独立的存储卷)。

      希望对您有所帮助。让我们知道它对您的运行速度。

      -drd

      【讨论】:

        【解决方案6】:

        Split 可以做你想做的事,只需要一点预处理

        sed -E $'s/(([^\t]+\t){4}[^\t]+)\t/\\1\\n/g' myfile.txt | split -nr/20
        

        这将写出 20 个带有 x 前缀的文件(在我的 split 版本中)。您可以验证这是否有效:

        paste x* | cmp - myfile.txt
        

        本质上,这是使用sed 将每一行拆分为二十行,然后使用带有循环块的拆分将每一行写入适当的文件。要使用不同的分隔符,请切换表达式中的制表符。数字 4 应该是每个文件的列数 - 1,拆分末尾的 20 是文件数。 split 的附加参数可用于修改写入的文件名。此示例使用 bashes 转义扩展将制表符写入 sed 表达式和可以使用 + 运算符的 sed 版本,但如果您的系统上不存在这些效果,则可以通过其他方式实现这些效果。

        我在 coreutils 邮件列表上从 Reuti 获得了这个解决方案的一个变体。

        【讨论】:

          【解决方案7】:

          没有直接类似的东西会按列拆分您的文件。但是,您可以直接使用 AWK:

          以下在包含NUMBER 列的输出文件中拆分input_file

          awk 'BEGIN{FS="\t"; m=NUMBER }
               { for(i=1;i<=NF;++i) { 
                    s = (i%m==1 ? $i : s FS $i);                                                                                                                                                 
                    if (i%m==0 || i==NF) {print s > (sprintf("out.%0.5d",int(i/m)+(i%m!=0)))}
               }}' input_file
          

          以下将input_file 拆分为CHUNKS 输出文件

          awk 'BEGIN{FS="\t"; n=CHUNKS}
               (NR==1){ m=int(NF/n)+(NF%n==0) }
               { for(i=1;i<=NF;++i) { 
                    s = (i%m==1 ? $i : s FS $i);                                                                                                                                                 
                    if (i%m==0 || i==NF) {print s > (sprintf("out.%0.5d",int(i/m)+(i%m!=0)))}
               }}' input_file
          

          【讨论】:

          • 脚本看起来很有希望。但是,我尝试在 Mac OS 上运行第一个脚本并收到“awk:在 mod 中除以零”。 \- E
          猜你喜欢
          • 2013-07-31
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-06-06
          • 2012-08-13
          • 2019-10-02
          • 1970-01-01
          相关资源
          最近更新 更多