Sprawdź, czy wszystkie wiersze pliku występują w innym pliku

14

Mam dwa pliki: plik 1 z około 10 000 linii i plik 2 z kilkuset liniami. Chcę sprawdzić, czy wszystkie linie pliku2 występują w pliku1. To znaczy: ∀ linia ℓ ∈ plik2: ℓ ∈ plik1

Jeżeli ktokolwiek nie wie, co oznaczają te symbole lub co „sprawdź, czy wszystkie linie pliku 2 występują w pliku 1”: Kilka równoważnych linii w obu plikach nie wpływa na to, czy kontrola zwraca, czy pliki spełniają wymagania, czy nie.

Jak mam to zrobic?

UTF-8
źródło
2
Czy te pliki mogą mieć zduplikowane linie? Jeśli file2zawiera 2 linie A, czy musisz file1zawierać co najmniej 2 linie A?
Stéphane Chazelas,
2
@ StéphaneChazelas Wszystkie linie (w obu plikach) mają gwarancję unikalności.
UTF-8
1
@ UTF-8 To byłby ważny szczegół do edycji w pytaniu.
David Z
2
@DavidZ Już nie, ponieważ istniejące odpowiedzi nie opierają się na tej gwarancji. Więc teraz edytując pytanie, zmniejszyłbym pozorny zakres odpowiedzi.
UTF-8
@ UTF-8 Przypuszczam, że tak, choć bez niej pytanie jest trochę dwuznaczne, np. Jeśli dana linia występuje 5 razy w pliku 2, to czy linia ta musi również wystąpić 5 razy w pliku 1 (w przeciwieństwie do tylko raz)? Jeśli miałeś ten wymóg, nie wygląda na to, że którakolwiek z istniejących odpowiedzi działałaby, więc sugerowałbym przynajmniej edycję w czymś, co wyjaśnia, że ​​nie to masz na myśli.
David Z

Odpowiedzi:

18
comm -13 <(sort -u file_1) <(sort -u file_2)

To polecenie wyświetli wiersze unikatowe dla file_2. Tak więc, jeśli dane wyjściowe są puste, wówczas wszystkie file_2wiersze są zawarte w file_1.

Od komunikatora:

   With  no  options,  produce  three-column  output.  Column one contains
   lines unique to FILE1, column two contains lines unique to  FILE2,  and
   column three contains lines common to both files.

   -1     suppress column 1 (lines unique to FILE1)

   -2     suppress column 2 (lines unique to FILE2)

   -3     suppress column 3 (lines that appear in both files)
MiniMax
źródło
@don_crissti Prawda. Naprawiono: -uopcja dodana do sortpolecenia. Teraz w obu posortowanych plikach pozostały tylko unikalne linie.
MiniMax,
Niesamowicie proste rozwiązanie! Czy ta składnia ma zastosowanie do każdego programu, który oczekuje plików? Zawsze myślałem, że <skończymy na standardowe wyjście. Czy termin w nawiasach to zmienia?
UTF-8
2
@ UTF-8 Nazywa się to substytucją procesu . Możesz przeczytać o tym tutaj . I tak, zachowuje się jak plik tymczasowy, więc można go używać zamiast prawdziwych plików w dowolnych programach, które oczekują plików.
MiniMax,
Jeśli robisz to często, możesz przechowywać file_1w zalecanej formie. Oszczędza zarówno pisanie, jak i czas.
Stig Hemmer
7
@minimax Dobry komentarz oprócz „any”. Zastąpienie procesu, chociaż wspaniałe, nie może być stosowane we wszystkich przypadkach, ponieważ powstałe „pliki” są strumieniami, a nie prawdziwymi plikami. Oznacza to, że nie można ich „zobaczyć” jak zwykły plik i można go używać tylko wtedy, gdy program odczytuje plik normalnie od początku, a nie wtedy, gdy program korzysta z funkcji tylko do plików, takich jak wyszukiwanie do określonego punktu lub przewijanie, aby zacząć od nowa. Na szczęście większość programów po prostu odczytuje () swoje pliki, więc podstawianie procesów działa z większością programów, ale nie z „żadnymi” programami.
Law29
7
[ $(grep -cxFf file2 <(sort -u file1)) = $(sort -u file2 | wc -l) ] && 
  echo all there || 
  echo some missing

Jeśli liczba dopasowań z pliku2 w (unikatowych wierszach) pliku1 jest taka sama jak liczba unikalnych wierszy w pliku2, wówczas wszystkie są dostępne; w przeciwnym razie nie są.

Jeff Schaller
źródło
5

