Jak usunąć linie, które pojawiają się w pliku B z innego pliku A?

160

Mam duży plik A (składający się z e-maili), jeden wiersz na każdą wiadomość. Mam też inny plik B zawierający inny zestaw wiadomości.

Którego polecenia użyłbym, aby usunąć wszystkie adresy, które pojawiają się w pliku B z pliku A.

Tak więc, jeśli plik A zawierał:

A
B
C

a plik B zawierał:

B    
D
E

Następnie plik A należy pozostawić z:

A
C

Teraz wiem, że jest to pytanie, które mogło być zadawane częściej, ale znalazłem tylko jedno polecenie w Internecie, które dało mi błąd ze złym separatorem.

Każda pomoc będzie mile widziana! Ktoś na pewno wymyśli sprytny, jednoliniowy, ale ja nie jestem ekspertem od muszli.

slhck
źródło
1
Większość odpowiedzi dotyczy posortowanych plików, a najbardziej oczywistego brakuje, co oczywiście nie jest twoją winą, ale to sprawia, że ​​ten drugi jest bardziej ogólnie przydatny.
tripleee

Odpowiedzi:

204

Jeśli pliki są posortowane (są w twoim przykładzie):

comm -23 file1 file2

-23pomija wiersze, które są w obu plikach lub tylko w pliku 2. Jeśli pliki nie są posortowane, przepuść je sortnajpierw ...

Zobacz stronę man tutaj

Archetypowy Paweł
źródło
8
comm -23 file1 file2 > file3wyświetli zawartość w plik1, a nie w plik2, do pliku3. A potem mv file3 file1ostatecznie wyczyści zbędną zawartość w pliku1.
Spectral
2
Alternatywnie użyj comm -23 file1 file2 | sponge file1. Nie jest potrzebne czyszczenie.
Socowi
Link do strony man nie ładuje się dla mnie - alternatywa: linux.die.net/man/1/comm
Felix Rabe
@Socowi Co to jest gąbka? Nie mam tego w moim systemie. (macos 10.13)
Felix Rabe
@FelixRabe, cóż, to męczące. Zastąpione Twoim linkiem. Dzięki
The Archetypal Paul
85

grep -Fvxf <lines-to-remove> <all-lines>

  • działa na niesortowanych plikach
  • utrzymuje porządek
  • jest POSIX

Przykład:

cat <<EOF > A
b
1
a
0
01
b
1
EOF

cat <<EOF > B
0
1
EOF

grep -Fvxf B A

Wynik:

b
a
01
b

Wyjaśnienie:

  • -F: użyj ciągów literałów zamiast domyślnego BRE
  • -x: rozważ tylko mecze, które pasują do całej linii
  • -v: wypisz niezgodne
  • -f file: pobiera wzorce z podanego pliku

Ta metoda jest wolniejsza w przypadku wstępnie posortowanych plików niż inne metody, ponieważ jest bardziej ogólna. Jeśli liczy się również szybkość, zobacz: Szybki sposób znajdowania wierszy w jednym pliku, których nie ma w innym?

Oto szybka automatyzacja bash do pracy w linii:

remove-lines() (
  remove_lines="$1"
  all_lines="$2"
  tmp_file="$(mktemp)"
  grep -Fvxf "$remove_lines" "$all_lines" > "$tmp_file"
  mv "$tmp_file" "$all_lines"
)

GitHub upstream .

stosowanie:

remove-lines lines-to-remove remove-from-this-file

Zobacz też: /unix/28158/is-there-a-tool-to-get-the-lines-in-one-file-that-are-not-in-another

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
55

awk na ratunek!

To rozwiązanie nie wymaga posortowanych danych wejściowych. Najpierw musisz podać plikB.

awk 'NR==FNR{a[$0];next} !($0 in a)' fileB fileA

zwroty

A
C

Jak to działa?

NR==FNR{a[$0];next} idiom służy do przechowywania pierwszego pliku w tablicy asocjacyjnej jako kluczy do późniejszego testu „zawiera”.

NR==FNR sprawdza, czy skanujemy pierwszy plik, gdzie globalny licznik wierszy (NR) jest równy bieżącemu licznikowi wierszy pliku (FNR).

a[$0] dodaje bieżącą linię do tablicy asocjacyjnej jako klucz, zauważ, że zachowuje się jak zestaw, w którym nie będzie żadnych zduplikowanych wartości (kluczy)

!($0 in a)jesteśmy teraz w następnym pliku (ach), into test zawiera, tutaj sprawdza, czy bieżąca linia jest w zestawie, który zapełniliśmy w pierwszym kroku z pierwszego pliku,! neguje warunek. Brakuje tutaj akcji, która domyślnie jest {print}i zwykle nie jest napisana wprost.

Zauważ, że teraz można tego użyć do usunięcia słów z czarnej listy.

$ awk '...' badwords allwords > goodwords

z niewielką zmianą może wyczyścić wiele list i utworzyć wyczyszczone wersje.

