Strumenti Linux per trattare i file come set ed eseguire operazioni sui set su di essi


82

Qualcuno conosce qualche strumento linux appositamente progettato per trattare i file come set ed eseguire operazioni sui set su di essi? Come la differenza, l'intersezione, ecc.?

Risposte:


110

Supponendo che gli elementi siano stringhe di caratteri diversi da NUL e newline (attenzione, tuttavia, che newline sia valida nei nomi dei file), è possibile rappresentare un set come file di testo con un elemento per riga e utilizzare alcune delle utility Unix standard.

Imposta iscrizione

$ 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 }'

Imposta intersezione

$ 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

Imposta uguaglianza

$ 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

Imposta cardinalità

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

$ awk 'END { print NR }' set

$ sed '$=' set

Test del sottoinsieme

$ 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

Imposta unione

$ 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

Imposta complemento

$ 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

Imposta la differenza simmetrica

$ 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

Power Set

Tutti i possibili sottoinsiemi di uno spazio visualizzato impostato sono separati, uno per riga:

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

(presuppone che gli elementi non contengano SPC, TAB (assumendo il valore predefinito di $IFS), barra rovesciata, caratteri jolly).

Imposta prodotto cartesiano

$ 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 set disgiunto

$ 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 set vuoto

$ 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

Minimo

$ 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

Massimo

$ 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

Tutti disponibili su http://www.catonmat.net/blog/set-operations-in-unix-shell-simplified/


1
Penso che la versione di Python sia molto più semplice e intuitiva. ;-)
Keith,

Penso che questa sia la risposta più completa. Sfortunatamente quali comandi eseguire o quali argomenti (comm -12, -23, -13) in ogni caso non sono sempre intuitivi come "intersezione" o "differenza". Forse creerò un involucro attorno a loro, dal momento che sto sempre usando queste cose.
nilton,

Ho eseguito [pol @ localhost inst] $ grep -xc e INSTALL-BINARY 0 [pol @ localhost inst] $ ma non capisco cosa significhi. La parola "e" dovrebbe apparire più volte nel file. Che cosa sto facendo di sbagliato?
Vérace,

1
Imposta intersezione: sort set1 set2 | uniq -dnon funziona per più insiemi. Prendi in considerazione l'utilizzo sort <(sort -u set1) <(sort -u set2) | uniq -d.
neo,

11

Una specie di. Devi occuparti dell'ordinamento da solo, ma commpuoi usarlo, trattando ogni linea come un membro impostato: -12per l'intersezione, -13per la differenza. (E -23ti dà la differenza capovolta, cioè al set2 - set1posto di set1 - set2.) Union è sort -uin questa configurazione.


1
In effetti, comm sembra fare la maggior parte delle cose. Sebbene gli argomenti non siano molto intuitivi. Grazie!
nilton,

7

Non conosco uno strumento specifico ma puoi usare Python, e la sua classe set e operatori, per scrivere un piccolo script per farlo.

Per esempio:

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

set(['awk',
     'basename',
     'chroot', ...

Sì, bella risposta. Perché usare awk se python è disponibile?
Guettli,

Hai dimenticato:Python> import os
James Bowery,

7

Il piccolo strumento per console "setop" è ora disponibile in Debian Stretch e in Ubuntu dal 16.10. Puoi ottenerlo tramite sudo apt install setop

Ecco alcuni esempi. I set su cui operare sono dati come file di input diversi: 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

Le query booleane vengono restituite solo EXIT_SUCCESSin caso di true e in caso EXIT_FAILUREcontrario un messaggio. In questo modo, setop può essere utilizzato nella shell. 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)?

È anche possibile descrivere in modo preciso come devono essere analizzati i flussi di input, in realtà mediante espressioni regolari:

  • setop input.txt --input-separator "[[:space:]-]"significa che uno spazio bianco (ovvero \v \t \n \r \funo spazio) o un segno meno viene interpretato come un separatore tra gli elementi (il valore predefinito è una nuova riga, ovvero ogni riga del file di input è un elemento)
  • setop input.txt --input-element "[A-Za-z]+" significa che gli elementi sono solo parole costituite da caratteri latini, tutti gli altri caratteri sono considerati separatori tra gli elementi

Inoltre, puoi farlo

  • --count tutti gli elementi del set di output,
  • --trim tutti gli elementi di input (cioè cancellare tutti i caratteri precedenti e successivi indesiderati come spazio, virgola ecc.),
  • considera gli elementi vuoti validi tramite --include-empty,
  • --ignore-case,
  • imposta --output-separatortra gli elementi del flusso di output (l'impostazione predefinita è \n),
  • e così via.

Vedere man setopo github.com/phisigma/setop per ulteriori informazioni.


3

Se vedi un file come un insieme di linee e i file sono ordinati, c'è comm.

Se vedi un file come un (multi) insieme di linee e le linee non sono ordinate, greppuò fare la differenza e l'intersezione (ottiene la differenza e l'intersezione impostate, ma non rispetta il conteggio per i multiset). L'unione è giusta cat.

grep -xF -f small large >intersection
grep -vxF -f small large >difference
cat small large >union

2

Ho creato un'utilità Python in grado di eseguire l'unione, l'intersezione, la differenza e il prodotto di più file. Si chiama SetOp, lo puoi trovare su PyPI ( qui ). La sintassi è simile alla seguente:

$ setop -i file1 file2 file3  # intersection
$ setop -d file1 file2 file3  # difference

1

Ho scritto un piccolo strumento per farlo, che mi è stato molto utile in vari luoghi. L'interfaccia utente non è lucidata e non sono sicuro delle caratteristiche prestazionali per file molto grandi (poiché legge l'intero elenco in memoria) ma "funziona per me". Il programma è su https://github.com/nibrahim/lines . È in Python. Puoi ottenerlo usando pip install lines.

