304 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			304 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
#!/bin/bash
 | 
						|
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
 | 
						|
source "$(dirname "$0")/lib/ulib/ulib" || exit 1
 | 
						|
urequire DEFAULTS multiconf
 | 
						|
 | 
						|
function display_help() {
 | 
						|
    uecho "$scriptname: classer des fichiers selon certains règles
 | 
						|
 | 
						|
Les règles sont spécifiées dans le fichier ~/etc/default/ufile
 | 
						|
 | 
						|
Dans ce fichier, le tableau RULES contient des règles qui sont chacune de la
 | 
						|
forme pattern:destdir[:renamef]
 | 
						|
 | 
						|
- pattern est au format glob et identifie les fichiers auxquels s'applique la
 | 
						|
  règle, sauf si la chaine commence par / auquel cas il s'agit d'une expression
 | 
						|
  régulière reconnue par awk. Par exemple, les deux patterns suivants sont
 | 
						|
  équivalents:
 | 
						|
    [ab]cd*.pdf
 | 
						|
    /[ab]cd.*\\\\.pdf\$
 | 
						|
- destdir est le répertoire de destination dans lequel classer le fichier
 | 
						|
- renamef est une fonction qui permet de supporter le renommage d'un fichier
 | 
						|
  lors de son classement. Sa signature est 'renamef filename pf destdir' où
 | 
						|
  filename est le nom du fichier source, pf son chemin complet et destdir la
 | 
						|
  valeur de destdir mentionnée dans la règle.
 | 
						|
  La fonction doit définir la variable dest qui est le nouveau nom. Si la
 | 
						|
  fonction retourne un code d'erreur autre que zéro, la règle est ignorée.
 | 
						|
  Si le nouveau nom contient un chemin, destdir est ignoré et le fichier est
 | 
						|
  déplacé dans le répertoire spécifié avec le nom spécifié. Si dest est un
 | 
						|
  tableau avec plusieurs destinations, alors le fichier est copié en plusieurs
 | 
						|
  fois.
 | 
						|
  Si dest est de la forme [user@]host:path alors le fichier est copié par scp
 | 
						|
  sur l'hôte spécifié vers la destination spécifiée, sauf si l'hôte courant est
 | 
						|
  déjà celui mentionné dans la valeur, auquel cas la copie est faite directement
 | 
						|
  dans le répertoire spécifié.
 | 
						|
  Si le user et l'hôte courant sont déjà à la valeur spécifiée, alors la copie
 | 
						|
  est faite en local sans utiliser scp sauf si l'option --force-scp est utilisée
 | 
						|
  Le chemin spécifié, en local ou distant, est toujours le chemin complet vers
 | 
						|
  le fichier destination. Si on veut copier le fichier sans le renommer vers un
 | 
						|
  répertoire, il faut mettre un slash e.g destdir/ ou user@host:destdir/
 | 
						|
  variables pouvant être définies mais non documentées: interaction(=-i)
 | 
						|
 | 
						|
USAGE
 | 
						|
    $scriptname [options] <files...>
 | 
						|
    $scriptname [options] -r <files|dirs...>
 | 
						|
 | 
						|
OPTIONS
 | 
						|
    -c, --config CONFIG
 | 
						|
        Utiliser le fichier de configuration spécifié au lieu de la valeur par
 | 
						|
        défaut ~/etc/default/ufile
 | 
						|
    --file
 | 
						|
        Classer les fichiers spécifiés. C'est l'action par défaut
 | 
						|
    --force-cp
 | 
						|
        Spécifier le mode de classement des fichiers. Par défaut, le fichier est
 | 
						|
        déplacé dans la destination s'il s'agit d'un classement local, ou copié
 | 
						|
        s'il s'agit d'un classement distant. Avec --force-cp, le fichier est
 | 
						|
        systématiquement copié dans la destination.
 | 
						|
    -S, --ssh SSH
 | 
						|
        S'il faut classer sur un hôte distant avec scp, utiliser le programme
 | 
						|
        spécifié pour la connexion par ssh
 | 
						|
    --force-scp
 | 
						|
        Toujours utiliser scp pour une copie distante. Par défaut s'il est
 | 
						|
        déterminé que l'hôte distant est en réalité l'hôte courant, alors la
 | 
						|
        copie est effectuée directement.
 | 
						|
    -f, --force
 | 
						|
        Si le fichier destination existe, alors l'écraser sans confirmation.
 | 
						|
        Cette option est ignorée pour un classement distant.
 | 
						|
    -r, --recursive
 | 
						|
        Classer récursivement tous les fichiers d'un répertoire. Sans cette
 | 
						|
        option, il n'est pas autorisé de fournir un répertoire comme argument.
 | 
						|
    -n, --fake
 | 
						|
        Afficher les opérations qui seraient faites
 | 
						|
    -l, --list
 | 
						|
        Lister les règles définies
 | 
						|
    -e, --edit
 | 
						|
        Lancer un éditeur sur le fichier de configuration"
 | 
						|
}
 | 
						|
 | 
						|
