Narzędzia systemu Linux do traktowania plików jako zbiorów i wykonywania na nich operacji na zestawach

81

Czy ktoś zna jakieś narzędzie linuksowe zaprojektowane specjalnie do traktowania plików jako zestawów i wykonywania na nich operacji na zestawach? Jak różnica, skrzyżowanie itp.?

Nilton
źródło

Odpowiedzi:

110

Zakładając, że elementy to ciągi znaków inne niż NUL i nowa linia (uważaj jednak, że nowa nazwa jest poprawna w nazwach plików), możesz reprezentować zestaw jako plik tekstowy z jednym elementem w linii i użyć niektórych standardowych narzędzi uniksowych.

Ustaw członkostwo

$ grep -Fxc 'element' set   # outputs 1 if element is in set
                            # outputs >1 if set is a multi-set
                            # outputs 0 if element is not in set

$ grep -Fxq 'element' set   # returns 0 (true)  if element is in set
                            # returns 1 (false) if element is not in set

$ awk '$0 == "element" { s=1; exit }; END { exit !s }' set
# returns 0 if element is in set, 1 otherwise.

$ awk -v e='element' '$0 == e { s=1; exit } END { exit !s }'

Ustaw skrzyżowanie

$ comm -12 <(sort set1) <(sort set2)  # outputs intersect of set1 and set2

$ grep -xF -f set1 set2

$ sort set1 set2 | uniq -d

$ join -t <(sort A) <(sort B)

$ awk '!done { a[$0]; next }; $0 in a' set1 done=1 set2

Ustaw równość

$ cmp -s <(sort set1) <(sort set2) # returns 0 if set1 is equal to set2
                                   # returns 1 if set1 != set2

$ cmp -s <(sort -u set1) <(sort -u set2)
# collapses multi-sets into sets and does the same as previous

$ awk '{ if (!($0 in a)) c++; a[$0] }; END{ exit !(c==NR/2) }' set1 set2
# returns 0 if set1 == set2
# returns 1 if set1 != set2

$ awk '{ a[$0] }; END{ exit !(length(a)==NR/2) }' set1 set2
# same as previous, requires >= gnu awk 3.1.5

Ustaw kardynalność

$ wc -l < set     # outputs number of elements in set

$ awk 'END { print NR }' set

$ sed '$=' set

Test podzbioru

$ comm -23 <(sort -u subset) <(sort -u set) | grep -q '^'
# returns true iff subset is not a subset of set (has elements not in set)

$ awk '!done { a[$0]; next }; { if !($0 in a) exit 1 }' set done=1 subset
# returns 0 if subset is a subset of set
# returns 1 if subset is not a subset of set

Ustaw związek

$ cat set1 set2     # outputs union of set1 and set2
                    # assumes they are disjoint

$ awk 1 set1 set2   # ditto

$ cat set1 set2 ... setn   # union over n sets

$ sort -u set1 set2  # same, but doesn't assume they are disjoint

$ sort set1 set2 | uniq

$ awk '!a[$0]++' set1 set2       # ditto without sorting

Ustaw dopełnienie

$ comm -23 <(sort set1) <(sort set2)
# outputs elements in set1 that are not in set2

$ grep -vxF -f set2 set1           # ditto

$ sort set2 set2 set1 | uniq -u    # ditto

$ awk '!done { a[$0]; next }; !($0 in a)' set2 done=1 set1

Ustaw różnicę symetryczną

$ comm -3 <(sort set1) <(sort set2) | tr -d '\t'  # assumes not tab in sets
# outputs elements that are in set1 or in set2 but not both

$ sort set1 set2 | uniq -u

$ cat <(grep -vxF -f set1 set2) <(grep -vxF -f set2 set1)

$ grep -vxF -f set1 set2; grep -vxF -f set2 set1

$ awk '!done { a[$0]; next }; $0 in a { delete a[$0]; next }; 1;
       END { for (b in a) print b }' set1 done=1 set2

Zestaw zasilający

Wszystkie możliwe podzbiory zestawu wyświetlanej przestrzeni oddzielone, po jednym w wierszu:

$ p() { [ "$#" -eq 0 ] && echo || (shift; p "$@") |
        while read r; do printf '%s %s\n%s\n' "$1" "$r" "$r"; done; }
