mardi 31 juillet 2012

Mettre à jour des propriétaires de fichier sous Unix (niveau avancé)

Il arrive parfois, des malheurs. Dernièrement, il m'ai arrivé, au travail, sous Linux en root de faire un rm -rf /*.
Ceux connaissant bien Unix comprenne l'étendu de la catastrophe !
Heureusement, seuls les répertoires "sbin", bin et etc on été supprimés.

Fort chanceusement, les postes sont tous des clones.
J'ai donc recopié les répertoires en question.

Oui mais voilà, le répertoire etc contient le fichier passwd et group qui associe UID et nom d'utilisateur.
De ce fait, je me retrouve avec des noms d'utilisateur inversé...

J'aurais pu m'arranger avec ces fichiers, mais les risques d'erreurs étaient trop nombreux.
Mieux vaut utiliser chmod.

D'accord, mais comment savoir les fichiers concernés et comment modifier juste l'utilisateur si c'est l'utilisateur qui est mauvais, ou simplement le groupe ?
Nous allons partir du principe que l'utilisateur toto doit devenir l'utilisateur emeric.

Avant d'écrire un script, il est bon d'essayer de le faire à la main pour comprendre ce qui va se passer.

La commande ls -l permet de lister les fichiers et répertoires avec leurs informations. Le premier problème vient du fait que si c'est un répertoire, le contenu est listé.
La parade ? ls -l -d, -d pour afficher un répertoire comme s'il s'agissait d'un fichier.
$> ls -l
 drwxr-xr-x   2 root  wheel      512 Aug 13  2008 altroot/
 drwxr-xr-x   2 root  wheel     1024 Aug 13  2008 bin/
 -rw-r--r--   1 root  wheel    43348 Feb 20 01:52 boot
 -rw-r--r--   1 root  wheel  6861562 Aug 13  2008 bsd
 -rw-r--r--   1 root  wheel  5534077 Aug 13  2008 bsd.rd
 drwxr-xr-x   3 root  wheel    20992 Apr  3 08:29 dev/
 drwxr-xr-x  19 root  wheel     2560 Apr  3 08:29 etc/
$>
On a donc comme information le user, le group et le nom du fichier.
La difficulté vient du fait que les champs sont séparés avec un ou plusieurs espaces.

Le contournement va faire appel à une astuce du BASH, les tableaux et IFS (internal field separator).

BASH ne connait que les chaînes de caractères. Pour lui, un tableau est simplement une chaîne avec des séparateurs.
Pour créer un tableau, il suffit de déclarer :
monTableau=( 1 2 3 )
Nous allons redéfinir l'IFS car par défaut, il n'est pas initialisé comme on le souhaite, et des effets de bord risque d'apparaître.
On le définit comme un espace :
IFS=' '
Si nous prenons une ligne pour faire un test à la main, voici ce qu'on peut faire :
$> IFS=' '
$> TAB=( $(echo ' -rw-r--r--   1 root  wheel    43348 Feb 20 01:52 boot') ) 
$> echo ${#TAB[*]} / ${TAB[*]}
9/-rw-r--r-- 1 root wheel 43348 Feb 20 01:52 boot
On voit donc que les multiples espaces ne sont pas pris en compte. En effet, BASH initialise une "case" de tableau que s'il y a une valeur.

Il sera donc nécessaire de faire le ls -l -d sur chaque fichier unitairement. Pour cela, find sera utilisé. On utilisera la capacité de find pour exécuter le ls sur les fichiers trouvés va l'option -exec.

Les éléments sont suffisants pour créer déjà ce premier script
#!/bin/sh

# Sauvegarde l'ancien field separator
OLDIFS="$IFS";

# Saut de ligne est le separateur
IFS_NL=$'\n'
IFS_SPACE=' '

IFS="$IFS_NL"

# Recherche des fichiers uniquement
for fichier in $(find $1 -exec ls -l -d {} \;)
do
    # Definit le fiel separator en tant qu'espace
    IFS="$IFS_SPACE" ;

    fichier_propriete=( $(echo $fichier) )

    username=${fichier_propriete[2]}
    groupname=${fichier_propriete[3]}
    filename=${fichier_propriete[8]}

    echo ------------
    echo $username
    echo $groupname
    echo $filename
    echo -----------

    IFS="$IFS_NL"
done

IFS="$OLDIFS"
A présent, il ne reste plus qu'à détecter les fichiers concernés et faire la modification nécessaire (user ou group ou user/group). U simple grep sur la ligne retournée par find sera suffisant.
Enfin, pas tout à fait. Une fois que la ligne est identifiée comme devant être modifiée, il faudra de nouveau utiliser grep.

Voici enfin le rendu final :
#!/bin/sh

PATTERN_USER=toto
REPLACE_USER=emeric

# Sauvegarde l'ancien field separator
OLDIFS="$IFS";

# Saut de ligne est le separateur
IFS_NL=$'\n'
IFS_SPACE=' '

IFS="$IFS_NL"

# Recherche des fichiers uniquement
for fichier in $(find $1 -exec ls -l -d {} \;)
do
    user_detect=$(echo $fichier | grep $PATTERN_USER)

    if [ -n "$user_detect" ] ;
    then
        # Definit le fiel separator en tant qu'espace
        IFS="$IFS_SPACE" ;

        fichier_propriete=( $(echo $fichier) )

        username=${fichier_propriete[2]}
        groupname=${fichier_propriete[3]}
        filename=${fichier_propriete[8]}

        echo "Proceed file "$filename

        if [ -n $(echo "$username" | grep "$PATTERN_USER") ] ;
        then
            echo "  > Change user name"
            username="$REPLACE_USER"
        fi

        if [ -n $(echo "$groupname" | grep "$PATTERN_USER") ] ;
        then
            echo "  > Change groupe name"
            groupname="$REPLACE_USER"
        fi

        chown "$username":"$groupname" "$filename"

        IFS="$IFS_NL"
    fi
done

IFS="$OLDIFS" 
Attention toutefois, si le nom de fichier contient un espace, l'utilisation de fichier_propriete[8] posera un problème car il n'y aura que la première partie du nom.

Comment résoudre ce problème ? En utilisant le nom de fichier récupéré par find plutôt que celui de ls.
Pour cela, il faudra modifier le début du script :
# Recherche des fichiers uniquement
for fichier in $(find $1)
do
    ligne=$(ls -l -d $fichier)
    user_detect=$(echo $ligne | grep $PATTERN_USER)

    if [ -n "$user_detect" ] ;
    then
        # Definit le fiel separator en tant qu'espace
        IFS="$IFS_SPACE" ;

        fichier_propriete=( $(echo $ligne) )

        username=${fichier_propriete[2]}
        groupname=${fichier_propriete[3]}
        filename="$fichier"

Aucun commentaire:

Enregistrer un commentaire