Forum Programmation.shell Script Bash, tronquer noms de fichiers pour eCryptFS

Posté par  (site web personnel) . Licence CC By‑SA.
Étiquettes :
3
16
sept.
2014

Sommaire

Ce script fonctionne déjà ici en production.

Je cherche à l'optimiser car je l'utilise depuis peu sur un plus gros dossier.

Contexte :

Nous souhaitons effectuer des sauvegardes externes de sorte à prévenir l'incendie ou le cambriolage.
Nous souhaitons que cette copie externe soit chiffrée.
Actuellement, elle se trouve sur un disque externe mais sera bientôt synchronisée à partir d'une machine extérieur, via internet (rsync).

eCryptfs est utilisé dans un premier temps pour obtenir une version chiffrée d'une sauvegarde locale. Mais eCryptfs limite à 110 caractères les noms de fichiers et dossiers.

J'ai donc écrit un script qui parcours l'ensemble du dossier de sorte à tronquer si nécessaire le nom des fichiers et dossier trop long.

Le Script :

Ça marche mais c'est très lent :(

Auriez-vous des idée pour optimiser ce script ? :)

spm_namelength_config.sh

# Define here the maximum end of file name
# to be consider as extension.
# The truncated file name version will keep extension.
#
# Should be greater or equal to 3
#
# Example: 6
#
EXTENTION_MAX_SIZE=6

# Define the hash size for truncated file name.
# Greater hash better avoid collisions but keep
# less original name chars.
#
# Example: 10
#
HASH_SIZE=10

spm_namelength.sh

#!/bin/bash

## Up is a bash function to simplify vertical file system navigation.
## 
## Copyright (C) 2013 Serge Smeesters
## 
## This program is free software: you can redistribute it
## and/or modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation, either version 3
## of the License, or (at your option) any later version.
## 
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
## See the GNU General Public License for more details.
## 
## You should have received a copy of the GNU General Public License
## along with this program. If not, see http://www.gnu.org/licenses/.


mydir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
[ -d $mydir ] || exit 4
configfile="$mydir/spm_namelength_config.sh"

if [ -f "$configfile" ]
then
    . "$configfile"
else
    echo "No config file find :("
    exit 4
fi


