Interseção de duas listas no Bash

162

Estou tentando escrever um script simples que irá listar o conteúdo encontrado em duas listas. Para simplificar, vamos usar ls como exemplo. Imagine "um" e "dois" são diretórios.

one = `ls one`
two = `ls two`
interseção $ um $ dois

Ainda estou muito verde no bash, então fique à vontade para corrigir como estou fazendo isso. Eu só preciso de algum comando que imprima todos os arquivos em "um" e "dois". Eles devem existir em ambos. Você pode chamar isso de "interseção" entre "um" e "dois".

Usuário1
fonte
Nada aqui realmente responde à pergunta: como interceptar duas variáveis em um script Bash.
jameshfisher
Parece uma nova pergunta na minha opinião, essa pergunta é claramente respondida aqui.
Jean-Christophe Meillaud
Uma abordagem sem dúvida mais útil está no stackoverflow.com/questions/2312762/…
tripleee

Respostas:

284
comm -12  <(ls 1) <(ls 2)
ghostdog74
fonte
37
Não posso acreditar que eu não tinha conhecimento commaté hoje. Isso só fez toda a minha semana :)
Darragh Enright
22
commrequer que as entradas sejam classificadas. Nesse caso, lsclassifica automaticamente sua saída, mas outros usos podem precisar fazer isso:comm -12 <(some-command | sort) <(some-other-command | sort)
Alexander Bird
11
NÃO USE a saída de ls para nada. ls é uma ferramenta para analisar interativamente os metadados do diretório. Quaisquer tentativas de analisar a saída de ls com o código são interrompidas. Globs são muito mais simples E corretos: '' para arquivo em * .txt ''. Leia mywiki.wooledge.org/ParsingLs
Rany Albeg Wein
2
Eu apenas usei isso em um esforço para encontrar usos de um publicmétodo error()fornecido por uma característica, em combinação com git grep, e foi incrível! Eu corri $ comm -12 <(git grep -il "\$this->error(" -- "*.php") <(git grep -il "Dash_Api_Json_Response" -- "*.php")e, felizmente, acabei com o nome do arquivo apenas que continha a característica.
Localheinz #
3
Isso é hilário. Eu estava tentando fazer algumas coisas loucas com o awk.
Rolf
54

Solução com comm

commé ótimo, mas realmente precisa trabalhar com a lista classificada. Felizmente, aqui usamos a página de manual lsdo lsBash

Classifique as entradas em ordem alfabética se nenhuma das opções -cftuSUX e --sort.

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

Alternativa com sort

Interseção de duas listas:

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

diferença simétrica de duas listas:

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

Bônus

Brinque com ele;)

cd $(mktemp -d) && mkdir {one,two} && touch {one,two}/file_{1,2}{0..9} && touch two/file_3{0..9}
Jean-Christophe Meillaud
fonte
2
Em vez de complemento , acho que é o que geralmente é chamado de diferença simétrica .
Andrew Lazarus
29

Use o commcomando:

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

"sort" não é realmente necessário, mas eu sempre o incluo antes de usar "comm" apenas por precaução.

DVK
fonte
5
É bom incluí-lo, pois ele precisa ser classificado e ele só usou ls como exemplo.
28412 Thor84no
3

Uma alternativa menos eficiente (do que comm):

cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -d
Benubird
fonte
1
Se você estiver usando o Debian bin / / dash ou algum outro shell não-Bash em seus scripts, você pode output comandos cadeia usando parênteses: (ls 1; ls 2) | sort -u | uniq -d.
nitrogênio
1
@ MikaëlMayer Você deve sinalizar o nome da pessoa a quem está respondendo, caso contrário, assume-se que você está falando comigo.
Benubird 23/02
@nitrogen MikaëlMayer está correto - a busca sort -u | uniq -dnão faz nada, porque a classificação removeu as duplicatas antes que o uniq comece a procurá-las. Eu acho que você não entendeu o que meu comando está fazendo.
Benubird 23/02
@Benubird Também não consegui que seu comando produzisse cat <(ls 1 | sort -u) <(ls 2 | sort -u) | uniq -dnada. Meu comando deve ler (ls 1; ls 2) | sort | uniq -d, sem o -u, para mostrar a interseção da lista. @ MikaëlMayer estava certo ao dizer que meu comando original foi quebrado.
nitrogênio
@ nitrogênio A razão pela qual estou usando o gato é porque quero que essa seja uma solução generalizável, para que você possa substituir lspor outra coisa, por exemplo find. Sua solução não permite isso porque, se um dos comandos retornar duas linhas iguais, ele será copiado como duplicado. O meu funciona mesmo que o usuário queira fazer ls 1/*e comparar todos os arquivos entre subdiretórios. Caso contrário, sim, também funciona. É possível que o meu seja específico do bash.
Benubird 24/02
2

A junção é outra boa opção, dependendo da entrada e da saída desejada

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

Há outra pergunta sobre o Stackoverflow "Interseção de matriz no bash", marcada como uma duplicata. Na minha opinião, não é exatamente o mesmo, pois essa pergunta fala sobre a comparação de duas matrizes bash, enquanto essa pergunta se concentra nos arquivos bash. Uma resposta de uma linha para a outra pergunta, que agora está fechada, é a seguinte:

# 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

O utilitário comm faz uma classificação alfanumérica, enquanto as respostas "Array intersection in bash" usam números; daí o uso "sort" e "sort -g".

Chuck Newman
fonte