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"