function joinp() {
 | 
						|
    # afficher le chemin $1/$2
 | 
						|
    local pf="$1"
 | 
						|
    [ -n "$2" -a "${pf%/}" == "$pf" ] && pf="$pf/"
 | 
						|
    pf="$pf${2#/}"
 | 
						|
    echo "$pf"
 | 
						|
}
 | 
						|
 | 
						|
function __check_destdir() {
 | 
						|
    local destdir="$1" rule="$2"
 | 
						|
    if [ -z "$destdir" ]; then
 | 
						|
        eerror "$rule: règle invalide: destdir est vide"
 | 
						|
        return 1
 | 
						|
    fi
 | 
						|
    return 0
 | 
						|
}
 | 
						|
function __set_dest() {
 | 
						|
    local dest="$1" destdir="$2" filename="$3" force_scp="$4"
 | 
						|
    local userhost remotedir destname
 | 
						|
    if [[ "$dest" == *:* ]]; then
 | 
						|
        splitpair "$dest" userhost remotedir
 | 
						|
        if [ -z "$force_scp" ] && check_userhostname "$userhost"; then
 | 
						|
            # on est déjà avec le bon user sur le bon hôte
 | 
						|
            if [ -n "$remotedir" ]; then
 | 
						|
                splitpath "$remotedir" destdir destname
 | 
						|
                setx destdir=abspath "$destdir" "$HOME"
 | 
						|
                [ -n "$destname" ] || destname="$filename"
 | 
						|
                setx dest=joinp "$destdir" "$destname"
 | 
						|
            else
 | 
						|
                setx dest=joinp "$HOME" "$filename"
 | 
						|
            fi
 | 
						|
        fi
 | 
						|
    elif [[ "$dest" == */* ]]; then
 | 
						|
        splitpath "$dest" destdir destname
 | 
						|
        [ -n "$destname" ] || destname="$filename"
 | 
						|
        setx dest=joinp "$destdir" "$destname"
 | 
						|
        setx dest=abspath "$dest"
 | 
						|
    else
 | 
						|
        __check_destdir "$destdir" "$rule" || return 1
 | 
						|
        setx dest=joinp "$destdir" "$dest"
 | 
						|
    fi
 | 
						|
    upvar dest "$dest"
 | 
						|
    return 0
 | 
						|
}
 | 
						|
 | 
						|
args=(%
 | 
						|
    --help '$exit_with display_help'
 | 
						|
    -c:,--config: config=
 | 
						|
    --file action=file
 | 
						|
    --force-cp force_cp=1
 | 
						|
    -S:,--ssh: SSH=
 | 
						|
    --force-scp force_scp=1
 | 
						|
    -f,--force force=1
 | 
						|
    -r,--recursive recursive=1
 | 
						|
    -n,--fake fake=1
 | 
						|
    -l,--list action=list
 | 
						|
    -e,--edit action=edit
 | 
						|
)
 | 
						|
parse_args "$@"; set -- "${args[@]}"
 | 
						|
 | 
						|
[ -n "$action" ] || action=file
 | 
						|
 | 
						|
## charger toutes les règles
 | 
						|
 | 
						|
conf_init -a RULES
 | 
						|
if [ -n "$config" ]; then
 | 
						|
    if [ "$action" != edit ]; then
 | 
						|
        # le fichier doit exister, sauf en mode édition où il sera créé s'il
 | 
						|
        # n'existe pas déjà
 | 
						|
        [ -f "$config" ] || die "$config: fichier introuvable"
 | 
						|
    fi
 | 
						|
    if [ -f "$config" ]; then
 | 
						|
        source "$config" || die "$config: erreur lors de la lecture du fichier"
 | 
						|
    fi
 | 
						|
else
 | 
						|
    set_defaults ufile
 | 
						|
fi
 | 
						|
conf_load "$HOME/etc/ufile.d/*.conf"
 | 
						|
 | 
						|
## actions particulières
 | 
						|
 | 
						|
if [ "$action" == list ]; then
 | 
						|
    array_to_lines RULES
 | 
						|
    exit 0
 | 
						|
elif [ "$action" == edit ]; then
 | 
						|
    [ -n "$config" ] || setx config=get_user_defaults_file ufile
 | 
						|
    if [ ! -f "$config" ]; then
 | 
						|
        einfo "Le fichier $(ppath "$config") n'existe pas. Il sera créé avec un contenu par défaut"
 | 
						|
        mkdirof "$config" || die
 | 
						|
        cp "$scriptdir/lib/default/ufile" "$config"
 | 
						|
    fi
 | 
						|
    "${EDITOR:-vi}" "$config"
 | 
						|
    exit $?
 | 
						|
elif [ "$action" != file ]; then
 | 
						|
    die "bug: $action: action non implémentée"
 | 
						|
fi
 | 
						|
 | 
						|
## classement des fichiers
 | 
						|
 | 
						|
if [ -n "$fake" ]; then
 | 
						|
    function docmd() { qvals "$@"; }
 | 
						|
else
 | 
						|
    function docmd() { "$@"; }
 | 
						|
fi
 | 
						|
 | 
						|
[ $# -gt 0 ] || die "Vous devez spécifier des fichiers à classer"
 | 
						|
[ ${#RULES[*]} -gt 0 ] || die "Vous devez spécifier des règles pour le classement des fichiers dans ~/etc/default/ufile ou ~/etc/ufile.d/*.conf"
 | 
						|
 | 
						|
# vérifier les règles
 | 
						|
for rule in "${RULES[@]}"; do
 | 
						|
    splitpair "$rule" pattern r2
 | 
						|
    splitpair "$r2" destdir r3
 | 
						|
    splitpair "$r3" renamef r4
 | 
						|
    if [ -z "$destdir" -o "${destdir#"~/"}" != "$destdir" ]; then
 | 
						|
        :
 | 
						|
    elif [ "${destdir#/}" == "$destdir" ]; then
 | 
						|
        ewarn "$rule: règle potentiellement problématique: destdir devrait être absolu"
 | 
						|
    fi
 | 
						|
done
 | 
						|
 | 
						|
# faire la liste des fichiers
 | 
						|
files=()
 | 
						|
for file in "$@"; do
 | 
						|
    if [ -d "$file" -a -n "$recursive" ]; then
 | 
						|
        setx file=abspath "$file"
 | 
						|
        array_from_lines rfiles "$(find "$file" -type f)"
 | 
						|
        array_extendu files rfiles
 | 
						|
    elif [ -f "$file" ]; then
 | 
						|
        setx file=abspath "$file"
 | 
						|
        array_addu files "$file"
 | 
						|
    elif [ -n "$fake" ]; then
 | 
						|
        : # on est en mode fake, pas grave si le fichier n'est pas trouvé
 | 
						|
    elif [ -d "$file" ]; then
 | 
						|
        eerror "$file: est un répertoire. essayez avec -r"
 | 
						|
    else
 | 
						|
        eerror "$file: fichier introuvable. il sera ignoré"
 | 
						|
    fi
 | 
						|
done
 | 
						|
 | 
						|
# faire le classement effectif
 | 
						|
r=
 | 
						|
for file in "${files[@]}"; do
 | 
						|
    setx pf=abspath "$file"
 | 
						|
    setx dir=dirname -- "$pf"
 | 
						|
    setx filename=basename -- "$pf"
 | 
						|
    found=
 | 
						|
    for rule in "${RULES[@]}"; do
 | 
						|
        splitpair "$rule" pattern r2
 | 
						|
        splitpair "$r2" odestdir r3
 | 
						|
        splitpair "$r3" renamef r4
 | 
						|
        if [ "${odestdir#"~/"}" != "$odestdir" ]; then
 | 
						|
            odestdir="$HOME/${odestdir#"~/"}"
 | 
						|
        elif [ "$odestdir" == "~" ]; then
 | 
						|
            odestdir="$HOME"
 | 
						|
        fi
 | 
						|
 | 
						|
        if [ "${pattern#/}" != "$pattern" ]; then
 | 
						|
            awk -v filename="$filename" -v pattern="${pattern#/}" 'BEGIN { exit(filename ~ pattern? 0: 1) }' || continue
 | 
						|
        else
 | 
						|
            eval "[[ \"\$filename\" == $(qwc "$pattern") ]]" || continue
 | 
						|
        fi
 | 
						|
 | 
						|
        unset dest
 | 
						|
        interaction=--DEFAULT--
 | 
						|
        if [ -n "$renamef" ]; then
 | 
						|
            "$renamef" "$filename" "$pf" "$odestdir" || continue
 | 
						|
        fi
 | 
						|
        if is_array dest; then
 | 
						|
            array_copy tmpdests dest
 | 
						|
            dests=()
 | 
						|
            for dest in "${tmpdests[@]}"; do
 | 
						|
                __set_dest "$dest" "$odestdir" "$filename" "$force_scp" || break
 | 
						|
                array_add dests "$dest"
 | 
						|
            done
 | 
						|
        elif is_defined dest; then
 | 
						|
            __set_dest "$dest" "$odestdir" "$filename" "$force_scp" || break
 | 
						|
            dests=("$dest")
 | 
						|
        else
 | 
						|
            __check_destdir "$odestdir" "$rule" || break
 | 
						|
            setx dest=joinp "$odestdir" "$filename"
 | 
						|
            dests=("$dest")
 | 
						|
        fi
 | 
						|
 | 
						|
        i=1
 | 
						|
        mvi=${#dests[*]}
 | 
						|
        [ -z "$force" ] && mvint=-i || mvint=
 | 
						|
        for dest in "${dests[@]}"; do
 | 
						|
            if [[ "$dest" == *:* ]]; then
 | 
						|
                [ "$interaction" == --DEFAULT-- ] && int= || int="$interaction"
 | 
						|
                estep "$filename --> $dest"
 | 
						|
                ask_yesno $int "Voulez-vous continuer?" O || {
 | 
						|
                    r=1; found=x; break
 | 
						|
                }
 | 
						|
                docmd scp ${SSH:+-S "$SSH"} "$file" "$dest" || die "problème lors de la copie du fichier"
 | 
						|
 | 
						|
            else
 | 
						|
                [ "$interaction" == --DEFAULT-- ] && int=-i || int="$interaction"
 | 
						|
                estep "$filename --> $dest"
 | 
						|
                ask_yesno $int "Voulez-vous continuer?" O || {
 | 
						|
                    r=1; found=x; break
 | 
						|
                }
 | 
						|
                setx destdir=dirname -- "$dest"
 | 
						|
                docmd mkdir -p "$destdir" || die "$destdir: impossible de créer le répertoire"
 | 
						|
                if [ $i -eq $mvi -a -z "$force_cp" ]; then
 | 
						|
                    mvdesc="du déplacement"
 | 
						|
                    mvcmd=mv
 | 
						|
                else
 | 
						|
                    mvdesc="de la copie"
 | 
						|
                    mvcmd=cp
 | 
						|
                fi
 | 
						|
                docmd "$mvcmd" $mvint "$file" "$dest" || die "problème lors $mvdesc du fichier"
 | 
						|
            fi
 | 
						|
            i=$(($i + 1))
 | 
						|
        done
 | 
						|
 | 
						|
        [ -n "$found" ] || found=1
 | 
						|
        break
 | 
						|
    done
 | 
						|
 | 
						|
    if [ -z "$found" ]; then
 | 
						|
        ewarn "$file: aucune correspondance n'a été trouvée"
 | 
						|
    fi
 | 
						|
done
 | 
						|
 | 
						|
[ -n "$r" ] || r=0
 | 
						|
exit $r
 |