2015-09-09 4 views
1

У меня есть куча CSV-файлов в папке (ОС: Ubuntu). Все они на одной структуре. более 2 тыс. столбцов (вот как это получилось). Первый столбец - ID.Текстовые файлы, управляемые в Linux

Я не в состоянии работать с SQL (фигу почему), поэтому я думаю, что мне нужно будет работать с командой Баш, такие как awk, cut, sed, и т.д., которые у меня есть базовые знания о них.

мне нужно сделать следующее: Выполнить над файлами (как файлы объединены в один файл): для каждого четного столбца, проверьте, если он имеет одно неоспоримое значение, равное 0 -> если да, удалите столбец и следующий столбец. Кроме того, мне нужно напечатать в новом файле индекс удаленных столбцов.

Пример

file_1: 
2231, 0, 5, 0, 9, 0, 9, 3, 3 
1322, 0, 5, 0, 1, 0, 9, 2, 5 
1233, 5, 5, 0, 3, 0, 9, 4, 6 
1543, 2, 5, 0, 4, 0, 9, 6, 1 
2341, 0, 5, 0, 7, 0, 9, 0, 2 

files_2: 
1322, 0, 5, 0, 3, 0, 9, 1, 2 
1432, 0, 5, 0, 0, 0, 9, 3, 7 
1434, 0, 5, 0, 8, 0, 9, 1, 4 
1132, 0, 5, 0, 4, 0, 9, 3, 5 
1434, 0, 5, 0, 7, 0, 9, 1, 0 

Ожидаемый результат:

Removed index columns file: 4, 5, 6, 7 

    file_1 content: 
    2231, 0, 5, 3, 3 
    1322, 0, 5, 2, 5 
    1233, 5, 5, 4, 6 
    1543, 2, 5, 6, 1 
    2341, 0, 5, 0, 2 

    files_2 content: 
    1322, 0, 5, 1, 2 
    1432, 0, 5, 3, 7 
    1434, 0, 5, 1, 4 
    1132, 0, 5, 3, 5 
    1434, 0, 5, 1, 0 

Можно ли сделать это с помощью одного из этих команд Баш? Если да, то как? Любое другое решение будет хорошо, но я предпочитаю команды bash.

+0

Есть много способов сделать это, в том числе bash и/или sed, awk и т. д. Q: Какие языки программирования - если таковые имеются - вы чувствуете себя наиболее комфортно? C? Ява? Perl? Python? Bash? Другие? – paulsm4

+0

Как я уже сказал, я предпочитаю баш. Python тоже будет хорош. Я очень хорошо разбираюсь в Java и SQL, но я не могу использовать их в этом случае (неважно, почему). – Omri

+0