$ p $(cat set)

(zakłada, że ​​elementy nie zawierają SPC, TAB (przy założeniu wartości domyślnej $IFS), odwrotnego ukośnika, symboli wieloznacznych).

Ustaw produkt kartezjański

$ while IFS= read -r a; do while IFS= read -r b; do echo "$a, $b"; done < set1; done < set2

$ awk '!done { a[$0]; next }; { for (i in a) print i, $0 }' set1 done=1 set2

Test rozłącznego zestawu

$ comm -12 <(sort set1) <(sort set2)  # does not output anything if disjoint

$ awk '++seen[$0] == 2 { exit 1 }' set1 set2 # returns 0 if disjoint
                                             # returns 1 if not

Test pustego zestawu

$ wc -l < set            # outputs 0  if the set is empty
                         # outputs >0 if the set is not empty

$ grep -q '^' set        # returns true (0 exit status) unless set is empty

$ awk '{ exit 1 }' set   # returns true (0 exit status) if set is empty

Minimum

$ sort set | head -n 1   # outputs the minimum (lexically) element in the set

$ awk 'NR == 1 { min = $0 }; $0 < min { min = $0 }; END { print min }'
# ditto, but does numeric comparison when elements are numerical

Maksymalny

$ sort test | tail -n 1    # outputs the maximum element in the set

$ sort -r test | head -n 1

$ awk '$0 > max { max = $0 }; END { print max }'
# ditto, but does numeric comparison when elements are numerical

Wszystkie dostępne na stronie http://www.catonmat.net/blog/set-operations-in-unix-shell-simplified/

llhuii
źródło
1
Myślę, że wersja Python jest znacznie prostsza i bardziej intuicyjna. ;-)
Keith
Myślę, że jest to najbardziej kompletna odpowiedź. Niestety, które polecenia uruchomić lub które argumenty (comm -12, -23, -13) w każdym przypadku nie zawsze są intuicyjne jako „skrzyżowanie” lub „różnica”. Może stworzę wokół nich opakowanie, ponieważ zawsze używam tych rzeczy.
nilton
Uruchomiłem [pol @ localhost inst] $ grep -xc i INSTALL-BINARY 0 [pol @ localhost inst] $, ale nie rozumiem co to znaczy. Słowo „i” powinno występować wiele razy w pliku. Co ja robię źle?
Vérace,
1
Przecięcie zestawu: sort set1 set2 | uniq -dnie działa w przypadku wielu zestawów. Rozważ użycie sort <(sort -u set1) <(sort -u set2) | uniq -d.
neo
11

Raczej. Musisz poradzić sobie z sortowaniem, ale commmożesz tego użyć, traktując każdą linię jako zestaw elementów: -12na skrzyżowaniu, -13dla różnicy. (I -23daje ci odwróconą różnicę, to znaczy set2 - set1zamiast set1 - set2.) Union jest sort -uw tej konfiguracji.

geekozaur
źródło
1
Rzeczywiście, komunikator wydaje się robić większość rzeczy. Chociaż argumenty są bardzo nieintuicyjne. Dzięki!
nilton
7

Nie znam konkretnego narzędzia, ale możesz użyć Pythona, jego zestawu klas i operatorów, aby napisać mały skrypt, aby to zrobić.

Na przykład:

Python> s1 = set(os.listdir("/bin"))
Python> s2 = set(os.listdir("/usr/bin"))
Python> s1 & s2

