Encontrar archivos duplicados y reemplazarlos con enlaces simbólicos

16

Estoy tratando de encontrar una manera de verificar dentro de un directorio dado los archivos duplicados (incluso con diferentes nombres) y reemplazarlos con enlaces simbólicos que apuntan a la primera aparición. Lo he intentado fdupespero solo enumera esos duplicados.
Ese es el contexto: estoy personalizando un tema de ícono a mi gusto, y he encontrado que muchos íconos, incluso si tienen diferentes nombres y diferentes ubicaciones dentro de su carpeta principal, y se usan para diferentes propósitos, básicamente son los mismos imagen. Dado que aplicar la misma modificación veinte o treinta veces es redundante cuando solo una es realmente necesaria, quiero mantener solo una imagen y vincular todas las demás.

Como ejemplo, si ejecuto fdupes -r ./dentro del directorio testdir, podría devolverme los siguientes resultados:

./file1.png
./file2.png
./subdir1/anotherfile.png
./subdir1/subdir2/yetanotherfile.png

Dado este resultado, me gustaría mantener solo el archivo file1.png, eliminar todos los demás y reemplazarlos con enlaces simbólicos apuntando a él, manteniendo todos los nombres de archivo originales. Por file2.pnglo tanto , conservará su nombre, pero se convertirá en un enlace en file1.pnglugar de ser un duplicado.

Esos enlaces no deben apuntar a una ruta absoluta, sino que deben ser relativos al testdirdirectorio padre ; es decir yetanotherfile.png, se señalará ../../file1.png, no a/home/testuser/.icons/testdir/file1.png

Estoy interesado en soluciones que involucran una GUI y CLI. No es obligatorio usarlo. fdupesLo he citado porque es una herramienta que conozco, pero estoy abierto a soluciones que también usan otras herramientas.

Estoy bastante seguro de que un script bash para manejar todo esto no debería ser tan difícil de crear, pero no soy lo suficientemente experto como para descubrir cómo escribirlo yo mismo.

Sekhemty
fuente

Respuestas:

3

Primero; ¿Hay alguna razón por la que necesite usar enlaces simbólicos y no los enlaces duros habituales? Me está costando entender la necesidad de enlaces simbólicos con rutas relativas. Así es como resolvería este problema:

Creo que la versión de Debian (Ubuntu) de fdupes puede reemplazar duplicados con enlaces duros usando la -Lopción, pero no tengo una instalación de Debian para verificar esto.

Si no tiene una versión con la -Lopción, puede usar este pequeño script bash que encontré en commandlinefu .
Tenga en cuenta que esta sintaxis solo funcionará en bash.

fdupes -r -1 path | while read line; do master=""; for file in ${line[*]}; do if [ "x${master}" == "x" ]; then master=$file; else ln -f "${master}" "${file}"; fi; done; done

El comando anterior encontrará todos los archivos duplicados en "ruta" y los reemplazará con enlaces duros. Puede verificar esto ejecutando ls -ilRy mirando el número de inodo. Aquí hay una muestra con diez archivos idénticos:

$ ls -ilR

total 20
3094308 -rw------- 1 username group  5 Sep 14 17:21 file
3094311 -rw------- 1 username group  5 Sep 14 17:21 file2
3094312 -rw------- 1 username group  5 Sep 14 17:21 file3
3094313 -rw------- 1 username group  5 Sep 14 17:21 file4
3094314 -rw------- 1 username group  5 Sep 14 17:21 file5
3094315 drwx------ 1 username group 48 Sep 14 17:22 subdirectory

./subdirectory:
total 20
3094316 -rw------- 1 username group 5 Sep 14 17:22 file
3094332 -rw------- 1 username group 5 Sep 14 17:22 file2
3094345 -rw------- 1 username group 5 Sep 14 17:22 file3
3094346 -rw------- 1 username group 5 Sep 14 17:22 file4
3094347 -rw------- 1 username group 5 Sep 14 17:22 file5

Todos los archivos tienen números de inodo separados, lo que los convierte en archivos separados. Ahora vamos a deduplicarlos:

