Używam dużo grep awk sort w mojej powłoce uniksowej do pracy ze średnimi (około 10M-100M linii) plikami tekstowymi kolumn oddzielonymi tabulatorami. Pod tym względem powłoka unix jest moim arkuszem kalkulacyjnym.
Ale mam jeden ogromny problem, a mianowicie wybór rekordów na podstawie listy identyfikatorów.
Mając table.csv
plik w formacie id\tfoo\tbar...
i ids.csv
plik z listą identyfikatorów, wybieraj tylko rekordy table.csv
z obecnym identyfikatorem ids.csv
.
rodzaj /programming/13732295/extract-all-lines-from-text-file-based-on-a-given-list-of-ids ale z powłoką, nie perl.
grep -F
oczywiście produkuje fałszywie dodatnie, jeśli identyfikatory mają zmienną szerokość.
join
to narzędzie, którego nigdy nie zrozumiałem. Przede wszystkim wymaga sortowania alfabetycznego (moje pliki są zwykle sortowane numerycznie), ale nawet wtedy nie mogę uruchomić go bez narzekań na niewłaściwą kolejność i pomijanie niektórych rekordów. Więc mi się nie podoba. grep -f przeciwko plikowi z ^id\t
-s jest bardzo wolny, gdy liczba id jest duża.
awk
jest uciążliwy.
Czy są na to jakieś dobre rozwiązania? Jakieś specjalne narzędzia do plików rozdzielanych tabulatorami? Dodatkowa funkcjonalność będzie również mile widziana.
UPD: Poprawione sort
->join
grep -f
jest zbyt wolny, utrzymanie tej strategii wydaje się większym kłopotem niż jest to warte - odmiany prawdopodobnie padną ofiarą tych samych problemów z wydajnością O (N * M). Może lepiej poświęcić swój czas na naukę korzystania ze znormalizowanej bazy danych SQL ...awk
.sort
potrafi wykonywać wszelkiego rodzaju sortowania, numeryczne, alfabetyczne i inne. Zobaczyćman sort
.Odpowiedzi:
Chyba chodziło o
grep -f
niegrep -F
, ale rzeczywiście trzeba kombinacji obu i-w
:Powodem, dla którego otrzymywałeś fałszywe alarmy, jest (myślę, że nie wyjaśniłeś), ponieważ jeśli identyfikator może być zawarty w innym, oba zostaną wydrukowane.
-w
usuwa ten problem i-F
upewnia się, że wzorce są traktowane jak ciągi, a nie wyrażenia regularne. Odman grep
:Jeśli twoje fałszywe alarmy są spowodowane tym, że identyfikator może znajdować się w polu innym niż ID, zamiast tego zapętl plik:
lub szybciej:
Osobiście zrobiłbym to w
perl
:źródło
^
z -F, nie możesz celować konkretnie w pierwszą kolumnę.^id\t
Nieco z PO oznaczaid
może występować w innej kolumnie. Jeśli nie, to nie ma znaczenia.join
Narzędzie to jest to, co chcesz. Wymaga to sortowania plików wejściowych.Zakładając, że twoja powłoka to bash lub ksh:
Bez konieczności sortowania, typowym rozwiązaniem jest awk
źródło
join
to nie kludge: twoich słów nie można było rozgryźć. Otwórz umysł i ucz się. Jakie wyniki otrzymałeś i czym to się różni od oczekiwań?join
.awk
Rozwiązaniem jest tutaj bardzo szybki i skuteczny dla moich celów (jestem wyodrębnianie podzbiorów kilkuset z plików liniami ~ 100m)Odpowiedzi na to pytanie SO pomogły mi ominąć niggles z dołączeniem. Zasadniczo, gdy sortujesz plik w ramach przygotowań do wysłania go do przyłączenia, musisz upewnić się, że sortujesz na podstawie kolumny, do której dołączasz. Więc jeśli jest to pierwszy, musisz powiedzieć mu, jaki znak separatora znajduje się w pliku i że chcesz, aby sortował według pierwszego pola (i tylko pierwszego pola). W przeciwnym razie, jeśli pierwsze pole ma zmienne szerokości (na przykład), separatory i ewentualnie inne pola mogą zacząć wpływać na porządek sortowania.
Tak więc użyj opcji -t sortowania, aby określić znak rozdzielający, i użyj opcji -k, aby określić pole (pamiętając, że potrzebujesz pola początkowego i końcowego - nawet jeśli jest to to samo - lub będzie sortować od tego znaku do końca linii).
Tak więc w przypadku pliku oddzielonego tabulatorami, jak w tym pytaniu, powinny działać następujące elementy (dzięki odpowiedzi Glenn na strukturę):
join -t$'\t' <(sort -d ids.csv) <(sort -d -t$'\t' -k1,1 table.csv) > output.csv
(Dla odniesienia, flaga -d oznacza sortowanie słownikowe. Możesz także użyć flagi -b, aby zignorować wiodące białe znaki, patrz
man sort
iman join
).Jako bardziej ogólny przykład, załóżmy, że łączysz dwa pliki oddzielone przecinkami -
input1.csv
w trzeciej kolumnie iinput2.csv
czwartej. Możesz użyćjoin -t, -1 3 -2 4 <(sort -d -t, -k3,3 input2.csv) <(sort -d -t, -k4,4 input2.csv) > output.csv
W tym miejscu opcje
-1
i-2
określają, które pola dołączyć odpowiednio w pierwszym i drugim pliku wejściowym.źródło
Możesz także użyć ruby, aby zrobić coś podobnego:
źródło