#!/bin/bash # script pour renommer des personnages ou lieux dans un document Manuskript # ./renomme [ins=poids] [del=poids] [rep=poids] # # Le document d'origine est sauvegardé (voir variable $backup) # # Si est "check", ou "prox" ou "leven", une étude de proximité est alors effectuée # sur l'algorithme Levenshtein. Les paramètres de poids sont des nombres entiers, et permettent # de pondérer l'ajout (ins=), la suppression (del=) et le remplacement (rep=) de caractère. # par défaut chacun des trois paramètres est égal à 1. # Il n'y a pas d'ordre obligatoire pour le paramétrage, et si un paramétrage est effectué plusieurs fois # c'est le plus à gauche qui prend la priorité. Il n'y a pas de backup effectué pour l'opération de vérification de la proximité # crédit algo: https://en.wikibooks.org/wiki/Algorithm_Implementation/Strings/Levenshtein_distance set -e manuscrit=${1:?} ancien=${2:?} nouveau=${3:?} if test "${manuscrit:0:1}" != '/'; then manuscrit="$PWD/$manuscrit" fi test -r "$manuscrit" || exit 1 test $(file --brief --mime-type --dereference "$manuscrit") == 'application/zip' || exit 2 backup="${manuscrit/%.msk} (avant renommage de «${ancien}» en «${nouveau}»).msk" function trap_exit () { rm -fr $temp cd - > /dev/null } function determinant () { eval "local nom=\"\$$1\"" if [[ ${nom:0:1} == @(A|E|H|I|O|U) \ || ${nom:0:1} == @(Â|Ê|H|Î|Ô|Û) \ || ${nom:0:1} == @(Ä|Ë|H|Ï|Ö|Ü) \ || ${nom:0:1} == @(À|È|H|Ì|Ò|Ù) \ || ${nom:0:1} == @(Á|É|H|Í|Ó|Ú) \ ]]; then eval "determinant_$1=\"d'\"" eval "determinant_$1_formatted=\"d-\"" else eval "determinant_$1=\"de \"" eval "determinant_$1_formatted=\"de-\"" fi } function format () { eval "$1_formatted=\$(tr --complement --squeeze-repeats 'A-Za-z-_\n' - <<< \"\${$1// /_}\")" } function renomme_fichiers () { for char in $(find characters -type f -regex "characters/[0-9]+-$1.txt"); do local new_char=$(sed "s/$1/$2/" <<< $char) echo "character: $char -> $new_char" mv $char $new_char break done for chapter in $(find outline -type d -regex "outline/.*$1.*"); do local new_chapter=$(sed "s/$1/$2/g" <<< $chapter) echo "chapter: $chapter -> $new_chapter" mv $chapter $new_chapter done for part in $(find outline -type f -regex "outline/[^/]*$1[^/]*.md"); do local new_part=$(sed "s/$1/$2/g" <<< $part) echo "part: $part -> $new_part" mv $part $new_part done } trap trap_exit EXIT temp=$(mktemp --directory /dev/shm/XXXXXXXXX) cd $temp if [[ $nouveau = @(check|prox|leven) ]]; then unzip -qq "$manuscrit" for param in $(seq 4 $#); do for dst in ins del rep max; do eval "if test -n '\$$param' && [[ \"\$$param\" =~ $dst=[0-9]+ ]]; then cost_$dst=\${$param#*=}; fi" done done for dst in ins del rep; do eval "cost_$dst=\${cost_$dst:-1}" done cost_max=${cost_max:-$(($cost_ins + $cost_del + $cost_rep))} if test $cost_max -ge ${#ancien}; then cost_max=$(( ${#ancien} - 1 )) fi echo paramètres d\'approximation echo "caractère manquant (del=): $cost_del" echo "caractère inséré (ins=): $cost_ins" echo "caractère remplacé (rep=): $cost_rep" echo "distance maximale (max=): $cost_max" for f in $(find . -type f); do let wc+=$(wc -w < $f) done awk -v ancien=$ancien -v wc=$wc -v cost_ins=$cost_ins -v cost_del=$cost_del -v cost_rep=$cost_rep -v cost_max=$cost_max ' BEGIN { RS="[[:punct:]]" progress_mod = 10 actual_progress = 0 pct_progress = 0 progress = 0 found_words = 0 cost_tot = cost_ins + cost_del + cost_rep cost_tot = cost_tot > cost_max ? cost_max : cost_tot str1_len = length(ancien) for (i=1; i<=str1_len; i++) str1_substr[i]=substr(ancien, i, 1) } function levenshtein(str2) { str2_len = length(str2) if(str2_len == 0) return str1_len * cost_del for(j = 1; j <= str2_len; j++) str2_substr[j]=substr(str2, j, 1) matrix[0, 0] = 0 for(i = 1; i <= str1_len; i++) { matrix[i, 0] = i * cost_del for(j = 1; j <= str2_len; j++) { matrix[0, j] = j * cost_ins x = matrix[i - 1, j] + cost_del y = matrix[i, j - 1] + cost_ins z = matrix[i - 1, j - 1] + (str1_substr[i] == str2_substr[j] ? 0 : cost_rep) x = x < y ? x : y matrix[i, j] = x < z ? x : z } } return matrix[str1_len, str2_len] } { for (word=1; word<=NF; word++) { progress++ lvstn = levenshtein(gensub("[[:punct:]]","","g",$word)) if (lvstn <= cost_tot && lvstn > 0) { key = sprintf("%s (%d)", $word, lvstn) approx_possibles[key]++ found_words++ } pct_progress=int(progress / wc * 100) if (actual_progress < pct_progress && pct_progress % progress_mod == 0) { actual_progress = pct_progress printf("%i%\n", actual_progress) } } } END { if (found_words > 0) { pluriel = found_words > 1 ? "s" : "" printf("mot%s proche%s de «%s» (distance) [occurences]\n", pluriel, pluriel, ancien) for (i in approx_possibles) printf("- %s [%i]\n", i, approx_possibles[i]) } else { print "aucun mot proche et différent de «" ancien "» trouvé" } } ' $(find . -type f) exit fi mv --backup=numbered "$manuscrit" "$backup" unzip -qq "$backup" for version in ancien nouveau; do format $version determinant $version done declare -A remplacement remplacement=( [$ancien]="$nouveau" [$ancien_formatted]="$nouveau_formatted" [$determinant_ancien$ancien]="$determinant_nouveau$nouveau" [$determinant_ancien_formatted$ancien_formatted]="$determinant_nouveau_formatted$nouveau_formatted" ) renomme_fichiers "$ancien_formatted" "$nouveau_formatted" renomme_fichiers "$determinant_ancien_formatted$ancien_formatted" "$determinant_nouveau_formatted$nouveau_formatted" egrep --word-regexp --only-matching --recursive --regexp="($determinant_ancien|$determinant_ancien_formatted)\?($ancien|$ancien_formatted)" . \ | awk -v name="$1" -F ':' ' { if ($NF > 0) { file[$1]++ nb++ } } END { printf("remplacement de %i occurences pour %s dans %i fichiers\n", nb, name, asort(file)) }' for regexp in "${!remplacement[@]}"; do egrep --word-regexp --files-with-matches --recursive --regexp="$regexp" . \ | xargs --no-run-if-empty sed --regexp-extended --in-place "s/(\W|^)$regexp(\W|$)/\1${remplacement[$regexp]}\2/g" done zip --recurse-paths --no-dir-entries -qq "${manuscrit}" *