Como todos los archivos de entrada ya están ordenados, podemos omitir el paso de clasificación real y simplemente usarlo sort -m
para fusionar los archivos.
En algunos sistemas Unix (que yo sepa, solo Linux), puede ser suficiente
sort -m *.words | uniq -d >dupes.txt
para obtener las líneas duplicadas escritas en el archivo dupes.txt
.
Para encontrar de qué archivos provienen estas líneas, puede hacer
grep -Fx -f dupes.txt *.words
Esto le indicará grep
que trate las líneas en dupes.txt
( -f dupes.txt
) como patrones de cadena fijos ( -F
). grep
también requerirá que toda la línea coincida perfectamente de principio a fin ( -x
). Imprimirá el nombre del archivo y la línea al terminal.
Unices no Linux (o incluso más archivos)
En algunos sistemas Unix, los nombres de los archivos 30000 se expandirán a una cadena que es demasiado larga para pasar a una sola utilidad (lo sort -m *.words
que significa que fallará Argument list too long
, lo que hace en mi sistema OpenBSD). Incluso Linux se quejará de esto si la cantidad de archivos es mucho mayor.
Encontrando a los engañados
Esto significa que en el caso general (esto también funcionará con muchos más que solo 30000 archivos), uno tiene que "fragmentar" la clasificación:
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
Alternativamente, creando tmpfile
sin 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 {} +
Esto encontrará todos los archivos en el directorio actual (o debajo) cuyos nombres coincidan *.words
. Para un fragmento de estos nombres de tamaño apropiado a la vez, cuyo tamaño está determinado por xargs
/ find
, los fusiona en el tmpfile
archivo ordenado . Si tmpfile
ya existe (para todos menos el primer fragmento), este archivo también se fusiona con los otros archivos en el fragmento actual. Dependiendo de la longitud de sus nombres de archivo y la longitud máxima permitida de una línea de comando, esto puede requerir más o mucho más de 10 ejecuciones individuales del script interno ( find
/ xargs
lo hará automáticamente).
El sh
guión "interno" ,
if [ -f tmpfile ]; then
sort -o tmpfile -m tmpfile "$@"
else
sort -o tmpfile -m "$@"
fi
se usa sort -o tmpfile
para enviar a tmpfile
(esto no se sobrescribirá tmpfile
incluso si esto también es una entrada para sort
) y -m
para hacer la fusión. En ambas ramas, "$@"
se expandirá a una lista de nombres de archivos entre comillas individuales pasados al script desde find
o xargs
.
A continuación, basta con ejecutar uniq -d
en tmpfile
obtener toda la línea que se duplican:
uniq -d tmpfile >dupes.txt
Si le gusta el principio "SECO" ("No se repita"), puede escribir el guión interno como
if [ -f tmpfile ]; then
t=tmpfile
else
t=/dev/null
fi
sort -o tmpfile -m "$t" "$@"
o
t=tmpfile
[ ! -f "$t" ] && t=/dev/null
sort -o tmpfile -m "$t" "$@"
¿De dónde vienen ellos?
Por las mismas razones que anteriormente, no podemos usar grep -Fx -f dupes.txt *.words
para encontrar de dónde provienen estas duplicaciones, así que en su lugar, usamos find
nuevamente:
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt {} +
Como no hay que realizar un procesamiento "complicado", podemos invocar grep
directamente desde -exec
. La -exec
opción toma un comando de utilidad y colocará los nombres encontrados {}
. Con +
al final, find
colocará tantos argumentos en lugar de {}
los que admite el shell actual en cada invocación de la utilidad.
Para ser totalmente correcto, uno puede usar cualquiera
find . -type f -name '*.words' \
-exec grep -H -Fx -f dupes.txt {} +
o
find . -type f -name '*.words' \
-exec grep -Fx -f dupes.txt /dev/null {} +
para asegurarse de que los nombres de archivo siempre se incluyan en la salida de grep
.
La primera variación se usa grep -H
para generar siempre nombres de archivo coincidentes. La última variación utiliza el hecho de que grep
incluirá el nombre del archivo coincidente si se proporciona más de un archivo en la línea de comando.
Esto es importante ya que la última parte de los nombres de archivo enviados grep
desde find
puede contener solo un nombre de archivo único, en cuyo caso grep
no lo mencionaría en sus resultados.
Material de bonificación:
Diseccionando el 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'
simplemente generará una lista de nombres de ruta desde el directorio actual (o debajo) donde cada nombre de ruta es el de un archivo normal ( -type f
) y que tiene un componente de nombre de archivo al final que coincide *.words
. Si solo se busca el directorio actual , se puede agregar -maxdepth 1
después del .
, antes -type f
.
-print0
garantizará que todos los nombres de ruta encontrados se muestren con un carácter \0
( nul
) como delimitador. Este es un carácter que no es válido en una ruta de Unix y nos permite procesar nombres de ruta incluso si contienen caracteres de nueva línea (u otras cosas extrañas).
find
canaliza su salida a xargs
.
xargs -0
leerá la \0
lista de nombres de ruta delimitada y ejecutará la utilidad dada repetidamente con fragmentos de estos, asegurando que la utilidad se ejecute con suficientes argumentos para no hacer que el shell se queje de una lista de argumentos demasiado larga, hasta que no haya más entradas de find
.
La utilidad invocada por xargs
es sh
con un script dado en la línea de comando como una cadena usando su -c
bandera.
Al invocar sh -c '...some script...'
con los siguientes argumentos, los argumentos estarán disponibles para el script $@
, excepto el primer argumento , que se colocará $0
(este es el "nombre del comando" en el que puede detectar, por ejemplo, top
si es lo suficientemente rápido). Es por eso que insertamos la cadena sh
como el primer argumento después del final del script real. La cadena sh
es un argumento ficticio y podría ser cualquier palabra (algunos parecen preferir _
o sh-find
).
fi' sh
?fi
es el final de laif
declaración en elsh
script de shell "interno" . Los'
extremos que shell script (todo el guión es una cadena por separado citado). Sesh
pasará al script interno en$0
(no forma parte de$@
, que contendrá los nombres de archivo). En este caso, esash
cadena puede ser cualquier palabra. Si se omitesh
al final, se pasará el primer nombre de archivo$0
y no formará parte del procesamiento que está realizando el script de shell interno.Lo que significa que probablemente pueda encontrar algún uso para
sort -m
:La otra alternativa obvia para hacer esto sería
awk
recolectar las líneas en una matriz y contarlas. Pero como comentó @ dave_thompson_085 , esos 3 000 millones de líneas (o las muchas que haya) probablemente requerirán una cantidad considerable de memoria para almacenar, por lo que puede que no funcione muy bien.fuente
Con awk puede obtener todas las líneas repetidas en todos los archivos en un comando corto:
Pero repetirá líneas si existe una línea 3 o más veces.
Hay una solución para obtener solo el primer duplicado:
Debería ser bastante rápido (si las repeticiones son pocas) pero consumirá mucha memoria para mantener todas las líneas en la memoria. Tal vez, dependiendo de sus archivos y repeticiones reales, intente primero con 3 o cuatro archivos.
De lo contrario, puedes hacer:
Lo que imprimirá líneas repetidas uniq.
fuente
sort -m * | uniq -d
'x[$0]++==1'
pero necesitará mucha memoria; si las líneas 3G tienen, digamos, valores distintos de 1G, y si su awk necesita decir 50 bytes para una entrada hasharray que asigna una cadena (presumiblemente corta) al valor uninit, eso es 50GB. Para la entrada ordenada, puede hacerlouniq -d
manualmente,awk '$0==p&&n++==1;$0!=p{p=$0;n=1}'
pero ¿por qué molestarse?==1
, gran idea.awk
almacenar 2.4E11 bytes (223 GiB).sort -m *.words | uniq -d
¡Funciona genial! Después del proceso, corrogrep
para encontrar un archivo que contenga una entrada duplicada. ¿Ves una manera de imprimir al menos un nombre de archivo que contiene una entrada duplicada?Solución optimizada
sort
+uniq
:--parallel=N
- cambie el número de tipos ejecutados simultáneamente aN
-d, --repeated
- solo imprime líneas duplicadas, una para cada grupofuente