$ fdupes -r -1 . | while read line; do j="0"; for file in ${line[*]}; do if [ "$j" == "0" ]; then j="1"; else ln -f ${line// .*/} $file; fi; done; done
$ ls -ilR
.:
total 20
3094308 -rw------- 10 username group  5 Sep 14 17:21 file
3094308 -rw------- 10 username group  5 Sep 14 17:21 file2
3094308 -rw------- 10 username group  5 Sep 14 17:21 file3
3094308 -rw------- 10 username group  5 Sep 14 17:21 file4
3094308 -rw------- 10 username group  5 Sep 14 17:21 file5
3094315 drwx------  1 username group 48 Sep 14 17:24 subdirectory

./subdirectory:
total 20
3094308 -rw------- 10 username group 5 Sep 14 17:21 file
3094308 -rw------- 10 username group 5 Sep 14 17:21 file2
3094308 -rw------- 10 username group 5 Sep 14 17:21 file3
3094308 -rw------- 10 username group 5 Sep 14 17:21 file4
3094308 -rw------- 10 username group 5 Sep 14 17:21 file5

Todos los archivos ahora tienen el mismo número de inodo, lo que significa que todos apuntan a los mismos datos físicos en el disco.

¡Espero que esto resuelva tu problema o al menos te señale en la dirección correcta!

arnefm
fuente
Recordé que fdupes tenía una opción para reemplazar dupes con enlaces, @arnefm, pero no puedo ver nada en el hombre ni es una opción en v1.51(Ubuntu 14.04.2 LTS).
Alastair
Mi bifurcación jdupesen github.com/jbruchon/jdupes tiene la -Lopción que realiza el enlace duro deseado de conjuntos duplicados.
Jody Lee Bruchon
Acabo de modificar el guión aquí. Todavía no manejará espacios, pero manejará otros caracteres especiales (tenía cadenas de consulta de URL en archivos). Además, la ${line//…/}parte no funcionaba para mí, así que hice una forma más limpia de obtener el primer archivo "maestro" en el enlace duro.
IBBoard
1
¿Necesitaríamos enlaces soft relativos si estamos utilizando rsyncun tipo diferente de sistema de archivos? ¿O si el sistema de archivos no conserva la jerarquía, por ejemplo, es un servidor de respaldo que pone todo bajo control /«machine-name»/...? ¿O si quieres restaurar desde una copia de seguridad? No puedo ver cómo se van a preservar los enlaces duros aquí. Los enlaces suaves relativos tendrían una mejor oportunidad de sobrevivir, podría pensar.
Amigo
6

Si no te gustan mucho los scripts, entonces puedo recomendar rdfind . Lo cual escaneará los directorios dados en busca de archivos duplicados y los enlazará de manera rígida o suave. Lo he usado para deduplicar mi directorio de gemas Ruby con gran éxito. Está disponible en Debian / Ubuntu.

Andrew Francia
fuente
4

Tuve una situación similar, pero en mi caso el enlace simbólico debería apuntar a una ruta relativa, así que escribí este script de Python para hacer el truco:

#!/usr/bin/env python
# Reads fdupes(-r -1) output and create relative symbolic links for each duplicate
# usage: fdupes -r1 . | ./lndupes.py

import os
from os.path import dirname, relpath, basename, join
import sys

lines = sys.stdin.readlines()

for line in lines:
    files = line.strip().split(' ')
    first = files[0]
    print "First: %s "% first
    for dup in files[1:]:
        rel = os.path.relpath(dirname(first), dirname(dup))
        print "Linking duplicate: %s to %s" % (dup, join(rel,basename(first)))
        os.unlink(dup)
        os.symlink(join(rel,basename(first)), dup)

Para cada línea de entrada (que es una lista de archivos), el script divide la lista de archivos (espacios en blanco separados), obtiene la ruta relativa de cada archivo al primero y luego crea el enlace simbólico.

filipenf
fuente
1

Entonces, la respuesta dada por arnefm (que se ha copiado en Internet) no trata con espacios en los nombres de archivo. He escrito un guión que trata con espacios en archivos.

#!/bin/bash
fdupes -r -1 CHANGE_THIS_PATH | sed -e 's/\(\w\) /\1|/g' -e 's/|$//' > files
while read line; do
        IFS='|' read -a arr <<< "$line"
        orig=${arr[0]}
        for ((i = 1; i < ${#arr[@]}; i++)); do
                file="${arr[$i]}"
                ln -sf "$orig" "$file"
        done 
done < files

Lo que esto hace es encontrar duplicados y escribirlos PIPE separados en un archivo llamado 'archivos'.

Luego lee el archivo, línea por línea, en una matriz, y cada elemento de la matriz está delimitado por el PIPE.

Luego itera sobre todos los elementos que no son los primeros de la matriz, reemplazando el archivo con un enlace simbólico al primer elemento.

El archivo externo ('archivos') podría eliminarse, si el comando fdupes se ejecuta en una subshell, eso se lee directamente por el momento, pero de esta manera parece más claro.

David Ventura
fuente
2
¿Esta versión trata con archivos con nombres que contienen una tubería? Supongo que ninguna versión maneja nombres de archivo que contengan nuevas líneas, pero eso es una limitación de fdupes en lugar de cualquier otra cosa.
dhag
No lo hace, pero puede configurar IFS a lo que desee (también modificar el valor en el reemplazo de sed), entonces no debería tener ningún problema (IFS a 'ñ' o algo así debería funcionar)
David Ventura
Esto crea enlaces simbólicos rotos, y tengo archivos vinculados a ellos mismos. NO UTILICE
MrMesees
0

Algunas advertencias por adelantado:

  • BASH específico
  • No hay espacio en los nombres de archivo
  • Asume que cada línea contiene 2 archivos como máximo.

fdupes -1r common/base/dir | while read -r -a line ; do ln -sf $(realpath --relative-to ${line[1]} ${line[0]}) ${line[1]}; done

Si hay más de 2 archivos duplicados (por ejemplo, archivo1 archivo2 archivo3) de los que necesitamos para crear un enlace simbólico para cada par: trate el archivo1, el archivo2 y el archivo1, archivo3 como 2 casos separados:

if [[ ${#line[@]} -gt 2 ]] ;then 
  ln -sf $(realpath --relative-to ${line[1]} ${line[0]}) ${line[1]} 
  ln -sf $(realpath --relative-to ${line[2]} ${line[0]}) ${line[2]} 
  ...
fi

Gastar esto para manejar automáticamente un número arbitrario de duplicados por línea requerirá un poco más de esfuerzo.

Otro enfoque sería crear primero enlaces simbólicos a rutas absolutas, luego convertirlos:

fdupes -1r /absolute/path/common/base/dir | while read -r -a line ; do ln -sf ${line[0]} ${line[1]}; done
chroot /absolute/path/common/base/dir ; symlinks -cr .

Esto se basa en la respuesta de @Gilles: /unix//a/100955/77319

Dani_l
fuente