Перетин двох списків у Баші


163

Я намагаюся написати простий скрипт, який перелічить вміст, знайдений у двох списках. Для спрощення скористаємося ls як приклад. Уявіть, що "один" і "два" - це каталоги.

one = `ls one`
два = `ls два`
перетин $ один $ два

Я все ще досить зелений в баші, тому сміливо виправляйте, як я це роблю. Мені просто потрібна якась команда, яка буде роздруковувати всі файли в "один" і "два". Вони повинні існувати в обох. Ви можете назвати це "перетином" між "одним" і "двома".


Ніщо тут насправді не відповідає на питання: як перетинати дві змінні в сценарії Bash.
jameshfisher

На мою думку, це нове запитання, на це питання чітко відповіли тут.
Жан-Крістоф Мейло

, Можливо , більш корисний підхід в ближньому дублікатом stackoverflow.com/questions/2312762 / ...
tripleee

Відповіді:


285
comm -12  <(ls 1) <(ls 2)

37
Не можу повірити, я не знав commдо сьогодні. Це щойно робило цілий тиждень :)
Дарраг Енрайт

22
commвимагає сортування входів. У цьому випадку lsавтоматично сортується вихід, але для цього може знадобитися інше використання:comm -12 <(some-command | sort) <(some-other-command | sort)
Олександр Птах

11
НЕ ВИКОРИСТОВУЙТЕ висновок ls ні для чого. ls - це інструмент для інтерактивного перегляду метаданих каталогів. Будь-які спроби розбору виводу ls з кодом порушені. Глобуси набагато простіші і правильні: '' для файлу в * .txt ''. Читайте mywiki.wooledge.org/ParsingLs
Rany Albeg Wein

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

"сортувати" насправді не потрібно, але я завжди включаю його перед тим, як використовувати "comm" на всякий випадок.


5
Це добре включити, оскільки його потрібно сортувати, і він використовував лише ls як приклад.
Thor84no

3

Менш ефективна (ніж комунальна) альтернатива:

cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -d

1
Якщо ви використовуєте Debian в / bin / тир або яку - або інша не-оболонку Bash в сценаріях, ви можете вивести ланцюгові команди з допомогою круглих дужок: (ls 1; ls 2) | sort -u | uniq -d.
азот

1
@ MikaëlMayer Ви повинні позначити ім’я людини, якій ви відповідаєте, інакше вважається, що ви мене маєте на увазі.
Benubird

@nitrogen MikaëlMayer правильний - chainging 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 Причина, чому я використовую кота, полягає в тому, що я хочу, щоб це було узагальненим рішенням, щоб ви могли замінити lsчимось іншим, наприклад find. Ваше рішення цього не дозволяє, оскільки якщо одна з команд повертає два рядки однаково, вона підбирає її як дублікат. Шахта працює, навіть якщо користувач хоче робити ls 1/*і порівнювати всі файли в підкаталогах. В іншому випадку, так, це також працює. Можливо, міна є специфічною для удару.
Benubird

2

Приєднання - ще один хороший варіант залежно від введення та бажаного виводу

join -j1 -a1 <(ls 1) <(ls 2)

-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" відповіді використовують числа; отже, використання "сортування" та "сортування -g".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.