Scripts utiles pour l’administration système Unix

Copier une arborescence complète par FTP

Pour copier une arborescence complète en utilisant un client FTP basique qui n’est pas capable de récupérer récursivement un ensemble de répertoires et sous-répertoires.

#!/bin/ksh
# Copie une arborescence complete par FTP
# Usage :
#   ftp -n < <(ksh  ./ftp_upload_tree.sh SOURCE DESTINATION USERNAME PASSWORD HOSTNAME)
SOURCE="$1"
DESTINATION="$2"
USER="$3"
PASSWORD="$4"
HOSTNAME="$5"
# ----------------------------------
printf 'open "%s"\n' "$HOSTNAME"
printf 'user "%s" "%s"\n' "$USER" "$PASSWORD"
cd "$SOURCE"
old_rep=""
while IFS= read -r fic; do
    if [ -d "$fic" ]; then
        printf 'mkdir "%s/%s"\n' "$DESTINATION" "$fic"
    elif [ -f "$fic" ]; then
        rep=$(dirname "$fic")
        if [[ "$rep" != "$old_rep" ]]; then
            old_rep="$rep"
            printf 'cd "%s/%s"\n' "$DESTINATION" "$rep"
            printf 'lcd "%s/%s"\n' "$SOURCE" "$rep"
        fi
        basefic=$(basename "$fic")
        printf 'put "%s"\n' "$basefic"
    fi
done < <(find . -print|sort)
printf "bye\n"
# EOF

Récupérer les permissions

Récupérer les droit unix d’un répertoire sous forme numérique (ex. 750 pour drwxr-x--) :

# Usage : get_octal_permission_for_directory /home/myrep
get_octal_permission_for_directory()
{
   ls -ld $1| awk '{k=0;for(i=0;i<=8;i++)k+=((substr($1,i+2,1)~/[rwx]/)*2^(8-i));if(k)printf("%0o",k);}'
}

Récupérer les droit unix d’un fichier sous forme numérique (ex. 750 pour -rwxr-x--) :

# Usage : get_octal_permission_for_file /home/myrep/myfile
get_octal_permission_for_file()
{
   ls -l $1| awk '{k=0;for(i=0;i<=8;i++)k+=((substr($1,i+1,1)~/[rwx]/)*2^(8-i));if(k)printf("%0o",k);}'
}

Renommer des fichiers dont le nom contient des espaces

Pour traiter l’ensemble des répertoire, lancer plusieurs fois (dépend du nombre de sous répertoires imbriqués) :

find . -type d | sed -e  "s/.*/\"&\"/g;p;y/\ /_/" | xargs -n2 mv

Pour traiter l’ensemble des fichiers une fois que les répertoires sont propres :

find . -type f | sed -e  "s/.*/\"&\"/g;p;y/\ /_/" | xargs -n2 mv

Comparaison de fichiers / droits entre deux plateformes

Ce script est utile pour comparer une arborescence de fichiers entre deux serveurs et mettre en évidence des différences entre les droits et propriétaires de fichiers ou des différences de taille.

Sur les deux serveurs à comparer, il faut extraire la liste des fichiers dans l’arborescence où l’on souhaite effectuer la comparaison au moyen de la procédure qui suit.

Sur le serveur de référence

find /applis/projects/PROD/directory -ls > /tmp/serveur_de_reference.txt

Sur le serveur à comparer

find /applis/projects/PROD/directory -ls > /tmp/serveur_a_comparer.txt

Astuce : si dans le chemin un nom de répertoire est dépendant du serveur ou de l’installation, il peut être utile de le modifier avec sed lors de la génération pour permettre une comparaison indépendante de la plateforme :

find /applis/projects/PROD/directory -ls | sed s/pcyym14p6/SERVEUR/g  > /tmp/serveur_de_reference.txt

Comparaison des fichiers résultats

Ensuite, rapatrier les deux fichiers sur votre poste et lancer d’utilitaire :

comparateur.py serveur_de_reference.txt serveur_a_comparer.txt > resultat.csv

Le code est visible en fin d’article et vous pouvez le télécharger ici : comparateur.py.

