Подсчитайте количество непустых записей в каждом столбце, например, вывод комм - PullRequest
0 голосов
/ 31 января 2019

Команда Unix comm file1 file2 имеет вывод из трех столбцов со строками, уникальными для file1 в первом столбце, строками, уникальными для file2 во втором, и строками, общими для обоих в третьем (при условии, что file1 и file2 отсортированы).В конечном итоге это выглядит примерно так:

$ echo -e "alpha\nbravo\ncharlie" > file1
$ echo -e "alpha\nbravo\ndelta" > file2
$ comm file1 file2
                alpha
                bravo
charlie
        delta

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

Я знаю, что для comm, в частности, я мог бы просто запустить

for i in {12,23,31}; do comm -$i file1 file2 | wc -l; done

, но мне любопытно найти решения, которые принимают выходной файл comm в качестве отправной точки, чтобы улучшить егов командной строке Unix.Я добавил тег , потому что у меня есть подозрение, что есть хорошее решение для awk.

Ответы [ 5 ]

0 голосов
/ 01 февраля 2019

Использование Perl

$  comm file1 file2 | perl -lne ' /^\t\t/ and $kv{2}++; /^\t\S+/ and $kv{1}++; /^\S+/ and $kv{3}++; END { print "col-$_:$kv{$_}" for(keys %kv) } '
col-3:1
col-1:1
col-2:2

$

или

$ comm file1 file2 | perl -lne ' /(^\t\t)|(^\t\S+)|(^.)/ and $x=$+[0]>2?3:$+[0]; $kv{$x}++; END { print "col-$_:$kv{$_}" for(keys %kv) } '
col-3:1
col-1:1
col-2:2

$

, где col-1 -> first file

col-3 -> second file

col-2 -> both file

0 голосов
/ 31 января 2019

Вопрос интересный, но не такой простой, как можно себе представить, особенно если у вас нет опции --total.

Несколько вещей о comm:

  1. comm работает с отсортированными файлами
  2. , если строка появляется n раз в file1 и m раз n раз в file2, comm выведет nm записей в столбце 2 и n записей в столбце 3.

    $ comm <(echo -e "1\n2\n3") <(echo "2\n2\n3\n4")
    1
                    2
            2
                    3
            4
    
  3. comm использует -характер в качестве разделителя по умолчанию, обработка его вывода становится проблематичной, если ваш ввод содержит этот символ.

    $ comm  <(echo -e "1\t2\n3") <(echo "2\n3\n4")
    1       2             << this is the weird line
            2
                    3
            4
    

    К счастью, у него есть возможность определить разделитель (--output-delimiter=STR)

  4. comm добавляет разделитель, только если другие непустые поля следуют

    $ comm  --output-delimiter=SEP <(echo -e "1\n2\n3") <(echo "2\n3\n4")
    1             << NO SEP (1 field)
    SEPSEP2       << TWO SEP (3 fields)
    SEPSEP3       << TWO SEP (3 fields)
    SEP4          << ONE SEP (2 fields)
    

Как мы можем решить это сейчас:

Мы явно не должны использовать символ ASCII в качестве разделителя, это вызывает проблемы при обработке файлов ASCII, так что выможно использовать непечатаемый символ в качестве разделителя.Вы могли бы использовать, например,-символ с восьмеричным значением \001 (он не принимает -характер).Как правило, это решает проблемы, которые могут возникнуть из-за пункта (3)

$ comm  --output-delimiter=$'\001' <(echo -e "1\t2\n3") <(echo "2\n3\n4")

, этот вывод теперь может быть передан в виде чрезвычайно простого awk

$ awk -F "\001" '{a[NF]++}END{print a[1],a[2],a[3] }'

, описанного выше работает из-заточка (4).

Так что вы можете просто сделать:

$ comm  --output-delimiter=$'\001' file1 file2 \
  | awk -F "\001" '{a[NF]++}END{print a[1],a[2],a[3] }'

Но у меня нет этой опции --output-delimiter: Это требует чистого awk решение.Мы отслеживаем 3 массива.a для file1 b для file2 и c для комбинации.(c отслеживает все записи).Мы обязательно учитываем пункт (2).

$ awk '(NR==FNR) { a[$0]++; c[$0]++ }
       (NR!=FNR) { b[$0]++; c[$0]-- }
       END { for(i in c) {
                if      (c[i] <  0) { countb+=-c[i]; countc+=a[i] }
                else if (c[i] == 0) {                countc+=a[i] }
                else                { counta+= c[i]; countc+=b[i] }
             }
             print counta, countb, countc
       }' file1 file2

Мы могли бы по существу избавиться от массива b, так как он может быть получен из a и c, но я хотелсделайте немного более понятным, как это работает;другая версия будет:

$ awk '(NR==FNR) { a[$0]++; c[$0]++; next } { c[$0]-- }
       END { for(i in c) {
               counta+=(c[i]>0 ? c[i] : 0)
               countb-=(c[i]<0 ? c[i] : 0)
               countc+=a[i] - (c[i]>0 ? c[i] : 0)
             }
             print counta, countb, countc
       }' file1 file2
0 голосов
/ 31 января 2019

Другой ответ охватывает ваш вопрос об использовании awk для выполнения работы достаточно хорошо, но также стоит упомянуть, что в версии GNU comm есть опция --total, которая будет печатать сумму каждого столбца в похожемобразом.

0 голосов
/ 31 января 2019

очевидно, что вы можете делать все за awk без comm или требовать отсортированных входных данных.

$ awk 'NR==FNR {a[$1]; next} 
               {if($1 in a) {c3++; delete a[$1]} 
                else c2++} 
           END {print length(a),c2,c3}' file1 file2

1 1 2

, что учитывается только для file1, file2 only и common.

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

0 голосов
/ 31 января 2019

Вы можете использовать это awk:

comm file1 file2 |
awk -F '\t' -v OFS='\n' '{ if ($1=="") if ($2=="") c3++; else c2++; else c1++ }
END { print c3, c2, c1 }'

2
1
1

Обратите внимание, что вывод comm ограничен табуляцией в следующих случаях:

  • 1-й и 2-й пустой столбец в общих строках
  • 1-й пустой столбец в строках, уникальных для file2
  • 1-й непустой столбец в строках, уникальных для file1
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...