Como todos os arquivos de entrada já foram classificados, podemos ignorar a etapa de classificação real e apenas usá sort -m
- los para mesclar os arquivos.
Em alguns sistemas Unix (que eu saiba apenas Linux), pode ser o suficiente para fazer
sort -m *.words | uniq -d >dupes.txt
para obter as linhas duplicadas gravadas no arquivo dupes.txt
.
Para descobrir de quais arquivos essas linhas vieram, você pode fazer
grep -Fx -f dupes.txt *.words
Isso instruirá grep
a tratar as linhas em dupes.txt
( -f dupes.txt
) como padrões de string fixos ( -F
). grep
também exigirá que toda a linha corresponda perfeitamente do início ao fim ( -x
). Irá imprimir o nome do arquivo e a linha do terminal.
Unices não Linux (ou ainda mais arquivos)
Em alguns sistemas Unix, os nomes de arquivos 30000 serão expandidos para uma cadeia longa demais para passar para um único utilitário (o significado sort -m *.words
falhará com o Argument list too long
que ocorre no meu sistema OpenBSD). Até o Linux reclamará se o número de arquivos for muito maior.
Encontrando os idiotas
Isso significa que, no caso geral (isso também funcionará com muito mais que apenas 30000 arquivos), é necessário "dividir" a classificação:
rm -f tmpfile
find . -type f -name '*.words' -print0 |
xargs -0 sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh
Como alternativa, criando tmpfile
sem xargs
:
rm -f tmpfile
find . -type f -name '*.words' -exec sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh {} +
Isso encontrará todos os arquivos no diretório atual (ou abaixo) cujos nomes correspondem *.words
. Para um pedaço desses nomes de tamanho apropriado por vez, cujo tamanho é determinado por xargs
/ find
, ele os mescla no tmpfile
arquivo classificado . Se tmpfile
já existir (para todos, exceto o primeiro bloco), esse arquivo também será mesclado com os outros arquivos no bloco atual. Dependendo do tamanho dos seus nomes de arquivos e do tamanho máximo permitido de uma linha de comando, isso pode exigir mais ou muito mais do que 10 execuções individuais do script interno ( find
/ xargs
fará isso automaticamente).
O sh
script "interno" ,
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi
usa sort -o tmpfile
para enviar para tmpfile
(isso não substituirá, tmpfile
mesmo que também seja uma entrada para sort
) e -m
para fazer a mesclagem. Nos dois ramos, "$@"
será expandido para uma lista de nomes de arquivos citados individualmente, passados para o script de find
ou xargs
.
Em seguida, basta executar uniq -d
em tmpfile
obter toda a linha que são duplicados:
uniq -d tmpfile >dupes.txt
Se você gosta do princípio "SECO" ("Não se repita"), pode escrever o script interno como
if [ -f tmpfile ]; then
t=tmpfile
else
t=/dev/null
fi
sort -o tmpfile -m "$t" "$@"
ou
t=tmpfile
[ ! -f "$t" ] && t=/dev/null
sort -o tmpfile -m "$t" "$@"
De onde eles vieram?
Pelas mesmas razões acima, não podemos usar grep -Fx -f dupes.txt *.words
para descobrir de onde vieram essas duplicações; portanto, usamos find
novamente:
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt {} +
Como não há processamento "complicado" a ser feito, podemos chamar grep
diretamente de -exec
. A -exec
opção usa um comando utilitário e coloca os nomes encontrados {}
. Com +
no final, find
colocará tantos argumentos no lugar {}
quanto o shell atual suporta em cada chamada do utilitário.
Para ser totalmente correto, pode-se querer usar
find . -type f -name '*.words' \
-exec grep -H -Fx -f dupes.txt {} +
ou
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt /dev/null {} +
para garantir que os nomes de arquivos sempre sejam incluídos na saída de grep
.
A primeira variação usa grep -H
para sempre gerar nomes de arquivos correspondentes. A última variação usa o fato de grep
incluir o nome do arquivo correspondente se mais de um arquivo for fornecido na linha de comando.
Isso é importante, já que o último pedaço de nome de arquivo enviado para grep
de, find
na verdade, pode conter apenas um único nome de arquivo, caso em grep
que não o mencionaria em seus resultados.
Material bônus:
Dissecando o comando find
+ xargs
+ sh
:
find . -type f -name '*.words' -print0 |
xargs -0 sh -c '
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi' sh
find . -type f -name '*.words'
simplesmente gerará uma lista de nomes de caminho a partir do diretório atual (ou abaixo), onde cada nome de caminho é o de um arquivo comum ( -type f
) e possui um componente de nome de arquivo no final correspondente *.words
. Se apenas o diretório atual precisar ser pesquisado, é possível adicionar -maxdepth 1
após o .
, antes -type f
.
-print0
garantirá que todos os nomes de caminho encontrados sejam gerados com um caractere \0
( nul
) como delimitador. Este é um caractere que não é válido em um caminho Unix e nos permite processar nomes de caminho, mesmo que contenham caracteres de nova linha (ou outras coisas estranhas).
find
canaliza sua saída para xargs
.
xargs -0
lerá a \0
lista de nomes de caminho -delimited e executará o utilitário especificado repetidamente com partes deles, garantindo que o utilitário seja executado com argumentos suficientes para não fazer com que o shell se queixe de uma lista de argumentos muito longa, até que não haja mais entrada de find
.
O utilitário invocado por xargs
é sh
com um script fornecido na linha de comando como uma string usando seu -c
sinalizador.
Ao invocar sh -c '...some script...'
com os argumentos a seguir, os argumentos estarão disponíveis para o script $@
, exceto pelo primeiro argumento , que será colocado $0
(este é o "nome do comando" no qual você poderá localizar, por exemplo, top
se for rápido o suficiente). É por isso que inserimos a string sh
como o primeiro argumento após o final do script real. A string sh
é um argumento fictício e pode ser qualquer palavra (algumas parecem preferir _
ou sh-find
).
fi' sh
?