Używanie GNU awktam, gdzie obsługuje określone length(array)funkcje (i niektóre inne awkimplementacje, które mogą obsługiwać) i nie jest wymagane, jeśli pliki są sortowane.

gawk 'FNR==NR{seen[$0];next} ($0 in seen){delete seen[$0]};
    END{print (!length(seen))?"Matched":"Not Matched"}' file2 file1

Odczytuje plik2 do tablicy wywoływanej seenz kluczem jako całą linią pliku2 .

Następnie przeczytaj plik 1 i dla każdej linii, jeśli pasują do linii w tablicy, a następnie usuń ten klucz.

Na koniec, jeśli tablica była pusta, oznacza to, że wszystkie wiersze w pliku 2 istnieją w pliku 1 i zostaną wydrukowane Matched, w przeciwnym razie zostaną wyświetlone Not Matched.


Dla kompatybilności we wszystkich awkimplementacjach.

awk 'FNR==NR{seen[$0];next} ($0 in seen){delete seen[$0]};
    END{for(x in seen);print (!x)?"Matched":"Not Matched"}' file2 file1

Aby zignorować puste linie / linie z białymi spacjami tylko w pliku 2 , należy dodać NFwarunek w, NR==FNR && NF {...aby pominąć czytanie ich w tablicy.

αғsнιη
źródło
length(array)jest AFAIK tylko dla gawk; to zdecydowanie nie jest POSIX.
dave_thompson_085
@ dave_thompson_085 Prawidłowo zaktualizowałem swoją odpowiedź. dzięki
αғsнιη
3

Za pomocą commmożesz znaleźć linie, które są wspólne w obu plikach.

comm -12 file1 file2

Zobacz man commwięcej szczegółów

Hunter.S. Thompson
źródło
Poprawnie, zwraca wspólne linie w obu plikach, ale to nie daje odpowiedzi na Q OP, gdzie jeśli masz linię w pliku 2, która nie wychodzi z pliku 1, więc wszystkie linie pliku 2 nie istnieją w pliku 1.
αғsнιη
1
pliki powinny być posortowane. Od człowieka ” comm- porównaj dwa posortowane pliki linia po linii”.
MiniMax,
@MiniMax ma rację. To nie działa Inna wykorzystująca odpowiedź commzawiera rozwiązanie, które nie jest oczywiście niepoprawne. Po uruchomieniu polecenia pojawia się ostrzeżenie, że pliki nie są posortowane i wiele wierszy, które zdecydowanie znajdują się w obu plikach.
UTF-8
3
diff -q <(sort -u file2) <(grep -Fxf file2 file1 | sort -u)

będzie produkować nie ma wyjścia, jeśli file1zawiera wszystkie linie w file2i wyjść ze stanu 0, w przeciwnym razie będzie drukować coś takiego

Files /proc/self/fd/11 and /proc/self/fd/12 differ

i wyjdź ze statusem 1

don_crissti
źródło
2

Użyj programu Python:

#!/usr/bin/env python3
import sys

def open_arg(path):
    return sys.stdin if path == '-' else open(path)

def strip_linebreak(s):
    return s[:-1] if s.endswith('\n') else s

with open_arg(sys.argv[1]) as pattern_file:
    patterns = set(map(strip_linebreak, pattern_file))

with open_arg(sys.argv[2]) as dataset_file:
    for l in map(strip_linebreak, dataset_file):
        patterns.remove(l)
        if not patterns:
            break

sys.exit(int(bool(patterns)))

Stosowanie:

python3 contains-all.py file2 file1

Status wyjścia z programu wskazuje, czy wszystkie wzorce pliku 2 były dopasowane:

  • 0 (sukces) oznacza, że ​​wszystkie wzorce zostały dopasowane.
  • 1 (błąd) oznacza, że ​​niektóre wzorce nie zostały dopasowane.

Aby zapytać o status wyjścia w powłoce (skrypt) można albo użyć $?specjalnej zmiennej lub innych wyrażeń, które oceniają stan wyjścia polecenia, np operatorów krótkich obwodów &&i ||i wyrażeń warunkowych typu iflub while. Przykład:

if python3 compare-all.py file2 file1 && some-other --condition; then
    # do stuff
fi
David Foerster
źródło
1

combinez moreutils pokaże wszystkie linie file2, których nie ma w file1:

combine file2 not file1

Następnie możesz policzyć liczbę linii, przesyłając je do pipety wc -l, na przykład:

if [ $(combine file2 not file1 | wc -l) != 0 ]; then
  echo "lines missing"
else
  echo "You're fine"
fi
Karl Bielefeldt
źródło