Intersezione di due elenchi in Bash


163

Sto cercando di scrivere un semplice script che elencherà i contenuti trovati in due elenchi. Per semplificare, usiamo ls come esempio. Immagina "uno" e "due" sono directory.

one = `ls one`
due = `ls two`
intersezione $ uno $ due

Sono ancora abbastanza verde in bash, quindi sentiti libero di correggere come lo sto facendo. Ho solo bisogno di un comando che stamperà tutti i file in "uno" e "due". Devono esistere in entrambi. Si potrebbe chiamare questo "incrocio" tra "uno" e "due".


Nulla qui in realtà risponde alla domanda: come intersecare due variabili in uno script Bash.
jameshfisher,

Sembra una nuova domanda secondo me, a questa domanda viene chiaramente data una risposta qui.
Jean-Christophe Meillaud,

Un approccio forse più utile è nella quasi duplicato stackoverflow.com/questions/2312762/...
tripleee

Risposte:


285
comm -12  <(ls 1) <(ls 2)

37
Non posso credere di non averne conoscenza commfino ad oggi. Questo ha reso la mia intera settimana :)
Darragh Enright il

22
commrichiede che gli input siano ordinati. In questo caso, lsordina automaticamente il suo output, ma per altri usi potrebbe essere necessario farlo:comm -12 <(some-command | sort) <(some-other-command | sort)
Alexander Bird

11
NON USARE l 'output per nulla. ls è uno strumento per esaminare interattivamente i metadati della directory. Qualsiasi tentativo di analizzare l'output di ls con il codice viene interrotto. I globi sono molto più semplici E corretti: '' per file in * .txt ''. Leggi mywiki.wooledge.org/ParsingLs
Rany Albeg Wein

2
Ho appena usato questo nel tentativo di trovare gli usi di un publicmetodo error()fornito da un tratto, in combinazione con git grep, ed è stato fantastico! Ho corso $ comm -12 <(git grep -il "\$this->error(" -- "*.php") <(git grep -il "Dash_Api_Json_Response" -- "*.php")e per fortuna ho finito con il nome del file che conteneva solo il tratto.
localheinz

3
Questo è divertente. Stavo cercando di fare cose folli con Awk.
Rolf,

55

Soluzione con comm

commè fantastico ma in effetti è necessario lavorare con un elenco ordinato. E fortunatamente qui usiamo lsquale dalla lspagina man di Bash

Ordina le voci in ordine alfabetico se nessuna di -cftuSUX né --sort.

comm -12  <(ls one) <(ls two)

Alternativa con sort

Intersezione di due elenchi:

sort <(ls one) <(ls two) | uniq -d

differenza simmetrica di due elenchi:

sort <(ls one) <(ls two) | uniq -u

indennità

Giocaci ;)

cd $(mktemp -d) && mkdir {one,two} && touch {one,two}/file_{1,2}{0..9} && touch two/file_3{0..9}

2
Invece del complemento , penso che sia quella che di solito si chiama differenza simmetrica .
Andrew Lazarus,

29

Usa il commcomando:

ls one | sort > /tmp/one_list
ls two | sort > /tmp/two_list
comm -12 /tmp/one_list /tmp/two_list

"sort" non è davvero necessario ma lo includo sempre prima di usare "comm" per ogni evenienza.


5
È utile includerlo poiché deve essere ordinato e ha usato solo ls come esempio.
Thor84no,

3

Un'alternativa meno efficiente (rispetto alle comunicazioni):

cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -d

1
Se si utilizza di Debian / bin / dash o qualche altro shell Bash non negli script, è possibile uscita comandi chain con parentesi: (ls 1; ls 2) | sort -u | uniq -d.
azoto

1
@ MikaëlMayer Dovresti contrassegnare il nome della persona a cui stai rispondendo, altrimenti si presume che tu intenda me.
Benubird,

@nitrogen MikaëlMayer è corretto: il chainging sort -u | uniq -dnon fa nulla, perché l'ordinamento ha rimosso i duplicati prima che uniq iniziasse a cercarli. Penso che tu non abbia capito cosa sta facendo il mio comando.
Benubird,

@Benubird Non sono stato nemmeno in grado di ottenere il tuo comando cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -dper produrre qualcosa. Il mio comando dovrebbe leggere (ls 1; ls 2) | sort | uniq -d, senza il -u, per mostrare l'intersezione dell'elenco. @ MikaëlMayer aveva ragione sul fatto che il mio comando originale fosse rotto.
azoto

@nitrogen Il motivo per cui sto usando cat, è perché voglio che questa sia una soluzione generalizzabile, in modo che tu possa sostituirla lscon qualcos'altro, ad es find. La tua soluzione non lo consente, perché se uno dei comandi restituisce due righe uguali, lo prende come duplicato. Il mio funziona anche se l'utente vuole fare ls 1/*e confrontare tutti i file tra le sottodirectory. Altrimenti, sì, funziona anche. È possibile che il mio sia specifico per Bash.
Benubird,

2

Unire è un'altra buona opzione a seconda dell'input e dell'output desiderato

join -j1 -a1 <(ls 1) <(ls 2)

-1

C'è un'altra domanda StackOverflow "Intersezione di array in bash", che è contrassegnata come duplicata di questo. A mio avviso, non è esattamente lo stesso, poiché questa domanda parla del confronto tra due array bash, mentre questa domanda si concentra sui file bash. Una risposta di una riga all'altra domanda, che ora è chiusa, è la seguente:

# List1=( 0 1 2 3 4   6 7 8 9 10 11 12)
# List2=(   1 2 3   5 6   8 9    11 )
# List3=($(comm -12 <(echo ${List1[*]}| tr " " "\n"| sort) <(echo ${List2[*]} | tr " " "\n"| sort)| sort -g))
# echo ${List3[*]}
1 2 3 6 8 9 11

L'utilità comm esegue un ordinamento alfanumerico, mentre le risposte "Intersezione di array in bash" utilizzano numeri; da qui l'uso "sort" e "sort -g".

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.