Как получить вывод из команды comm в 3 отдельных файла?

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

comm -12 1.sorted.txt 2.sorted.txt

Здесь показаны линии, общие для двух файлов (-1 подавляет строки, которые находятся только в первом файле, а -2 подавляет строки только во втором файле, оставляя только строки, общие для обоих файлов как выход). Как указывают имена файлов, входные файлы должны быть отсортированы в порядке.

В comment к этому вопросу bapors спрашивает:

Как можно получить выходы в разных файлах?

В поисках разъяснений я спросил:

Если вы хотите, чтобы строки только в File1 в одном файле, только в File2 в другом, а те, что были в третьем, (при условии, что ни одна из строк в файлах не начинается с вкладки), вы можете использовать sed, чтобы разделить вывод на три файла.

Пользовательские подтверждения подтверждены:

Это именно то, что я просил. Вы могли бы показать пример?

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

Ответ 1

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

Он также полагается на sed w команду для записи в файлы.

Данный файл 1.sorted.txt содержащий:

1.line-1
1.line-2
1.line-4
1.line-6
2.line-2
3.line-5

и файл 2.sorted.txt, содержащий:

1.line-3
2.line-1
2.line-2
2.line-4
2.line-6
3.line-5

основной вывод из comm 1.sorted.txt 2.sorted.txt:

1.line-1
1.line-2
        1.line-3
1.line-4
1.line-6
        2.line-1
                2.line-2
        2.line-4
        2.line-6
                3.line-5

Для файла script.sed, содержащего:

/^\t\t/ {
    s///
    w file.3
    d
}
/^\t/ {
    s///
    w file.2
    d
}
/^[^\t]/ {
    w file.1
    d
}

вы можете запустить приведенную ниже команду и получить желаемый результат следующим образом:

$ comm 1.sorted.txt 2.sorted.txt | sed -f script.sed
$ cat file.1
1.line-1
1.line-2
1.line-4
1.line-6
$ cat file.2
1.line-3
2.line-1
2.line-4
2.line-6
$ cat file.3
2.line-2
3.line-5
$

script работает по:

  • совпадающие строки, начинающиеся с 2 вкладок, удаление вкладок, запись строки на file.3 и удаление строки (так что остальная часть script игнорируется),
  • совпадающие строки, начинающиеся с 1 вкладки, удаление вкладки, запись строки на file.2 и удаление строки (так что остальная часть script игнорируется),
  • совпадающие строки, которые не начинаются с вкладки, записывая строку на file.1 и удаляя строку.

Операции совпадения и удаления на шаге 3 больше для симметрии, чем что-либо еще; они могут быть опущены (оставив только w file.1), и этот script будет работать одинаково. Однако см. Ниже script3.sed для дальнейшего обоснования сохранения симметрии.

Как написано, для этого требуется GNU sed; BSD sed не распознает экраны \t. Очевидно, что файл можно записать с фактическими вкладками вместо обозначения \t, а затем BSD sed в порядке с script.

Можно заставить все работать в командной строке, но это неудобно (и это вежливо об этом). Используя Bash ANSI C Quoting, вы можете написать:

$ comm 1.sorted.txt 2.sorted.txt |
> sed -e $'/^\t\t/  { s///\n w file.3\n d\n }' \
>     -e $'/^\t/    { s///\n w file.2\n d\n }' \
>     -e $'/^[^\t]/ {        w file.1\n d\n }'
$

который записывает каждый из трех "абзацев" script.sed в отдельный параметр -e. Команда w суетлива; он ожидает имя файла и только имя файла после него в той же строке script, следовательно, использование \n после имен файлов в script. Есть пробелы, которые могут быть устранены, но симметрия становится яснее с изображением. И использование файла -f script.sed, вероятно, проще - это, безусловно, метод, который стоит знать, поскольку он может избежать проблем, когда sed script должен работать с одинарными, двойными и обратными кавычками, что затрудняет запись script в командной строке Bash.

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

script3.sed (при замене вкладок на 8 пробелов) - обратите внимание, что на этот раз в третьем абзаце есть подстановка s/// (d по-прежнему необязательна, но также может быть включена):

/^              X/ {
    s///
    w file.3
    d
}
/^      X/ {
    s///
    w file.2
    d
}
/^X/ {
    s///
    w file.1
    d
}

И в командной строке:

$ comm <(sed 's/^/X/' 1.sorted.txt) <(sed 's/^/X/' 2.sorted.txt) |
> sed -f script3.sed
$

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

Вы также можете легко писать решения, которые используют Perl или Awk, и этим даже не нужно использовать comm (и их можно заставить работать с несортированными файлами при условии, что файлы вписываются в память).

Ответ 2

сообщение + awk:

Сложные файлы с образцами:

1.txt

1. line-1 with spaces (                 |   | here
1.line-2
1.line-4    with tabs > 
 1.line-6
2.line-2
        3.line-5 (tabs)

2.txt

1.line-3
  2.line-1 with spaces
2.line-2
2.line-4
    2.line-6 with tabs
        3.line-5 (tabs)

Задача:

comm -12 1.txt 2.txt > file-common 
awk 'NR==FNR{ a[$0];next }!($0 in a){ print $0 > "file"ARGIND-1 }' file-common 1.txt 2.txt
  • comm -12 1.txt 2.txt > file-common - сохранит общие строки в файле file-common

  • awk ... - печатает строки, уникальные для 1.txt и 2.txt, в файлы file1 и file2 соответственно


Результаты поиска:

head file*
==> file1 <==
1. line-1 with spaces (                 |   | here
1.line-2
1.line-4    with tabs > 
 1.line-6

==> file2 <==
1.line-3
  2.line-1 with spaces
2.line-4
    2.line-6 with tabs

==> file-common <==
2.line-2
        3.line-5 (tabs)