Przecięcie dwóch list w Bash


163

Próbuję napisać prosty skrypt, który wyświetli zawartość znalezioną na dwóch listach. Aby uprościć, użyjmy ls jako przykładu. Wyobraź sobie, że „jeden” i „dwa” to katalogi.

one = `ls one`
two = `ls two`
przecięcie $ jeden $ dwa

Nadal jestem dość zielony w bashu, więc nie krępuj się poprawić, jak to robię. Potrzebuję tylko jakiegoś polecenia, które wypisze wszystkie pliki w „jednym” i „dwóch”. Muszą istnieć w obu. Można to nazwać „przecięciem” między „jeden” i „dwa”.


Nic tutaj właściwie nie odpowiada na pytanie: jak przeciąć dwie zmienne w skrypcie Bash.
jameshfisher

Wydaje mi się, że jest to nowe pytanie, na to pytanie udzielono tutaj jasnej odpowiedzi.
Jean-Christophe Meillaud

Prawdopodobnie bardziej użytecznym podejściem jest prawie duplikat stackoverflow.com/questions/2312762/ ...
tripleee

Odpowiedzi:


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

37
Nie mogę uwierzyć, że commdo dzisiaj nic nie wiedziałem . To właśnie zrobiło mój cały tydzień :)
Darragh Enright

22
commwymaga sortowania danych wejściowych. W tym przypadku lsautomatycznie sortuje dane wyjściowe, ale inne zastosowania mogą wymagać tego:comm -12 <(some-command | sort) <(some-other-command | sort)
Alexander Bird

11
NIE UŻYWAJ wyjścia ls do niczego. ls to narzędzie do interaktywnego przeglądania metadanych katalogu. Wszelkie próby przeanalizowania wyjścia ls za pomocą kodu są przerywane. Globy są znacznie prostsze ORAZ poprawne: '' dla pliku w * .txt ''. Przeczytaj mywiki.wooledge.org/ParsingLs
Rany Albeg Wein

2
Po prostu użyłem tego, aby znaleźć zastosowania publicmetody error()dostarczanej przez cechę w połączeniu z git grep, i to było niesamowite! Pobiegłem $ comm -12 <(git grep -il "\$this->error(" -- "*.php") <(git grep -il "Dash_Api_Json_Response" -- "*.php")i na szczęście skończyłem z nazwą pliku zawierającego tylko cechę.
localheinz

3
To przezabawne. Próbowałem robić szalone rzeczy z awk.
Rolf

55

Rozwiązanie z comm

commjest świetny, ale rzeczywiście trzeba pracować z posortowaną listą. I na szczęście tutaj używamy lstego ze strony lspodręcznika Bash

Sortuj wpisy alfabetycznie, jeśli nie ma opcji -cftuSUX ani --sort.

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

Alternatywa z sort

Przecięcie dwóch list:

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

symetryczna różnica dwóch list:

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

Premia

Baw się tym ;)

cd $(mktemp -d) && mkdir {one,two} && touch {one,two}/file_{1,2}{0..9} && touch two/file_3{0..9}

2
Zamiast dopełnienia myślę, że to zwykle nazywa się różnicą symetryczną .
Andrew Lazarus

29

Użyj commpolecenia:

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

„sort” nie jest tak naprawdę potrzebne, ale zawsze dołączam go przed użyciem „comm” na wszelki wypadek.


5
Dobrze jest go dołączyć, ponieważ trzeba go posortować, a on użył ls tylko jako przykładu.
Thor84no

3

Mniej wydajna (niż komunikacja) alternatywa:

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

1
Jeśli używasz Debiana / bin / dash lub innego niż powłoki Bash w skryptach można wyjście komend łańcuch używając nawiasów: (ls 1; ls 2) | sort -u | uniq -d.
azot

1
@ MikaëlMayer Powinieneś oznaczyć imię i nazwisko osoby, której odpowiadasz, w przeciwnym razie zakłada się, że masz na myśli mnie.
Benubird

@nitrogen MikaëlMayer ma rację - chainging sort -u | uniq -dnic nie daje, ponieważ sortowanie usuwa duplikaty, zanim uniq zacznie ich szukać. Myślę, że nie zrozumiałeś, co robi moje polecenie.
Benubird

@Benubird Nie udało mi się również uzyskać twojego polecenia, cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -daby cokolwiek wyprowadzić. Moje polecenie powinno czytać (ls 1; ls 2) | sort | uniq -d, bez -u, aby pokazać przecięcie listy. @ MikaëlMayer miał rację, że moje pierwotne polecenie zostało złamane.
azot

@nitrogen Powodem, dla którego używam cat, jest to, że chcę, aby było to rozwiązanie dające się uogólnić, aby można było zastąpić lscoś innym, np find. Twoje rozwiązanie na to nie pozwala, ponieważ jeśli jedno z poleceń zwraca dwie takie same linie, pobiera je jako duplikat. Mój działa, nawet jeśli użytkownik chce zrobić ls 1/*i porównać wszystkie pliki w podkatalogach. W przeciwnym razie tak, to też działa. Możliwe, że mój jest specyficzny dla basha.
Benubird

2

Dołącz to kolejna dobra opcja w zależności od wejścia i pożądanego wyjścia

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

-1

Jest jeszcze jedno pytanie Stackoverflow „Przecięcie tablicy w bash”, które jest oznaczone jako duplikat tego. Moim zdaniem to nie to samo, ponieważ to pytanie dotyczy porównania dwóch tablic bash, podczas gdy to pytanie koncentruje się na plikach bash. Jednowierszowa odpowiedź na drugie pytanie, które jest teraz zamknięte, jest następująca:

# 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

Narzędzie comm wykonuje sortowanie alfanumeryczne, podczas gdy odpowiedzi „Przecięcie tablicy w bash” używają liczb; stąd użycie "sort" i "sort -g".

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.