Вы можете преобразовать их в некоторые [sqlite] (http://sqlite.org/), а затем использовать SQL в этом файле '.sqlite'. –

ответ

3

Вы можете использовать AWK, чтобы пропустить эти столбцы со всеми нулями:

awk 'BEGIN { FS=OFS=", " } 
NR==1 { 
    for (i=2; i<=NF; i+=2) 
     a[i] 
} FNR==NR { 
    for (i=2; i<=NF; i+=2) 
     if (i in a && $i>0) 
     delete a[i]; 
    next 
} { 
    for (i=1; i<=NF; i++) 
     if (!(i in a)) 
     printf "%s%s", $i, (i<NF)? OFS : RS 
}' file1 file1 

Выход:

2231, 0, 5, 9, 9, 3, 3 
1322, 0, 5, 1, 9, 2, 5 
1233, 5, 5, 3, 9, 4, 6 
1543, 2, 5, 4, 9, 6, 1 
2341, 0, 5, 7, 9, 0, 2 

Он использует массив a держать даже столбцов номера, которые должны быть пропущены от производства.

В 1-м проходе, когда:

NR==1 # will run for first row to create an array a with even # of columns as index 
FNR==NR # block will run for 1st pass of the file. It will delete entries from array a 
     # if current value is greater than zero. 
{...} # in the 2nd pass we iterate each column and print if col is not in array a 

UPDATE:

Согласно комментариям ниже

awk 'BEGIN{FS=OFS=","} 
FNR==NR { 
    for (i=1; i<=NF; i++) 
     sums[i] += $i; 
    ++r; 
    next 
} { 
    for (i=1; i<=NF; i++) 
     if (sums[i] > 0 && sums[i+1]>0 && sums[i] != 100*r) 
     printf "%s%s", (i>1)?OFS:"", $i; 
     print "" 
}' file file 
+0

Спасибо, но я не знаю, что такое «плохие» столбцы заранее. У меня есть ~ 2k столбцов и миллионы строк на файл. Это был просто пример. – Omri

+1

Итак, каков определяющий фактор, который делает столбцы «плохими»? Ваше первоначальное описание довольно не хватает. – tink

+0

@tink. Это столбец со всеми '0' в каждом файле. Написание двухпроходного ответа. Надеемся, что эффективный способ постепенного отслеживания того, какие столбцы являются кандидатами на убийство. –

0

Вы не можете сказать, какие столбцы, чтобы удалить, не глядя в каждой строке каждого файла.

Я выбрал perl для броска вокруг строк и списков. Вы могли сделать это в awk, но awk только имеет ассоциативные массивы. Я думаю, что было бы неприятно отфильтровывать кандидатов на убийство в awk.

Если вы уже знаете Python, используйте его. Это в основном бросает списки, а не строки, и питон хорош в этом.

Алгоритм 2 прохода может идти, как это:

# pseudo-perl, untested code 
#!/usr/bin/perl -w 
$line_scalar = <>; # read the first line; 
@line = split /\s*,\s*/, $list_scalar; 
killcols= indices of columns that are '0' in @line; 

while(<>) { 
    @line = split /\s*,\s*/, $_; 

    # filter out candidates that aren't zero in this line. (string '0' is false in perl) 
    @killcols = grep { ! $line[$_] } @killcols; 
} 

Поскольку количество столбцов, которые будут удалены, вероятно, невелико, список кандидатов будет быстро разрядиться. Проверка оставшихся кандидатов в каждой строке должна быть значительно быстрее, чем просмотр каждого поля, чтобы найти нулевые. Вам все равно нужно читать и разделять каждую строку, но это должно быть хорошо.Это far лучше, чем читать все это в памяти, а затем перебирать каждую строку по одному для каждого столбца.

Если ваш набор данных слишком велик для хранения в памяти, просто перечитайте и повторно разложите входные файлы, как только вы узнаете, какие столбцы удалить.

Если ваш набор данных невелик, вы можете хранить строки в памяти по мере их чтения и избегать их считывания и разбиения. Однако вам нужно будет отслеживать, какая строка идет с каким файлом. В perl, вероятно, список списков проанализированных строк для каждого файла будет хорошим. Чтобы отслеживать, какой список идет с каким файлом, держите LoL в хеше.

В perl нормальные списки выравниваются. To make a list of lists, you need to make a list of list-refs.

Мой perl ржавый, так что я, вероятно, получаю reference syntax неправильно.

# UNTESTED code, pretty sure there are minor bugs 
# You'll prob. rewrite in python anyway 

... # get number of columns from the first line, outside the loop 
while(<>) { 
    @line = split /\s*,\s*/, $_; 
    push @{$lines{$ARGV}}, [ @line ]; # append the parsed fields to the list of lines for the current file 

    # filter out candidates that aren't zero in this line. (string '0' is false in perl) 
    @killcols = grep { ! $line[$_] } @killcols; 
} 

#my @keepcols; 
#foreach my $col (1..$maxcol) { 
# push @keepcols, $col if ! grep { $col == $_ } @killcols; 
#} 
# IDK if this is the most efficient way to invert a set 
# In any case, actually storing a list of all the column numbers *to* print 
# is probably slower than looping over the ranges between @killcol. 

foreach my $f (@ARGV) { 
    open OUTFILE, ">", "$f.new" or die "error opening $f.new: $!"; 
    foreach my $lref (@$lines{$f}) { 
     my $printcol = 0; 
     foreach my $kcol (@killcols) { 
      # FIXME: delimiter logic is probably not bulletproof e.g. when the last killed column = maxcol 
      print(OUTFILE, join(', ', $lref->[$printcol .. $kcol-1])); 
      print OUTFILE, ', '; 
      $printcol = $kcol+1; # skip the killed column 
     } 
     print(OUTFILE, join(', ', $lref->[$printcol .. $maxcol]), "\n"); 
     # no trailing ", " for the last one. 
     # Could append $maxcol+1 to @killcols, since I think the delimiter logic needs fixing anyway. 
    } 
    close OUTFILE; 
    # close "$f.new", and if there weren't any I/O errors, rename it over "$f" 
} 
1
for File in ListOfFile 
do 
    sed -i 's/\(\(\[^,]*,\)\{3\}\)\(\[^,]*,\)\{4\}\(.*\)/\1\4/' File 
done 
  • обеспечивают ListOfFile, как вы хотите (список файлов, найти, LS, ..., дикая карта)
  • с GNU SED для использования -i для редактирования инлайн (прямая модификация самого файла)
Смежные вопросы