set(['awk',
     'basename',
     'chroot', ...
Keith
źródło
Tak, fajna odpowiedź. Po co używać awk, jeśli python jest dostępny?
guettli,
Zapomniałeś:Python> import os
James Bowery
7

Małe narzędzie konsolowe „setop” jest teraz dostępne w Debian Stretch i Ubuntu od 16.10. Możesz to uzyskać za pośrednictwem sudo apt install setop

Oto kilka przykładów. Zestawy do obsługi są podane jako różne pliki wejściowe: setop input # is equal to "sort input --unique" setop file1 file2 --union # option --union is default and can be omitted setop file1 file2 file3 --intersection # more than two inputs are allowed setop file1 - --symmetric-difference # ndash stands for standard input setop file1 -d file2 # all elements contained in 1 but not 2

Zapytania boolowskie zwracane są tylko EXIT_SUCCESSw przypadku wartości true, a EXIT_FAILUREtakże w innym przypadku. W ten sposób setop może być używany w powłoce. setop inputfile --contains "value" # is element value contained in input? setop A.txt B.txt --equal C.txt # union of A and B equal to C? setop bigfile --subset smallfile # analogous --superset setop -i file1 file2 --is-empty # intersection of 1 and 2 empty (disjoint)?

Możliwe jest również precyzyjne opisanie sposobu analizowania strumieni wejściowych, w rzeczywistości za pomocą wyrażeń regularnych:

  • setop input.txt --input-separator "[[:space:]-]"oznacza, że ​​spacja (tj. \v \t \n \r \flub spacja) lub znak minus jest interpretowany jako separator między elementami (domyślnie jest to nowa linia, tzn. każda linia pliku wejściowego to jeden element)
  • setop input.txt --input-element "[A-Za-z]+" oznacza, że ​​elementy to tylko słowa składające się ze znaków łacińskich, wszystkie pozostałe znaki są uważane za separatory między elementami

Ponadto możesz

  • --count wszystkie elementy zestawu wyjściowego,
  • --trim wszystkie elementy wejściowe (tj. usuwają wszystkie niechciane poprzedzające i następujące po sobie znaki, takie jak spacja, przecinek itp.),
  • uważaj puste elementy za ważne przez --include-empty,
  • --ignore-case,
  • ustaw --output-separatormiędzy elementami strumienia wyjściowego (domyślnie jest \n),
  • i tak dalej.

Aby uzyskać więcej informacji, zobacz man setoplub github.com/phisigma/setop .

Szczery
źródło
3

Jeśli widzisz plik jako zestaw linii, a pliki są posortowane, oznacza to, że istnieje comm.

Jeśli widzisz plik jako (wielokrotny) zestaw linii, a linie nie są posortowane, grepmoże robić różnicę i przecięcie (osiąga ustawioną różnicę i przecięcie, ale nie uwzględnia liczenia dla wielu zestawów). Unia jest sprawiedliwa cat.

grep -xF -f small large >intersection
grep -vxF -f small large >difference
cat small large >union
Gilles
źródło
2

Stworzyłem narzędzie Python, które może tworzyć liniowe połączenie, przecięcie, różnicę i iloczyn wielu plików. Nazywa się SetOp, można go znaleźć na PyPI ( tutaj ). Składnia wygląda następująco:

$ setop -i file1 file2 file3  # intersection
$ setop -d file1 file2 file3  # difference
Tygrys
źródło
1

Napisałem małe narzędzie do tego, które było mi bardzo przydatne w różnych miejscach. Interfejs użytkownika nie jest dopracowany i nie jestem pewien co do wydajności bardzo dużych plików (ponieważ wczytuje całą listę do pamięci), ale „działa dla mnie”. Program znajduje się na https://github.com/nibrahim/lines . Jest w Pythonie. Możesz to uzyskać za pomocą pip install lines.

Obecnie obsługuje łączenie, przecięcie, różnicę i różnicę symetryczną dwóch plików. Każdy wiersz pliku wejściowego jest traktowany jako element zestawu.

Ma również dwie dodatkowe operacje. Jednym z wyciskania pustych linii w pliku, a drugim (co było dla mnie bardzo przydatne) jest przeglądanie pliku i dzielenie go na zestawy podobnych ciągów. Potrzebowałem tego, aby wyszukać pliki na liście, które nie pasują do ogólnego wzorca.

Chciałbym poznać opinie.

Noufal Ibrahim
źródło
0

System plików traktuje nazwy plików (całe nazwy plików, w tym ścieżki) jako unikalne.

Operacje

Możesz skopiować pliki z / ib / do pustego katalogu c /, aby uzyskać nowy zestaw unii.

Dzięki testom plików, takim jak -e namei pętle lub find, możesz sprawdzić pliki istniejące w dwóch lub więcej katalogach, aby uzyskać skrzyżowanie lub różnicę.

nieznany użytkownik
źródło
1
Miałem na myśli traktowanie zawartości plików jako elementów zestawu (powiedzmy, jeden element na linię), a same pliki jako zestawy.
nilton
0

Najlepsza odpowiedź tutaj: Setdown (dedykowane narzędzie)

Napisałem program o nazwie setdown, który wykonuje operacje Set z cli.

Może wykonywać operacje ustawiania, pisząc definicję podobną do tego, co napisałeś w Makefile:

someUnion: "file-1.txt" \/ "file-2.txt"
someIntersection: "file-1.txt" /\ "file-2.txt"
someDifference: someUnion - someIntersection

Jest całkiem fajny i powinieneś to sprawdzić. Ja osobiście nie polecam używania komend ad-hoc, które nie zostały zbudowane dla zadania do wykonywania ustawionych operacji. Nie zadziała to dobrze, gdy naprawdę musisz wykonać wiele operacji ustawiania lub jeśli masz jakieś operacje ustawiania, które są od siebie zależne . Poza tym setdown pozwala pisać operacje na zestawach, które zależą od innych operacji na zestawach!

W każdym razie uważam, że to całkiem fajne i powinieneś to całkowicie sprawdzić.

Robert Massaioli
źródło
0

Przykładowy wzór dla wielu plików (w tym przypadku przecięcie):

eval `perl -le 'print "cat ",join(" | grep -xF -f- ", @ARGV)' t*`

Rozwija się do:

cat t1 | grep -xF -f- t2 | grep -xF -f- t3

Pliki testowe:

seq 0 20 | tee t1; seq 0 2 20 | tee t2; seq 0 3 20 | tee t3

Wynik:

0
6
12
18
bsb
źródło
0

Z zshtablicami ( zshtablice mogą zawierać dowolną sekwencję bajtów, nawet 0).

(pamiętaj też, że możesz zrobić, typeset -U arrayaby zagwarantować, że jego elementy są unikalne).

ustawić członkostwo

if ((${array[(Ie)$element]})); then
  echo '$element is in $array'
fi

(za pomocą Iflagi indeksu tablicy, aby uzyskać indeks ostatniego wystąpienia $elementw tablicy (lub 0, jeśli nie znaleziono). Usuń e(dla exact), $elementaby zostać wziętym jako wzorzec)

if ((n = ${(M)#array:#$element})); then
  echo "\$element is found $n times in \$array'
fi

${array:#pattern}jest odmianą ksh, ${var#pattern}która usuwa elementy pasujące do wzorca, a nie tylko usuwa wiodącą część pasującą do wzorca. Opcja (M)(dla dopasowanego ) odwraca znaczenie i usuwa wszystkie elementy oprócz dopasowanych (użyj $~element, aby wziąć to jako wzorzec).

ustaw przecięcie

common=("${(@)set1:*set2}")

${set1:*set2}robi przecięcie tablicy, ale "${(@)...}"składnia jest potrzebna do zachowania pustych elementów.

ustawić równość

[[ ${(j: :)${(q)array1}} = ${(j: :)${(q)array2}} ]]

Sprawdza, czy tablice są identyczne (i w tej samej kolejności). qFlag ekspansja parametr cytuje elementów (w celu uniknięcia problemów z rzeczy, takich jak a=(1 "2 3")vs b=("1 2" 3)) i (j: :)łączy je z przestrzeni przed wykonaniem porównania ciągów.

Aby sprawdzić, czy mają te same elementy, niezależnie od kolejności, użyj oflagi, aby je zamówić. Zobacz także uflagę (unikalną), aby usunąć duplikaty.

[[ ${(j: :)${(qo)array1}} = ${(j: :)${(qo)array2}} ]]

ustawić liczność

n=$#array

test podzestawu

if ((${#array1:*array2} == ${#array2})); then
  echo '$array2 is included in $array1'
fi

unia

union=("$array1[@]" "$array2[@]")

(patrz typeset -Uwyżej lub uflaga rozwinięcia parametru, aby uwzględnić przypadki duplikatów). Ponownie, jeśli pusty ciąg nie jest jedną z możliwych wartości, możesz uprościć:

union=($array1 $array2)

komplement

complement=("${(@)array1:|array2}")

dla elementów $array1, których nie ma w $array2.

minimum / maksimum (porównanie leksykalne)

min=${${(o)array}[1]} max=${${(o)array}[-1]}

minimum / maksimum (porównanie liczb całkowitych dziesiętnych)

min=${${(no)array}[1]} max=${${(no)array}[-1]}
Stéphane Chazelas
źródło