nutools/ufile

309 lines
11 KiB
Plaintext
Raw Normal View History

#!/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
2017-10-17 22:45:47 +04:00
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.
2017-04-22 10:08:13 +04:00
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...>
2017-09-22 21:14:16 +04:00
$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 et ~/etc/ufile.d/*.conf
-C, --other-configs
Charger les fichiers ~/etc/ufile.d/*.conf en plus du fichier spécifié
avec --config. Cette option est ignorée si --config n'est pas utilisé.
--file
Classer les fichiers spécifiés. C'est l'action par défaut
2017-07-19 23:12:06 +04:00
--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.
2017-09-22 21:14:16 +04:00
-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
2017-10-17 22:45:47 +04:00
setx dest=joinp "$destdir" "$dest"
fi
upvar dest "$dest"
return 0
}
args=(%
--help '$exit_with display_help'
-c:,--config: config=
-C,--other-configs other_configs=1
--file action=file
2017-07-19 23:12:06 +04:00
--force-cp force_cp=1
2017-04-22 10:08:13 +04:00
-S:,--ssh: SSH=
--force-scp force_scp=1
-f,--force force=1
2017-09-22 21:14:16 +04:00
-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
[ -n "$other_configs" ] && conf_load "$HOME/etc/ufile.d/*.conf"
else
set_defaults ufile
conf_load "$HOME/etc/ufile.d/*.conf"
fi
## 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 $?
2017-04-22 10:08:13 +04:00
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
2017-09-22 21:14:16 +04:00
# faire la liste des fichiers
files=()
for file in "$@"; do
2017-09-22 21:14:16 +04:00
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é"
2017-09-22 21:14:16 +04:00
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
2017-10-17 22:45:47 +04:00
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"
2017-04-22 10:08:13 +04:00
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"
2017-07-19 23:12:06 +04:00
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
2017-04-22 10:08:13 +04:00
[ -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