Attualmente supporta unione, intersezione, differenza e differenza simmetrica di due file. Ogni riga del file di input viene trattata come un elemento di un set.

Ha anche due operazioni extra. Uno di spremere le righe vuote in un file e il secondo (che è stato molto utile per me) è quello di guardare attraverso il file e dividerlo in serie di stringhe simili. Ne avevo bisogno per cercare i file in un elenco che non corrispondevano al modello generale.

Gradirei feedback.


0

Il filesystem tratta i nomi di file (nomi di file interi, inclusi i percorsi) come unici.

Le operazioni?

È possibile copiare i file in a / eb / nella directory vuota c /, per ottenere un nuovo set di unione.

Con test di file come -e namee loop o find, è possibile verificare la presenza di file esistenti in due o più directory, per ottenere l'intersezione o la differenza.


1
Intendevo trattare i contenuti dei file come elementi di un set (diciamo, un elemento per riga) e i file stessi come set.
nilton,

0

La migliore risposta qui: Setdown (uno strumento dedicato)

Ho scritto un programma chiamato setdown che esegue le operazioni Set dal cli.

Può eseguire operazioni di set scrivendo una definizione simile a quella che si scriverebbe in un Makefile:

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

È piuttosto bello e dovresti dare un'occhiata. Personalmente non raccomando di usare comandi ad hoc che non sono stati creati per il lavoro per eseguire operazioni di set. Non funzionerà bene quando è davvero necessario eseguire molte operazioni di set o se ci sono operazioni di set che dipendono l'una dall'altra . Non solo, ma setdown ti consente di scrivere operazioni set che dipendono da altre operazioni set!

Ad ogni modo, penso che sia abbastanza bello e dovresti assolutamente provarlo.


0

Schema di esempio per più file (intersezione in questo caso):

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

Si espande in:

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

File di test:

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

Produzione:

0
6
12
18

0

Con le zshmatrici (le zshmatrici possono contenere qualsiasi sequenza arbitraria di byte, anche 0).

(nota anche che puoi fare typeset -U arrayper garantire che i suoi elementi siano unici).

impostare l'appartenenza

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

(utilizzando il Iflag di indice dell'array, per ottenere l'indice dell'ultima occorrenza $elementdell'array (o 0 se non trovato). Rimuovi e(per exact) per $elementessere preso come modello)

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

${array:#pattern}essendo una variazione su ksh ${var#pattern}che rimuove gli elementi che corrispondono al modello invece di rimuovere semplicemente la parte iniziale che corrisponde al modello. Il (M)(per abbinato ) inverte il significato e rimuove tutti gli elementi tranne quelli abbinati (usalo $~elementper essere preso come modello).

impostare l'intersezione

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

${set1:*set2}esegue l'intersezione di array, ma la "${(@)...}"sintassi è necessaria per conservare gli elementi vuoti.

impostare l'uguaglianza

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

Verifica se le matrici sono identiche (e nello stesso ordine). Il qflag di espansione dei parametri cita gli elementi (per evitare problemi con cose come a=(1 "2 3")vs b=("1 2" 3)) e (j: :)li unisce con lo spazio prima di fare un confronto tra stringhe.

Per verificare che abbiano gli stessi elementi, indipendentemente dall'ordine, usa la obandiera per ordinarli. Vedi anche il uflag (unico) per rimuovere i duplicati.

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

impostare cardinalità

n=$#array

test del sottoinsieme

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

unione

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

(vedi typeset -Usopra o il uflag di espansione del parametro per prendere il caso di duplicati). Ancora una volta se la stringa vuota non è uno dei possibili valori, è possibile semplificare per:

union=($array1 $array2)

complemento

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

per gli elementi di $array1quello non sono in $array2.

minimo / massimo (confronto lessicale)

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

minimo / massimo (confronto intero decimale)

min=${${(no)array}[1]} max=${${(no)array}[-1]}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.