Ferramentas Linux para tratar arquivos como conjuntos e executar operações de conjunto neles

82

Alguém conhece alguma ferramenta Linux especificamente projetada para tratar arquivos como conjuntos e executar operações de conjunto neles? Como diferença, interseção, etc?

nilton
fonte

Respostas:

110

Supondo que os elementos sejam cadeias de caracteres que não sejam NUL e newline (tenha em atenção que a nova linha é válida nos nomes dos arquivos), você pode representar um conjunto como um arquivo de texto com um elemento por linha e usar alguns dos utilitários padrão do Unix.

Definir associação

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

Definir interseção

$ 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

Definir igualdade

$ 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

Definir cardinalidade

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

$ awk 'END { print NR }' set

$ sed '$=' set

Teste de subconjunto

$ 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

Definir união

$ 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

Definir 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

Definir diferença simétrica

$ 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

Conjunto de força

Todos os subconjuntos possíveis de um conjunto exibiam o espaço separado, um por linha:

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

(assume que os elementos não contêm SPC, TAB (assumindo o valor padrão de $IFS), barra invertida, caracteres curinga).

Definir produto 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

Teste de conjunto disjuntivo

$ 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

Teste de conjunto vazio

$ 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

Mínimo

$ 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

Máximo

$ 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

Todos disponíveis em http://www.catonmat.net/blog/set-operations-in-unix-shell-simplified/

llhuii
fonte
1
Eu acho que a versão Python é muito mais simples e mais intuitiva. ;-)
Keith
Eu acho que essa é a resposta mais completa. Infelizmente, quais comandos executar ou quais argumentos (comm -12, -23, -13) em cada caso nem sempre são intuitivos como "interseção" ou "diferença". Talvez crie um invólucro em torno deles, pois estou sempre usando essas coisas.
Nilton 25/04
Executei [pol @ localhost inst] $ grep -xc e INSTALL-BINARY 0 [pol @ localhost inst] $, mas não entendo o que isso significa. A palavra "e" deve ocorrer muitas vezes no arquivo. O que estou fazendo errado?
Vérace
1
Definir interseção: sort set1 set2 | uniq -dnão funciona para vários conjuntos. Considere usar sort <(sort -u set1) <(sort -u set2) | uniq -d.
neo
11

Tipo de. Você precisa lidar com a classificação, mas commpode ser usado para fazer isso, tratando cada linha como um membro definido: -12para interseção, -13para diferença. (E -23dá a você a diferença invertida, ou seja, em set2 - set1vez de set1 - set2.) A união está sort -unessa configuração.

geekosaur
fonte
1
De fato, comm parece fazer a maioria das coisas. Embora os argumentos sejam muito pouco intuitivos. Obrigado!
Nilton
7

Não conheço uma ferramenta específica, mas você pode usar o Python, sua classe e operadores de conjunto, para escrever um pequeno script para fazer isso.

Por exemplo:

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

