bash, Linux:Imposta la differenza tra due file di testo
-
22-09-2019 - |
Domanda
Ho due file A
-nodes_to_delete
E B
-nodes_to_keep
.Ogni file ha molte righe con ID numerici.
Voglio avere l'elenco degli ID numerici presenti in nodes_to_delete
ma NON dentro nodes_to_keep
, per esempio.
.
Farlo all'interno di un database PostgreSQL è irragionevolmente lento.Qualche modo accurato per farlo in bash usando gli strumenti CLI di Linux?
AGGIORNAMENTO: Sembrerebbe un lavoro Pythonic, ma i file sono davvero, davvero grandi.Ho risolto alcuni problemi simili utilizzando uniq
, sort
e alcune tecniche di teoria degli insiemi.Questo era circa due o tre ordini di grandezza più veloce degli equivalenti del database.
Altri suggerimenti
Qualcuno mi ha mostrato come fare esattamente questo sh un paio di mesi fa, e poi non sono riuscito a trovare per un po '... e guardando mi sono imbattuto su sua domanda. Eccolo:
set_union () {
sort $1 $2 | uniq
}
set_difference () {
sort $1 $2 $2 | uniq -u
}
set_symmetric_difference() {
sort $1 $2 | uniq -u
}
Usa comm
- si confronterà due ordinato file riga per riga
La breve risposta alla tua domanda
Questo comando restituirà linee uniche per deleteNodes, ma non le linee in keepNodes.
comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
Esempio di configurazione
Creiamo i file denominati keepNodes
e deleteNodes
, e li usano come input per il comando indifferenziato comm
.
$ cat > keepNodes <(echo bob; echo amber;)
$ cat > deleteNodes <(echo bob; echo ann;)
Per impostazione predefinita, in esecuzione comm senza argomenti stamperà 3 colonne con questo layout:
lines_unique_to_FILE1
lines_unique_to_FILE2
lines_which_appear_in_both
Usando il nostro esempio i file di cui sopra, eseguire comm senza argomenti. Notare le tre colonne.
$ comm <(sort keepNodes) <(sort deleteNodes)
amber
ann
bob
Soppressione di uscita colonna
Elimina colonna 1, 2 o 3 con -N; notare che quando una colonna è nascosta, gli spazi si restringe fino.
$ comm -1 <(sort keepNodes) <(sort deleteNodes)
ann
bob
$ comm -2 <(sort keepNodes) <(sort deleteNodes)
amber
bob
$ comm -3 <(sort keepNodes) <(sort deleteNodes)
amber
ann
$ comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
ann
$ comm -2 -3 <(sort keepNodes) <(sort deleteNodes)
amber
$ comm -1 -2 <(sort keepNodes) <(sort deleteNodes)
bob
L'ordinamento è importante!
Se si esegue comm senza prima l'ordinamento del file, non riesce con grazia con un messaggio circa cui file non è ordinato.
comm: file 1 is not in sorted order
comm
è stato specificamente progettato per questo tipo di caso d'uso, ma richiede input ordinato.
awk
è senza dubbio uno strumento migliore per questo in quanto è abbastanza semplice da trovare impostare la differenza, non richiede sort
, e offre una maggiore flessibilità.
awk 'NR == FNR { a[$0]; next } !($0 in a)' nodes_to_keep nodes_to_delete
Forse, per esempio, si desidera trovare solo la differenza nelle linee che rappresentano i numeri non negativi:
awk -v r='^[0-9]+$' 'NR == FNR && $0 ~ r {
a[$0]
next
} $0 ~ r && !($0 in a)' nodes_to_keep nodes_to_delete
Forse hai bisogno di un modo migliore per farlo in Postgres, posso praticamente scommettere che non troverai un modo più veloce per farlo usando file flat.Dovresti essere in grado di eseguire un semplice inner join e supponendo che entrambi gli ID siano indicizzati, dovrebbe essere molto veloce.
Quindi, questo è un po 'diversa dalle altre risposte. Non posso dire che un compilatore C ++ è esattamente uno "strumento Linux CLI", ma in esecuzione g++ -O3 -march=native -o set_diff main.cpp
(con il codice qui sotto in main.cpp
può fare il trucco):
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
Per usare, basta eseguire set_diff B A
( non A B
, dal momento che è B
nodes_to_keep
) e la differenza risultante verrà stampato sullo standard output.
Si noti che ho rinunciato migliori pratiche pochi C ++ per mantenere il codice più semplice.
Molte ottimizzazioni di velocità aggiuntivi potrebbero essere fatti (al prezzo di più memoria). mmap
sarebbe anche particolarmente utile per grandi insiemi di dati, ma che sarebbe rendere il codice molto più coinvolto.
Dato che lei ha detto che i set di dati sono grandi, ho pensato che la lettura nodes_to_delete
una linea alla volta potrebbe essere una buona idea per ridurre il consumo di memoria. L'approccio adottato nel codice di cui sopra non è particolarmente efficiente se ci sono un sacco di doppioni nella vostra nodes_to_delete
. Inoltre, l'ordine non viene mantenuto.
Qualcosa di più facile da copiare e incollare nel bash
(vale a dire saltare creazione di main.cpp
):
g++ -O3 -march=native -xc++ -o set_diff - <<EOF
#include<algorithm>
#include<iostream>
#include<iterator>
#include<fstream>
#include<string>
#include<unordered_set>
using namespace std;
int main(int argc, char** argv) {
ifstream keep_file(argv[1]), del_file(argv[2]);
unordered_multiset<string> init_lines{istream_iterator<string>(keep_file), istream_iterator<string>()};
string line;
while (getline(del_file, line)) {
init_lines.erase(line);
}
copy(init_lines.begin(),init_lines.end(), ostream_iterator<string>(cout, "\n"));
}
EOF