trunc_item ()
{
    local filefullpathname="$1"
    local shortsize="$2"

    local filefullname=$(basename "$filefullpathname")

    if [ ${#filefullname} -gt $sizemax ] ; then

        local filepath=$(dirname "$filefullpathname")
        local filename=${filefullname%.*}
        local filextension=${filefullname##*.}
        local hashvalue="$(echo "$filename" | md5sum | cut -c1-$HASH_SIZE)"

        echo "---"
        echo "$filefullname"

        local filextensionsize=${#filextension}

        if [ $filextensionsize -gt $EXTENTION_MAX_SIZE ] ; then
            local fileshortfullname="$(echo "$filefullname" | cut -c1-$shortsize)$hashvalue"
        else
            local newsize
            (( newsize = shortsize - ( filextensionsize + 1 ) ))
            local fileshortname="$(echo "$filename" | cut -c1-$newsize)$hashvalue"
            local fileshortfullname="$fileshortname.$filextension"
        fi

        echo "→ $fileshortfullname"

        local fileshortfullpathname="$filepath/$fileshortfullname"

        # echo "$fileshortfullpathname"

        if [ -e "$fileshortfullpathname" ] ; then
            echo "Waw... the shorten file seam to yet exists ! :("
            exit 4
        fi

        pushd "$filepath" >/dev/null
            echo "mv \"$fileshortfullname\" \"$filefullname\"" >> .namelengthrestore
            mv "$filefullname" "$fileshortfullname"
        popd >/dev/null

    fi
}


trunc_directory ()
{
    local root="$(readlink -f "$1")"
    local shortsize="$2"
    # echo "(trunc_directory \"$root\""
    local directoryfullpathname
    find "$root" -mindepth 1 -maxdepth 1 -type d | \
    while read directoryfullpathname ; do

        trunc_directory "$directoryfullpathname" "$shortsize"

        trunc_item "$directoryfullpathname" "$shortsize"

    done
    local filefullpathname
    find "$root" -maxdepth 1 -type f | \
    while read filefullpathname ; do
        trunc_item "$filefullpathname" "$shortsize"
    done
}


restore_directory ()
{
    local root="$(readlink -f "$1")"
    # echo "(restore_directory \"$root\""
    local directoryfullpathname
    find "$root" -mindepth 1 -maxdepth 1 -type d | \
    while read directoryfullpathname ; do
        restore_directory "$directoryfullpathname"
    done

    if [ -f "$root/.namelengthrestore" ] ; then
        pushd "$root" >/dev/null
            cat .namelengthrestore
            . .namelengthrestore
            rm .namelengthrestore
        popd >/dev/null
    fi
}


main ()
{
    if [ $# -lt 2 ] ; then
        echo "Less than 2 arguments :("
        exit 1
    fi

    # echo "\$2 : \"$2\""
    local root="$(readlink -f "$2")"
    # echo "root : \"$root\""
    if [ ! -d "$root" ] ; then
        echo "\"$root\" don't appear to be a directory :("
        exit 1
    fi

    case "$1" in
        trunc)

            if [ $# -lt 3 ] ; then
                echo "Trunc need 2 arguments..."
                exit 2
            fi

            local sizemax="$3"

            if [ ! "$sizemax" ] ; then exit 1 ; fi

            local minsizemax
            (( minsizemax = 5 * HASH_SIZE + EXTENTION_MAX_SIZE ))
            # echo "A reasonable sizemax should be $minsizemax"
            if [ "$sizemax" -lt "$minsizemax" ] ; then
                echo "sizemax, $sizemax less then minsizemax, $minsizemax :("
                exit 1
            fi

            local shortsize
            (( shortsize = sizemax - HASH_SIZE ))

            trunc_directory "$root" "$shortsize"
        ;;

       restore)
            restore_directory "$root"
       ;;

       *)
          echo "Usage: $0 {trunc <DIR> <MAXLENGTH> |restore <DIR>} " >&2
          exit 1
          ;;
    esac
}

main "$@"
  • # Trop de fork

    Posté par  . Évalué à 4.

    J'ai pas regardé dans le détail ce que fait ton algo, mais à vu de nez tu lance un paquet de fois find et md5sum. C'est dommage. Je pense qu'au lieu de rendre trunc_directory récursive tu devrait utiliser find une bonne fois pour toute et piper son résultat.

    Tu ne calcul pas le md5 sur le fichier mais sur son nom, ce qui n'est pas très pratique avec la commande md5sum, mais tu devrait pouvoir le faire en perl1 :

    perl -MDigest::MD5 -lnE 'print md5_hex($_).\' \'.$_'

    Qui va prendre les noms de fichiers en entrée et sortir le hash et le nom de fichier (pas testé). Tu peut ajouter des rafinements coté perl pour ne prendre que le nom du fichier (sans le chemin ni l'extension) et tronquer le hash comme tu le souhaite.

    Ainsi finalement tu pourra faire un script de la forme :

    find "${dir}" | perl -MDigest::MD5 -lnE 'print md5_hex($_).\' \'.$_' | ./mon_script.sh

    Et ça devrait, AMHA, aller beaucoup mieux coté perf (find n'est lancé qu'une fois, perl aussi).


    1. je parle de perl parce que c'est classique d'utiliser perl pour ça, mais tu peux le faire avec ce qui te plaît. 

    Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

    • [^] # Re: Trop de fork

      Posté par  (site web personnel) . Évalué à 2.

      C'est chaque nom de dossier et de fichier dont la longueur doit être vérifier et écourter si nécessaire. La somme MD5 est alors utiliser pour éviter les éventuelles collisions.

      J'aime pas non plus faire deux find dans chaque dossier de manière récursive.

      Mais c'est le seul moyen que j'ai trouvé pour ne pas "scier la branche sur laquelle je suis assis".

      Comment utiliser find pour trouver les fichiers et dossiers don le nom est plus long que n caractères ?

      Si je ne fait qu'un seul find, le traitement du résultat sera complexe car si je renomme le nom d'un dossier, cela invalide tout les chemins passant par-là… Je devrait donc m'assurer de traiter les résultat dans un ordre bien déterminé… Qu'en pensez-vous ?

      • [^] # Re: Trop de fork

        Posté par  . Évalué à 5.

        Je devrait donc m'assurer de traiter les résultat dans un ordre bien déterminé…

        Ça tombe bien, l'option -depth de find est là pour ça. Par défaut il faut un parcourt en largeur avec -depth, il présente les fichiers et dossiers du dossier courant avant le dossier courant.

        Si je ne fait qu'un seul find, le traitement du résultat sera complexe car si je renomme le nom d'un dossier, cela invalide tout les chemins passant par-là…

        Au contraire si find t'assure d'avoir l'ordre qui va bien tu ne t'occupe plus que de vérifier la taille, faire de la substitution et un mv qui va bien.

        Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

      • [^] # Re: Trop de fork

        Posté par  . Évalué à 3. Dernière modification le 16 septembre 2014 à 15:49.

        A vu de nez, j'aurai écrit

        $ find <rep> | xargs -n 1 basename

        te donnera la liste des noms de fichiers répertoire compris. Tu mets ça dans un for puis tu calcule la longueur du nom.

      • [^] # Re: Trop de fork

        Posté par  (site web personnel) . Évalué à 2. Dernière modification le 16 septembre 2014 à 16:20.

        Bah un sort -r te fera trier traiter les fichiers en premier puis les répertoires

        find ... | sort -r | while read filename; do
        ...
          if [ -d "${filename}" ]; then
            ...
          else # tout le reste c'est fichier, lien, socket, etc
            ...
          fi
          ...
        done

        Et encore le test sur répertoire est pas vraiment nécessaire, tu travailles sur le nom et pis c'est tout. Ça te permet de remplacer ton trunc_dir et trunc_item.

        • [^] # Re: Trop de fork

          Posté par  . Évalué à 5.

          L'option --depth de find est largement plus efficace sort demande à ce que find finisse totalement son traitement on perd tout l'intérêt du pipe.

          Tous les contenus que j'écris ici sont sous licence CC0 (j'abandonne autant que possible mes droits d'auteur sur mes écrits)

          • [^] # Re: Trop de fork

            Posté par  . Évalué à 3.

            J'ai dû mal comprendre le problème initial, car pour moi on parlait de la longueur du nom du répertoire ou du fichier. Pas de la profondeur de la récursivité (--depth).

            • [^] # Re: Trop de fork

              Posté par  (site web personnel) . Évalué à 2.

              Dans ce que dit Michel, le -depth sert à trier comme le ferait le | sort -r.

            • [^] # Re: Trop de fork

              Posté par  (site web personnel) . Évalué à 4.

              Tu confonds -depth avec -maxdepth et -mindepth. L'option -depth demande à find d'explorer l'arborescence de fichiers en profondeur tandisque les deux autres options contrôlent le niveau de récursion.

  • # Nouvelle version...

    Posté par  (site web personnel) . Évalué à 3.

    … beaucoup plus rapide !
    Merci à tous :)

    #!/bin/bash
    
    ## Up is a bash function to simplify vertical file system navigation.
    ## 
    ## Copyright (C) 2013 Serge Smeesters
    ## 
    ## This program is free software: you can redistribute it
    ## and/or modify it under the terms of the GNU General Public License
    ## as published by the Free Software Foundation, either version 3
    ## of the License, or (at your option) any later version.
    ## 
    ## This program is distributed in the hope that it will be useful,
    ## but WITHOUT ANY WARRANTY; without even the implied warranty of
    ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
    ## See the GNU General Public License for more details.
    ## 
    ## You should have received a copy of the GNU General Public License
    ## along with this program. If not, see http://www.gnu.org/licenses/.
    
    mydir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
    [ -d $mydir ] || exit 4
    configfile="$mydir/spm_namelength_config.sh"
    
    if [ -f "$configfile" ]
    then
        . "$configfile"
    else
        echo "No config file find :("
        exit 4
    fi
    
    trunc_directory ()
    {
        local shortsize
        (( shortsize = sizemax - HASH_SIZE ))
    
        local PATTERN="" ; for i in `seq 1 $sizemax`; do PATTERN="${PATTERN}?"; done; PATTERN="${PATTERN}*"
    
        local filefullpathname
        find "$root" -depth -name "$PATTERN" | \
        while read filefullpathname ; do
    
            local filefullname=$(basename "$filefullpathname")
    
            local filepath=$(dirname "$filefullpathname")
            local filename=${filefullname%.*}
            local filextension=${filefullname##*.}
            local hashvalue="$(echo "$filename" | md5sum | cut -c1-$HASH_SIZE)"
    
            echo "--------------------------------"
            echo "↓ $filefullname"
    
            local filextensionsize=${#filextension}
    
            if [ $filextensionsize -gt $EXTENTION_MAX_SIZE ] ; then
                local fileshortfullname="$(echo "$filefullname" | cut -c1-$shortsize)$hashvalue"
            else
                local newsize
                (( newsize = shortsize - ( filextensionsize + 1 ) ))
                local fileshortname="$(echo "$filename" | cut -c1-$newsize)$hashvalue"
                local fileshortfullname="$fileshortname.$filextension"
            fi
    
            echo "→ $fileshortfullname"
    
            local fileshortfullpathname="$filepath/$fileshortfullname"
    
            if [ -e "$fileshortfullpathname" ] ; then
                echo "Waw... the shorten file seam to yet exists ! :("
                exit 4
            fi
    
            pushd "$filepath" >/dev/null
                echo "mv \"$fileshortfullname\" \"$filefullname\"" >> .namelengthrestore
                mv "$filefullname" "$fileshortfullname"
            popd >/dev/null
    
        done
    }
    
    restore_directory ()
    {
        local filefullpathname
        find "$root" -depth -type f -name ".namelengthrestore" | \
        while read filefullpathname ; do
    
            local filepath=$(dirname "$filefullpathname")
            pushd "$filepath" >/dev/null
                cat .namelengthrestore
                . .namelengthrestore
                rm .namelengthrestore
            popd >/dev/null
    
        done
    }
    
    main ()
    {
        if [ $# -lt 2 ] ; then
            echo "Less than 2 arguments :("
            exit 1
        fi
    
        # echo "\$2 : \"$2\""
        local root="$(readlink -f "$2")"
        # echo "root : \"$root\""
        if [ ! -d "$root" ] ; then
            echo "\"$root\" don't appear to be a directory :("
            exit 1
        fi
    
        case "$1" in
            trunc)
    
                if [ $# -lt 3 ] ; then
                    echo "Trunc need 2 arguments..."
                    exit 2
                fi
    
                local sizemax="$3"
    
                if [ ! "$sizemax" ] ; then exit 1 ; fi
    
                local minsizemax
                (( minsizemax = 5 * HASH_SIZE + EXTENTION_MAX_SIZE ))
                # echo "A reasonable sizemax should be $minsizemax"
                if [ "$sizemax" -lt "$minsizemax" ] ; then
                    echo "sizemax, $sizemax less then minsizemax, $minsizemax :("
                    exit 1
                fi
    
                trunc_directory
            ;;
    
           restore)
                restore_directory
           ;;
    
           *)
              echo "Usage: $0 {trunc <DIR> <MAXLENGTH> |restore <DIR>} " >&2
              exit 1
              ;;
        esac
    }
    
    main "$@"

Suivre le flux des commentaires

Note : les commentaires appartiennent à celles et ceux qui les ont postés. Nous n’en sommes pas responsables.