sábado, 4 de octubre de 2025

Disco NTFS con problemas de lectura.Multiples ficheros duplicados.Copiar sin duplicados

 1. Detectar el problema

En la herramienta "disks" de Ubuntu me dice:

s'ha produit un error en muntar el sistema de fitxers Error mounting /dev/sdc4 at /media/eduard/22143F41143F176F;wrong fs type, bad option, bad superblock on /dev/sdc4, missing codepage or helper program, or other error (udisks-error-quark,0)

Por tanto el problema es en el disco /dev/sdc4


2. Detectar el tipo de partición

Ejecutamos:

sudo fdisk -l

Y vemos que es NTFS (que también se podía ver en la herramienta "Disks" de ubuntu

3. Probar a montar manualmente

Ejecutamos:

sudo mount -t ntfs /dev/sdc4 /mnt

y responde:

$MFTMirr does not match $MFT (record 3). Failed to mount '/dev/sdc4': Error d’Entrada/Sortida NTFS is either inconsistent, or there is a hardware fault, or it's a SoftRAID/FakeRAID hardware. In the first case run chkdsk /f on Windows then reboot into Windows twice. The usage of the /f parameter is very important! If the device is a SoftRAID/FakeRAID then first activate it and mount a different device under the /dev/mapper/ directory, (e.g. /dev/mapper/nvidia_eahaabcc1). Please see the 'dmraid' documentation for more details.

4. Instalar software adicional y utilizarlo


sudo apt install ntfs-3g

sudo ntfsfix /dev/sdc4

y contesta:
Mounting volume... Windows is hibernated, refused to mount. FAILED Attempting to correct errors... Processing $MFT and $MFTMirr... Reading $MFT... OK Reading $MFTMirr... OK Comparing $MFTMirr to $MFT... OK Processing of $MFT and $MFTMirr completed successfully. Setting required flags on partition... OK Going to empty the journal ($LogFile)... OK Windows is hibernated, refused to mount. Remount failed: Operation not permitted

cosa que dice que windows está "hibernated" y se debe a que se interrumpioun proceso de E/AS en el disco y que no se debe acceder a el para modificar nada si no se opera desde windows.

Por tanto lo que podemos hacer es montarlo de solo lectura y copiar los datos 

4. Montar el disco de solo lectura


sudo mount -t ntfs-3g -o ro /dev/sdc4 /mnt

y ya podemos acceder a el disco o la partición en el directorio /mnt

5. Copia de seguridad multifichero


Se opta por copiar a otro disco mas grande y luego crear un zip multivolumen de ficheros de 4 gigas con:

cd /home/eduard
zip -r -s 4g MIS_DATOS.zip MIS_DATOS

donde la carpeta que contiene los datos a guradar es /home/eduard/MIS_DATOS
 y esto genera estos ficheros:

MIS_DATOS.z01, MIS_DATOS.z02, ....MIS_DATOS.88, MIS_DATOS.zip

Siendo MIS_DATOS.88 en mi caso el penúltimo fichero y MIS_DATOS.zip el último

Para descomprimir utilizar:

unzip MIS_DATOS.zip

asegurándose que todos los ficheros generados esten disponibles en la misma carpeta.

Si solo se quiere extraer una subcarpeta llamada MI_SUBCARPETA se haría_

unzip MIS_DATOS.zip "MIS_DATOS/MI_SUBCARPETA/*" -d /ruta/de/destino

y si solo queremos listar el contenido:

unzip -l MIS_DATOS.zip | less


6. Ver si hay muchos duplicados. Borrado y copia sin duplicados


Con chatgpt he creado un bash para detectar duplicados y en su caso borrarlos. Pero en el siguiente punto se ha hecho un bash que solo copia los ficheros no duplicados.

El fichero a no borrar de los duplicados es aquel que tiene menor numero de anidamientos en carpeta.

Ver y/o eliminar duplicados (buscar_duplicados_carpeta.sh)

#!/bin/bash
# buscar_duplicados.sh
# Busca y (opcionalmente) elimina ficheros duplicados (por nombre y tamaño)
# e informa del espacio total y del potencial ahorro.

# Uso:
#   ./buscar_duplicados.sh /ruta/a/carpeta [eliminar_duplicados]

BASE_DIR="$1"
ACCION="$2"

# --- Validaciones iniciales
if [[ -z "$BASE_DIR" ]]; then
  echo "❌ Uso: $0 /ruta/a/carpeta [eliminar_duplicados]"
  exit 1
fi

if [[ ! -d "$BASE_DIR" ]]; then
  echo "❌ No existe el directorio: $BASE_DIR"
  exit 1
fi

# --- Variables
REPORT_FILE="duplicados_$(date +%Y%m%d_%H%M%S).txt"
> "$REPORT_FILE"
ELIMINAR=false
if [[ "$ACCION" == "eliminar_duplicados" ]]; then
  ELIMINAR=true
fi

echo "🔍 Buscando duplicados en: $BASE_DIR"
echo "📝 Informe: $REPORT_FILE"
echo

# --- Calcular espacio total de la carpeta (en bytes)
TOTAL_BYTES=$(du -sb "$BASE_DIR" | awk '{print $1}')
TOTAL_HUMAN=$(du -sh "$BASE_DIR" | awk '{print $1}')

echo "💾 Espacio total actual ocupado por la carpeta: $TOTAL_HUMAN ($TOTAL_BYTES bytes)"
echo "💾 Espacio total actual ocupado por la carpeta: $TOTAL_HUMAN ($TOTAL_BYTES bytes)" >> "$REPORT_FILE"
echo

# --- Buscar duplicados
POTENCIAL_AHORRO=0

find "$BASE_DIR" -type f -printf '%s\t%p\n' | \
while IFS=$'\t' read -r SIZE FILE; do
    BASENAME=$(basename "$FILE")
    echo -e "$BASENAME\t$SIZE\t$FILE"
done | sort -k1,1 -k2,2n | \
awk -F'\t' -v ELIMINAR="$ELIMINAR" -v REPORT_FILE="$REPORT_FILE" -v BASE_DIR="$BASE_DIR" '
function human(bytes) {
    hum[1024^4]="TB"; hum[1024^3]="GB"; hum[1024^2]="MB"; hum[1024]="KB";
    for (x=1024^4; x>=1024; x/=1024) if (bytes>=x) return sprintf("%.2f %s", bytes/x, hum[x]);
    return bytes " B";
}

{
    name=$1; size=$2; path=$3;
    key=name"|"size;
    files[key]=files[key]?files[key]"\n"path:path;
    sizes[key]=size;
}
END {
    total_savings=0;
    for (k in files) {
        split(files[k], arr, "\n");
        if (length(arr) > 1) {
            print "📁 Duplicado: " k >> REPORT_FILE;
            print "📁 Duplicado: " k;

            # Calcular profundidad
            min=999999; minpath="";
            for (i in arr) {
                split(arr[i], dirs, "/");
                depth[i]=length(dirs);
                if (depth[i] < min) {
                    min=depth[i];
                    minpath=arr[i];
                }
            }

            print "   ✅ Se conservará: " minpath >> REPORT_FILE;
            print "   ✅ Se conservará: " minpath;

            print "   ❌ Duplicados encontrados:" >> REPORT_FILE;
            for (i in arr) {
                if (arr[i] != minpath && arr[i] != "") {
                    print "      " arr[i] >> REPORT_FILE;
                    print "      " arr[i];
                    total_savings += sizes[k];  # sumamos el tamaño de los que se podrían eliminar
                }
            }

            if (ELIMINAR == "true") {
                printf "\n¿Eliminar todos los duplicados de este grupo (excepto el conservado)? (s/n): ";
                getline resp < "/dev/tty";
                if (resp == "s" || resp == "S") {
                    for (i in arr) {
                        if (arr[i] != minpath && arr[i] != "") {
                            cmd="rm -i \""arr[i]"\"";
                            system(cmd);
                            print "      ➜ Eliminado: " arr[i] >> REPORT_FILE;
                        }
                    }
                } else {
                    print "      ➜ No se eliminaron duplicados de este grupo." >> REPORT_FILE;
                }
            }
            print "" >> REPORT_FILE;
            print "";
        }
    }
    print "--------------------------------------------" >> REPORT_FILE;
    print "📊 Espacio total potencial a liberar: " human(total_savings) " (" total_savings " bytes)" >> REPORT_FILE;
    print "--------------------------------------------" >> REPORT_FILE;

    print "\n📊 Espacio total potencial a liberar: " human(total_savings) " (" total_savings " bytes)";
}' 

echo
echo "✅ Proceso finalizado."
echo "📄 Informe generado en: $(realpath "$REPORT_FILE")"

# --- Calcular de nuevo el tamaño si se eliminaron duplicados
if [[ "$ELIMINAR" == "true" ]]; then
  FINAL_BYTES=$(du -sb "$BASE_DIR" | awk '{print $1}')
  FINAL_HUMAN=$(du -sh "$BASE_DIR" | awk '{print $1}')
  echo
  echo "💾 Espacio tras eliminar duplicados: $FINAL_HUMAN ($FINAL_BYTES bytes)"
  echo "💾 Espacio tras eliminar duplicados: $FINAL_HUMAN ($FINAL_BYTES bytes)" >> "$REPORT_FILE"
fi

copiar carpeta sin duplicados creando un fichero de duplicados dentro de la carpeta destino (copiar_carpeta_no_duplicados.sh)


#!/bin/bash
# copiar_sin_duplicados.sh
# Copia solo los ficheros no duplicados (por nombre y tamaño)
# y genera informes de los archivos copiados y duplicados.

echo "📁 Introduce la ruta de la carpeta origen:"
read -r ORIGEN

if [[ ! -d "$ORIGEN" ]]; then
  echo "❌ La carpeta origen no existe."
  exit 1
fi

echo "📂 Introduce la ruta de la carpeta destino:"
read -r DESTINO

# Crear carpeta destino si no existe
mkdir -p "$DESTINO"

# Archivos temporales e informes
TMP_FILE=$(mktemp)
REPORT_FILE="copiados_$(date +%Y%m%d_%H%M%S).txt"
DUPLICADOS_FILE="$DESTINO/duplicados_$(date +%Y%m%d_%H%M%S).txt"
> "$REPORT_FILE"
> "$DUPLICADOS_FILE"

echo "🔍 Analizando duplicados en: $ORIGEN"
echo

# --- Calcular tamaño total de la carpeta origen
TOTAL_BYTES=$(du -sb "$ORIGEN" | awk '{print $1}')
TOTAL_HUMAN=$(du -sh "$ORIGEN" | awk '{print $1}')
echo "💾 Tamaño total de la carpeta origen: $TOTAL_HUMAN ($TOTAL_BYTES bytes)"
echo "💾 Tamaño total de la carpeta origen: $TOTAL_HUMAN ($TOTAL_BYTES bytes)" >> "$REPORT_FILE"
echo >> "$REPORT_FILE"

# --- Buscar duplicados (por nombre + tamaño)
find "$ORIGEN" -type f -printf '%s\t%p\n' | \
while IFS=$'\t' read -r SIZE FILE; do
    BASENAME=$(basename "$FILE")
    echo -e "$BASENAME\t$SIZE\t$FILE"
done | sort -k1,1 -k2,2n | \
awk -F'\t' -v TMP_FILE="$TMP_FILE" -v DUPLICADOS_FILE="$DUPLICADOS_FILE" '
{
    name=$1; size=$2; path=$3;
    key=name"|"size;
    files[key]=files[key]?files[key]"\n"path:path;
    sizes[key]=size;
}
END {
    for (k in files) {
        split(files[k], arr, "\n");
        if (length(arr) > 1) {
            print "-----------------------------------" >> DUPLICADOS_FILE;
            print "Duplicado (" k "):" >> DUPLICADOS_FILE;
            for (i in arr) {
                print arr[i] >> TMP_FILE;
                print "   " arr[i] >> DUPLICADOS_FILE;
            }
        }
    }
}'

# --- Copiar solo los no duplicados
echo "🚀 Iniciando copia de ficheros no duplicados..."
echo >> "$REPORT_FILE"
echo "=============================" >> "$REPORT_FILE"
echo "COPIA DE FICHEROS NO DUPLICADOS" >> "$REPORT_FILE"
echo "=============================" >> "$REPORT_FILE"
echo >> "$REPORT_FILE"

COUNT=0
BYTES_TOTAL_COPIADOS=0
BYTES_DUPLICADOS=0

# Calcular espacio potencial de duplicados
while IFS= read -r FILE_DUP; do
    if [[ -f "$FILE_DUP" ]]; then
        SIZE=$(stat -c%s "$FILE_DUP")
        BYTES_DUPLICADOS=$((BYTES_DUPLICADOS + SIZE))
    fi
done < "$TMP_FILE"

# Copiar los no duplicados
while IFS= read -r FILE; do
    # Saltar duplicados
    if grep -Fxq "$FILE" "$TMP_FILE"; then
        continue
    fi

    REL_PATH="${FILE#$ORIGEN/}"
    DEST_PATH="$DESTINO/$REL_PATH"
    DEST_DIR=$(dirname "$DEST_PATH")

    mkdir -p "$DEST_DIR"
    cp -p "$FILE" "$DEST_PATH"

    SIZE=$(stat -c%s "$FILE")
    BYTES_TOTAL_COPIADOS=$((BYTES_TOTAL_COPIADOS + SIZE))
    COUNT=$((COUNT + 1))

    echo "✔ Copiado: $DEST_PATH" >> "$REPORT_FILE"
done < <(find "$ORIGEN" -type f)

# --- Función para tamaños legibles
function human_readable {
    local bytes=$1
    local units=("B" "KB" "MB" "GB" "TB")
    local i=0
    local value=$bytes
    while (( value >= 1024 && i < 4 )); do
        value=$(( value / 1024 ))
        ((i++))
    done
    echo "${value} ${units[$i]}"
}

TOTAL_HUMAN_COPIADOS=$(human_readable "$BYTES_TOTAL_COPIADOS")
TOTAL_HUMAN_DUPLICADOS=$(human_readable "$BYTES_DUPLICADOS")

# --- Resultados finales
echo
echo "✅ Copia completada."
echo "📊 Archivos copiados: $COUNT"
echo "📦 Tamaño total origen: $TOTAL_HUMAN ($TOTAL_BYTES bytes)"
echo "📥 Tamaño copiado (sin duplicados): $TOTAL_HUMAN_COPIADOS ($BYTES_TOTAL_COPIADOS bytes)"
echo "💾 Espacio ahorrado (duplicados no copiados): $TOTAL_HUMAN_DUPLICADOS ($BYTES_DUPLICADOS bytes)"
echo
echo "📄 Informe de copiados: $(realpath "$REPORT_FILE")"
echo "📁 Listado de duplicados: $(realpath "$DUPLICADOS_FILE")"

echo >> "$REPORT_FILE"
echo "-------------------------------------" >> "$REPORT_FILE"
echo "📦 Tamaño total origen: $TOTAL_HUMAN ($TOTAL_BYTES bytes)" >> "$REPORT_FILE"
echo "📥 Tamaño copiado (sin duplicados): $TOTAL_HUMAN_COPIADOS ($BYTES_TOTAL_COPIADOS bytes)" >> "$REPORT_FILE"
echo "💾 Espacio ahorrado (duplicados no copiados): $TOTAL_HUMAN_DUPLICADOS ($BYTES_DUPLICADOS bytes)" >> "$REPORT_FILE"
echo "-------------------------------------" >> "$REPORT_FILE"

# --- Limpieza
rm -f "$TMP_FILE"




No hay comentarios :

Publicar un comentario