Options possibles

Quelques options sont possibles :

  • -d : répertoire. Compare uniquement les répertoires (donc juste les droits et les propriétaires)
  • -t : taille. Compare la taille des fichiers en plus des comparaisons standards. Cet argument ne sert à rien si l’argument -d est utilisé
  • -h : aide

Un mode correctif est possible :

  • -u : affichage des suggestions de commandes pour la correction des UID
  • -g : affichage des suggestions de commandes pour la correction des GID
  • -p : affichage des suggestions de commandes pour la correction des permissions

Traitement des résultats

Le fichier généré est un fichier CSV utilisable avec OpenOffice ou Excel. Il comprend quatre colonnes :

  • Raison : la raison de la remontée
  • Fichier : le chemin du fichier concerné
  • Attendu : l’état attendu (sur le serveur de référence)
  • Trouve : l’état trouvé (sur le serveur à comparer)
Raison Fichier Attendu Trouve
FNF htdocs/WebHelp.4/whskin_tbars.htm -rwxr-x--- 3458
FNF htdocs/WebHelp.1/whgdata/whlstf56.htm -rwxr-x--- 40357
FEP Plugins/config/WebSrv02 drwxr-x--- 256

Pour les raisons, voici les typologies remontées :

  • FNF : Fichier présent sur le serveur de référence mais non trouvé sur le serveur cible
  • DNC : Droits non conformes entre les deux serveurs
  • UID : Le propriétaire du fichier est différent
  • GID : Le groupe propriétaire du fichier est différent
  • TNC : Taille non conforme entre les deux serveurs
  • FEP : Fichier en plus. Présent sur le serveur à comparer, mais pas sur le serveur de référence

Code de comparateur.py

Voici le code de comparateur.py :

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
comparateur.py - version and date, see below

Author : Alexandre Norman - norman at xael.org
Licence : GPL v3 or any later version

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
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/>.

===============
Documentation :
===============

Ce script permet de comparer deux environnements. Il faut
lui fournir la liste des fichiers de ces deux environnements
issues de la commande 'find . -ls'

