Пересечение двух списков в Bash

163

Я пытаюсь написать простой скрипт, который будет перечислять содержимое, найденное в двух списках. Чтобы упростить, давайте использовать ls в качестве примера. Представьте, что «один» и «два» являются каталогами.

один = `лс один`
two = `ls two`
пересечение $ один $ два

Я все еще довольно зеленый в Bash, так что не стесняйтесь исправить, как я это делаю. Мне просто нужна команда, которая распечатает все файлы в «один» и «два». Они должны существовать в обоих. Вы можете назвать это «пересечением» между «один» и «два».

User1
источник
Ничто здесь фактически не отвечает на вопрос: как пересечь две переменные в сценарии Bash.
jameshfisher
По-моему, это новый вопрос, на который здесь четко дан ответ.
Жан-Кристоф Мейо,
, Возможно , более полезный подход в ближнем дубликатом stackoverflow.com/questions/2312762/...
tripleee

Ответы:

285
comm -12  <(ls 1) <(ls 2)
ghostdog74
источник
37
Не могу поверить, что я ничего не знала commдо сегодняшнего дня. Это просто сделало всю мою неделю :)
Дарра Энрайт
22
commтребует, чтобы входы были отсортированы. В этом случае lsавтоматически сортируется вывод, но для этого может потребоваться другое использование:comm -12 <(some-command | sort) <(some-other-command | sort)
Александр Бёрд
11
НЕ ИСПОЛЬЗУЙТЕ вывод ls ни для чего. ls - инструмент для интерактивного просмотра метаданных каталогов. Любые попытки синтаксического анализа вывода ls с кодом потерпели неудачу. Глобусы намного проще и правильнее: '' для файла в * .txt ''. Читайте mywiki.wooledge.org/ParsingLs
Рани Альбег Вайн
2
Я просто использовал это, чтобы найти применение publicметода, error()обеспеченного чертой, в сочетании с git grep, и это было потрясающе! Я побежал $ comm -12 <(git grep -il "\$this->error(" -- "*.php") <(git grep -il "Dash_Api_Json_Response" -- "*.php"), и, к счастью, я получил только имя файла, который содержал черту.
localheinz
3
Это весело. Я пытался сделать сумасшедшие вещи с помощью awk.
Рольф
55

Решение с comm

commэто здорово, но действительно нужно работать с отсортированным списком. И, к счастью, здесь мы используем lsкоторый из lsстраницы руководства Bash

Сортировать записи по алфавиту, если ни -cftuSUX, ни --sort.

comm -12  <(ls one) <(ls two)

Альтернатива с sort

Пересечение двух списков:

sort <(ls one) <(ls two) | uniq -d

симметричная разница двух списков:

sort <(ls one) <(ls two) | uniq -u

бонус

Играть с этим ;)

cd $(mktemp -d) && mkdir {one,two} && touch {one,two}/file_{1,2}{0..9} && touch two/file_3{0..9}
Жан-Кристоф Мейо
источник
2
Вместо дополнения , я думаю, это то, что обычно называют симметричной разностью. .
Андрей Лазарь
29

Используйте commкоманду:

ls one | sort > /tmp/one_list
ls two | sort > /tmp/two_list
comm -12 /tmp/one_list /tmp/two_list

«sort» на самом деле не нужен, но я всегда включаю его перед использованием «comm» на всякий случай.

DVK
источник
5
Хорошо включить его, так как он должен быть отсортирован, и он использовал только ls в качестве примера.
Thor84no
3

Менее эффективная (чем коммуникативная) альтернатива:

cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -d
Benubird
источник
1
Если вы используете Debian в / bin / тир или какую - либо другая не-оболочку Bash в сценариях, вы можете вывести цепные команды с помощью круглых скобок: (ls 1; ls 2) | sort -u | uniq -d.
азот
1
@ MikaëlMayer Вы должны отметить имя человека, которому вы отвечаете, в противном случае предполагается, что вы имеете в виду меня.
Benubird
@nitrogen MikaëlMayer прав - чейнджинг sort -u | uniq -dничего не делает, потому что сортировка удалила дубликаты до того, как uniq начнет их искать. Я думаю, вы не поняли, что делает моя команда.
Benubird
@Benubird Я тоже не смог заставить вашу команду cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -dчто-либо выводить. Моя команда должна читать (ls 1; ls 2) | sort | uniq -d, без -u, чтобы показать пересечение списка. @ MikaëlMayer был прав, что моя первоначальная команда была нарушена.
азот
@nitrogen Причина, по которой я использую cat, заключается в том, что я хочу, чтобы это было обобщаемое решение, чтобы вы могли заменить его lsчем-то другим, например find. Ваше решение не допускает этого, потому что если одна из команд возвращает две одинаковые строки, она выбирает ее как дубликат. Мой работает, даже если пользователь хочет сделать ls 1/*и сравнить все файлы в разных подкаталогах. В противном случае, да, это работает так же. Возможно, мое зависит от bash.
Benubird
2

Присоединение является еще одним хорошим вариантом в зависимости от ввода и желаемого выхода

join -j1 -a1 <(ls 1) <(ls 2)
frogstarr78
источник
-1

Есть еще один вопрос Stackoverflow «Пересечение массива в bash», который помечен как дубликат этого. На мой взгляд, это не совсем то же самое, поскольку этот вопрос говорит о сравнении двух массивов bash, в то время как этот вопрос касается файлов bash. Однострочный ответ на другой вопрос, который сейчас закрыт, выглядит следующим образом:

# List1=( 0 1 2 3 4   6 7 8 9 10 11 12)
# List2=(   1 2 3   5 6   8 9    11 )
# List3=($(comm -12 <(echo ${List1[*]}| tr " " "\n"| sort) <(echo ${List2[*]} | tr " " "\n"| sort)| sort -g))
# echo ${List3[*]}
1 2 3 6 8 9 11

Утилита comm выполняет буквенно-цифровую сортировку, тогда как «пересечение массивов в bash» использует числа; отсюда использование "sort" и "sort -g".

Чак Ньюман
источник