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
?