"""
__author__ = 'Alexandre Norman (norman at xael.org)'
__version__ = '0.2.0'
__last_modification__ = '2014.07.27'

import sys

try:
    import clize
except ImportError:
    print("This program uses clize. See : https://github.com/epsy/clize")
    sys.exit(1)

############################################################################


def analyse_fichier(fichier, uniquement_repertoires=False):
    """
    Recupere la sortie d'une commande 'find . -ls' et la transforme en objets analysables.
    """
    with open(fichier) as file_handle:
        all_files = {}
        for line in file_handle.readlines():
            # 50725038    4 drwxrwxr-x   7 xael     xael         4096 Oct 21 00:10 ../actuvelo_veilleurs
            try:
                inode, size_in_block, droits, unknow, uid, gid, taille, month, day, time_or_year = line.split()[0:10]
            except ValueError:
                print('ERR', line[0:67], line[0:67].split())

            if uniquement_repertoires:
                if droits[0] != 'd':
                    continue

            filename = ' '.join(line.split()[10:]).strip()

            all_files[filename] = {
                'droits': droits,
                'uid': uid,
                'gid': gid,
                'taille': taille,
                'month': month,
                'day': day,
                'time_or_year': time_or_year
                }
    return all_files

############################################################################


def print_error(raison, fichier, attendu='', trouve=''):
    """
    Affiche l'erreur au format CSV
    """
    print('"{0}";"{1}";"{2}";"{3}"'.format(raison, fichier.replace('"', '""'),
                                           attendu.replace('"', '""'),
                                           trouve.replace('"', '""')))
    return

############################################################################


def conversion_droits(droits):
    """
    """
    def doctal(droits):
        """
        """
        doct = 0
        if 'r' in droits:
            doct += 4
        if 'w' in droits:
            doct += 2
        if 'x' in droits:
            doct += 1
        return str(doct)

    uid = droits[1:4]
    gid = droits[4:7]
    others = droits[7:10]
    return doctal(uid) + doctal(gid) + doctal(others)


############################################################################

def analyse(reference="liste_originale.txt", a_comparer="liste_modifiee.txt", 
            uniquement_repertoires=False, comparaison_taille=False, correction_uid=False,
            correction_gid=False, correction_permissions=False):
    """
    Analyse les fichiers fournis en entree
    """
    fr = analyse_fichier(reference, uniquement_repertoires)
    ac = analyse_fichier(a_comparer, uniquement_repertoires)

    mode_correction = correction_permissions or correction_gid or correction_uid

    if not mode_correction:
        print_error('Raison', 'Fichier', 'Attendu', 'Trouve')

    for fichier in fr:
        if uniquement_repertoires:
            if fr[fichier]['droits'][0] != 'd':
                continue

        if fichier not in ac:
            # FNT : Fichier non trouve
            if not mode_correction:
                print_error('FNF', fichier, fr[fichier]['droits'], fr[fichier]['taille'])
        else:
            if fr[fichier]['droits'] != ac[fichier]['droits']:
                # DNC : Droits non conformes
                if not mode_correction:
                    print_error('DNC', fichier, fr[fichier]['droits'], ac[fichier]['droits'])
                elif correction_permissions:
                    print('chmod {1} {0}'.format(fichier, conversion_droits(fr[fichier]['droits'])))

            if fr[fichier]['uid'] != ac[fichier]['uid']:
                # UID : Uid non conforme
                if not mode_correction:
                    print_error('UID', fichier, fr[fichier]['uid'], ac[fichier]['uid'])
                elif correction_uid:
                    print('chown {1} {0}'.format(fichier, fr[fichier]['uid']))

            if fr[fichier]['gid'] != ac[fichier]['gid']:
                # GID : Gid non conforme
                if not mode_correction:
                    print_error('GID', fichier, fr[fichier]['gid'], ac[fichier]['gid'])
                elif correction_gid:
                    print('chgrp {1} {0}'.format(fichier, fr[fichier]['gid']))

            if not mode_correction:
                if fr[fichier]['droits'][0] != 'd' and comparaison_taille and fr[fichier]['taille'] != ac[fichier]['taille']:
                    # TNC : Taille non conforme
                    print_error('TNC', fichier, fr[fichier]['taille'], ac[fichier]['taille'])

    if not mode_correction:
        for fichier in ac:
            if ac[fichier]['droits'][0] != 'd':
                continue
            if not fichier in fr:
                # FEP : Fichier en plus
                print_error('FEP', fichier, ac[fichier]['droits'], ac[fichier]['taille'])

    return

############################################################################

def __show_version__(name, **kwargs):
    """
    Show version
    """
    import os
    print("{0} version {1}".format(os.path.basename(name), __version__))
    return True


############################################################################

@clize.clize(
    alias = {
        'reference': ('r',),
        'compare': ('c',),
        'taille': ('t',),
        'repertoires': ('d',),
        'correction_uid': ('u',),
        'correction_gid': ('g',),
        'correction_permissions': ('p',),
        },
    extra = (
        clize.make_flag(
            source=__show_version__,
            names=('version', 'v'),
            help="Show the version",
            ),
        )
    )
def __main__(reference, compare, repertoires=False, taille=False, correction_uid=False, correction_gid=False, correction_permissions=False):
    """
    comparateur.py

    reference: nom du fichier de reference

    compare: nom du fichier a comparer

    taille: compare egalement la taille

    repertoires: compare uniquement les repertoires

    Ce script permet de comparer deux environnements. Il faut
    lui fournir la liste des fichiers de ces deux environnements
    issues de la commande 'find . -ls' en tant que reference et compare


    Written by : Alexandre Norman <norman at xael.org>
    """

    analyse(reference=reference, a_comparer=compare, uniquement_repertoires=repertoires, 
            comparaison_taille=taille, correction_uid=correction_uid, 
            correction_gid=correction_gid, correction_permissions=correction_permissions)
    return

############################################################################

# MAIN -------------------
if __name__ == '__main__':
    clize.run(__main__)
    sys.exit(0)

#<EOF>######################################################################