set(['awk',
     'basename',
     'chroot', ...
Keith
fonte
Sim, boa resposta. Por que usar o awk se o python estiver disponível?
guettli
Você esqueceu:Python> import os
James Bowery
7

A pequena ferramenta de console “setop” agora está disponível no Debian Stretch e no Ubuntu desde 16.10. Você pode obtê-lo via sudo apt install setop

Aqui estão alguns exemplos. Os conjuntos a serem operados são fornecidos como arquivos de entrada diferentes: 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

As consultas booleanas retornam apenas EXIT_SUCCESSem caso de true e EXIT_FAILURE, assim como uma mensagem em contrário. Dessa forma, o setop pode ser usado no 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)?

Também é possível descrever com precisão como os fluxos de entrada devem ser analisados, na verdade por expressões regulares:

  • setop input.txt --input-separator "[[:space:]-]"significa que um espaço em branco ( \v \t \n \r \fou seja, espaço) ou um sinal de menos é interpretado como um separador entre elementos (o padrão é uma nova linha, ou seja, cada linha do arquivo de entrada é um elemento)
  • setop input.txt --input-element "[A-Za-z]+" significa que elementos são apenas palavras que consistem em caracteres latinos, todos os outros caracteres são considerados separadores entre elementos

Além disso, você pode

  • --count todos os elementos do conjunto de saída,
  • --trim todos os elementos de entrada (ou seja, apagar todos os caracteres anteriores e seguintes indesejados, como espaço, vírgula etc.),
  • considere os elementos vazios como válidos por --include-empty,
  • --ignore-case,
  • defina os --output-separatorelementos entre o fluxo de saída (o padrão é \n),
  • e assim por diante.

Veja man setopou github.com/phisigma/setop para mais informações.

Frank
fonte
3

Se você vê um arquivo como um conjunto de linhas e os arquivos são classificados, existe comm.

Se você vir um arquivo como um (multi) conjunto de linhas, e as linhas não forem classificadas, greppoderá fazer diferença e interseção (atinge a diferença e a interseção definidas, mas não respeita a contagem de várias configurações). União é justa cat.

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

Eu criei um utilitário Python que pode fazer união, interseção, diferença e produto de vários arquivos por linhas. É chamado SetOp, você pode encontrá-lo no PyPI ( aqui ). A sintaxe fica assim:

$ setop -i file1 file2 file3  # intersection
$ setop -d file1 file2 file3  # difference
Tigr
fonte
1

Escrevi uma pequena ferramenta para fazer isso, que me foi bastante útil em vários lugares. A interface do usuário é polida e não tenho certeza sobre as características de desempenho de arquivos muito grandes (já que ela lê toda a lista na memória), mas "funciona para mim". O programa está em https://github.com/nibrahim/lines . Está em Python. Você pode obtê-lo usando pip install lines.

Atualmente, ele suporta união, interseção, diferença e diferença simétrica de dois arquivos. Cada linha do arquivo de entrada é tratada como um elemento de um conjunto.

Ele também tem duas operações extras. Uma das linhas em branco de um arquivo e a segunda (que foi muito útil para mim) é examinar o arquivo e dividi-lo em conjuntos de cadeias semelhantes. Eu precisava disso para procurar arquivos em uma lista que não correspondia ao padrão geral.

Eu gostaria de receber feedback.

Noufal Ibrahim
fonte
0

O sistema de arquivos trata os nomes de arquivos (nomes de arquivos inteiros, incluindo caminhos) como exclusivos.

Operações?

Você pode copiar os arquivos em a / eb / para o diretório vazio c /, para obter um novo conjunto de união.

Com testes de arquivo como -e namee loops ou localização, você pode procurar por arquivos existentes em dois ou mais diretórios, para obter a interseção ou a diferença.

Usuário desconhecido
fonte
1
Eu quis dizer tratar o conteúdo dos arquivos como os elementos de um conjunto (digamos, um elemento por linha) e os próprios arquivos como conjuntos.
Nilton
0

Melhor resposta aqui: Setdown (uma ferramenta dedicada)

Eu escrevi um programa chamado setdown que executa operações Set do CLI.

Ele pode executar operações de conjunto escrevendo uma definição semelhante à que você escreveria em um Makefile:

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

É bem legal e você deve conferir. Pessoalmente, não recomendo o uso de comandos ad-hoc que não foram criados para o trabalho para executar operações definidas. Não funcionará bem quando você realmente precisar executar muitas operações definidas ou se tiver operações definidas que dependam uma da outra. . Não apenas isso, mas a configuração permite escrever operações definidas que dependem de outras operações definidas!

De qualquer forma, acho que é bem legal e você deve conferir totalmente.

Robert Massaioli
fonte
0

Padrão de amostra para vários arquivos (neste caso, interseção):

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

Expande para:

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

Arquivos de teste:

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

Resultado:

0
6
12
18
bsb
fonte
0

Com zshmatrizes ( zshmatrizes podem conter qualquer sequência arbitrária de bytes, até 0).

(observe também que você pode fazer typeset -U arraypara garantir que seus elementos sejam únicos).

definir associação

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

(usando o Isinalizador subscrito da matriz, para obter o índice da última ocorrência de $elementna matriz (ou 0 se não for encontrado). Remova e(para exact) para $elementser tomado como padrão)

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

${array:#pattern}sendo uma variação nos ksh ${var#pattern}que remove os elementos que correspondem ao padrão, em vez de apenas remover a parte inicial que corresponde ao padrão. O (M)(para correspondência ) reverte o significado e remove todos os elementos correspondentes, exceto os usados ​​(use $~element-o como padrão).

definir interseção

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

${set1:*set2}faz a interseção da matriz, mas a "${(@)...}"sintaxe é necessária para preservar os elementos vazios.

definir igualdade

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

Testa se as matrizes são idênticas (e na mesma ordem). O qsinalizador de expansão de parâmetro cita os elementos (para evitar problemas com itens como a=(1 "2 3")vs b=("1 2" 3)) e os (j: :)une com espaço antes de fazer uma comparação de cadeias.

Para verificar se eles têm os mesmos elementos, independentemente da ordem, use o osinalizador para ordená-los. Veja também o usinalizador (exclusivo) para remover duplicatas.

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

definir cardinalidade

n=$#array

teste de subconjunto

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

União

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

(veja typeset -Uacima ou o usinalizador de expansão de parâmetro para obter duplicatas). Novamente, se a sequência vazia não for um dos valores possíveis, você pode simplificar para:

union=($array1 $array2)

complemento

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

para os elementos $array1que não estão $array2.

mínimo / máximo (comparação lexical)

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

mínimo / máximo (comparação decimal decimal)

min=${${(no)array}[1]} max=${${(no)array}[-1]}
Stéphane Chazelas
fonte