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”.

Użytkownik1
źródło
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)
ghostdog74
źródło
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}
Jean-Christophe Meillaud
źródło
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.

DVK
źródło
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
Benubird
źródło
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)
frogstarr78
źródło
-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".

Chuck Newman
źródło