$ awk 'NR==FNR{a[$0];next} !($0 in a){print > FILENAME".clean"}' bad file1 file2 file3 ...
karakfa
źródło
pełne oceny na tym. Aby użyć tego w wierszu poleceń w GnuWin32 w Windows, zamień pojedyncze fragmenty na podwójne cudzysłowy. działa wspaniale. Wielkie dzięki.
dwie osoby
To działa, ale jak będę mógł przekierować dane wyjściowe do pliku A w postaci A (z nową linią) B
Anand Builders
Chyba masz na myśli A\nC, zapisać do pliku tymczasowego pierwszy i zastąpić oryginalny plik... > tmp && mv tmp fileA
karakfa
Pełne oceny z tego też ode mnie. Ten awk zajmuje całą 1 sekundę, aby przetworzyć plik zawierający 104 000 wpisów: +1:
MitchellK
Używając tego w skryptach, upewnij się najpierw, że fileBnie jest puste (długość 0 bajtów), ponieważ jeśli tak, otrzymasz pusty wynik zamiast oczekiwanej zawartości fileA. (Przyczyna: FNR==NRbędzie obowiązywać fileAwtedy.)
Peter Nowee
18

Inny sposób na zrobienie tego samego (wymaga również posortowanych danych wejściowych):

join -v 1 fileA fileB

W Bash, jeśli pliki nie są wstępnie posortowane:

join -v 1 <(sort fileA) <(sort fileB)
Wstrzymano do odwołania.
źródło
7

Możesz to zrobić, chyba że Twoje pliki są posortowane

diff file-a file-b --new-line-format="" --old-line-format="%L" --unchanged-line-format="" > file-a

--new-line-formatdotyczy wierszy znajdujących się w pliku b, ale nie a, --old-..dotyczy wierszy znajdujących się w pliku a, ale nie znajduje się w b, --unchanged-..dotyczy wierszy znajdujących się w obu. %Lsprawia, że ​​wiersz jest drukowany dokładnie.

man diff

po więcej szczegółów

aec
źródło
1
Mówisz, że to zadziała, jeśli pliki nie zostaną posortowane. Jakie problemy pojawiają się, jeśli zostaną posortowane? A jeśli są częściowo posortowane?
Carlos Macasaet
1
To była odpowiedź na powyższe rozwiązanie, które sugerowało użycie commpolecenia. commwymaga sortowania plików, więc jeśli są posortowane, możesz również użyć tego rozwiązania. Możesz użyć tego rozwiązania niezależnie od tego, czy plik jest posortowany, czy nie
aec
7

To udoskonalenie ładnej odpowiedzi @ karakfa może być zauważalnie szybsze w przypadku bardzo dużych plików. Podobnie jak w przypadku tej odpowiedzi, żaden plik nie musi być sortowany, ale szybkość jest zapewniona dzięki tablicom asocjacyjnym awk. W pamięci przechowywany jest tylko plik wyszukiwania.

Sformułowanie to dopuszcza również możliwość wykorzystania tylko jednego określonego pola ($ N) w pliku wejściowym w porównaniu.

# Print lines in the input unless the value in column $N
# appears in a lookup file, $LOOKUP;
# if $N is 0, then the entire line is used for comparison.

awk -v N=$N -v lookup="$LOOKUP" '
  BEGIN { while ( getline < lookup ) { dictionary[$0]=$0 } }
  !($N in dictionary) {print}'

(Kolejną zaletą tego podejścia jest to, że można łatwo zmodyfikować kryterium porównania, np. W celu przycięcia wiodących i końcowych białych znaków).

szczyt
źródło
Jest to trudniejsze w użyciu w scenariuszu z wieloma platformami z narożną skrzynką niż druga wkładka. Jednak czapki z głów za wysiłek występu
dwa razy
2

Możesz użyć Pythona:

python -c '
lines_to_remove = set()
with open("file B", "r") as f:
    for line in f.readlines():
        lines_to_remove.add(line.strip())

with open("file A", "r") as f:
    for line in [line.strip() for line in f.readlines()]:
        if line not in lines_to_remove:
            print(line)
'
Cześć Żegnaj
źródło
2

Możesz użyć - diff fileA fileB | grep "^>" | cut -c3- > fileA

Będzie to działać w przypadku plików, które nie są również posortowane.

Darpan
źródło
-1

Aby usunąć wspólne linie między dwoma plikami, możesz użyć polecenia grep, comm lub join.

grep działa tylko dla małych plików. Użyj -v razem z -f.

grep -vf file2 file1 

Wyświetla wiersze z plik1, które nie pasują do żadnej linii w pliku2.

comm to polecenie narzędzia, które działa na plikach posortowanych leksykalnie. Pobiera dwa pliki jako dane wejściowe i tworzy trzy kolumny tekstowe jako dane wyjściowe: wiersze tylko w pierwszym pliku; linie tylko w drugim pliku; i wiersze w obu plikach. Możesz wyłączyć drukowanie dowolnej kolumny, używając odpowiednio opcji -1, -2 lub -3.

comm -1 -3 file2 file1

Wyświetla wiersze z plik1, które nie pasują do żadnej linii w pliku2.

Wreszcie istnieje join, polecenie narzędzia, które wykonuje łączenie równości na określonych plikach. Jego opcja -v pozwala również na usunięcie wspólnych linii między dwoma plikami.

join -v1 -v2 file1 file2
Aakarsh Gupta
źródło
Wszystko to zostało już podane w innych odpowiedziach. Twój grep potrzebuje -F, albo uzyskasz dziwne wyniki, gdy linie będą wyglądać jak wyrażenia regularne
The Archetypal Paul