Bash, Linux: Defina a diferença entre dois arquivos de texto
-
22-09-2019 - |
Pergunta
Eu tenho dois arquivos A
-nodes_to_delete
e B
-nodes_to_keep
. Cada arquivo possui muitas linhas com IDs numéricos.
Eu quero ter a lista de IDs numéricos que estão em nodes_to_delete
mas não em nodes_to_keep
, por exemplo
.
Fazer isso dentro de um banco de dados PostgreSQL é irracionalmente lento. Alguma maneira interessante de fazer isso no Bash usando as ferramentas Linux CLI?
ATUALIZAR: Isso parece ser um trabalho pitônico, mas os arquivos são muito, muito grandes. Eu resolvi alguns problemas semelhantes usando uniq
, sort
e algumas técnicas de teoria de conjuntos. Isso foi cerca de duas ou três ordens de magnitude mais rápida que os equivalentes de banco de dados.
Solução
o Comm O comando faz isso.
Outras dicas
Alguém me mostrou como fazer exatamente isso em Sh alguns meses atrás, e então eu não consegui encontrá -lo por um tempo ... e, enquanto olhava, tropecei em sua pergunta. Aqui está :
set_union () {
sort $1 $2 | uniq
}
set_difference () {
sort $1 $2 $2 | uniq -u
}
set_symmetric_difference() {
sort $1 $2 | uniq -u
}
Usar comm
- Ele comparará dois arquivos classificados linha por linha.
A resposta curta para sua pergunta
Este comando retornará linhas exclusivas dos deletenodos, mas não linhas em KeepNodes.
comm -1 -3 <(sort keepNodes) <(sort deleteNodes)
Exemplo de configuração
Vamos criar os arquivos nomeados keepNodes
e deleteNodes
, e use -os como entrada não classificada para o comm
comando.
$ cat > keepNodes <(echo bob; echo amber;)
$ cat > deleteNodes <(echo bob; echo ann;)
Por padrão, executar o Comm sem argumentos imprime 3 colunas com este layout:
lines_unique_to_FILE1
lines_unique_to_FILE2
lines_which_appear_in_both
Usando nossos arquivos de exemplo acima, execute o comando sem argumentos. Observe as três colunas.
$ comm <(sort keepNodes) <(sort deleteNodes)
amber
ann
bob
Supressão de saída da coluna
Suprimir a coluna 1, 2 ou 3 com -n; Observe que quando uma coluna está oculta, o espaço em branco diminui.
$ 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
A classificação é importante!
Se você executar o Comm sem primeiro classificar o arquivo, ele falha graciosamente com uma mensagem sobre qual arquivo não foi classificado.
comm: file 1 is not in sorted order
comm
foi projetado especificamente para esse tipo de caso de uso, mas requer entrada classificada.
awk
é sem dúvida uma ferramenta melhor para isso, pois é bastante direto para encontrar diferença de conjunto, não requer sort
, e oferece flexibilidade adicional.
awk 'NR == FNR { a[$0]; next } !($0 in a)' nodes_to_keep nodes_to_delete
Talvez, por exemplo, você gostaria de encontrar apenas a diferença nas linhas que representam números não negativos:
awk -v r='^[0-9]+$' 'NR == FNR && $0 ~ r {
a[$0]
next
} $0 ~ r && !($0 in a)' nodes_to_keep nodes_to_delete
Talvez você precise de uma maneira melhor de fazê -lo no Postgres, posso apostar que você não encontrará uma maneira mais rápida de fazê -lo usando arquivos planos. Você deve ser capaz de fazer uma junção interna simples e assumindo que os dois cols de ID sejam indexados que devem ser muito rápidos.
Então, isso é um pouco diferente das outras respostas. Não posso dizer que um compilador C ++ é exatamente uma "ferramenta Linux CLI", mas executando g++ -O3 -march=native -o set_diff main.cpp
(com o código abaixo em main.cpp
pode fazer o truque):
#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"));
}
Para usar, basta correr set_diff B A
(não A B
, desde B
é nodes_to_keep
) e a diferença resultante será impressa no stdout.
Observe que perdoo algumas práticas recomendadas do C ++ para manter o código mais simples.
Muitas otimizações de velocidade adicionais podem ser feitas (pelo preço de mais memória). mmap
também seria particularmente útil para grandes conjuntos de dados, mas isso tornaria o código muito mais envolvido.
Desde que você mencionou que os conjuntos de dados são grandes, pensei que a leitura nodes_to_delete
Uma linha de cada vez pode ser uma boa idéia para reduzir o consumo de memória. A abordagem adotada no código acima não é particularmente eficiente se houver muitas bobagens em seu nodes_to_delete
. Além disso, a ordem não é preservada.
Algo mais fácil de copiar e colar em bash
(ou seja, pulando a criação de 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