Разделить файл с разделителями на меньшие файлы по столбцам - PullRequest
4 голосов
/ 10 марта 2011

Я знаком с командой split в linux.Если у меня есть файл длиной 100 строк,

split -l 5 myfile.txt

... разделит файл myfile.txt на 20 файлов, каждый из которых содержит 5 строк, и запишет их в файл.

Мой вопрос, я хочу сделать это по столбцу.Учитывая файл с 100 столбцами, разделенными табуляцией, есть ли подобная команда для разделения этого файла на 20 меньших файлов, каждый из которых имеет 5 столбцов и все строки?

Я знаю, как использовать вырезать, ноЯ надеюсь, что есть простая команда UNIX, о которой я никогда не слышал, которая справится с этим без обрезки perl или чего-то еще.

Заранее спасибо.

Ответы [ 7 ]

8 голосов
/ 11 марта 2011
#!/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
3 голосов
/ 12 сентября 2014

, если вам нужно только решение 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"
1 голос
/ 11 марта 2011

Спасибо за помощь.Я надеялся, что будет команда unix, похожая на split, но в итоге я обернул команду cut командой perl по предложению SiegeX.

#!/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;
}
0 голосов
/ 23 июля 2018

Нет ничего подобного, что разделит ваш файл по столбцам.Однако вы можете использовать AWK для этого простым способом:

Следующие разбиения input_file в выходных файлах, содержащих NUMBER столбцов

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
0 голосов
/ 26 января 2017

Split может фактически делать то, что вы хотите, с небольшой предварительной обработкой

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

Это позволит записать двадцать файлов с префиксом x (в моей версии split). Вы можете проверить это работало с:

paste x* | cmp - myfile.txt

По сути, это то, что вы используете sed, чтобы разбить каждую строку на двадцать строк, а затем с помощью разбивки на круглые кусочки, чтобы записать каждую строку в соответствующий файл. Чтобы использовать другой разделитель, переключите вкладки в выражении. Число 4 должно быть числом столбцов в файле - 1, а число 20 в конце разделения - это количество файлов. Дополнительные параметры для разделения могут быть использованы для изменения имен файлов, которые пишутся. В этом примере используется расширение escape-строк для записи вкладок в выражение sed и версию sed, в которой можно использовать оператор +, но эти эффекты могут быть достигнуты альтернативными способами, если их нет в вашей системе.

Я получил вариант этого решения от Reuti в списке рассылки coreutils.

0 голосов
/ 11 марта 2011

Вот вам мое решение:

Первый входной генератор:

    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}

Обратите внимание, что инструмент разбивки сбрасывает в stderr значение числа линии он обрабатывает, чтобы вы могли понять, как быстро идет.

Кроме того, я предполагаю, что разделитель является пробелом.

Пример того, как его запустить:

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

Создайте 1000 строк по 10 столбцов в каждой и разбейте первые 5 столбцов. Я использую время (1) измерить время бега.

Мы можем использовать небольшой oneliner для выполнения запрошенного вами разбиения (последовательно). Это очень легко обрабатывать его параллельно в одном узле (проверьте команду bash wait) или отправить их в кластер.

$ 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

И попадает в канал.

Будьте осторожны с количеством процессов (или заданий), которые вы вычисляете параллельно, потому что это затопит ваш хранилище (если у вас нет независимых томов хранилища).

Надеюсь, это поможет. Дайте нам знать, как быстро он работает для вас.

-дрд

0 голосов
/ 11 марта 2011
# 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 (у кого-нибудь есть идея получше - подоболочка?)

Также обратите внимание, что это добавляется, если вы работаете во второй раз - вы хотите удалить выходные данные предыдущего запуска ...

...