Ces scripts, bien que ne rentrant pas dans le texte de ce document, illustrent quelques techniques intéressantes de programmation shell. Ils sont aussi utiles. Amusez-vous à les analyser et à les lancer.
Exemple A.1. mailformat : Formater un courrier électronique
#!/bin/bash # mail-format.sh (ver. 1.1) : Formate les courriers électroniques. # Supprime les caractères '>', les tabulations et coupe aussi les lignes #+ excessivement longues. # ================================================================= # Vérification standard des argument(s) du script ARGS=1 E_MAUVAISARGS=65 E_PASDEFICHIER=66 if [ $# -ne $ARGS ] # Le bon nombre d'arguments a-t'il été passé au script? then echo "Usage: `basename $0` nomfichier" exit $E_MAUVAISARGS fi if [ -f "$1" ] # Vérifie si le fichier existe. then nomfichier=$1 else echo "Le fichier \"$1\" n'existe pas." exit $E_PASDEFICHIER fi # ================================================================= LONGUEUR_MAX=70 # Longueur à partir de laquelle on coupe les lignes excessivement longues. # --------------------------------- # Une variable peut contenir un script sed. scriptsed='s/^>// s/^ *>// s/^ *// s/ *//' # --------------------------------- # Supprime les caractères '>' et tabulations en début de lignes, #+ puis coupe les lignes à $LONGUEUR_MAX caractères. sed "$scriptsed" $1 | fold -s --width=$LONGUEUR_MAX # option -s pour couper les lignes à une espace blanche, si possible. # Ce script a été inspiré par un article d'un journal bien connu #+ proposant un utilitaire Windows de 164Ko pour les mêmes fonctionnalités. # # Un joli ensemble d'utilitaires de manipulation de texte et un langage de #+ scripts efficace apportent une alternative à des exécutables gonflés. exit 0
Exemple A.2. rn : Un utilitaire simple pour renommer des fichiers
Ce script est une modification de l'Exemple 15.20, « lowercase : Change tous les noms de fichier du répertoire courant en minuscule. ».
#! /bin/bash # # Un très simplifié "renommeur" de fichiers (basé sur "lowercase.sh"). # # L'utilitaire "ren", par Vladimir Lanin (lanin@csd2.nyu.edu), #+ fait un bien meilleur travail que ceci. ARGS=2 E_MAUVAISARGS=65 UN=1 # Pour avoir correctement singulier ou pluriel # (voir plus bas.) if [ $# -ne "$ARGS" ] then echo "Usage: `basename $0` ancien-modele nouveau-modele" # Comme avec "rn gif jpg", qui renomme tous les fichiers gif du répertoire #+ courant en jpg. exit $E_MAUVAISARGS fi nombre=0 # Garde la trace du nombre de fichiers renommés. for fichier in *$1* # Vérifie tous les fichiers correspondants du répertoire. do if [ -f "$fichier" ] # S'il y a correspondance... then fname=`basename $fichier` # Supprime le chemin. n=`echo $fname | sed -e "s/$1/$2/"` # Substitue ancien par nouveau dans # le fichier. mv $fname $n # Renomme. let "nombre += 1" fi done if [ "$nombre" -eq "$UN" ] # Pour une bonne grammaire. then echo "$nombre fichier renommé." else echo "$nombre fichiers renommés." fi exit 0 # Exercices: # --------- # Avec quel type de fichiers cela ne fonctionnera pas? # Comment corriger cela? # # Réécrire ce script pour travailler sur tous les fichiers d'un répertoire, #+ contenant des espaces dans leur noms, et en les renommant après avoir #+ substitué chaque espace par un tiret bas.
Exemple A.3. blank-rename : Renommer les fichiers dont le nom contient des espaces
C'est une version encore plus simple du script précédent.
#! /bin/bash # blank-rename.sh # # Substitue les tirets soulignés par des blancs dans tous les fichiers d'un #+ répertoire. UN=1 # Pour obtenir le singulier/pluriel correctement (voir # plus bas). nombre=0 # Garde trace du nombre de fichiers renommés. TROUVE=0 # Valeur de retour en cas de succès. for fichier in * #Traverse tous les fichiers du répertoire. do echo "$fichier" | grep -q " " # Vérifie si le nom du fichier if [ $? -eq $TROUVE ] #+ contient un (des) espace(s). then nomf=$fichier # Oui, ce nom de fichier doit être travaillé. n=`echo $nomf | sed -e "s/ /_/g"` # Remplace l'espace par un tiret. mv "$nomf" "$n" # Réalise le renommage. let "nombre += 1" fi done if [ "$nombre" -eq "$UN" ] # Pour une bonne grammaire. then echo "$nombre fichier renommé." else echo "$nombre fichiers renommés." fi exit 0
Exemple A.4. encryptedpw : Charger un fichier sur un site ftp, en utilisant un mot de passe crypté en local
#!/bin/bash # Exemple "ex72.sh" modifié pour utiliser les mots de passe cryptés. # Notez que c'est toujours moyennement sécurisé, car le mot de passe décrypté #+ est envoyé en clair. # Utilisez quelque chose comme "ssh" si cela vous préoccupe. E_MAUVAISARGS=65 if [ -z "$1" ] then echo "Usage: `basename $0` nomfichier" exit $E_MAUVAISARGS fi NomUtilisateur=bozo # Changez suivant vos besoins. motpasse=/home/bozo/secret/fichier_avec_mot_de_passe_crypte # Le fichier contient un mot de passe crypté. Nomfichier=`basename $1` # Supprime le chemin du fichier Serveur="XXX" # Changez le nom du serveur et du répertoire suivant Repertoire="YYY" #+ vos besoins. MotDePasse=`cruft <$motpasse` # Décrypte le mot de passe. # Utilise le paquetage de cryptage de fichier de l'auteur, #+ basé sur l'algorithme classique "onetime pad", #+ et disponible à partir de: #+ Site primaire: ftp://ibiblio.org/pub/Linux/utils/file #+ cruft-0.2.tar.gz [16k] ftp -n $Serveur <<Fin-de-Session user $NomUtilisateur $MotDePasse binary bell cd $Repertoire put $Nomfichier bye Fin-de-Session # L'option -n de "ftp" désactive la connexion automatique. # Notez que "bell" fait sonner une cloche après chaque transfert. exit 0
Exemple A.5. copy-cd : Copier un CD de données
#!/bin/bash # copy-cd.sh: copier un CD de données CDROM=/dev/cdrom # périphérique CD ROM OF=/home/bozo/projects/cdimage.iso # fichier de sortie # /xxxx/xxxxxxx/ A modifier suivant votre système. TAILLEBLOC=2048 VITESSE=2 # Utiliser une vitesse supèrieure #+ si elle est supportée. PERIPHERIQUE=cdrom #PERIPHERIQUE="0,0" pour les anciennes versions de cdrecord echo; echo "Insérez le CD source, mais ne le montez *pas*." echo "Appuyez sur ENTER lorsque vous êtes prêt. " read pret # Attendre une entrée, $pret n'est # pas utilisé. echo; echo "Copie du CD source vers $OF." echo "Ceci peut prendre du temps. Soyez patient." dd if=$CDROM of=$OF bs=$TAILLEBLOC # Copie brute du périphérique. echo; echo "Retirez le CD de données." echo "Insérez un CDR vierge." echo "Appuyez sur ENTER lorsque vous êtes prêt. " read pret # Attendre une entrée, $pret n'est # pas utilisé. echo "Copie de $OF vers CDR." cdrecord -v -isosize speed=$VITESSE dev=$PERIPHERIQUE $OF # Utilise le paquetage "cdrecord" de Joerg Schilling's (voir sa doc). # http://www.fokus.gmd.de/nthp/employees/schilling/cdrecord.html echo; echo "Copie terminée de $OF vers un CDR du périphérique $CDROM." echo "Voulez-vous écraser le fichier image (o/n)? " # Probablement un fichier # immense. read reponse case "$reponse" in [oO]) rm -f $OF echo "$OF supprimé." ;; *) echo "$OF non supprimé.";; esac echo # Exercice: # Modifiez l'instruction "case" pour aussi accepter "oui" et "Oui" comme #+ entrée. exit 0
Exemple A.6. collatz : Séries de Collatz
#!/bin/bash # collatz.sh # Le célèbre "hailstone" ou la série de Collatz. # ---------------------------------------------- # 1) Obtenir un entier "de recherche" à partir de la ligne de commande. # 2) NOMBRE <--- seed # 3) Afficher NOMBRE. # 4) Si NOMBRE est pair, divisez par 2, ou # 5)+ si impair, multiplier par 3 et ajouter 1. # 6) NOMBRE <--- résultat # 7) Boucler à l'étape 3 (pour un nombre spécifié d'itérations). # # La théorie est que chaque séquence, quelle soit la valeur initiale, #+ se stabilisera éventuellement en répétant des cycles "4,2,1...", #+ même après avoir fluctuée à travers un grand nombre de valeurs. # # C'est une instance d'une "itération", une opération qui remplit son #+ entrée par sa sortie. # Quelque fois, le résultat est une série "chaotique". MAX_ITERATIONS=200 # Pour une grande échelle de nombre (>32000), augmenter MAX_ITERATIONS. h=${1:-$$} # Nombre de recherche # Utiliser $PID comme nombre de recherche, #+ si il n'est pas spécifié en argument de la #+ ligne de commande. echo echo "C($h) --- $MAX_ITERATIONS Iterations" echo for ((i=1; i<=MAX_ITERATIONS; i++)) do echo -n "$h " # ^^^^^ # tab let "reste = h % 2" if [ "$reste" -eq 0 ] # Pair? then let "h /= 2" # Divise par 2. else let "h = h*3 + 1" # Multiplie par 3 et ajoute 1. fi COLONNES=10 # Sortie avec 10 valeurs par ligne. let "retour_ligne = i % $COLONNES" if [ "$retour_ligne" -eq 0 ] then echo fi done echo # Pour plus d'informations sur cette fonction mathématique, #+ voir _Computers, Pattern, Chaos, and Beauty_, par Pickover, p. 185 ff., #+ comme listé dans la bibliographie. exit 0
Exemple A.7. days-between : Calculer le nombre de jours entre deux dates
#!/bin/bash # days-between.sh: Nombre de jours entre deux dates. # Usage: ./days-between.sh [M]M/[D]D/AAAA [M]M/[D]D/AAAA # # Note: Script modifié pour tenir compte des changements dans Bash 2.05b + #+ qui ont fermé la "fonctionnalité" permettant de renvoyer des valeurs #+ entières négatives grandes. ARGS=2 # Deux arguments attendus en ligne de commande. E_PARAM_ERR=65 # Erreur de paramètres. ANNEEREF=1600 # Année de référence. SIECLE=100 JEA=365 AJUST_DIY=367 # Ajusté pour l'année bissextile + fraction. MEA=12 JEM=31 CYCLE=4 MAXRETVAL=256 # Valeur de retour positive la plus grande possible #+ renvoyée par une fonction. diff= # Déclaration d'une variable globale pour la différence #+ de date. value= # Déclaration d'une variable globale pour la valeur #+ absolue. jour= # Déclaration de globales pour jour, mois, année. mois= annee= Erreur_Param () # Mauvais paramètres en ligne de commande. { echo "Usage: `basename $0` [M]M/[D]D/YYYY [M]M/[D]D/YYYY" echo " (la date doit être supérieure au 1/3/1600)" exit $E_PARAM_ERR } Analyse_Date () # Analyse la date à partir des paramètres en { #+ ligne de commande. mois=${1%%/**} jm=${1%/**} # Jour et mois. jour=${dm#*/} let "annee = `basename $1`" # Pas un nom de fichier mais fonctionne de la #+ même façon. } verifie_date () # Vérifie la validité d'une date. { [ "$jour" -gt "$JEM" ] || [ "$mois" -gt "$MEA" ] || [ "$annee" -lt "$ANNEEREF" ] && Erreur_Param # Sort du script si mauvaise(s) valeur(s). # Utilise une liste-ou ou une liste-et. # # Exercice: Implémenter une vérification de date plus rigoureuse. } supprime_zero_devant () # Il est préférable de supprimer les zéros possibles { #+ du jour et/ou du mois sinon Bash va les val=${1#0} #+ interpréter comme des valeurs octales return $val #+ (POSIX.2, sect 2.9.2.1). } index_jour () # Formule de Gauss: { # Nombre de jours du 1er mars 1600 jusqu'à la date passée # en arguments. jour=$1 mois=$2 annee=$3 let "mois = $mois - 2" if [ "$mois" -le 0 ] then let "mois += 12" let "annee -= 1" fi let "annee -= $ANNEEREF" let "indexyr = $annee / $SIECLE" let "Jours = $JEA*$annee + $annee/$CYCLE - $indexyr \ + $indexyr/$CYCLE + $AJUST_DIY*$mois/$MEA + $jour - $JEM" # Pour une explication en détails de cet algorithme, voir #+ http://weblogs.asp.net/pgreborio/archive/2005/01/06/347968.aspx echo $Days } calcule_difference () # Différence entre les indices de deux jours. { let "diff = $1 - $2" # Variable globale. } abs () # Valeur absolue. { # Utilise une variable globale "valeur". if [ "$1" -lt 0 ] # Si négatif then #+ alors let "value = 0 - $1" #+ change de signe, else #+ sinon let "value = $1" #+ on le laisse. fi } if [ $# -ne "$ARGS" ] # Requiert deux arguments en ligne de commande. then Erreur_Param fi Analyse_Date $1 verifie_date $jour $mois $annee # Vérifie si la date est valide. supprime_zero_devant $jour # Supprime tout zéro débutant jour=$? #+ sur le jour et/ou le mois. supprime_zero_devant $mois mois=$? let "date1 = `day_index $jour $mois $annee`" Analyse_Date $2 verifie_date $jour $mois $annee supprime_zero_devant $jour jour=$? supprime_zero_devant $mois mois=$? date2 = $(day_index $jour $mois $annee) # Substitution de commande calcule_difference $date1 $date2 abs $diff # S'assure que c'est positif. diff=$value echo $diff exit 0 # Comparez ce script avec l'implémentation de la formule de Gauss en C sur #+ http://buschencrew.hypermart.net/software/datedif
Exemple A.8. makedict : Créer un « dictionnaire »
#!/bin/bash # makedict.sh [make dictionary] # Modification du script /usr/sbin/mkdict. # Script original copyright 1993, par Alec Muffett. # # Ce script modifié inclus dans ce document d'une manière consistente avec le #+ document "LICENSE" du paquetage "Crack" dont fait partie le script original. # Ce script manipule des fichiers texte pour produire une liste triée de mots #+ trouvés dans les fichiers. # Ceci pourrait être utile pour compiler les dictionnaires et pour des #+ recherches lexicographiques. E_MAUVAISARGS=65 if [ ! -r "$1" ] # Au moins un argument, qui doit être then #+ un fichier valide. echo "Usage: $0 fichiers-à-manipuler" exit $E_MAUVAISARGS fi # SORT="sort" # Plus nécessaire de définir des options #+ pour sort. Modification du script #+ original. cat $* | # Contenu des fichiers spécifiés vers stdout. tr A-Z a-z | # Convertion en minuscule. tr ' ' '\012' | # Nouveau: modification des espaces en #+ retours chariot. # tr -cd '\012[a-z][0-9]' | # Suppression de tout ce qui n'est pas # alphanumérique #+ (script original). tr -c '\012a-z' '\012' | # Plutôt que de supprimer, #+ modification des non alpha en retours #+ chariot. sort | # Les options $SORT ne sont plus #+ nécessaires maintenant. uniq | # Suppression des mots dupliqués. grep -v '^#' | # Suppression des lignes commençant avec #+ le symbole '#'. grep -v '^$' # Suppression des lignes blanches. exit 0
Exemple A.9. soundex : Conversion phonétique
#!/bin/bash # soundex.sh: Calcule le code "soundex" pour des noms # ======================================================= # Script soundex # par # Mendel Cooper # thegrendel@theriver.com # 23 Janvier 2002 # # Placé dans le domaine public. # # Une version légèrement différente de ce script est apparu dans #+ la colonne "Shell Corner" d'Ed Schaefer en juillet 2002 #+ du magazine en ligne "Unix Review", #+ http://www.unixreview.com/documents/uni1026336632258/ # ======================================================= NBARGS=1 # A besoin du nom comme argument. E_MAUVAISARGS=70 if [ $# -ne "$NBARGS" ] then echo "Usage: `basenom $0` nom" exit $E_MAUVAISARGS fi affecte_valeur () # Affecte une valeur numérique { #+ aux lettres du nom. val1=bfpv # 'b,f,p,v' = 1 val2=cgjkqsxz # 'c,g,j,k,q,s,x,z' = 2 val3=dt # etc. val4=l val5=mn val6=r # Une utilisation particulièrement intelligente de 'tr' suit. # Essayez de comprendre ce qui se passe ici. valeur=$( echo "$1" \ | tr -d wh \ | tr $val1 1 | tr $val2 2 | tr $val3 3 \ | tr $val4 4 | tr $val5 5 | tr $val6 6 \ | tr -s 123456 \ | tr -d aeiouy ) # Affecte des valeurs aux lettres. # Supprime les numéros dupliqués, sauf s'ils sont séparés par des voyelles. # Ignore les voyelles, sauf en tant que séparateurs, donc les supprime à la fin. # Ignore 'w' et 'h', même en tant que séparateurs, donc les supprime au début. # # La substitution de commande ci-dessus utilise plus de tube qu'un plombier # <g>. } nom_en_entree="$1" echo echo "Nom = $nom_en_entree" # Change tous les caractères en entrée par des minuscules. # ------------------------------------------------ nom=$( echo $nom_en_entree | tr A-Z a-z ) # ------------------------------------------------ # Au cas où cet argument est un mélange de majuscules et de minuscules. # Préfixe des codes soundex: première lettre du nom. # -------------------------------------------- pos_caract=0 # Initialise la position du caractère. prefixe0=${nom:$pos_caract:1} prefixe=`echo $prefixe0 | tr a-z A-Z` # Met en majuscule la première lettre de soundex. let "pos_caract += 1" # Aller directement au deuxième caractères. nom1=${nom:$pos_caract} # ++++++++++++++++++++++++++ Correctif Exception +++++++++++++++++++++++++++++++++ # Maintenant, nous lançons à la fois le nom en entrée et le nom décalé d'un #+ caractère vers la droite au travers de la fonction d'affectation de valeur. # Si nous obtenons la même valeur, cela signifie que les deux premiers #+ caractères du nom ont la même valeur et que l'une d'elles doit être annulée. # Néanmoins, nous avons aussi besoin de tester si la première lettre du nom est #+ une voyelle ou 'w' ou 'h', parce que sinon cela va poser problème. caract1=`echo $prefixe | tr A-Z a-z` # Première lettre du nom en minuscule. affecte_valeur $nom s1=$valeur affecte_valeur $nom1 s2=$valeur affecte_valeur $caract1 s3=$valeur s3=9$s3 # Si la première lettre du nom est une #+ voyelle ou 'w' ou 'h', #+ alors sa "valeur" sera nulle (non #+ initialisée). #+ Donc, positionnons-la à 9, une autre #+ valeur non utilisée, qui peut être #+ vérifiée. if [[ "$s1" -ne "$s2" || "$s3" -eq 9 ]] then suffixe=$s2 else suffixe=${s2:$pos_caract} fi # ++++++++++++++++++++++ fin Correctif Exception +++++++++++++++++++++++++++++++++ fin=000 # Utilisez au moins 3 zéro pour terminer. soun=$prefixe$suffixe$fin # Terminez avec des zéro. LONGUEURMAX=4 # Tronquer un maximum de 4 caractères soundex=${soun:0:$LONGUEURMAX} echo "Soundex = $soundex" echo # Le code soundex est une méthode d'indexage et de classification de noms #+ en les groupant avec ceux qui sonnent de le même façon. # Le code soundex pour un nom donné est la première lettre de ce nom, suivi par #+ un code calculé sur trois chiffres. # Des noms similaires devraient avoir les mêmes codes soundex # Exemples: # Smith et Smythe ont tous les deux le soundex "S-530" # Harrison = H-625 # Hargison = H-622 # Harriman = H-655 # Ceci fonctionne assez bien en pratique mais il existe quelques anomalies. # # # Certaines agences du gouvernement U.S. utilisent soundex, comme le font les # généalogistes. # # Pour plus d'informations, voir #+ "National Archives and Records Administration home page", #+ http://www.nara.gov/genealogy/soundex/soundex.html # Exercice: # -------- # Simplifier la section "Correctif Exception" de ce script. exit 0
Exemple A.10. « life : Jeu de la Vie »
#!/bin/bash # life.sh: "Life in the Slow Lane" # Version 2: Corrigé par Daniel Albers #+ pour permettre d'avoir en entrée des grilles non carrées. # ############################################################################### # # Ce script est la version Bash du "Jeu de la vie" de John Conway. # # "Life" est une implémentation simple d'automatisme cellulaire. # # ------------------------------------------------------------------------------- # # Sur un tableau rectangulaire, chaque "cellule" sera soit "vivante" # # soit "morte". On désignera une cellule vivante avec un point et une # # cellule morte avec une espace. # # Nous commençons avec un tableau composé aléatoirement de points et # #+ d'espaces. Ce sera la génération de départ, "génération 0". # # Déterminez chaque génération successive avec les règles suivantes : # # 1) Chaque cellule a huit voisins, les cellules voisines (gauche, # #+ droite, haut, bas ainsi que les quatre diagonales. # # 123 # # 4*5 # # 678 # # # # 2) Une cellule vivante avec deux ou trois voisins vivants reste # #+ vivante. # # 3) Une cellule morte avec trois cellules vivantes devient vivante # #+ (une "naissance"). # SURVIE=2 # NAISSANCE=3 # # 4) Tous les autres cas concerne une cellule morte pour la prochaine génération. # # ############################################################################### # fichier_de_depart=gen0 # Lit la génération de départ à partir du fichier "gen0". # Par défaut, si aucun autre fichier n'est spécifié à #+ l'appel de ce script. # if [ -n "$1" ] # Spécifie un autre fichier "génération 0". then fichier_de_depart="$1" fi ###################################################### # Annule le script si fichier_de_depart non spécifié #+ ET #+ gen0 non présent. E_PASDEFICHIERDEPART=68 if [ ! -e "$fichier_de_depart" ] then echo "Fichier de départ \""$fichier_de_depart"\" manquant !" exit $E_PASDEFICHIERDEPART fi ###################################################### VIVANT1=. MORT1=_ # Représente des cellules vivantes et "mortes" dans le fichier de départ. # ---------------------------------------------------------- # # Ce script utilise un tableau 10 sur 10 (pourrait être augmenté #+ mais une grande grille ralentirait de beaucoup l'exécution). LIGNES=10 COLONNES=10 # Modifiez ces deux variables pour correspondre à la taille #+ de la grille, si nécessaire. # ---------------------------------------------------------- # GENERATIONS=10 # Nombre de générations pour le cycle. # Ajustez-le en l'augmentant si vous en avez le temps. AUCUNE_VIVANTE=80 # Code de sortie en cas de sortie prématurée, #+ si aucune cellule n'est vivante. VRAI=0 FAUX=1 VIVANTE=0 MORTE=1 avar= # Global; détient la génération actuelle. generation=0 # Initialise le compteur des générations. # ================================================================= let "cellules = $LIGNES * $COLONNES" # Nombre de cellules. declare -a initial # Tableaux contenant les "cellules". declare -a current affiche () { alive=0 # Nombre de cellules "vivantes" à un moment donné. # Initialement à zéro. declare -a tab tab=( `echo "$1"` ) # Argument convertit en tableau. nombre_element=${#tab[*]} local i local verifligne for ((i=0; i<$nombre_element; i++)) do # Insère un saut de ligne à la fin de chaque ligne. let "verifligne = $i % COLONNES" if [ "$verifligne" -eq 0 ] then echo # Saut de ligne. echo -n " " # Indentation. fi cellule=${tab[i]} if [ "$cellule" = . ] then let "vivante += 1" fi echo -n "$cellule" | sed -e 's/_/ /g' # Affiche le tableau et modifie les tirets bas en espaces. done return } EstValide () # Teste si les coordonnées sont valides. { if [ -z "$1" -o -z "$2" ] # Manque-t'il des arguments requis ? then return $FAUX fi local ligne local limite_basse=0 # Désactive les coordonnées négatives. local limite_haute local gauche local droite let "limite_haute = $LIGNES * $COLONNES - 1" # Nombre total de cellules. if [ "$1" -lt "$limite_basse" -o "$1" -gt "$limite_haute" ] then return $FAUX # En dehors des limites. fi ligne=$2 let "gauche = $ligne * $COLONNES" # Limite gauche. let "droite = $gauche + $COLONNES - 1" # Limite droite. if [ "$1" -lt "$gauche" -o "$1" -gt "$droite" ] then return $FAUX # En dehors des limites. fi return $VRAI # Coordonnées valides. } EstVivante () # Teste si la cellule est vivante. # Prend un tableau, un numéro de cellule et un état de #+ cellule comme arguments. { ObtientNombre "$1" $2 # Récupère le nombre de cellules vivantes dans le voisinage. local voisinage=$? if [ "$voisinage" -eq "$NAISSANCE" ] # Vivante dans tous les cas. then return $VIVANTE fi if [ "$3" = "." -a "$voisinage" -eq "$SURVIE" ] then # Vivante uniquement si précédemment vivante. return $VIVANTE fi return $MORTE # Par défaut. } ObtientNombre () # Compte le nombre de cellules vivantes dans le # voisinage de la cellule passée en argument. # Deux arguments nécessaires : # $1) tableau contenant les variables # $2) numéro de cellule { local numero_cellule=$2 local tableau local haut local centre local bas local l local ligne local i local t_hau local t_cen local t_bas local total=0 local LIGNE_NHBD=3 tableau=( `echo "$1"` ) let "haut = $numero_cellule - $COLONNES - 1" # Initialise le voisinage de la #+ cellule. let "centre = $numero_cellule - 1" let "bas = $numero_cellule + $COLONNES - 1" let "l = $numero_cellule / $COLONNES" for ((i=0; i<$LIGNE_NHBD; i++)) # Parcours de gauche à droite. do let "t_hau = $haut + $i" let "t_cen = $centre + $i" let "t_bas = $bas + $i" let "ligne = $l" # Calcule la ligne centrée du voisinage. EstValide $t_cen $ligne # Position de la cellule valide ? if [ $? -eq "$VRAI" ] then if [ ${tableau[$t_cen]} = "$VIVANT1" ] # Est-elle vivante ? then # Oui ? let "total += 1" # Incrémenter le total. fi fi let "ligne = $l - 1" # Compte la ligne du haut. EstValide $t_haut $haut if [ $? -eq "$VRAI" ] then if [ ${tableau[$t_haut]} = "$VIVANT1" ] then let "total += 1" fi fi let "ligne = $l + 1" # Compte la ligne du bas. EstValide $t_bas $ligne if [ $? -eq "$VRAI" ] then if [ ${tableau[$t_bas]} = "$VIVANT1" ] then let "total += 1" fi fi done if [ ${tableau[$numero_cellule]} = "$VIVANT1" ] then let "total -= 1" # S'assurer que la valeur de la cellule testée fi #+ n'est pas elle-même comptée. return $total } prochaine_gen () # Mise à jour du tableau des générations. { local tableau local i=0 tableau=( `echo "$1"` ) # Argument passé converti en tableau. while [ "$i" -lt "$cellules" ] do EstVivante "$1" $i ${tableau[$i]} # La cellule est-elle vivante ? if [ $? -eq "$VIVANTE" ] then # Si elle l'est, alors tableau[$i]=. #+ représente la cellule avec un point. else tableau[$i]="_" # Sinon, avec un tiret bas. fi #+ (qui sera transformé plus tard en espace). let "i += 1" done # let "generation += 1" # Incrémente le nombre de générations. # Pourquoi cette ligne a-t'elle été mise en commentaire ? # Initialise la variable à passer en tant que paramètre à la fonction # "affiche". une_var=`echo ${tableau[@]}` # Convertit un tableau en une variable de type chaîne. affiche "$une_var" # L'affiche. echo; echo echo "Génération $generation - $vivante vivante" if [ "$alive" -eq 0 ] then echo echo "Sortie prématurée : aucune cellule encore vivante !" exit $AUCUNE_VIVANTE # Aucun intérêt à continuer fi #+ si aucune cellule n'est vivante. } # ========================================================= # main () # Charge un tableau initial avec un fichier de départ. initial=( `cat "$fichier_de_depart" | sed -e '/#/d' | tr -d '\n' |\ sed -e 's/\./\. /g' -e 's/_/_ /g'` ) # Supprime les lignes contenant le symbole de commentaires '#'. # Supprime les retours chariot et insère des espaces entre les éléments. clear # Efface l'écran. echo # Titre echo "=======================" echo " $GENERATIONS générations" echo " du" echo " \"Jeu de la Vie\"" echo "=======================" # -------- Affiche la première génération. -------- Gen0=`echo ${initial[@]}` affiche "$Gen0" # Affiche seulement. echo; echo echo "Génération $generation - $alive vivante" # ------------------------------------------- let "generation += 1" # Incrémente le compteur de générations. echo # ------- Affiche la deuxième génération. ------- Actuelle=`echo ${initial[@]}` prochaine_gen "$Actuelle" # Mise à jour & affichage. # ------------------------------------------ let "generation += 1" # Incrémente le compteur de générations. # ------ Boucle principale pour afficher les générations conséquentes ------ while [ "$generation" -le "$GENERATIONS" ] do Actuelle="$une_var" prochaine_gen "$Actuelle" let "generation += 1" done # ============================================================== echo exit 0 # FIN # Le tableau dans ce script a un "problème de bordures". # Les bordures haute, basse et des côtés avoisinent une absence de cellules mortes. # Exercice: Modifiez le script pour avoir la grille # + de façon à ce que les côtés gauche et droit se touchent, # + comme le haut et le bas. # # Exercice: Créez un nouveau fichier "gen0" pour ce script. # Utilisez une grille 12 x 16, au lieu du 10 x 10 original. # Faites les modifications nécessaires dans le script, #+ de façon à ce qu'il s'exécute avec le fichier modifié. # # Exercice: Modifiez ce script de façon à ce qu'il puisse déterminer la taille #+ de la grille à partir du fichier "gen0" et initialiser toute variable #+ nécessaire au bon fonctionnement du script. # Ceci rend inutile la modification des variables dans le script #+ suite à un modification de la taille de la grille.
Exemple A.11. Fichier de données pour le « Jeu de la Vie »
# gen0 # # This is an example "generation 0" start-up file for "life.sh". # -------------------------------------------------------------- # The "gen0" file is a 10 x 10 grid using a period (.) for live cells, #+ and an underscore (_) for dead ones. We cannot simply use spaces #+ for dead cells in this file because of a peculiarity in Bash arrays. # [Exercise for the reader: explain this.] # # Lines beginning with a '#' are comments, and the script ignores them. __.__..___ ___._.____ ____.___.. _._______. ____._____ ..__...___ ____._____ ___...____ __.._..___ _..___..__
+++
Les deux scripts suivants sont de Mark Moraes de l'Université de Toronto. Voir le fichier Moraes-COPYRIGHT pour les droits. Ce fichier est inclus dans l'archive tar HTML/source du guide ABS.
Exemple A.12. behead: Supprimer les en-têtes des courriers électroniques et des nouvelles
#! /bin/sh # Supprime l'entête d'un message mail/news jusqu'à la première ligne vide. # Mark Moraes, Université de Toronto # ==> Ces commentaires sont ajoutés par l'auteur de ce document. if [ $# -eq 0 ]; then # ==> Si pas d'arguments en ligne de commande, alors fonctionne avec un # ==> fichier redirigé vers stdin. sed -e '1,/^$/d' -e '/^[ ]*$/d' # --> Supprime les lignes vides et les autres jusqu'à la première # --> commençant avec une espace blanche. else # ==> Si des arguments sont présents en ligne de commande, alors fonctionne avec # ==> des fichiers nommés. for i do sed -e '1,/^$/d' -e '/^[ ]*$/d' $i # --> De même. done fi # ==> Exercice: Ajouter la vérification d'erreurs et d'autres options. # ==> # ==> Notez que le petit script sed se réfère à l'exception des arguments # ==> passés. # ==> Est-il intéressant de l'embarquer dans une fonction? Pourquoi?
Exemple A.13. ftpget: Télécharger des fichiers via ftp
#! /bin/sh # $Id: ftpget.sh,v 1.7 2006/08/09 09:07:52 gleu Exp $ # Script pour réaliser une suite d'actions avec un ftp anonyme. Généralement, # convertit une liste d'arguments de la ligne de commande en entrée vers ftp. # ==> Ce script n'est rien de plus qu'un emballage shell autour de "ftp"... # Simple et rapide - écrit comme compagnon de ftplist # -h spécifie l'hôte distant (par défaut prep.ai.mit.edu) # -d spécifie le répertoire distant où se déplacer - vous pouvez spécifier une # séquence d'options -d - elles seront exécutées chacune leur tour. Si les # chemins sont relatifs, assurez-vous d'avoir la bonne séquence. Attention aux # chemins relatifs, il existe bien trop de liens symboliques de nos jours. # (par défaut, le répertoire distant est le répertoire au moment de la connexion) # -v active l'option verbeux de ftp et affiche toutes les réponses du serveur # ftp # -f fichierdistant[:fichierlocal] récupère le fichier distant et le renomme en # localfile # -m modele fait un mget suivant le modèle spécifié. Rappelez-vous de mettre # entre guillemets les caractères shell. # -c fait un cd local vers le répertoire spécifié # Par exemple example, # ftpget -h expo.lcs.mit.edu -d contrib -f xplaces.shar:xplaces.sh \ # -d ../pub/R3/fixes -c ~/fixes -m 'fix*' # récupèrera xplaces.shar à partir de ~ftp/contrib sur expo.lcs.mit.edu et # l'enregistrera sous xplaces.sh dans le répertoire actuel, puis obtiendra # tous les correctifs de ~ftp/pub/R3/fixes et les placera dans le répertoire # ~/fixes. # De façon évidente, la séquence des options est importante, car les commandes # équivalentes sont exécutées par ftp dans le même ordre. # # Mark Moraes (moraes@csri.toronto.edu), Feb 1, 1989 # # ==> Ces commentaires ont été ajoutés par l'auteur de ce document. # PATH=/local/bin:/usr/ucb:/usr/bin:/bin # export PATH # ==> Les deux lignes ci-dessus faisaient parti du script original et étaient # ==> probablement inutiles E_MAUVAISARGS=65 FICHIER_TEMPORAIRE=/tmp/ftp.$$ # ==> Crée un fichier temporaire, en utilisant l'identifiant du processus du # ==> script ($$) pour construire le nom du fichier. SITE=`domainname`.toronto.edu # ==> 'domainname' est similaire à 'hostname' # ==> Ceci pourrait être réécrit en ajoutant un paramètre ce qui rendrait son # ==> utilisation plus générale. usage="Usage: $0 [-h hotedistant] [-d repertoiredistant]... [-f fichierdistant:fichierlocal]... \ [-c repertoirelocal] [-m modele] [-v]" optionsftp="-i -n" verbflag= set -f # So we can use globbing in -m set x `getopt vh:d:c:m:f: $*` if [ $? != 0 ]; then echo $usage exit $E_MAUVAISARGS fi shift trap 'rm -f ${FICHIER_TEMPORAIRE} ; exit' 0 1 2 3 15 # ==> Signaux: HUP INT (Ctl-C) QUIT TERM # ==> Supprimer FICHIER_TEMPORAIRE dans le cas d'une sortie anormale du script. echo "user anonymous ${USER-gnu}@${SITE} > ${FICHIER_TEMPORAIRE}" # ==> Ajout des guillemets (recommandé pour les echo complexes). echo binary >> ${FICHIER_TEMPORAIRE} for i in $* # ==> Analyse les arguments de la ligne de commande. do case $i in -v) verbflag=-v; echo hash >> ${FICHIER_TEMPORAIRE}; shift;; -h) hotedistant=$2; shift 2;; -d) echo cd $2 >> ${FICHIER_TEMPORAIRE}; if [ x${verbflag} != x ]; then echo pwd >> ${FICHIER_TEMPORAIRE}; fi; shift 2;; -c) echo lcd $2 >> ${FICHIER_TEMPORAIRE}; shift 2;; -m) echo mget "$2" >> ${FICHIER_TEMPORAIRE}; shift 2;; -f) f1=`expr "$2" : "\([^:]*\).*"`; f2=`expr "$2" : "[^:]*:\(.*\)"`; echo get ${f1} ${f2} >> ${FICHIER_TEMPORAIRE}; shift 2;; --) shift; break;; esac # ==> 'lcd' et 'mget' sont des commandes ftp. Voir "man ftp"... done if [ $# -ne 0 ]; then echo $usage exit $E_MAUVAISARGS # ==> Modifié de l'"exit 2" pour se conformer avec le standard du style. fi if [ x${verbflag} != x ]; then optionsftp="${optionsftp} -v" fi if [ x${hotedistant} = x ]; then hotedistant=prep.ai.mit.edu # ==> À modifier pour utiliser votre site ftp favori. fi echo quit >> ${FICHIER_TEMPORAIRE} # ==> Toutes les commandes sont sauvegardées dans fichier_temporaire. ftp ${optionsftp} ${hotedistant} < ${FICHIER_TEMPORAIRE} # ==> Maintenant, exécution par ftp de toutes les commandes contenues dans le # ==> fichier fichier_temporaire. rm -f ${FICHIER_TEMPORAIRE} # ==> Enfin, fichier_temporaire est supprimé (vous pouvez souhaiter le copier # ==> dans un journal). # ==> Exercices: # ==> --------- # ==> 1) Ajouter une vérification d'erreurs. # ==> 2) Ajouter des tas de trucs.
+
Antek Sawicki a contribué avec le script suivant, qui fait une utilisation très intelligente des opérateurs de substitution de paramètres discutés dans la Section 9.3, « Substitution de paramètres ».
Exemple A.14. password: Générer des mots de passe aléatoires de 8 caractères
#!/bin/bash # Pourrait nécessiter d'être appelé avec un #!/bin/bash2 sur les anciennes #+ machines. # # Générateur de mots de passe aléatoires pour Bash 2.x + #+ par Antek Sawicki <tenox@tenox.tc>, # qui a généreusement permis à l'auteur du guide ABS de l'utiliser ici. # # ==> Commentaires ajoutés par l'auteur du document ==> MATRICE="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" # ==> Les mots de passe seront constitués de caractères alphanumériques. LONGUEUR="8" # ==> Modification possible de 'LONGUEUR' pour des mots de passe plus longs. while [ "${n:=1}" -le "$LONGUEUR" ] # ==> Rappelez-vous que := est l'opérateur de "substitution par défaut". # ==> Donc, si 'n' n'a pas été initialisé, l'initialiser à 1. do PASS="$PASS${MATRICE:$(($RANDOM%${#MATRICE})):1}" # ==> Très intelligent, pratiquement trop astucieux. # ==> Commençons par le plus intégré... # ==> ${#MATRICE} renvoie la longueur du tableau MATRICE. # ==> $RANDOM%${#MATRICE} renvoie un nombre aléatoire entre 1 et la # ==> longueur de MATRICE - 1. # ==> ${MATRICE:$(($RANDOM%${#MATRICE})):1} # ==> renvoie l'expansion de MATRICE à une position aléatoire, par # ==> longueur 1. # ==> Voir la substitution de paramètres {var:pos:len}, section 3.3.1 # ==> et les exemples suivants. # ==> PASS=... copie simplement ce résultat dans PASS (concaténation). # ==> Pour mieux visualiser ceci, décommentez la ligne suivante # ==> echo "$PASS" # ==> pour voir la construction de PASS, un caractère à la fois, # ==> à chaque itération de la boucle. let n+=1 # ==> Incrémentez 'n' pour le prochain tour. done echo "$PASS" # ==> Ou, redirigez le fichier, comme voulu. exit 0
+
James R. Van Zandt a contribué avec ce script, qui utilise les tubes nommés et, ce sont ses mots, « really exercises quoting and escaping ».
Exemple A.15. fifo: Faire des sauvegardes journalières, en utilisant des tubes nommés
#!/bin/bash # ==> Script de James R. Van Zandt, et utilisé ici avec sa permission. # ==> Commentaires ajoutés par l'auteur de ce document. ICI=`uname -n` # ==> nom d'hôte LA_BAS=bilbo echo "début de la sauvegarde distante vers $LA_BAS à `date +%r`" # ==> `date +%r` renvoie l'heure en un format sur 12 heures, par exempe # ==> "08:08:34 PM". # Assurez-vous que /pipe est réellement un tube et non pas un fichier #+ standard. rm -rf /tube mkfifo /tube # ==> Crée un fichier "tube nommé", nommé "/tube". # ==> 'su xyz' lance les commandes en tant qu'utilisateur "xyz". # ==> 'ssh' appele le shell sécurisé (client de connexion à distance). su xyz -c "ssh $LA_BAS \"cat > /home/xyz/sauve/${ICI}-jour.tar.gz\" < /tube"& cd / tar -czf - bin boot dev etc home info lib man root sbin share usr var > /tube # ==> Utilise un tube nommé, /tube, pour communiquer entre processus: # ==> 'tar/gzip' écrit dans le tube et 'ssh' lit /tube. # ==> Le résultat final est que cela sauvegarde les répertoires principaux; #+ ==> à partir de /. # ==> Quels sont les avantages d'un "tube nommé" dans cette situation, # ==>+ en opposition avec le "tube anonyme", avec |? # ==> Est-ce qu'un tube anonyme pourrait fonctionner ici? # ==> Est-il nécessaire de supprimer le tube avant de sortir du script ? # ==> Comment le faire ? exit 0
+
Stéphane Chazelas a contribué avec le script suivant pour démontrer que générer des nombres premiers ne requiert pas de tableaux.
Exemple A.16. primes: Générer des nombres premiers en utilisant l'opérateur modulo
#!/bin/bash # primes.sh: Génère des nombres premiers, sans utiliser des tableaux. # Script contribué par Stephane Chazelas. # Il n'utilise *pas* l'algorithme classique du crible d'Ératosthène, #+ mais utilise à la place la méthode plus intuitive de test de chaque nombre #+ candidat pour les facteurs (diviseurs), en utilisant l'opérateur modulo "%". LIMITE=1000 # Premiers de 2 à 1000 Premiers() { (( n = $1 + 1 )) # Va au prochain entier. shift # Prochain paramètre dans la liste. # echo "_n=$n i=$i_" if (( n == LIMITE )) then echo $* return fi for i; do # "i" est initialisé à "@", les précédentes #+ valeurs de $n. # echo "-n=$n i=$i-" (( i * i > n )) && break # Optimisation. (( n % i )) && continue # Passe les non premiers en utilisant l'opérateur #+ modulo. Premiers $n $@ # Récursion à l'intérieur de la boucle. return done Premiers $n $@ $n # Récursion à l'extérieur de la boucle. # Accumule successivement les paramètres de #+ position. # "$@" est la liste des premiers accumulés. } Premiers 1 exit 0 # Décommentez les lignes 16 et 24 pour vous aider à comprendre ce qui se passe. # Comparez la vitesse de cet algorithme de génération des nombres premiers avec #+ celui de "Sieve of Eratosthenes" (ex68.sh). # Exercice: Réécrivez ce script sans récursion, pour une exécution plus rapide.
+
C'est la version de Rick Boivie du script de Jordi Sanfeliu, qui a donné sa permission pour utiliser son script élégant sur les arborescences.
Exemple A.17. tree: Afficher l'arborescence d'un répertoire
#!/bin/sh # tree.sh # Écrit par Rick Boivie. # Utilisé avec sa permission. # Ceci est une version revue et simplifiée d'un script #+ par Jordi Sanfeliu (et corrigée par Ian Kjos). # Ce script remplace la version précédente utilisée dans #+ les précédentes versions du Guide d'écriture avancé de scripts Bash. # ==> Commentaires ajoutés par l'auteur de ce document. search () { for dir in `echo *` # ==> `echo *` affiche tous les fichiers du répertoire actuel sans retour à # ==> la ligne. # ==> Même effet que for dir in * # ==> mais "dir in `echo *`" ne gère pas les noms de fichiers comprenant des # ==> espaces blancs. do if [ -d "$dir" ] ; then # ==> S'il s'agit d'un répertoire (-d)... zz=0 # ==> Variable temporaire, pour garder trace du niveau du # ==> répertoire. while [ $zz != $1 ] # Conserve la trace de la boucle interne. do echo -n "| " # ==> Affiche le symbole du connecteur vertical # ==> avec 2 espaces mais pas de retour à la ligne # ==> pour l'indentation. zz=`expr $zz + 1` # ==> Incrémente zz. done if [ -L "$dir" ] ; then # ==> Si le répertoire est un lien symbolique... echo "+---$dir" `ls -l $dir | sed 's/^.*'$dir' //'` # ==> Affiche le connecteur horizontal et affiche le nom du # ==> répertoire mais... # ==> supprime la partie date/heure des longues listes. else echo "+---$dir" # ==> Affiche le symbole du connecteur # ==> horizontal et le nom du répertoire. numdirs=`expr $numdirs + 1` # ==> Incrémente le compteur de répertoire. if cd "$dir" ; then # ==> S'il peut se déplacer dans le sous-répertoire... search `expr $1 + 1` # avec la récursivité ;-) # ==> La fonction s'appelle elle-même. cd .. fi fi fi done } if [ $# != 0 ] ; then cd $1 # se déplace au répertoire indiqué. #else # reste dans le répertoire actuel. fi echo "Répertoire initial = `pwd`" numdirs=0 search 0 echo "Nombre total de répertoires = $numdirs" exit 0
+
Noah Friedman a donné sa permission pour utiliser son script contenant des fonctions sur les chaînes de caractères, qui reproduit les fonctions de manipulations de la bibliothèque C string.
Exemple A.18. string: Manipuler les chaînes de caractères comme en C
#!/bin/bash # string.bash --- bash emulation of string(3) library routines # Author: Noah Friedman <friedman@prep.ai.mit.edu> # ==> Used with his kind permission in this document. # Created: 1992-07-01 # Last modified: 1993-09-29 # Public domain # Conversion to bash v2 syntax done by Chet Ramey # Commentary: # Code: #:docstring strcat: # Usage: strcat s1 s2 # # Strcat appends the value of variable s2 to variable s1. # # Example: # a="foo" # b="bar" # strcat a b # echo $a # => foobar # #:end docstring: ###;;;autoload ==> Autoloading of function commented out. function strcat () { local s1_val s2_val s1_val=${!1} # indirect variable expansion s2_val=${!2} eval "$1"=\'"${s1_val}${s2_val}"\' # ==> eval $1='${s1_val}${s2_val}' avoids problems, # ==> if one of the variables contains a single quote. } #:docstring strncat: # Usage: strncat s1 s2 $n # # Line strcat, but strncat appends a maximum of n characters from the value # of variable s2. It copies fewer if the value of variabl s2 is shorter # than n characters. Echoes result on stdout. # # Example: # a=foo # b=barbaz # strncat a b 3 # echo $a # => foobar # #:end docstring: ###;;;autoload function strncat () { local s1="$1" local s2="$2" local -i n="$3" local s1_val s2_val s1_val=${!s1} # ==> indirect variable expansion s2_val=${!s2} if [ ${#s2_val} -gt ${n} ]; then s2_val=${s2_val:0:$n} # ==> substring extraction fi eval "$s1"=\'"${s1_val}${s2_val}"\' # ==> eval $1='${s1_val}${s2_val}' avoids problems, # ==> if one of the variables contains a single quote. } #:docstring strcmp: # Usage: strcmp $s1 $s2 # # Strcmp compares its arguments and returns an integer less than, equal to, # or greater than zero, depending on whether string s1 is lexicographically # less than, equal to, or greater than string s2. #:end docstring: ###;;;autoload function strcmp () { [ "$1" = "$2" ] && return 0 [ "${1}" '<' "${2}" ] > /dev/null && return -1 return 1 } #:docstring strncmp: # Usage: strncmp $s1 $s2 $n # # Like strcmp, but makes the comparison by examining a maximum of n # characters (n less than or equal to zero yields equality). #:end docstring: ###;;;autoload function strncmp () { if [ -z "${3}" -o "${3}" -le "0" ]; then return 0 fi if [ ${3} -ge ${#1} -a ${3} -ge ${#2} ]; then strcmp "$1" "$2" return $? else s1=${1:0:$3} s2=${2:0:$3} strcmp $s1 $s2 return $? fi } #:docstring strlen: # Usage: strlen s # # Strlen returns the number of characters in string literal s. #:end docstring: ###;;;autoload function strlen () { eval echo "\${#${1}}" # ==> Returns the length of the value of the variable # ==> whose name is passed as an argument. } #:docstring strspn: # Usage: strspn $s1 $s2 # # Strspn returns the length of the maximum initial segment of string s1, # which consists entirely of characters from string s2. #:end docstring: ###;;;autoload function strspn () { # Unsetting IFS allows whitespace to be handled as normal chars. local IFS= local result="${1%%[!${2}]*}" echo ${#result} } #:docstring strcspn: # Usage: strcspn $s1 $s2 # # Strcspn returns the length of the maximum initial segment of string s1, # which consists entirely of characters not from string s2. #:end docstring: ###;;;autoload function strcspn () { # Unsetting IFS allows whitspace to be handled as normal chars. local IFS= local result="${1%%[${2}]*}" echo ${#result} } #:docstring strstr: # Usage: strstr s1 s2 # # Strstr echoes a substring starting at the first occurrence of string s2 in # string s1, or nothing if s2 does not occur in the string. If s2 points to # a string of zero length, strstr echoes s1. #:end docstring: ###;;;autoload function strstr () { # if s2 points to a string of zero length, strstr echoes s1 [ ${#2} -eq 0 ] && { echo "$1" ; return 0; } # strstr echoes nothing if s2 does not occur in s1 case "$1" in *$2*) ;; *) return 1;; esac # use the pattern matching code to strip off the match and everything # following it first=${1/$2*/} # then strip off the first unmatched portion of the string echo "${1##$first}" } #:docstring strtok: # Usage: strtok s1 s2 # # Strtok considers the string s1 to consist of a sequence of zero or more # text tokens separated by spans of one or more characters from the # separator string s2. The first call (with a non-empty string s1 # specified) echoes a string consisting of the first token on stdout. The # function keeps track of its position in the string s1 between separate # calls, so that subsequent calls made with the first argument an empty # string will work through the string immediately following that token. In # this way subsequent calls will work through the string s1 until no tokens # remain. The separator string s2 may be different from call to call. # When no token remains in s1, an empty value is echoed on stdout. #:end docstring: ###;;;autoload function strtok () { : } #:docstring strtrunc: # Usage: strtrunc $n $s1 {$s2} {$...} # # Used by many functions like strncmp to truncate arguments for comparison. # Echoes the first n characters of each string s1 s2 ... on stdout. #:end docstring: ###;;;autoload function strtrunc () { n=$1 ; shift for z; do echo "${z:0:$n}" done } # provide string # string.bash ends here # ========================================================================== # # ==> Everything below here added by the document author. # ==> Suggested use of this script is to delete everything below here, # ==> and "source" this file into your own scripts. # strcat string0=one string1=two echo echo "Testing \"strcat\" function:" echo "Original \"string0\" = $string0" echo "\"string1\" = $string1" strcat string0 string1 echo "New \"string0\" = $string0" echo # strlen echo echo "Testing \"strlen\" function:" str=123456789 echo "\"str\" = $str" echo -n "Length of \"str\" = " strlen str echo # Exercise: # -------- # Add code to test all the other string functions above. exit 0
L'exemple de tableaux complexes par Michael Zick utilise la commande de vérification de sommes md5sum pour coder les informations sur le répertoire.
Exemple A.19. Informations sur un répertoire
#! /bin/bash # directory-info.sh # Analyse et affiche des informations sur le répertoire. # NOTE: Modification des lignes 273 et 353 suivant le fichier "README". # Michael Zick est l'auteur de ce script. # Utilisé ici avec son autorisation. # Contrôles # Si outrepassé par les arguments de la commande, ils doivent être dans l'ordre: # Arg1: "Descripteur du répertoire" # Arg2: "Chemins à exclure" # Arg3: "Répertoires à exclure" # # Les variables d'environnement outrepassent les valeurs par défaut. # Les arguments de la commande outrepassent les variables d'environnement. # Emplacement par défaut du contenu des descripteurs de fichiers. MD5UCFS=${1:-${MD5UCFS:-'/tmpfs/ucfs'}} # Répertoires à exclure declare -a \ CHEMINS_A_EXCLURE=${2:-${CHEMINS_A_EXCLURE:-'(/proc /dev /devfs /tmpfs)'}} # Répertoires à exclure declare -a \ REPERTOIRES_A_EXCLURE=${3:-${REPERTOIRES_A_EXCLURE:-'(ucfs lost+found tmp wtmp)'}} # Fichiers à exclure declare -a \ FICHIERS_A_EXCLURE=${3:-${FICHIERS_A_EXCLURE:-'(core "Nom avec des espaces")'}} # Document intégré utilisé comme bloc de commentaires. : <<LSfieldsDoc # # # Affiche les informations sur les répertoires du système de fichiers # # # # # AfficheRepertoire "FileGlob" "Field-Array-Name" # ou # AfficheRepertoire -of "FileGlob" "Field-Array-Filename" # '-of' signifiant 'sortie vers fichier' # # # # # Description du format de la chaîne : ls (GNU fileutils) version 4.0.36 Produit une ligne (ou plus) formattée : inode droits liens propriétaire groupe ... 32736 -rw------- 1 mszick mszick taille jour mois date hh:mm:ss année chemin 2756608 Sun Apr 20 08:53:06 2003 /home/mszick/core Sauf, s'il est formatté : inode droits liens propriétaire groupe ... 266705 crw-rw---- 1 root uucp majeur mineur jour mois date hh:mm:ss année chemin 4, 68 Sun Apr 20 09:27:33 2003 /dev/ttyS4 NOTE: cette virgule bizarre après le nombre majeur NOTE: le 'chemin' pourrait avoir plusieurs champs : /home/mszick/core /proc/982/fd/0 -> /dev/null /proc/982/fd/1 -> /home/mszick/.xsession-errors /proc/982/fd/13 -> /tmp/tmpfZVVOCs (deleted) /proc/982/fd/7 -> /tmp/kde-mszick/ksycoca /proc/982/fd/8 -> socket:[11586] /proc/982/fd/9 -> pipe:[11588] Si ce n'est pas suffisant pour que votre analyseur continue à deviner, soit une soit les deux parties du chemin peuvent être relatives : ../Built-Shared -> Built-Static ../linux-2.4.20.tar.bz2 -> ../../../SRCS/linux-2.4.20.tar.bz2 Le premier caractère du champ des droits (sur 11 (10 ?) caractères) : 's' Socket 'd' Répertoire 'b' Périphérique bloc 'c' Périphérique caractère 'l' Lien symbolique NOTE: Les liens non symboliques ne sont pas identifiés - testés pour des numéros d'inodes identiques sur le même système de fichiers. Toutes les informations sur les fichiers liés sont partagées sauf le nom et l'emplacement. NOTE: Un "lien" est connu comme un "alias" sur certains systèmes. '-' fichier sans distinction. Suivi par trois groupes de lettres pour l'utilisateur, le groupe et les autres. Caractère 1: '-' non lisible; 'r' lisible Caractère 2: '-' pas d'écriture; 'w' écriture (writable) Caractère 3, utilisateur et groupe: Combine l'éxécution et un spécial '-' non exécutable, non spécial 'x' exécutable, non spécial 's' exécutable, spécial 'S' non exécutable, spécial Caractère 3, autres: Combine l'éxécution et le sticky (tacky?) '-' non éxécutable, non tacky 'x' exécutable, non tacky 't' exécutable, tacky 'T' non exécutable, tacky Suivi par un indicateur d'accès Non testé, il pourrait être le onzième caractère ou il pourrait générer un autre champ ' ' Pas d'accès autre '+' Accès autre LSfieldsDoc AfficheRepertoire() { local -a T local -i of=0 # Valeur par défaut # OLD_IFS=$IFS # Utilise la variable BASH par défaut ' \t\n' case "$#" in 3) case "$1" in -of) of=1 ; shift ;; * ) return 1 ;; esac ;; 2) : ;; # L'instruction "continue" du pauvre *) return 1 ;; esac # NOTE: la commande (ls) N'est PAS entre guillemets (") T=( $(ls --inode --ignore-backups --almost-all --directory \ --full-time --color=none --time=status --sort=none \ --format=long $1) ) case $of in # Affecte T en retour pour le tableau dont le nom a été passé #+ à $2 0) eval $2=\( \"\$\{T\[@\]\}\" \) ;; # Ecrit T dans le nom du fichier passé à $2 1) echo "${T[@]}" > "$2" ;; esac return 0 } # # # # # Est-ce que cette chaîne est un nombre légal ? # # # # # # # EstNombre "Var" # # # # # Il doit y avoir un meilleur moyen, hum... EstNombre() { local -i int if [ $# -eq 0 ] then return 1 else (let int=$1) 2>/dev/null return $? # Code de sortie du thread créé pour let fi } # # # Informations sur l'index des répertoires du système de fichiers # # # # # AfficheIndex "Field-Array-Name" "Index-Array-Name" # ou # AfficheIndex -if Field-Array-Filename Index-Array-Name # AfficheIndex -of Field-Array-Name Index-Array-Filename # AfficheIndex -if -of Field-Array-Filename Index-Array-Filename # # # # # : <<AfficheIndexDoc Parcourt un tableau de champs répertoire créé par AfficheRepertoire Ayant supprimé les retours chariots dans un rapport habituellement ligne par ligne, construit un index vers l'élement du tableau commençant à chaque ligne. Chaque ligne obtient deux entrées de l'index, le premier élément de chaque ligne (inode) et l'élément qui contient le chemin du fichier. La première paire d'entrée de l'index (Numero-Ligne==0) apporte une information : Nom-Tableau-Index[0] : Nombre de "lignes" indexé Nom-Tableau-Index[1] : Pointeur de la "ligne courante" vers Nom-Tableau-Index Les paires d'index suivantes (si elles existent) contiennent les index des éléments dans Nom-Tableau-Champ avec : Nom-Tableau-Index[Numero-Ligne * 2] : L'élément champ "inode". NOTE: La distance peut être de +11 ou +12 éléments. Nom-Tableau-Index[(Numero-Ligne * 2) + 1] : L'élément "chemin". NOTE: La distance est un nombre variable d'éléments. La prochaine paire de lignes d'index pour Numero-Ligne+1. AfficheIndexDoc AfficheIndex() { local -a LISTE # Variable locale du nom de liste local -a -i INDEX=( 0 0 ) # Variable locale de l'index à renvoyer local -i Lidx Lcpt local -i if=0 of=0 # Par défaut case "$#" in # Test simpliste des options 0) return 1 ;; 1) return 1 ;; 2) : ;; # Instruction "continue" du pauvre 3) case "$1" in -if) if=1 ;; -of) of=1 ;; * ) return 1 ;; esac ; shift ;; 4) if=1 ; of=1 ; shift ; shift ;; *) return 1 esac # Fait une copie locale de liste case "$if" in 0) eval LISTE=\( \"\$\{$1\[@\]\}\" \) ;; 1) LISTE=( $(cat $1) ) ;; esac # "Grok (grope?)" le tableau Lcpt=${#LISTE[@]} Lidx=0 until (( Lidx >= Lcpt )) do if EstNombre ${LISTE[$Lidx]} then local -i inode nom local ft inode=Lidx local m=${LISTE[$Lidx+2]} # Champ des liens ft=${LISTE[$Lidx+1]:0:1} # Stats rapides case $ft in b) ((Lidx+=12)) ;; # Périphérique bloc c) ((Lidx+=12)) ;; # Périphérique caractère *) ((Lidx+=11)) ;; # Le reste esac nom=Lidx case $ft in -) ((Lidx+=1)) ;; # Le plus simple b) ((Lidx+=1)) ;; # Périphérique bloc c) ((Lidx+=1)) ;; # Périphérique caractère d) ((Lidx+=1)) ;; # Encore un autre l) ((Lidx+=3)) ;; # Au MOINS deux autres champs # Un peu plus d'élégance ici permettrait de gérer des tubes, des sockets, #+ des fichiers supprimés - plus tard. *) until EstNombre ${LISTE[$Lidx]} || ((Lidx >= Lcpt)) do ((Lidx+=1)) done ;; # Non requis. esac INDEX[${#INDEX[*]}]=$inode INDEX[${#INDEX[*]}]=$nom INDEX[0]=${INDEX[0]}+1 # Une "ligne" de plus # echo "Ligne: ${INDEX[0]} Type: $ft Liens: $m Inode: \ # ${LIST[$inode]} Nom: ${LIST[$name]}" else ((Lidx+=1)) fi done case "$of" in 0) eval $2=\( \"\$\{INDEX\[@\]\}\" \) ;; 1) echo "${INDEX[@]}" > "$2" ;; esac return 0 # Que pourrait'il arriver de mal ? } # # # # # Fichier identifié par son contenu # # # # # # # DigestFile Nom-Tableau-Entree Nom-Tableau-Digest # ou # DigestFile -if NomFichier-EnEntree Nom-Tableau-Digest # # # # # # Document intégré utilisé comme bloc de commentaires. : <<DigestFilesDoc La clé (no pun intended) vers un Système de Fichiers au Contenu Unifié (UCFS) permet de distinguer les fichiers du système basés sur leur contenu. Distinguer des fichiers par leur nom est tellement 20è siècle. Le contenu se distingue en calculant une somme de contrôle de ce contenu. Cette version utilise le programme md5sum pour générer une représentation de la somme de contrôle 128 bit du contenu. Il existe une chance pour que deux fichiers ayant des contenus différents génèrent la même somme de contrôle utilisant md5sum (ou tout autre outil de calcul de somme de contrôle). Si cela devait devenir un problème, alors l'utilisation de md5sum peut être remplacée par une signature cryptographique. Mais jusque là... La documentation de md5sum précise que la sortie de cette commande affiche trois champs mais, à la lecture, il apparaît comme deux champs (éléments du tableau). Ceci se fait par le manque d'espaces blancs entre le second et le troisième champ. Donc, cette fonction groupe la sortie du md5sum et renvoit : [0] Somme de contrôle sur 32 caractères en héxidecimal (nom du fichier UCFS) [1] Caractère seul : ' ' fichier texte, '*' fichier binaire [2] Nom système de fichiers (style 20è siècle) Note: Ce nom pourrait être le caractère '-' indiquant la lecture de STDIN DigestFilesDoc DigestFile() { local if=0 # Par défaut. local -a T1 T2 case "$#" in 3) case "$1" in -if) if=1 ; shift ;; * ) return 1 ;; esac ;; 2) : ;; # Instruction "continue" du pauvre *) return 1 ;; esac case $if in 0) eval T1=\( \"\$\{$1\[@\]\}\" \) T2=( $(echo ${T1[@]} | md5sum -) ) ;; 1) T2=( $(md5sum $1) ) ;; esac case ${#T2[@]} in 0) return 1 ;; 1) return 1 ;; 2) case ${T2[1]:0:1} in # SanScrit-2.0.5 \*) T2[${#T2[@]}]=${T2[1]:1} T2[1]=\* ;; *) T2[${#T2[@]}]=${T2[1]} T2[1]=" " ;; esac ;; 3) : ;; # Suppose qu'il fonctionne *) return 1 ;; esac local -i len=${#T2[0]} if [ $len -ne 32 ] ; then return 1 ; fi eval $2=\( \"\$\{T2\[@\]\}\" \) } # # # # # Trouve l'emplacement du fichier # # # # # # # LocateFile [-l] NomFichier Nom-Tableau-Emplacement # ou # LocateFile [-l] -of NomFichier NomFichier-Tableau-Emplacement # # # # # # L'emplacement d'un fichier correspond à l'identifiant du système de fichiers #+ et du numéro de l'inode. # Document intégré comme bloc de commentaire. : <<StatFieldsDoc Basé sur stat, version 2.2 champs de stat -t et stat -lt [0] nom [1] Taille totale Fichier - nombre d'octets Lien symbolique - longueur de la chaîne représentant le chemin [2] Nombre de blocs (de 512 octets) alloués [3] Type de fichier et droits d'accès (hex) [4] ID utilisateur du propriétaire [5] ID groupe du propriétaire [6] Numéro de périphérique [7] Numéro de l'inode [8] Nombre de liens [9] Type de périphérique (si périphérique d'inode) Majeur [10] Type de périphérique (si périphérique d'inode) Mineur [11] Heure du dernier accès Pourrait être désactivé dans 'mount' avec noatime atime des fichiers changés par exec, read, pipe, utime, mknod (mmap?) atime des répertoires changés par ajout/suppression des fichiers [12] Heure de dernière modification mtime des fichiers changés par write, truncate, utime, mknod mtime des répertoires changés par ajout/suppression des fichiers [13] Heure de dernier changement ctime reflète le temps de la dernière modification de l'inode (propriétaire, groupe, droits, nombre de liens) -*-*- Pour : Code de sortie: 0 Taille du tableau: 14 Contenu du tableau Elément 0: /home/mszick Elément 1: 4096 Elément 2: 8 Elément 3: 41e8 Elément 4: 500 Elément 5: 500 Elément 6: 303 Elément 7: 32385 Elément 8: 22 Elément 9: 0 Elément 10: 0 Elément 11: 1051221030 Elément 12: 1051214068 Elément 13: 1051214068 Pour un lien de la forme nom_lien -> vrai_nom stat -t nom_lien renvoit des informations sur le lien stat -lt nom_lien renvoit des informations sur le vrai fichier Champs stat -tf et stat -ltf [0] nom [1] ID-0? # Peut-être un jour, mais la structure stat de [2] ID-0? # Linux n'a ni le champ LABEL ni UUID, # actuellement l'information doit provenir # d'utilitaires système spécifiques Ceci sera transformé en : [1] UUID si possible [2] Label du volume si possible Note: 'mount -l' renvoit le label et pourrait renvoyer le UUID [3] Longueur maximum des noms de fichier [4] Type de système de fichiers [5] Nombre total de blocs dans le système de fichiers [6] Blocs libres [7] Blocs libres pour l'utilisateur non root [8] Taille du bloc du système de fichiers [9] Nombre total d'inodes [10] Inodes libres -*-*- Per: Code de sortie: 0 Taille du tableau : 11 Contenu du tableau Elément 0: /home/mszick Elément 1: 0 Elément 2: 0 Elément 3: 255 Elément 4: ef53 Elément 5: 2581445 Elément 6: 2277180 Elément 7: 2146050 Elément 8: 4096 Elément 9: 1311552 Elément 10: 1276425 StatFieldsDoc # LocateFile [-l] NomFichier Nom-Tableau-Emplacement # LocateFile [-l] -of NomFichier Nom-Tableau-Emplacement LocateFile() { local -a LOC LOC1 LOC2 local lk="" of=0 case "$#" in 0) return 1 ;; 1) return 1 ;; 2) : ;; *) while (( "$#" > 2 )) do case "$1" in -l) lk=-1 ;; -of) of=1 ;; *) return 1 ;; esac shift done ;; esac # Plus de Sanscrit-2.0.5 # LOC1=( $(stat -t $lk $1) ) # LOC2=( $(stat -tf $lk $1) ) # Supprimez le commentaire des deux lignes ci-dessus si le système #+ dispose de la commande "stat" installée. LOC=( ${LOC1[@]:0:1} ${LOC1[@]:3:11} ${LOC2[@]:1:2} ${LOC2[@]:4:1} ) case "$of" in 0) eval $2=\( \"\$\{LOC\[@\]\}\" \) ;; 1) echo "${LOC[@]}" > "$2" ;; esac return 0 # Ce qui rend comme résultat (si vous êtes chanceux et avez installé "stat") # -*-*- Descripteur de l'emplacement -*-*- # Code de sortie : 0 # Taille du tableau : 15 # Contenu du tableau # Elément 0: /home/mszick Nom du 20è siècle # Elément 1: 41e8 Type et droits # Elément 2: 500 Utilisateur # Elément 3: 500 Groupe # Elément 4: 303 Périphérique # Elément 5: 32385 Inode # Elément 6: 22 Nombre de liens # Elément 7: 0 Numéro majeur # Elément 8: 0 Numéro mineur # Elément 9: 1051224608 Dernier accès # Elément 10: 1051214068 Dernière modification # Elément 11: 1051214068 Dernier statut # Elément 12: 0 UUID (à faire) # Elément 13: 0 Volume Label (à faire) # Elément 14: ef53 Type de système de fichiers } # Et enfin, voici un code de test AfficheTableau() # AfficheTableau Nom { local -a Ta eval Ta=\( \"\$\{$1\[@\]\}\" \) echo echo "-*-*- Liste de tableaux -*-*-" echo "Taille du tableau $1: ${#Ta[*]}" echo "Contenu du tableau $1:" for (( i=0 ; i<${#Ta[*]} ; i++ )) do echo -e "\tElément $i: ${Ta[$i]}" done return 0 } declare -a CUR_DIR # Pour de petits tableaux AfficheRepertoire "${PWD}" CUR_DIR AfficheTableau CUR_DIR declare -a DIR_DIG DigestFile CUR_DIR DIR_DIG echo "Le nouveau \"nom\" (somme de contrôle) pour ${CUR_DIR[9]} est ${DIR_DIG[0]}" declare -a DIR_ENT # BIG_DIR # Pour de réellement gros tableaux - utilise un fichier temporaire en # disque RAM # BIG-DIR # AfficheRepertoire -of "${CUR_DIR[11]}/*" "/tmpfs/junk2" AfficheRepertoire "${CUR_DIR[11]}/*" DIR_ENT declare -a DIR_IDX # BIG-DIR # AfficheIndex -if "/tmpfs/junk2" DIR_IDX AfficheIndex DIR_ENT DIR_IDX declare -a IDX_DIG # BIG-DIR # DIR_ENT=( $(cat /tmpfs/junk2) ) # BIG-DIR # DigestFile -if /tmpfs/junk2 IDX_DIG DigestFile DIR_ENT IDX_DIG # Les petits (devraient) être capable de paralléliser AfficheIndex & DigestFile # Les grands (devraient) être capable de paralléliser AfficheIndex & DigestFile # & l'affectation echo "Le \"nom\" (somme de contrôle) pour le contenu de ${PWD} est ${IDX_DIG[0]}" declare -a FILE_LOC LocateFile ${PWD} FILE_LOC AfficheTableau FILE_LOC exit 0
Stéphane Chazelas montre la programmation objet dans un script Bash.
Exemple A.20. obj-oriented: Bases de données orientées objet
#!/bin/bash # obj-oriented.sh: programmation orientée objet dans un script shell. # Script par Stephane Chazelas. # Note Importante : # ---- ---------- # Si vous exécutez ce script avec une version 3 ou ultérieure de Bash, #+ remplacez tous les points dans les noms de fonctions avec un #+ caractère légal, par exemple un tiret bas. person.new() # Ressemble à la déclaration d'une classe en C++. { local nom_objet=$1 nom=$2 prenom=$3 datenaissance=$4 eval "$nom_objet.set_nom() { eval \"$nom_objet.get_nom() { echo \$1 }\" }" eval "$nom_objet.set_prenom() { eval \"$nom_objet.get_prenom() { echo \$1 }\" }" eval "$nom_objet.set_datenaissance() { eval \"$nom_objet.get_datenaissance() { echo \$1 }\" eval \"$nom_objet.show_datenaissance() { echo \$(date -d \"1/1/1970 0:0:\$1 GMT\") }\" eval \"$nom_objet.get_age() { echo \$(( (\$(date +%s) - \$1) / 3600 / 24 / 365 )) }\" }" $nom_objet.set_nom $nom $nom_objet.set_prenom $prenom $nom_objet.set_datenaissance $datenaissance } echo person.new self Bozeman Bozo 101272413 # Crée une instance de "person.new" (en fait, passe les arguments à la #+ fonction). self.get_prenom # Bozo self.get_nom # Bozeman self.get_age # 28 self.get_datenaissance # 101272413 self.show_datenaissance # Sat Mar 17 20:13:33 MST 1973 echo # typeset -f #+ pour voir les fonctions créées (attention, cela fait défiler la page). exit 0
Mariusz Gniazdowski a contribué avec une bibliothèque de hachage à utiliser dans des scripts.
Exemple A.21. Bibliothèque de fonctions de hachage
# Hash: # Bibliothèque de fonctions de hachage # Auteur : Mariusz Gniazdowski <mgniazd-at-gmail.com> # Date : 2005-04-07 # Fonctions rendant l'émulation de hâchage en Bash un peu moins pénible. # Limitations: # * Seules les variables globales sont supportées. # * Chaque instance de hâchage génère une variable globale par valeur. # * Les collisions de noms de variables sont possibles #+ si vous définissez des variables comme __hash__hashname_key # * Les clés doivent utilisant des caractères faisant partie du nom d'une variable Bash #+ (pas de tirets, points, etc.). # * Le hâchage est créé comme une variable : # ... hashname_keyname # Donc si quelqu'un crée des hâchages ainsi : # myhash_ + mykey = myhash__mykey # myhash + _mykey = myhash__mykey # Alors, il y aura collision. # (Ceci ne devrait pas poser un problème majeur.) Hash_config_varname_prefix=__hash__ # Émule: hash[key]=value # # Paramètres: # 1 - hash (hachage) # 2 - key (clé) # 3 - value (valeur function hash_set { eval "${Hash_config_varname_prefix}${1}_${2}=\"${3}\"" } # Émule: value=hash[key] # # Paramètres: # 1 - hash # 2 - key # 3 - value (nom d'une variable globale à initialiser) function hash_get_into { eval "$3=\"\$${Hash_config_varname_prefix}${1}_${2}\"" } # Émule: echo hash[key] # # Paramètres: # 1 - hash # 2 - key # 3 - echo params (like -n, for example) function hash_echo { eval "echo $3 \"\$${Hash_config_varname_prefix}${1}_${2}\"" } # Émule: hash1[key1]=hash2[key2] # # Paramètres: # 1 - hash1 # 2 - key1 # 3 - hash2 # 4 - key2 function hash_copy { eval "${Hash_config_varname_prefix}${1}_${2}\ =\"\$${Hash_config_varname_prefix}${3}_${4}\"" } # Émule: hash[keyN-1]=hash[key2]=...hash[key1] # # Copie la première clé au reste des clés. # # Paramètres: # 1 - hash1 # 2 - key1 # 3 - key2 # . . . # N - keyN function hash_dup { local hashName="$1" keyName="$2" shift 2 until [ ${#} -le 0 ]; do eval "${Hash_config_varname_prefix}${hashName}_${1}\ =\"\$${Hash_config_varname_prefix}${hashName}_${keyName}\"" shift; done; } # Émule: unset hash[key] # # Paramètres: # 1 - hash # 2 - key function hash_unset { eval "unset ${Hash_config_varname_prefix}${1}_${2}" } # Emulates something similar to: ref=&hash[key] # # The reference is name of the variable in which value is held. # # Paramètres: # 1 - hash # 2 - key # 3 - ref - Nom d'une variable globale à initialiser. function hash_get_ref_into { eval "$3=\"${Hash_config_varname_prefix}${1}_${2}\"" } # Émule quelque chose de similaire à: echo &hash[key] # # Cette référence est le nom d'une variable dans laquelle est contenue la valeur. # # Paramètres: # 1 - hash # 2 - key # 3 - echo params (comme -n par exemple) function hash_echo_ref { eval "echo $3 \"${Hash_config_varname_prefix}${1}_${2}\"" } # Émule quelque chose de similaire à: $$hash[key](param1, param2, ...) # # Paramètres: # 1 - hash # 2 - key # 3,4, ... - Paramètres de fonction. function hash_call { local hash key hash=$1 key=$2 shift 2 eval "eval \"\$${Hash_config_varname_prefix}${hash}_${key} \\\"\\\$@\\\"\"" } # Émule quelque chose de similaire à: isset(hash[key]) ou hash[key]==NULL # # Paramètres: # 1 - hash # 2 - key # Renvoit: # 0 - cette clé existe # 1 - cette clé n'existe pas function hash_is_set { eval "if [[ \"\${${Hash_config_varname_prefix}${1}_${2}-a}\" = \"a\" && \"\${${Hash_config_varname_prefix}${1}_${2}-b}\" = \"b\" ]]; then return 1; else return 0; fi" } # Émule quelque chose de similaire à: # foreach($hash as $key => $value) { fun($key,$value); } # # Il est possible d'écrite plusieurs variations de cette fonction. # Ici, nous utilisons un appel de fonction pour la rendre aussi "générique" que possible. # # Paramètres: # 1 - hash # 2 - function name function hash_foreach { local keyname oldIFS="$IFS" IFS=' ' for i in $(eval "echo \${!${Hash_config_varname_prefix}${1}_*}"); do keyname=$(eval "echo \${i##${Hash_config_varname_prefix}${1}_}") eval "$2 $keyname \"\$$i\"" done IFS="$oldIFS" } # NOTE : Sur les lignes 103 et 116, modification de l'arobase. # Mais, cela n'a pas d'importance parce qu'il s'agit de lignes de commentaires.
Voici un exemple de script utilisant cette bibliothèque de hachage.
Exemple A.22. Coloriser du texte en utilisant les fonctions de hachage
#!/bin/bash # hash-example.sh: Colorisation de texte. # Auteur : Mariusz Gniazdowski <mgniazd-at-gmail.com> . Hash.lib # Chargement de la bibliothèque des fonctions. hash_set couleurs rouge "\033[0;31m" hash_set couleurs bleu "\033[0;34m" hash_set couleurs bleu_leger "\033[1;34m" hash_set couleurs rouge_leger "\033[1;31m" hash_set couleurs cyan "\033[0;36m" hash_set couleurs vert_leger "\033[1;32m" hash_set couleurs gris_leger "\033[0;37m" hash_set couleurs vert "\033[0;32m" hash_set couleurs jaune "\033[1;33m" hash_set couleurs violet_leger "\033[1;35m" hash_set couleurs violet "\033[0;35m" hash_set couleurs reset_couleur "\033[0;00m" # $1 - nom de la clé # $2 - valeur essaie_couleurs() { echo -en "$2" echo "Cette ligne est $1." } hash_foreach couleurs essaie_couleurs hash_echo couleurs reset_couleur -en echo -e '\nSurchargeons quelques couleurs avec du jaune.\n' # Il est difficile de lire du texte jaune sur certains terminaux. hash_dup couleurs jaune rouge vert_leger bleu vert gris_leger cyan hash_foreach couleurs essaie_couleurs hash_echo couleurs reset_color -en echo -e '\nSupprimons-les et essayons couleurs une fois encore...\n' for i in rouge vert_leger bleu vert gris_leger cyan; do hash_unset couleurs $i done hash_foreach couleurs essaie_couleurs hash_echo couleurs reset_couleur -en hash_set autre texte "Autres exemples..." hash_echo autre texte hash_get_into autre txt texte echo $texte hash_set autre my_fun essaie_couleurs hash_call autre my_fun purple "`hash_echo couleurs violet`" hash_echo couleurs reset_couleur -en echo; echo "Retour à la normale ?"; echo exit $? # Sur certains terminaux, les couleurs "légères" sont affichées en gras # et finissent par sembler plus sombres que les normales. # Pourquoi ?
Un exemple illustrant les mécanismes de hachage à partir d'un autre point de vue.
Exemple A.23. Encore plus sur les fonctions de hachage
#!/bin/bash # $Id: ha.sh,v 1.2 2007/01/08 23:58:45 gleu Exp $ # Copyright 2005 Oliver Beckstein # Sous licence GNU Public # L'auteur du script a donné le droit de l'inclure dans le guide ABS. # (Merci !) #---------------------------------------------------------------- # pseudo hachage basé sur l'expansion des paramètres indirects # API : accès par les fonctions : # # création du hachage : # # newhash Lovers # # ajout des entrées (notez les guillemets pour les espaces) # # addhash Lovers Tristan Isolde # addhash Lovers 'Romeo Montague' 'Juliet Capulet' # # accès à la valeur par la clé # # gethash Lovers Tristan ----> Isolde # # affichage de toutes les valeurs # # keyshash Lovers ----> 'Tristan' 'Romeo Montague' # # # Convention : au lieu de la syntaxe perls foo{bar} = boing', # utiliser # '_foo_bar=boing' (deux tirets bas, pas d'espaces) # # 1) stocke la clé dans _NAME_keys[] # 2) stocke la valeur dans _NAME_values[] en utilisant le même index # L'index de la dernière entrée est _NAME_ptr # # NOTE : pas de vérification d'erreurs. function _inihash () { # fonction privée # appelée au début de chaque procédure # définit : _keys _values _ptr # # usage : _inihash NAME local name=$1 _keys=_${name}_keys _values=_${name}_values _ptr=_${name}_ptr } function newhash () { # usage : newhash NAME # NAME ne devrait pas contenir d'espaces ou de '.'. # En fait, il doit être un nom syntaxiquement correct pour une variable Bash. # Nous nous reposons sur Bash pour reconnaître automatiquement des tableaux. local name=$1 local _keys _values _ptr _inihash ${name} eval ${_ptr}=0 } function addhash () { # usage : addhash NAME KEY 'VALUE with spaces' # les arguments avec espaces doivent être mis entre guillemets '' local name=$1 k="$2" v="$3" local _keys _values _ptr _inihash ${name} #echo "DEBUG(addhash): ${_ptr}=${!_ptr}" eval let ${_ptr}=${_ptr}+1 eval "$_keys[${!_ptr}]=\"${k}\"" eval "$_values[${!_ptr}]=\"${v}\"" } function gethash () { # usage: gethash NAME KEY # returns boing # ERR=0 si le nom est trouvée, 1 sinon # Ce n'est pas un bon hachage -- nous cherchons simplement dans les clés local name=$1 key="$2" local _keys _values _ptr local k v i found h _inihash ${name} # _ptr contient l'index le plus haut dans le hachage found=0 for i in $(seq 1 ${!_ptr}); do h="\${${_keys}[${i}]}" # plus propre de le faire en deux étapes eval k=${h} # (tout spécialement avec les guillemets pour les espaces) if [ "${k}" = "${key}" ]; then found=1; break; fi done; [ ${found} = 0 ] && return 1; # sinon i est l'index qui correspond à la clé h="\${${_values}[${i}]}" eval echo "${h}" return 0; } function keyshash () { # usage : keyshash NAME # renvoie la liste de toutes les clés définies pour le nom du hachage local name=$1 key="$2" local _keys _values _ptr local k i h _inihash ${name} # _ptr contient l'index le plus haut du hachage for i in $(seq 1 ${!_ptr}); do h="\${${_keys}[${i}]}" # plus propre de le faire en deux étapes eval k=${h} # (tout spécialement avec les guillemets pour les espaces) echo -n "'${k}' " done; } # -------------------------------------------------------------------- # Maintenant, testons-le. # (d'après les commentaires au début du script). newhash Lovers addhash Lovers Tristan Isolde addhash Lovers 'Romeo Montague' 'Juliet Capulet' # Résultats en sortie. echo gethash Lovers Tristan # Isolde echo keyshash Lovers # 'Tristan' 'Romeo Montague' echo; echo exit 0 # Exercice : #---------- # # Ajouter des vérifications d'erreur aux fonctions.
Maintenant, un script qui installe et monte ces jolies clés USB, version « disques durs ».
Exemple A.24. Monter des périphériques de stockage USB
#!/bin/bash # ==> usb.sh # ==> Script pour monter et installer les périphériques de stockage d'une clé USB. # ==> Lancer en tant que root au démarrage du système (voir ci-dessous). # ==> # ==> Les nouvelles distributions Linux (2004 ou ultérieures) détectent # ==> automatiquement et installent les clés USB. # ==> Elles n'ont donc pas besoin de ce script. # ==> Mais c'est toujours instructif. # This code is free software covered by GNU GPL license version 2 or above. # Please refer to http://www.gnu.org/ for the full license text. # # Ce code est un logiciel libre couvert par la licence GNU GPL version 2 et #+ ultérieure. Référez-vous à http://www.gnu.org/ pour le texte complet. # # Une partie du code provient de usb-mount écrit par Michael Hamilton (LGPL) #+ voir http://users.actrix.co.nz/michael/usbmount.html # # INSTALLATION # ------------ # Placez ceci dans /etc/hotplug/usb/clefusb. # Puis regardez dans /etc/hotplug/usb.distmap, copiez toutes les entrées de #+ stockage USB dans /etc/hotplug/usb.usermap, en substituant "usb-storage" par #+ "diskonkey". # Sinon, ce code est seulement lancé lors de l'appel/suppression du module du #+ noyau (au moins lors de mes tests), ce qui annule le but. # # A FAIRE # ------- # Gère plus d'un périphérique "diskonkey" en même temps (c'est-à-dire #+ /dev/diskonkey1 et /mnt/clefusb1), etc. Le plus gros problème ici concerne #+ la gestion par devlabel, que je n'ai pas essayé. # # AUTEUR et SUPPORT # ----------------- # Konstantin Riabitsev, <icon linux duke edu>. # Envoyez tout problème via mon adresse de courrier électronique. # # ==> Commentaires ajoutés par l'auteur du guide ABS. PERIPH_LIENSYMBOLIQUE=/dev/diskonkey POINT_MONTAGE=/mnt/clefusb LABEL_PERIPH=/sbin/devlabel CONFIG_LABEL_PERIPH=/etc/sysconfig/devlabel JE_SUIS=$0 ## # Fonctions pratiquement récupérées du code d'usb-mount. # function tousUsbScsiAttaches { find /proc/scsi/ -path '/proc/scsi/usb-storage*' -type f | xargs grep -l 'Attaché: Oui' } function periphScsiAPartirScsiUsb { echo $1 | awk -F"[-/]" '{ n=$(NF-1); print "/dev/sd" substr("abcdefghijklmnopqrstuvwxyz", n+1, 1) }' } if [ "${ACTION}" = "add" ] && [ -f "${DEVICE}" ]; then ## # Récupéré du code d'usbcam. # if [ -f /var/run/console.lock ]; then PROPRIETAIRE_CONSOLE=`cat /var/run/console.lock` elif [ -f /var/lock/console.lock ]; then PROPRIETAIRE_CONSOLE=`cat /var/lock/console.lock` else PROPRIETAIRE_CONSOLE= fi for entreeProc in $(tousUsbScsiAttaches); do scsiDev=$(periphScsiAPartirScsiUsb $entreeProc) # Quelques bogues avec usb-storage? # Les partitions ne sont pas dans /proc/partitions jusqu'à ce qu'elles #+ soient utilisées. /sbin/fdisk -l $scsiDev >/dev/null ## # La plupart des périphériques ont des informations de partitionnement, #+ donc les données sont sur /dev/sd?1. Néanmois, quelques-uns plus #+ stupides n'ont pas du tout de partitions et utilisent le périphérique #+ complet pour du stockage de données. Il essaie de deviner si vous #+ avez un /dev/sd?1 et si non, il utilise le périphérique entier. # if grep -q `basename $scsiDev`1 /proc/partitions; then part="$scsiDev""1" else part=$scsiDev fi ## # Modifie le propriétaire de la partition par l'utilisateur de la #+ console pour qu'ils puissent le monter. # if [ ! -z "$PROPRIETAIRE_CONSOLE" ]; then chown $PROPRIETAIRE_CONSOLE:disk $part fi ## # Ceci vérifie si nous avons déjà cet UID défini avec devlabel. Sinon, # il ajoute alors le périphérique à la liste. # prodid=`$LABEL_PERIPH printid -d $part` if ! grep -q $prodid $CONFIG_LABEL_PERIPH; then # croisez les doigts et espérez que cela fonctionne $LABEL_PERIPH add -d $part -s $PERIPH_LIENSYMBOLIQUE 2>/dev/null fi ## # Vérifie si le point de montage existe et le crée dans le cas contraire. # if [ ! -e $POINT_MONTAGE ]; then mkdir -p $POINT_MONTAGE fi ## # S'occupe de /etc/fstab pour faciliter le montage. # if ! grep -q "^$PERIPH_LIENSYMBOLIQUE" /etc/fstab; then # Ajoute une entrée fstab echo -e \ "$PERIPH_LIENSYMBOLIQUE\t\t$POINT_MONTAGE\t\tauto\tnoauto,owner,kudzu 0 0" \ >> /etc/fstab fi done if [ ! -z "$REMOVER" ]; then ## # Assurez-vous que ce script est appelé lors de la suppression du #+ périphérique. # mkdir -p `dirname $REMOVER` ln -s $JE_SUIS $REMOVER fi elif [ "${ACTION}" = "remove" ]; then ## # Si le périphérique est monté, le démonte proprement. # if grep -q "$POINT_MONTAGE" /etc/mtab; then # Démonte proprement. umount -l $POINT_MONTAGE fi ## # Le supprime à partir de /etc/fstab s'il existe. # if grep -q "^$PERIPH_LIENSYMBOLIQUE" /etc/fstab; then grep -v "^$PERIPH_LIENSYMBOLIQUE" /etc/fstab > /etc/.fstab.new mv -f /etc/.fstab.new /etc/fstab fi fi exit 0
Un script qui réalise une conversion texte vers HTML.
Exemple A.25. Convertir en HTML
#!/bin/bash # tohtml.sh # Convertit un fichier texte au format HTML. # Auteur : Mendel Cooper # Licence : GPL3 # Utilisation : sh tohtml.sh < fichiertexte > fichierhtml # Ce script est facilement modifiable pour accepter #+ des noms de fichier source et destination. # Suppositions : # 1) Les paragraphes du fichier texte (cible) sont séparés par une ligne blanche. # 2) Les images JPEG (*.jpg) sont situées dans le sous-répertoire "images". # 3) Les phrases importantes (en italique) commencent avec un espace suivi d'un #+ tiret bas ou sont le premier caractère sur la ligne et finissent avec un #+ tiret bas suivi d'un espace ou d'une fin de ligne. # Paramétrages TAILLEPOLICE=2 # Taille de police. REPIMG="images" # Répertoire images. # En-têtes ENT01='<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' ENT02='<!-- Convertit en HTML par le script ***tohtml.sh*** -->' ENT03='<!-- auteur du script : M. Leo Cooper <thegrendel@theriver.com> -->' ENT10='<html>' ENT11='<head>' ENT11a='</head>' ENT12a='<title>' ENT12b='</title>' ENT121='<META NAME="GENERATOR" CONTENT="tohtml.sh script">' ENT13='<body bgcolor="#dddddd">' # Modifie la couleur du fond. ENT14a='<font size=' ENT14b='>' # Bas de page FTR10='</body>' FTR11='</html>' # Balises GRAS="<b>" CENTRE="<center>" FIN_CENTRE="</center>" LF="<br>" ecrire_entetes () { echo "$ENT01" echo echo "$ENT02" echo "$ENT03" echo echo echo "$ENT10" echo "$ENT11" echo "$ENT121" echo "$ENT11a" echo "$ENT13" echo echo -n "$ENT14a" echo -n "$TAILLEPOLICE" echo "$ENT14b" echo echo "$GRAS" # Tout en gras (plus facile à lire). } traitement_texte () { while read ligne # Lire une ligne à la fois. do { if [ ! "$ligne" ] # Ligne vide ? then # Alors un nouveau paragraphe doit suivre. echo echo "$LF" # Insérer deux balises <br>. echo "$LF" echo continue # Ignorer le test du tiret bas. else # Sinon... if [[ "$ligne" =~ "\[*jpg\]" ]] # Une image ? then # Supprimer les crochets. temp=$( echo "$ligne" | sed -e 's/\[//' -e 's/\]//' ) line=""$CENTRE" <img src="\"$REPIMG"/$temp\"> "$FIN_CENTRE" " # Ajouter la balise de l'image # et la centrer. fi fi echo "$ligne" | grep -q _ if [ "$?" -eq 0 ] # Si la ligne contient un tiret bas.. then # ============================================================ # Placer en italique une phrase entre tiret bas. temp=$( echo "$ligne" | sed -e 's/ _/ <i>/' -e 's/_ /<\/i> /' | sed -e 's/^_/<i>/' -e 's/_$/<\/i>/' ) # Traiter seulement les tirets bas préfixés par un espace, #+ suivi par un espace ou en fin ou en début de ligne. # Ne pas convertir les tirets bas contenus dans un mot ! line="$temp" # Ralentit l'exécution du script. Cela peut-il être optimisé ? # ============================================================ fi echo echo "$ligne" echo } # Fin while done } # Fin traitement_texte () ecrire_basdepage () # Fin des balises. { echo "$FTR10" echo "$FTR11" } # main () { # ========= ecrire_entetes traitement_texte ecrire_basdepage # ========= # } exit $? # Exercices : # ---------- # 1) Correction : vérifiez le tiret bas de fermeture avant une virgule ou #+ un point. # 2) Ajoutez un test sur le présence d'un tiret bas de fin dans les phrases #+ à mettre en italique.
Voici quelque chose qui va réchauffer le coeur des webmasters : un script qui sauvegarde les traces du serveur web.
Exemple A.26. Préserver les weblogs
#!/bin/bash # archiveweblogs.sh v1.0 # Troy Engel <tengel@fluid.com> # Légèrement modifié par l'auteur du document # Utilisé avec sa permission. # # Ce script préservera les traces web habituellement supprimées à partir d'une #+ installation RedHat/Apache par défaut. # Il sauvegardera les fichiers en indiquant la date et l'heure dans le nom du #+ fichier, compressé avec bzip, dans un répertoire donné. # # Lancez ceci avec crontab la nuit car bzip2 avale la puissance du CPU sur des #+ journaux particulièrement gros. # 0 2 * * * /opt/sbin/archiveweblogs.sh PROBLEME=66 # Modifiez-le par votre répertoire de sauvegarde. REP_SAUVEGARDE=/opt/sauvegardes/journaux_web # Apache/RedHat par défaut JOURS_DE_SAUVEGARDE="4 3 2 1" REP_JOURNAUX=/var/log/httpd JOURNAUX="access_log error_log" # Emplacement par défaut des programmes RedHat LS=/bin/ls MV=/bin/mv ID=/usr/bin/id CUT=/bin/cut COL=/usr/bin/column BZ2=/usr/bin/bzip2 # Sommes-nous root? USER=`$ID -u` if [ "X$USER" != "X0" ]; then echo "PANIQUE : Seul root peut lancer ce script !" exit $PROBLEME fi # Le répertoire de sauvegarde existe-t'il ? est-il modifiable ? if [ ! -x $REP_SAUVEGARDE ]; then echo "PANIQUE : $REP_SAUVEGARDE n'existe pas ou n'est pas modifiable !" exit $PROBLEME fi # Déplace, renomme et compresse avec bzip2 les journaux for jour in $JOURS_DE_SAUVEGARDE; do for journal in $JOURNAUX; do MONFICHIER="$REP_JOURNAUX/$journal.$jour" if [ -w $MONFICHIER ]; then DTS=`$LS -lgo --time-style=+%Y%m%d $MONFICHIER | $COL -t | $CUT -d ' ' -f7` $MV $MONFICHIER $REP_SAUVEGARDE/$journal.$DTS $BZ2 $REP_SAUVEGARDE/$journal.$DTS else # Affiche une erreur seulement si le fichier existe (ne peut # s'écrire sur lui-même). if [ -f $MONFICHIER ]; then echo "ERREUR : $MONFICHIER n'est pas modifiable. Je passe au suivant." fi fi done done exit 0
Comment empêcher le shell d'étendre et de réinterpréter les chaînes ?
Exemple A.27. Protéger les chaînes littérales
#! /bin/bash # protect_literal.sh # set -vx :<<-'_Protect_Literal_String_Doc' Copyright (c) Michael S. Zick, 2003; All Rights Reserved License: Unrestricted reuse in any form, for any purpose. Warranty: None Revision: $ID$ Copyright (c) Michael S. Zick, 2003; Tous droits réservés Licence: Utilisation non restreinte quelque soit sa forme, quelque soit le but. Garantie : Aucune Revision: $ID$ Documentation redirigée vers no-operation sous Bash. Bash enverra ce bloc vers '/dev/null' lorsque le script sera lu la première fois. (Supprimez le commentaire de la commande ci-dessus pour voir cette action.) Supprimez la première ligne (Sha-Bang, #!) lors de l'utilisation de ce script en tant que procédure d'une bibliothèque. Décommentez aussi le code d'exemple utilisé dans les deux places indiquées. Usage: _protect_literal_str 'une chaine quelconque qui correspond à votre ${fantaisie}' Affiche simplement l'argument sur la sortie standard, les guillemets étant restaurés. $(_protect_literal_str 'une chaine quelconque qui correspond à votre ${fantaisie}') sur le côté droit d'une instruction d'affectation. Fait: Utilisé sur le côté droit d'une affectation, préserve les guillemets protégeant le contenu d'un littéral lors de son affectation. Notes: Les noms étranges (_*) sont utilisé pour éviter de rencontrer ceux choisis par l'utilisateur lorsqu'il l'utilise en tant que bibliothèque. _Protect_Literal_String_Doc # La fonction 'pour illustration' _protect_literal_str() { # Récupére un caractère inutilisé, non affichable comme IFS local. # Non requis, mais montre ce que nous ignorons. local IFS=$'\x1B' # caractère \ESC # Entoure tous_elements_de entre guillemets lors de l'affectation. local tmp=$'\x27'$@$'\x27' # local tmp=$'\''$@$'\'' # Encore plus sale. local len=${#tmp} # Info seulement. echo $tmp a une longueur de $len. # Sortie ET information. } # Ceci est la version nom-court. _pls() { local IFS=$'x1B' # caractère \ESC (non requis) echo $'\x27'$@$'\x27' # Paramètre global codé en dur } # :<<-'_Protect_Literal_String_Test' # # # Supprimez le "# " ci-dessus pour désactiver ce code. # # # # Voir à quoi ressemble ceci une fois affiché. echo echo "- - Test Un - -" _protect_literal_str 'Bonjour $utilisateur' _protect_literal_str 'Bonjour "${nom_utilisateur}"' echo # Ce qui donne : # - - Test Un - - # 'Bonjour $utilisateur' fait 13 caractères de long. # 'Bonjour "${nom_utilisateur}"' a une taille de 21 caractères. # Cela ressemble à notre attente, donc pourquoi tout ceci ? # La différence est cachée à l'intérieur de l'ordonnancement interne des opérations #+ de Bash. # Ce qui s'affiche lorsque vous l'utilisez sur le côté droit de l'affectation. # Déclarez un tableau pour les valeurs de tests. declare -a tableauZ # Affecte les éléments comprenant différents types de guillemets et de caractères #+ d'échappement. tableauZ=( zero "$(_pls 'Bonjour ${Moi}')" 'Bonjour ${Toi}' "\'Passe: ${pw}\'" ) # Maintenant, affiche ce tableau. echo "- - Test Deux - -" for (( i=0 ; i<${#tableauZ[*]} ; i++ )) do echo Elément $i: ${tableauZ[$i]} fait ${#tableauZ[$i]} caractères de long. done echo # Ce qui nous donne : # - - Test Deux - - # Elément 0: zero fait 4 caractères de long. # Notre élément marqueur # Elément 1: 'Bonjour ${Moi}' fait 13 caractères de long.# Notre "$(_pls '...' )" # Elément 2: Bonjour ${Toi} fait 12 caractères de long. # Les guillemets manquent # Elément 3: \'Passe: \' fait 10 caractères de long. # ${pw} n'affiche rien # Maintenant, affectez ce résultat. declare -a tableau2=( ${tableauZ[@]} ) # Et affiche ce qui s'est passé. echo "- - Test Trois - -" for (( i=0 ; i<${#tableau2[*]} ; i++ )) do echo Elément $i: ${tableau2[$i]} fait ${#tableau2[$i]} caractères de long. done echo # Ce qui nous donne : # - - Test Trois - - # Elément 0: zero fait 4 caractères de long. # Notre élément marqueur. # Elément 1: Hello ${Moi} fait 11 caractères de long.# Résultat attendu. # Elément 2: Hello fait 5 caractères de long. # ${Toi} n'affiche rien. # Elément 3: 'Passe: fait 6 caractères de long. # Se coupe sur les espaces. # Elément 4: ' fait 1 caractères de long. # Le guillemet final est ici # maintenant. # Les guillemets de début et de fin de notre élément 1 sont supprimés. # Bien que non affiché, les espaces blancs de début et de fin sont aussi supprimés. # Maintenant que le contenu des chaînes est initialisé, Bash placera toujours, en interne, #+ entre guillemets les contenus comme requis lors de ses opérations. # Pourquoi? # En considérant notre construction "$(_pls 'Hello ${Moi}')" : # " ... " -> Supprime les guillemets. # $( ... ) -> Remplace avec le resultat de ..., supprime ceci. # _pls ' ... ' -> appelé avec des arguments littérales, supprime les guillemets. # Le résultat renvoyé inclut les guillemets ; MAIS le processus ci-dessus a déjà #+ été réalisé, donc il devient une partie de la valeur affectée. # # De manière identique, lors d'une utilisation plus poussée de la variable de type #+ chaînes de caractères, le ${Moi} fait partie du contenu (résultat) et survit à #+ toutes les opérations. # (Jusqu'à une indication explicite pour évaluer la chaîne). # Astuce : Voir ce qui arrive lorsque les guillemets ($'\x27') sont remplacés par #+ des caractères ($'\x22') pour les procédures ci-dessus. # Intéressant aussi pour supprimer l'ajout de guillemets. # _Protect_Literal_String_Test # # # Supprimez le caractère "# " ci-dessus pour désactiver ce code. # # # exit 0
Et si vous voulez que le shell étende et réinterprète les chaînes ?
Exemple A.28. Ne pas protéger les chaînes littérales
#! /bin/bash # unprotect_literal.sh # set -vx :<<-'_UnProtect_Literal_String_Doc' Copyright (c) Michael S. Zick, 2003; All Rights Reserved License: Unrestricted reuse in any form, for any purpose. Warranty: None Revision: $ID$ Copyright (c) Michael S. Zick, 2003; Tous droits réservés Licence: Utilisation non restreinte quelque soit sa forme, quelque soit le but. Garantie : Aucune Revision: $ID$ Documentation redirigée vers no-operation sous Bash. Bash enverra ce bloc vers '/dev/null' lorsque le script est lu la première fois. (Supprimez le commentaire de la commande ci-dessus pour voir cette action.) Supprimez la première ligne (Sha-Bang, #!) lors de l'utilisation de ce script en tant que procédure d'une bibliothèque. Dé-commentez aussi le code d'exemple utilisé dans les deux places indiquées. Utilisation: Complément de la fonction "$(_pls 'Chaine litterale')". (Voir l'exemple protect_literal.sh.) VarChaine=$(_upls VariableChaineProtege) Fait: Lorsqu'utilisé sur le côté droit d'une instruction d'affectation ; fait que la substition est intégré à la chaîne protégée. Notes: Les noms étranges (_*) sont utilisé pour éviter de rencontrer ceux choisis par l'utilisateur lorsqu'il l'utilise en tant que bibliothèque. _UnProtect_Literal_String_Doc _upls() { local IFS=$'x1B' # Caractère \ESC (non requis) eval echo $@ # Substitution on the glob. } # :<<-'_UnProtect_Literal_String_Test' # # # Supprimez le "# " ci-dessus pour désactiver ce code. # # # _pls() { local IFS=$'x1B' # Caractère \ESC (non requis) echo $'\x27'$@$'\x27' # Paramètre global codé en dur. } # Déclare un tableau pour les valeurs de tests. declare -a tableauZ # Affecte les éléments avec des types différents de guillements et échappements. tableauZ=( zero "$(_pls 'Bonjour ${Moi}')" 'Bonjour ${Toi}' "\'Passe: ${pw}\'" ) # Maintenant, faire une affectation avec ce résultat. declare -a tableau2=( ${tableauZ[@]} ) # Ce qui fait : # - - Test trois - - # Elément 0: zero est d'une longueur 4 # Notre élément marqueur. # Elément 1: Bonjour ${Moi} est d'une longueur 11 # Résultat attendu. # Elément 2: Bonjour est d'une longueur 5 # ${Toi} ne renvoit rien. # Elément 3: 'Passe est d'une longueur 6 # Divisé sur les espaces. # Elément 4: ' est d'une longueur 1 # La fin du guillemet est ici # maintenant. # set -vx # Initialise 'Moi' avec quelque-chose pour la substitution imbriqué ${Moi}. # Ceci a besoin d'être fait SEULEMENT avant d'évaluer la chaîne protégée. # (C'est pourquoi elle a été protégée.) Moi="au gars du tableau." # Initialise une variable de chaînes de caractères pour le résultat. nouvelleVariable=$(_upls ${tableau2[1]}) # Affiche le contenu. echo $nouvelleVariable # Avons-nous réellement besoin d'une fonction pour faire ceci ? variablePlusRecente=$(eval echo ${tableau2[1]}) echo $variablePlusRecente # J'imagine que non mais la fonction _upls nous donne un endroit où placer la #+ documentation. # Ceci aide lorsque nous oublions une construction # comme ce que signifie #+ $(eval echo ... ). # Que se passe-t'il si Moi n'est pas initialisé quand la chaîne protégée est #+ évaluée ? unset Moi variableLaPlusRecente=$(_upls ${tableau2[1]}) echo $variableLaPlusRecente # Simplement partie, pas d'aide, pas d'exécution, pas d'erreurs. # Pourquoi ? # Initialiser le contenu d'une variable de type chaîne contenant la séquence de #+ caractères qui ont une signification dans Bash est un problème général #+ d'écriture des scripts. # # Ce problème est maintenant résolu en huit lignes de code (et quatre pages de #+ description). # Où cela nous mène-t'il ? # Les pages web au contenu dynamique en tant que tableau de chaînes Bash. # Le contenu par requête pour une commande Bash 'eval' sur le modèle de page #+ stocké. # Pas prévu pour remplacer PHP, simplement quelque chose d'intéressant à faire. ### # Vous n'avez pas une application pour serveur web ? # Aucun problème, vérifiez dans le répertoire d'exemples des sources Bash : #+ il existe aussi un script Bash pour faire ça. # _UnProtect_Literal_String_Test # # # Supprimez le "# " ci-dessus pour désactiver ce code. # # # exit 0
Ce puissant script chasse les spammers.
Exemple A.29. Identification d'un spammer
#!/bin/bash # $Id: is_spammer.bash,v 1.6 2007/01/08 23:58:45 gleu Exp $ # L'information ci-dessus est l'ID RCS. # La dernière version de ce script est disponible sur http://www.morethan.org. # # Spammer-identification # par Michael S. Zick # Utilisé dans le guide ABS Guide avec sa permission. ####################################################### # Documentation # Voir aussi "Quickstart" à la fin du script. ####################################################### :<<-'__is_spammer_Doc_' Copyright (c) Michael S. Zick, 2004 Licence : Ré-utilisation non restreinte quelque soit la forme et le but Garantie: Aucune -{C'est un script; l'utilisateur est seul responsable.}- Impatient? Code de l'application : Allez à "# # # Code 'Chez le spammeur' # # #" Sortie d'exemple : ":<<-'_is_spammer_outputs_'" Comment l'utiliser : Entrer le nom du script sans arguments. Ou allez à "Quickstart" à la fin du script. Fournit Avec un nom de domaine ou une adresse IP(v4) en entrée : Lance un ensemble exhaustif de requêtes pour trouver les ressources réseau associées (raccourci pour un parcours récursif dans les TLD). Vérifie les adresses IP(v4) disponibles sur les serveurs de noms Blacklist. S'il se trouve faire partie d'une adresse IP(v4) indiquée, rapporte les enregistrements texte de la liste noire. (habituellement des liens hypertextes vers le rapport spécifique.) Requiert Une connexion Internet fonctionnelle. (Exercice : ajoutez la vérification et/ou annulez l'opération si la connexion n'est pas établie lors du lancement du script.) Une version de Bash disposant des tableaux (2.05b+). Le programme externe 'dig' -- ou outil fourni avec l'ensemble de programmes 'bind'. Spécifiquement, la version qui fait partie de Bind série 9.x Voir : http://www.isc.org Toutes les utilisations de 'dig' sont limitées à des fonctions d'emballage, qui pourraient être ré-écrites si nécessaire. Voir : dig_wrappers.bash pour plus de détails. ("Documentation supplémentaire" -- ci-dessous) Usage Ce script requiert un seul argument, qui pourrait être: 1) Un nom de domaine ; 2) Une adresse IP(v4) ; 3) Un nom de fichier, avec un nom ou une adresse par ligne. Ce script accepte un deuxième argument optionnel, qui pourrait être: 1) Un serveur de noms Blacklist ; 2) Un nom de fichier avec un serveur de noms Blacklist par ligne. Si le second argument n'est pas fourni, le script utilise un ensemble intégré de serveurs Blacklist (libres). Voir aussi la section Quickstart à la fin de ce script (après 'exit'). Codes de retour 0 - Tout est OK 1 - Échec du script 2 - Quelque chose fait partie de la liste noire Variables d'environnement optionnelles SPAMMER_TRACE S'il comprend le nom d'un fichier sur lequel le script a droit d'écriture, le script tracera toute l'exécution. SPAMMER_DATA S'il comprend le nom d'un fichier sur lequel le script a droit d'écriture, le script y enregitrera les données trouvées sous la forme d'un fichier GraphViz. Voir : http://www.research.att.com/sw/tools/graphviz SPAMMER_LIMIT Limite la profondeur des recherches de ressources. Par défaut à deux niveaux. Un paramètrage de 0 (zero) signifie 'illimité' . . . Attention : le script pourrait parcourir tout Internet ! Une limite de 1 ou 2 est plus utile dans le cas d'un fichier de noms de domaine et d'adresses. Une limite encore plus haute est utile pour chasser les gangs de spam. Documentation supplémentaire Téléchargez l'ensemble archivé de scripts expliquant et illustrant la fonction contenue dans ce script. http://personal.riverusers.com/mszick_clf.tar.bz2 Notes d'étude Ce script utilise un grand nombre de fonctions. Pratiquement toutes les fonctions générales ont leur propre script d'exemple. Chacun des scripts d'exemples ont leur commentaires (niveau tutoriel). Projets pour ce script Ajoutez le support des adresses IP(v6). Les adresses IP(v6) sont reconnues mais pas gérées. Projet avancé Ajoutez le détail de la recherche inverse dans les informations découvertes. Rapportez la chaîne de délégation et les contacts d'abus. Modifiez la sortie du fichier GraphViz pour inclure les informations nouvellement découvertes. __is_spammer_Doc_ ####################################################### #### Configuration spéciale pour l'IFS utilisée pour l'analyse des chaînes. #### # Espace blanc == :Espace:Tabulation:Retour à la ligne:Retour chariot: WSP_IFS=$'\x20'$'\x09'$'\x0A'$'\x0D' # Pas d'espace blanc == Retour à la ligne:Retour chariot NO_WSP=$'\x0A'$'\x0D' # Séparateur de champ pour les adresses IP décimales ADR_IFS=${NO_WSP}'.' # Tableau de conversions de chaînes DOT_IFS='.'${WSP_IFS} # # # Machine à pile pour les opérations restantes # # # # Cet ensemble de fonctions est décrite dans func_stack.bash. # (Voir "Documentation supplémentaire" ci-dessus.) # # # # Pile globale des opérations restantes. declare -f -a _pending_ # Sentinelle gloable pour les épuiseurs de pile declare -i _p_ctrl_ # Déteneur global pour la fonction en cours d'exécution declare -f _pend_current_ # # # Version de déboguage seulement - à supprimer pour une utilisation normale # # # # # La fonction stockée dans _pend_hook_ est appellée immédiatement avant que # chaque fonction en cours ne soit évaluée. Pile propre, _pend_current_ configuré. # # Ceci est démontré dans pend_hook.bash. declare -f _pend_hook_ # # # # La fonction ne faisant rien. pend_dummy() { : ; } # Efface et initialise la pile des fonctions. pend_init() { unset _pending_[@] pend_func pend_stop_mark _pend_hook_='pend_dummy' # Débogage seulement. } # Désactive la fonction du haut de la pile. pend_pop() { if [ ${#_pending_[@]} -gt 0 ] then local -i _top_ _top_=${#_pending_[@]}-1 unset _pending_[$_top_] fi } # pend_func function_name [$(printf '%q\n' arguments)] pend_func() { local IFS=${NO_WSP} set -f _pending_[${#_pending_[@]}]=$@ set +f } # La fonction qui arrête la sortie : pend_stop_mark() { _p_ctrl_=0 } pend_mark() { pend_func pend_stop_mark } # Exécute les fonctions jusqu'à 'pend_stop_mark' . . . pend_release() { local -i _top_ # Déclare _top_ en tant qu'entier. _p_ctrl_=${#_pending_[@]} while [ ${_p_ctrl_} -gt 0 ] do _top_=${#_pending_[@]}-1 _pend_current_=${_pending_[$_top_]} unset _pending_[$_top_] $_pend_hook_ # Débogage seulement. eval $_pend_current_ done } # Supprime les fonctions jusqu'à 'pend_stop_mark' . . . pend_drop() { local -i _top_ local _pd_ctrl_=${#_pending_[@]} while [ ${_pd_ctrl_} -gt 0 ] do _top_=$_pd_ctrl_-1 if [ "${_pending_[$_top_]}" == 'pend_stop_mark' ] then unset _pending_[$_top_] break else unset _pending_[$_top_] _pd_ctrl_=$_top_ fi done if [ ${#_pending_[@]} -eq 0 ] then pend_func pend_stop_mark fi } #### Éditeurs de tableaux #### # Cette fonction est décrite dans edit_exact.bash. # (Voir "Additional documentation", ci-dessus.) # edit_exact <excludes_array_name> <target_array_name> edit_exact() { [ $# -eq 2 ] || [ $# -eq 3 ] || return 1 local -a _ee_Excludes local -a _ee_Target local _ee_x local _ee_t local IFS=${NO_WSP} set -f eval _ee_Excludes=\( \$\{$1\[@\]\} \) eval _ee_Target=\( \$\{$2\[@\]\} \) local _ee_len=${#_ee_Target[@]} # Longueur originale. local _ee_cnt=${#_ee_Excludes[@]} # Exclut la longueur de la liste. [ ${_ee_len} -ne 0 ] || return 0 # Ne peut pas éditer une longueur nulle. [ ${_ee_cnt} -ne 0 ] || return 0 # Ne peut pas éditer une longueur nulle. for (( x = 0; x < ${_ee_cnt} ; x++ )) do _ee_x=${_ee_Excludes[$x]} for (( n = 0 ; n < ${_ee_len} ; n++ )) do _ee_t=${_ee_Target[$n]} if [ x"${_ee_t}" == x"${_ee_x}" ] then unset _ee_Target[$n] # Désactive la correspondance. [ $# -eq 2 ] && break # Si deux arguments, alors terminé. fi done done eval $2=\( \$\{_ee_Target\[@\]\} \) set +f return 0 } # Cette fonction est décrite dans edit_by_glob.bash. # edit_by_glob <excludes_array_name> <target_array_name> edit_by_glob() { [ $# -eq 2 ] || [ $# -eq 3 ] || return 1 local -a _ebg_Excludes local -a _ebg_Target local _ebg_x local _ebg_t local IFS=${NO_WSP} set -f eval _ebg_Excludes=\( \$\{$1\[@\]\} \) eval _ebg_Target=\( \$\{$2\[@\]\} \) local _ebg_len=${#_ebg_Target[@]} local _ebg_cnt=${#_ebg_Excludes[@]} [ ${_ebg_len} -ne 0 ] || return 0 [ ${_ebg_cnt} -ne 0 ] || return 0 for (( x = 0; x < ${_ebg_cnt} ; x++ )) do _ebg_x=${_ebg_Excludes[$x]} for (( n = 0 ; n < ${_ebg_len} ; n++ )) do [ $# -eq 3 ] && _ebg_x=${_ebg_x}'*' # Do prefix edit if [ ${_ebg_Target[$n]:=} ] #+ if defined & set. then _ebg_t=${_ebg_Target[$n]/#${_ebg_x}/} [ ${#_ebg_t} -eq 0 ] && unset _ebg_Target[$n] fi done done eval $2=\( \$\{_ebg_Target\[@\]\} \) set +f return 0 } # Cette fonction est décrite par unique_lines.bash. # unique_lines <in_name> <out_name> unique_lines() { [ $# -eq 2 ] || return 1 local -a _ul_in local -a _ul_out local -i _ul_cnt local -i _ul_pos local _ul_tmp local IFS=${NO_WSP} set -f eval _ul_in=\( \$\{$1\[@\]\} \) _ul_cnt=${#_ul_in[@]} for (( _ul_pos = 0 ; _ul_pos < ${_ul_cnt} ; _ul_pos++ )) do if [ ${_ul_in[${_ul_pos}]:=} ] # Si définie et non vide then _ul_tmp=${_ul_in[${_ul_pos}]} _ul_out[${#_ul_out[@]}]=${_ul_tmp} for (( zap = _ul_pos ; zap < ${_ul_cnt} ; zap++ )) do [ ${_ul_in[${zap}]:=} ] && [ 'x'${_ul_in[${zap}]} == 'x'${_ul_tmp} ] && unset _ul_in[${zap}] done fi done eval $2=\( \$\{_ul_out\[@\]\} \) set +f return 0 } # Cette fonction est décrite par char_convert.bash. # to_lower <string> to_lower() { [ $# -eq 1 ] || return 1 local _tl_out _tl_out=${1//A/a} _tl_out=${_tl_out//B/b} _tl_out=${_tl_out//C/c} _tl_out=${_tl_out//D/d} _tl_out=${_tl_out//E/e} _tl_out=${_tl_out//F/f} _tl_out=${_tl_out//G/g} _tl_out=${_tl_out//H/h} _tl_out=${_tl_out//I/i} _tl_out=${_tl_out//J/j} _tl_out=${_tl_out//K/k} _tl_out=${_tl_out//L/l} _tl_out=${_tl_out//M/m} _tl_out=${_tl_out//N/n} _tl_out=${_tl_out//O/o} _tl_out=${_tl_out//P/p} _tl_out=${_tl_out//Q/q} _tl_out=${_tl_out//R/r} _tl_out=${_tl_out//S/s} _tl_out=${_tl_out//T/t} _tl_out=${_tl_out//U/u} _tl_out=${_tl_out//V/v} _tl_out=${_tl_out//W/w} _tl_out=${_tl_out//X/x} _tl_out=${_tl_out//Y/y} _tl_out=${_tl_out//Z/z} echo ${_tl_out} return 0 } #### Fonctions d'aide de l'application #### # Tout le monde n'utilise pas de points comme séparateur (APNIC, par exemple). # Cette fonction est décrite par to_dot.bash # to_dot <string> to_dot() { [ $# -eq 1 ] || return 1 echo ${1//[#|@|%]/.} return 0 } # Cette fonction est décrite par is_number.bash. # is_number <input> is_number() { [ "$#" -eq 1 ] || return 1 # est-ce blanc ? [ x"$1" == 'x0' ] && return 0 # est-ce zéro ? local -i tst let tst=$1 2>/dev/null # sinon, c'est numérique ! return $? } # Cette fonction est décrite par is_address.bash. # is_address <input> is_address() { [ $# -eq 1 ] || return 1 # Blanc ==> faux local -a _ia_input local IFS=${ADR_IFS} _ia_input=( $1 ) if [ ${#_ia_input[@]} -eq 4 ] && is_number ${_ia_input[0]} && is_number ${_ia_input[1]} && is_number ${_ia_input[2]} && is_number ${_ia_input[3]} && [ ${_ia_input[0]} -lt 256 ] && [ ${_ia_input[1]} -lt 256 ] && [ ${_ia_input[2]} -lt 256 ] && [ ${_ia_input[3]} -lt 256 ] then return 0 else return 1 fi } # Cette fonction est décrite par split_ip.bash. # split_ip <IP_address> <array_name_norm> [<array_name_rev>] split_ip() { [ $# -eq 3 ] || # Soit trois [ $# -eq 2 ] || return 1 #+ soit deux arguments local -a _si_input local IFS=${ADR_IFS} _si_input=( $1 ) IFS=${WSP_IFS} eval $2=\(\ \$\{_si_input\[@\]\}\ \) if [ $# -eq 3 ] then # Construit le tableau de l'ordre des requêtes. local -a _dns_ip _dns_ip[0]=${_si_input[3]} _dns_ip[1]=${_si_input[2]} _dns_ip[2]=${_si_input[1]} _dns_ip[3]=${_si_input[0]} eval $3=\(\ \$\{_dns_ip\[@\]\}\ \) fi return 0 } # Cette fonction est décrite par dot_array.bash. # dot_array <array_name> dot_array() { [ $# -eq 1 ] || return 1 # Un seul argument requis. local -a _da_input eval _da_input=\(\ \$\{$1\[@\]\}\ \) local IFS=${DOT_IFS} local _da_output=${_da_input[@]} IFS=${WSP_IFS} echo ${_da_output} return 0 } # Cette fonction est décrite par file_to_array.bash # file_to_array <file_name> <line_array_name> file_to_array() { [ $# -eq 2 ] || return 1 # Deux arguments requis. local IFS=${NO_WSP} local -a _fta_tmp_ _fta_tmp_=( $(cat $1) ) eval $2=\( \$\{_fta_tmp_\[@\]\} \) return 0 } # Columnized print of an array of multi-field strings. # col_print <array_name> <min_space> <tab_stop [tab_stops]> col_print() { [ $# -gt 2 ] || return 0 local -a _cp_inp local -a _cp_spc local -a _cp_line local _cp_min local _cp_mcnt local _cp_pos local _cp_cnt local _cp_tab local -i _cp local -i _cpf local _cp_fld # ATTENTION : LIGNE SUIVANTE NON BLANCHE -- CE SONT DES ESPACES ENTRE #+ GUILLEMET. local _cp_max=' ' set -f local IFS=${NO_WSP} eval _cp_inp=\(\ \$\{$1\[@\]\}\ \) [ ${#_cp_inp[@]} -gt 0 ] || return 0 # Le cas vide est simple. _cp_mcnt=$2 _cp_min=${_cp_max:1:${_cp_mcnt}} shift shift _cp_cnt=$# for (( _cp = 0 ; _cp < _cp_cnt ; _cp++ )) do _cp_spc[${#_cp_spc[@]}]="${_cp_max:2:$1}" #" shift done _cp_cnt=${#_cp_inp[@]} for (( _cp = 0 ; _cp < _cp_cnt ; _cp++ )) do _cp_pos=1 IFS=${NO_WSP}$'\x20' _cp_line=( ${_cp_inp[${_cp}]} ) IFS=${NO_WSP} for (( _cpf = 0 ; _cpf < ${#_cp_line[@]} ; _cpf++ )) do _cp_tab=${_cp_spc[${_cpf}]:${_cp_pos}} if [ ${#_cp_tab} -lt ${_cp_mcnt} ] then _cp_tab="${_cp_min}" fi echo -n "${_cp_tab}" (( _cp_pos = ${_cp_pos} + ${#_cp_tab} )) _cp_fld="${_cp_line[${_cpf}]}" echo -n ${_cp_fld} (( _cp_pos = ${_cp_pos} + ${#_cp_fld} )) done echo done set +f return 0 } # # # # Flux de données 'Chassez le spammeur' # # # # # Code de retour de l'application declare -i _hs_RC # Entrée originale, à partir de laquelle les adresses IP sont supprimées # Après cela, les noms de domaine à vérifier declare -a uc_name # Les adresses IP de l'entrée originale sont déplacées ici # Après cela, les adresses IP à vérifier declare -a uc_address # Noms contre lesquels l'expansion d'adresses est lancée # Prêt pour la recherche des détails des noms declare -a chk_name # Noms contre lesquelles l'expansion de noms est lancée # Prêt pour la recherche des détails des adresses declare -a chk_address # La récursion est depth-first-by-name. # expand_input_address maintient cette liste pour prohiber #+ deux fois les adresses à rechercher durant la récursion #+ des noms de domaine. declare -a been_there_addr been_there_addr=( '127.0.0.1' ) # Liste blanche pour localhost # Noms que nous avons vérifié (ou abandonné) declare -a known_name # Adresses que nous avons vérifié (ou abandonné) declare -a known_address # Liste de zéro ou plus de serveurs Blacklist pour la vérification. # Chaque 'known_address' vérifiera chaque serveur, #+ avec des réponses négatives et des échecs supprimés. declare -a list_server # limite d'indirection - initialisée à zéro == pas de limite indirect=${SPAMMER_LIMIT:=2} # # # # données de sortie d'informations 'Chassez le spammeur' # # # # # Tout nom de domaine pourrait avoir de nombreuses adresses IP. # Toute adresse IP pourrait avoir de multiples noms de domaines. # Du coup, trace des paires uniques adresse-nom. declare -a known_pair declare -a reverse_pair # En plus des variables de flux de données ; known_address #+ known_name et list_server, ce qui suit est sorti vers le fichier d'interface #+ graphique externe. # Chaîne d'autorité, parent -> champs SOA. declare -a auth_chain # Référence la chaîne, nom du parent -> nom du fils declare -a ref_chain # Chaîne DNS - nom de domaine -> adresse declare -a name_address # Paires de nom et service - nom de domaine -> service declare -a name_srvc # Paires de nom et ressource - nom de domaine -> enregistrement de ressource declare -a name_resource # Paires de parent et fils - nom de parent -> nom du fils # Ceci POURRAIT NE PAS être identique au ref_chain qui suit ! declare -a parent_child # Paires des correspondances d'adresses et des listes noires - adresse->serveur declare -a address_hits # Liste les données du fichier d'interface declare -f _dot_dump _dot_dump=pend_dummy # Initialement un no-op # Les traces des données sont activées en initialisant la variable #+ d'environnement SPAMMER_DATA avec le nom d'un fichier sur lequel le script #+ peut écrire. declare _dot_file # Fonction d'aide pour la fonction dump-to-dot-file # dump_to_dot <array_name> <prefix> dump_to_dot() { local -a _dda_tmp local -i _dda_cnt local _dda_form=' '${2}'%04u %s\n' local IFS=${NO_WSP} eval _dda_tmp=\(\ \$\{$1\[@\]\}\ \) _dda_cnt=${#_dda_tmp[@]} if [ ${_dda_cnt} -gt 0 ] then for (( _dda = 0 ; _dda < _dda_cnt ; _dda++ )) do printf "${_dda_form}" \ "${_dda}" "${_dda_tmp[${_dda}]}" >>${_dot_file} done fi } # Qui initialise aussi _dot_dump par cette fonction . . . dump_dot() { local -i _dd_cnt echo '# Data vintage: '$(date -R) >${_dot_file} echo '# ABS Guide: is_spammer.bash; v2, 2004-msz' >>${_dot_file} echo >>${_dot_file} echo 'digraph G {' >>${_dot_file} if [ ${#known_name[@]} -gt 0 ] then echo >>${_dot_file} echo '# Known domain name nodes' >>${_dot_file} _dd_cnt=${#known_name[@]} for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ )) do printf ' N%04u [label="%s"] ;\n' \ "${_dd}" "${known_name[${_dd}]}" >>${_dot_file} done fi if [ ${#known_address[@]} -gt 0 ] then echo >>${_dot_file} echo '# Known address nodes' >>${_dot_file} _dd_cnt=${#known_address[@]} for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ )) do printf ' A%04u [label="%s"] ;\n' \ "${_dd}" "${known_address[${_dd}]}" >>${_dot_file} done fi echo >>${_dot_file} echo '/*' >>${_dot_file} echo ' * Known relationships :: User conversion to' >>${_dot_file} echo ' * graphic form by hand or program required.' >>${_dot_file} echo ' *' >>${_dot_file} if [ ${#auth_chain[@]} -gt 0 ] then echo >>${_dot_file} echo '# Authority reference edges followed and field source.' >>${_dot_file} dump_to_dot auth_chain AC fi if [ ${#ref_chain[@]} -gt 0 ] then echo >>${_dot_file} echo '# Name reference edges followed and field source.' >>${_dot_file} dump_to_dot ref_chain RC fi if [ ${#name_address[@]} -gt 0 ] then echo >>${_dot_file} echo '# Known name->address edges' >>${_dot_file} dump_to_dot name_address NA fi if [ ${#name_srvc[@]} -gt 0 ] then echo >>${_dot_file} echo '# Known name->service edges' >>${_dot_file} dump_to_dot name_srvc NS fi if [ ${#name_resource[@]} -gt 0 ] then echo >>${_dot_file} echo '# Known name->resource edges' >>${_dot_file} dump_to_dot name_resource NR fi if [ ${#parent_child[@]} -gt 0 ] then echo >>${_dot_file} echo '# Known parent->child edges' >>${_dot_file} dump_to_dot parent_child PC fi if [ ${#list_server[@]} -gt 0 ] then echo >>${_dot_file} echo '# Known Blacklist nodes' >>${_dot_file} _dd_cnt=${#list_server[@]} for (( _dd = 0 ; _dd < _dd_cnt ; _dd++ )) do printf ' LS%04u [label="%s"] ;\n' \ "${_dd}" "${list_server[${_dd}]}" >>${_dot_file} done fi unique_lines address_hits address_hits if [ ${#address_hits[@]} -gt 0 ] then echo >>${_dot_file} echo '# Known address->Blacklist_hit edges' >>${_dot_file} echo '# CAUTION: dig warnings can trigger false hits.' >>${_dot_file} dump_to_dot address_hits AH fi echo >>${_dot_file} echo ' *' >>${_dot_file} echo ' * That is a lot of relationships. Happy graphing.' >>${_dot_file} echo ' */' >>${_dot_file} echo '}' >>${_dot_file} return 0 } # # # # Flux d'exécution 'Chassez le spammeur' # # # # # La trace d'exécution est activée en initialisant la variable d'environnement #+ SPAMMER_TRACE avec le nom d'un fichier sur lequel le script peut écrire. declare -a _trace_log declare _log_file # Fonction pour remplir le journal de traces trace_logger() { _trace_log[${#_trace_log[@]}]=${_pend_current_} } # Enregistre le journal des traces vers la variable fichier. declare -f _log_dump _log_dump=pend_dummy # Initialement un no-op. # Enregistre le journal des traces vers un fichier. dump_log() { local -i _dl_cnt _dl_cnt=${#_trace_log[@]} for (( _dl = 0 ; _dl < _dl_cnt ; _dl++ )) do echo ${_trace_log[${_dl}]} >> ${_log_file} done _dl_cnt=${#_pending_[@]} if [ ${_dl_cnt} -gt 0 ] then _dl_cnt=${_dl_cnt}-1 echo '# # # Operations stack not empty # # #' >> ${_log_file} for (( _dl = ${_dl_cnt} ; _dl >= 0 ; _dl-- )) do echo ${_pending_[${_dl}]} >> ${_log_file} done fi } # # # Emballages de l'outil 'dig' # # # # # Ces emballages sont dérivées des exemples affichés dans #+ dig_wrappers.bash. # # La différence majeur est que ceux-ci retournent leur résultat comme une liste #+ dans un tableau. # # Voir dig_wrappers.bash pour les détails et utiliser ce script pour développer #+ toute modification. # # # # # Réponse courte : 'dig' analyse la réponse. # Recherche avant :: Nom -> Adresse # short_fwd <domain_name> <array_name> short_fwd() { local -a _sf_reply local -i _sf_rc local -i _sf_cnt IFS=${NO_WSP} echo -n '.' # echo 'sfwd: '${1} _sf_reply=( $(dig +short ${1} -c in -t a 2>/dev/null) ) _sf_rc=$? if [ ${_sf_rc} -ne 0 ] then _trace_log[${#_trace_log[@]}]='## Lookup error '${_sf_rc}' on '${1}' ##' # [ ${_sf_rc} -ne 9 ] && pend_drop return ${_sf_rc} else # Quelques versions de 'dig' renvoient des avertissements sur stdout. _sf_cnt=${#_sf_reply[@]} for (( _sf = 0 ; _sf < ${_sf_cnt} ; _sf++ )) do [ 'x'${_sf_reply[${_sf}]:0:2} == 'x;;' ] && unset _sf_reply[${_sf}] done eval $2=\( \$\{_sf_reply\[@\]\} \) fi return 0 } # Recherche inverse :: Adresse -> Nom # short_rev <ip_address> <array_name> short_rev() { local -a _sr_reply local -i _sr_rc local -i _sr_cnt IFS=${NO_WSP} echo -n '.' # echo 'srev: '${1} _sr_reply=( $(dig +short -x ${1} 2>/dev/null) ) _sr_rc=$? if [ ${_sr_rc} -ne 0 ] then _trace_log[${#_trace_log[@]}]='## Lookup error '${_sr_rc}' on '${1}' ##' # [ ${_sr_rc} -ne 9 ] && pend_drop return ${_sr_rc} else # Quelques versions de 'dig' renvoient des avertissements sur stdout. _sr_cnt=${#_sr_reply[@]} for (( _sr = 0 ; _sr < ${_sr_cnt} ; _sr++ )) do [ 'x'${_sr_reply[${_sr}]:0:2} == 'x;;' ] && unset _sr_reply[${_sr}] done eval $2=\( \$\{_sr_reply\[@\]\} \) fi return 0 } # Recherche du format spécial utilisé pour lancer des requêtes sur les serveurs #+ de listes noires (blacklist). # short_text <ip_address> <array_name> short_text() { local -a _st_reply local -i _st_rc local -i _st_cnt IFS=${NO_WSP} # echo 'stxt: '${1} _st_reply=( $(dig +short ${1} -c in -t txt 2>/dev/null) ) _st_rc=$? if [ ${_st_rc} -ne 0 ] then _trace_log[${#_trace_log[@]}]='## Text lookup error '${_st_rc}' on '${1}' ##' # [ ${_st_rc} -ne 9 ] && pend_drop return ${_st_rc} else # Quelques versions de 'dig' renvoient des avertissements sur stdout. _st_cnt=${#_st_reply[@]} for (( _st = 0 ; _st < ${#_st_cnt} ; _st++ )) do [ 'x'${_st_reply[${_st}]:0:2} == 'x;;' ] && unset _st_reply[${_st}] done eval $2=\( \$\{_st_reply\[@\]\} \) fi return 0 } # Les formes longues, aussi connues sous le nom de versions "Analyse toi-même" # RFC 2782 Recherche de service # dig +noall +nofail +answer _ldap._tcp.openldap.org -t srv # _<service>._<protocol>.<domain_name> # _ldap._tcp.openldap.org. 3600 IN SRV 0 0 389 ldap.openldap.org. # domain TTL Class SRV Priority Weight Port Target # Recherche avant :: Nom -> transfert de zone du pauvre # long_fwd <domain_name> <array_name> long_fwd() { local -a _lf_reply local -i _lf_rc local -i _lf_cnt IFS=${NO_WSP} echo -n ':' # echo 'lfwd: '${1} _lf_reply=( $( dig +noall +nofail +answer +authority +additional \ ${1} -t soa ${1} -t mx ${1} -t any 2>/dev/null) ) _lf_rc=$? if [ ${_lf_rc} -ne 0 ] then _trace_log[${#_trace_log[@]}]='## Zone lookup error '${_lf_rc}' on '${1}' ##' # [ ${_lf_rc} -ne 9 ] && pend_drop return ${_lf_rc} else # Quelques versions de 'dig' renvoient des avertissements sur stdout. _lf_cnt=${#_lf_reply[@]} for (( _lf = 0 ; _lf < ${_lf_cnt} ; _lf++ )) do [ 'x'${_lf_reply[${_lf}]:0:2} == 'x;;' ] && unset _lf_reply[${_lf}] done eval $2=\( \$\{_lf_reply\[@\]\} \) fi return 0 } # La recherche inverse de nom de domaine correspondant à l'adresse IPv6: # 4321:0:1:2:3:4:567:89ab # pourrait donnée (en hexadécimal) : # b.a.9.8.7.6.5.0.4.0.0.0.3.0.0.0.2.0.0.0.1.0.0.0.0.0.0.0.1.2.3.4.IP6.ARPA. # Recherche inverse :: Adresse -> chaîne de délégation du pauvre # long_rev <rev_ip_address> <array_name> long_rev() { local -a _lr_reply local -i _lr_rc local -i _lr_cnt local _lr_dns _lr_dns=${1}'.in-addr.arpa.' IFS=${NO_WSP} echo -n ':' # echo 'lrev: '${1} _lr_reply=( $( dig +noall +nofail +answer +authority +additional \ ${_lr_dns} -t soa ${_lr_dns} -t any 2>/dev/null) ) _lr_rc=$? if [ ${_lr_rc} -ne 0 ] then _trace_log[${#_trace_log[@]}]='## Delegation lookup error '${_lr_rc}' on '${1}' ##' # [ ${_lr_rc} -ne 9 ] && pend_drop return ${_lr_rc} else # Quelques versions de 'dig' renvoient des avertissements sur stdout. _lr_cnt=${#_lr_reply[@]} for (( _lr = 0 ; _lr < ${_lr_cnt} ; _lr++ )) do [ 'x'${_lr_reply[${_lr}]:0:2} == 'x;;' ] && unset _lr_reply[${_lr}] done eval $2=\( \$\{_lr_reply\[@\]\} \) fi return 0 } ## Fonctions spécifiques à l'application ## # Récupère un nom possible ; supprime root et TLD. # name_fixup <string> name_fixup(){ local -a _nf_tmp local -i _nf_end local _nf_str local IFS _nf_str=$(to_lower ${1}) _nf_str=$(to_dot ${_nf_str}) _nf_end=${#_nf_str}-1 [ ${_nf_str:${_nf_end}} != '.' ] && _nf_str=${_nf_str}'.' IFS=${ADR_IFS} _nf_tmp=( ${_nf_str} ) IFS=${WSP_IFS} _nf_end=${#_nf_tmp[@]} case ${_nf_end} in 0) # Pas de point, seulement des points echo return 1 ;; 1) # Seulement un TLD. echo return 1 ;; 2) # Pourrait être bon. echo ${_nf_str} return 0 # Besoin d'une table de recherche ? if [ ${#_nf_tmp[1]} -eq 2 ] then # TLD codé suivant le pays. echo return 1 else echo ${_nf_str} return 0 fi ;; esac echo ${_nf_str} return 0 } # Récupère le(s) entrée(s) originale(s). split_input() { [ ${#uc_name[@]} -gt 0 ] || return 0 local -i _si_cnt local -i _si_len local _si_str unique_lines uc_name uc_name _si_cnt=${#uc_name[@]} for (( _si = 0 ; _si < _si_cnt ; _si++ )) do _si_str=${uc_name[$_si]} if is_address ${_si_str} then uc_address[${#uc_address[@]}]=${_si_str} unset uc_name[$_si] else if ! uc_name[$_si]=$(name_fixup ${_si_str}) then unset ucname[$_si] fi fi done uc_name=( ${uc_name[@]} ) _si_cnt=${#uc_name[@]} _trace_log[${#_trace_log[@]}]='## Input '${_si_cnt}' unchecked name input(s). ##' _si_cnt=${#uc_address[@]} _trace_log[${#_trace_log[@]}]='## Input '${_si_cnt}' unchecked address input(s). ##' return 0 } ## Fonctions de découverte -- verrouillage récursif par des données externes ## ## Le début 'si la liste est vide; renvoyer 0' de chacun est requis. ## # Limiteur de récursion # limit_chk() <next_level> limit_chk() { local -i _lc_lmt # Vérifiez la limite d'indirection. if [ ${indirect} -eq 0 ] || [ $# -eq 0 ] then # Le choix 'faites-à-chaque-fois' echo 1 # Toute valeur le fera. return 0 # OK pour continuer. else # La limite est effective. if [ ${indirect} -lt ${1} ] then echo ${1} # Quoi que ce soit. return 1 # Arrêter ici. else _lc_lmt=${1}+1 # Augmenter la limite donnée. echo ${_lc_lmt} # L'afficher. return 0 # OK pour continuer. fi fi } # Pour chaque nom dans uc_name: # Déplacez le nom dans chk_name. # Ajoutez les adresses à uc_address. # Lancez expand_input_address. # Répétez jusqu'à ce que rien de nouveau ne soit trouvé. # expand_input_name <indirection_limit> expand_input_name() { [ ${#uc_name[@]} -gt 0 ] || return 0 local -a _ein_addr local -a _ein_new local -i _ucn_cnt local -i _ein_cnt local _ein_tst _ucn_cnt=${#uc_name[@]} if ! _ein_cnt=$(limit_chk ${1}) then return 0 fi for (( _ein = 0 ; _ein < _ucn_cnt ; _ein++ )) do if short_fwd ${uc_name[${_ein}]} _ein_new then for (( _ein_cnt = 0 ; _ein_cnt < ${#_ein_new[@]}; _ein_cnt++ )) do _ein_tst=${_ein_new[${_ein_cnt}]} if is_address ${_ein_tst} then _ein_addr[${#_ein_addr[@]}]=${_ein_tst} fi done fi done unique_lines _ein_addr _ein_addr # Scrub duplicates. edit_exact chk_address _ein_addr # Scrub pending detail. edit_exact known_address _ein_addr # Scrub already detailed. if [ ${#_ein_addr[@]} -gt 0 ] # Anything new? then uc_address=( ${uc_address[@]} ${_ein_addr[@]} ) pend_func expand_input_address ${1} _trace_log[${#_trace_log[@]}]='## Added '${#_ein_addr[@]}' unchecked address input(s). ##' fi edit_exact chk_name uc_name # Scrub pending detail. edit_exact known_name uc_name # Scrub already detailed. if [ ${#uc_name[@]} -gt 0 ] then chk_name=( ${chk_name[@]} ${uc_name[@]} ) pend_func detail_each_name ${1} fi unset uc_name[@] return 0 } # Pour chaque adresse dans uc_address: # Déplacez l'adresse vers chk_address. # Ajoutez les noms à uc_name. # Lancez expand_input_name. # Répétez jusqu'à ce que rien de nouveau ne soit trouvé. # expand_input_address <indirection_limit> expand_input_address() { [ ${#uc_address[@]} -gt 0 ] || return 0 local -a _eia_addr local -a _eia_name local -a _eia_new local -i _uca_cnt local -i _eia_cnt local _eia_tst unique_lines uc_address _eia_addr unset uc_address[@] edit_exact been_there_addr _eia_addr _uca_cnt=${#_eia_addr[@]} [ ${_uca_cnt} -gt 0 ] && been_there_addr=( ${been_there_addr[@]} ${_eia_addr[@]} ) for (( _eia = 0 ; _eia < _uca_cnt ; _eia++ )) do if short_rev ${_eia_addr[${_eia}]} _eia_new then for (( _eia_cnt = 0 ; _eia_cnt < ${#_eia_new[@]} ; _eia_cnt++ )) do _eia_tst=${_eia_new[${_eia_cnt}]} if _eia_tst=$(name_fixup ${_eia_tst}) then _eia_name[${#_eia_name[@]}]=${_eia_tst} fi done fi done unique_lines _eia_name _eia_name # Scrub duplicates. edit_exact chk_name _eia_name # Scrub pending detail. edit_exact known_name _eia_name # Scrub already detailed. if [ ${#_eia_name[@]} -gt 0 ] # Anything new? then uc_name=( ${uc_name[@]} ${_eia_name[@]} ) pend_func expand_input_name ${1} _trace_log[${#_trace_log[@]}]='## Added '${#_eia_name[@]}' unchecked name input(s). ##' fi edit_exact chk_address _eia_addr # Scrub pending detail. edit_exact known_address _eia_addr # Scrub already detailed. if [ ${#_eia_addr[@]} -gt 0 ] # Anything new? then chk_address=( ${chk_address[@]} ${_eia_addr[@]} ) pend_func detail_each_address ${1} fi return 0 } # La réponse de la zone analysez-le-vous-même. # L'entrée est la liste chk_name. # detail_each_name <indirection_limit> detail_each_name() { [ ${#chk_name[@]} -gt 0 ] || return 0 local -a _den_chk # Noms à vérifier local -a _den_name # Noms trouvés ici local -a _den_address # Adresses trouvées ici local -a _den_pair # Paires trouvés ici local -a _den_rev # Paires inverses trouvées ici local -a _den_tmp # Ligne en cours d'analyse local -a _den_auth # Contact SOA en cours d'analyse local -a _den_new # La réponse de la zone local -a _den_pc # Parent-Fils devient très rapide local -a _den_ref # Ainsi que la chaîne de référence local -a _den_nr # Nom-Ressource peut être gros local -a _den_na # Nom-Adresse local -a _den_ns # Nom-Service local -a _den_achn # Chaîne d'autorité local -i _den_cnt # Nombre de noms à détailler local -i _den_lmt # Limite d'indirection local _den_who # Named en cours d'exécution local _den_rec # Type d'enregistrement en cours d'exécution local _den_cont # Domaine du contact local _den_str # Correction du nom local _den_str2 # Correction inverse local IFS=${WSP_IFS} # Copie locale, unique de noms à vérifier unique_lines chk_name _den_chk unset chk_name[@] # Fait avec des globales. # Moins de noms déjà connus edit_exact known_name _den_chk _den_cnt=${#_den_chk[@]} # S'il reste quelque chose, ajoutez à known_name. [ ${_den_cnt} -gt 0 ] && known_name=( ${known_name[@]} ${_den_chk[@]} ) # pour la liste des (précédents) noms inconnus . . . for (( _den = 0 ; _den < _den_cnt ; _den++ )) do _den_who=${_den_chk[${_den}]} if long_fwd ${_den_who} _den_new then unique_lines _den_new _den_new if [ ${#_den_new[@]} -eq 0 ] then _den_pair[${#_den_pair[@]}]='0.0.0.0 '${_den_who} fi # Analyser chaque ligne de la réponse. for (( _line = 0 ; _line < ${#_den_new[@]} ; _line++ )) do IFS=${NO_WSP}$'\x09'$'\x20' _den_tmp=( ${_den_new[${_line}]} ) IFS=${WSP_IFS} # Si l'enregistrement est utilisable et n'est pas un message #+ d'avertissement . . . if [ ${#_den_tmp[@]} -gt 4 ] && [ 'x'${_den_tmp[0]} != 'x;;' ] then _den_rec=${_den_tmp[3]} _den_nr[${#_den_nr[@]}]=${_den_who}' '${_den_rec} # Début de RFC1033 (+++) case ${_den_rec} in #<name> [<ttl>] [<class>] SOA <origin> <person> SOA) # Début de l'autorité if _den_str=$(name_fixup ${_den_tmp[0]}) then _den_name[${#_den_name[@]}]=${_den_str} _den_achn[${#_den_achn[@]}]=${_den_who}' '${_den_str}' SOA' # origine SOA -- nom de domaine de l'enregistrement #+ de la zone maître if _den_str2=$(name_fixup ${_den_tmp[4]}) then _den_name[${#_den_name[@]}]=${_den_str2} _den_achn[${#_den_achn[@]}]=${_den_who}' '${_den_str2}' SOA.O' fi # Adresse mail responsable (peut-être boguée). # Possibilité d'un premier.dernier@domaine.nom # ignoré. set -f if _den_str2=$(name_fixup ${_den_tmp[5]}) then IFS=${ADR_IFS} _den_auth=( ${_den_str2} ) IFS=${WSP_IFS} if [ ${#_den_auth[@]} -gt 2 ] then _den_cont=${_den_auth[1]} for (( _auth = 2 ; _auth < ${#_den_auth[@]} ; _auth++ )) do _den_cont=${_den_cont}'.'${_den_auth[${_auth}]} done _den_name[${#_den_name[@]}]=${_den_cont}'.' _den_achn[${#_den_achn[@]}]=${_den_who}' '${_den_cont}'. SOA.C' fi fi set +f fi ;; A) # Enregistrement d'adresse IP(v4) if _den_str=$(name_fixup ${_den_tmp[0]}) then _den_name[${#_den_name[@]}]=${_den_str} _den_pair[${#_den_pair[@]}]=${_den_tmp[4]}' '${_den_str} _den_na[${#_den_na[@]}]=${_den_str}' '${_den_tmp[4]} _den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' A' else _den_pair[${#_den_pair[@]}]=${_den_tmp[4]}' unknown.domain' _den_na[${#_den_na[@]}]='unknown.domain '${_den_tmp[4]} _den_ref[${#_den_ref[@]}]=${_den_who}' unknown.domain A' fi _den_address[${#_den_address[@]}]=${_den_tmp[4]} _den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_tmp[4]} ;; NS) # Enregistrement du nom de serveur # Nom de domaine en cours de service (peut être autre #+ chose que l'actuel) if _den_str=$(name_fixup ${_den_tmp[0]}) then _den_name[${#_den_name[@]}]=${_den_str} _den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' NS' # Nom du domaine du fournisseur de services if _den_str2=$(name_fixup ${_den_tmp[4]}) then _den_name[${#_den_name[@]}]=${_den_str2} _den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str2}' NSH' _den_ns[${#_den_ns[@]}]=${_den_str2}' NS' _den_pc[${#_den_pc[@]}]=${_den_str}' '${_den_str2} fi fi ;; MX) # Enregistrement du serveur de mails # Nom de domaine en service (jokers non gérés ici) if _den_str=$(name_fixup ${_den_tmp[0]}) then _den_name[${#_den_name[@]}]=${_den_str} _den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' MX' fi # Nom du domaine du fournisseur de service if _den_str=$(name_fixup ${_den_tmp[5]}) then _den_name[${#_den_name[@]}]=${_den_str} _den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' MXH' _den_ns[${#_den_ns[@]}]=${_den_str}' MX' _den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_str} fi ;; PTR) # Enregistrement de l'adresse inverse # Nom spécial if _den_str=$(name_fixup ${_den_tmp[0]}) then _den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' PTR' # Nom d'hôte (pas un CNAME) if _den_str2=$(name_fixup ${_den_tmp[4]}) then _den_rev[${#_den_rev[@]}]=${_den_str}' '${_den_str2} _den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str2}' PTRH' _den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_str} fi fi ;; AAAA) # Enregistrement de l'adresse IP(v6) if _den_str=$(name_fixup ${_den_tmp[0]}) then _den_name[${#_den_name[@]}]=${_den_str} _den_pair[${#_den_pair[@]}]=${_den_tmp[4]}' '${_den_str} _den_na[${#_den_na[@]}]=${_den_str}' '${_den_tmp[4]} _den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' AAAA' else _den_pair[${#_den_pair[@]}]=${_den_tmp[4]}' unknown.domain' _den_na[${#_den_na[@]}]='unknown.domain '${_den_tmp[4]} _den_ref[${#_den_ref[@]}]=${_den_who}' unknown.domain' fi # Aucun travaux sur les adresses IPv6 _den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_tmp[4]} ;; CNAME) # Enregistrement du nom de l'alias # Pseudo if _den_str=$(name_fixup ${_den_tmp[0]}) then _den_name[${#_den_name[@]}]=${_den_str} _den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' CNAME' _den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_str} fi # Nom d'hôte if _den_str=$(name_fixup ${_den_tmp[4]}) then _den_name[${#_den_name[@]}]=${_den_str} _den_ref[${#_den_ref[@]}]=${_den_who}' '${_den_str}' CHOST' _den_pc[${#_den_pc[@]}]=${_den_who}' '${_den_str} fi ;; # TXT) # ;; esac fi done else # Erreur de recherche == enregistrement 'A' 'adresse inconnue' _den_pair[${#_den_pair[@]}]='0.0.0.0 '${_den_who} fi done # Tableau des points de contrôle grandit. unique_lines _den_achn _den_achn # Fonctionne mieux, tout identique. edit_exact auth_chain _den_achn # Fonctionne mieux, éléments uniques. if [ ${#_den_achn[@]} -gt 0 ] then IFS=${NO_WSP} auth_chain=( ${auth_chain[@]} ${_den_achn[@]} ) IFS=${WSP_IFS} fi unique_lines _den_ref _den_ref # Fonctionne mieux, tout identique. edit_exact ref_chain _den_ref # Fonctionne mieux, éléments uniques. if [ ${#_den_ref[@]} -gt 0 ] then IFS=${NO_WSP} ref_chain=( ${ref_chain[@]} ${_den_ref[@]} ) IFS=${WSP_IFS} fi unique_lines _den_na _den_na edit_exact name_address _den_na if [ ${#_den_na[@]} -gt 0 ] then IFS=${NO_WSP} name_address=( ${name_address[@]} ${_den_na[@]} ) IFS=${WSP_IFS} fi unique_lines _den_ns _den_ns edit_exact name_srvc _den_ns if [ ${#_den_ns[@]} -gt 0 ] then IFS=${NO_WSP} name_srvc=( ${name_srvc[@]} ${_den_ns[@]} ) IFS=${WSP_IFS} fi unique_lines _den_nr _den_nr edit_exact name_resource _den_nr if [ ${#_den_nr[@]} -gt 0 ] then IFS=${NO_WSP} name_resource=( ${name_resource[@]} ${_den_nr[@]} ) IFS=${WSP_IFS} fi unique_lines _den_pc _den_pc edit_exact parent_child _den_pc if [ ${#_den_pc[@]} -gt 0 ] then IFS=${NO_WSP} parent_child=( ${parent_child[@]} ${_den_pc[@]} ) IFS=${WSP_IFS} fi # Mise à jour de la liste known_pair (adresse et nom). unique_lines _den_pair _den_pair edit_exact known_pair _den_pair if [ ${#_den_pair[@]} -gt 0 ] # Rien de nouveau? then IFS=${NO_WSP} known_pair=( ${known_pair[@]} ${_den_pair[@]} ) IFS=${WSP_IFS} fi # Mise à jour de la liste des pairs inversés. unique_lines _den_rev _den_rev edit_exact reverse_pair _den_rev if [ ${#_den_rev[@]} -gt 0 ] # Rien de nouveau ? then IFS=${NO_WSP} reverse_pair=( ${reverse_pair[@]} ${_den_rev[@]} ) IFS=${WSP_IFS} fi # Vérification de la limite d'indirection -- abandon si elle est atteinte. if ! _den_lmt=$(limit_chk ${1}) then return 0 fi # Le moteur d'exécution est LIFO. L'ordre des opérations en attente est #+ important. # Avons-nous défini de nouvelles adresses ? unique_lines _den_address _den_address # Scrub duplicates. edit_exact known_address _den_address # Scrub already processed. edit_exact un_address _den_address # Scrub already waiting. if [ ${#_den_address[@]} -gt 0 ] # Anything new? then uc_address=( ${uc_address[@]} ${_den_address[@]} ) pend_func expand_input_address ${_den_lmt} _trace_log[${#_trace_log[@]}]='## Added '${#_den_address[@]}' unchecked address(s). ##' fi # Avons-nous trouvé de nouveaux noms ? unique_lines _den_name _den_name # Scrub duplicates. edit_exact known_name _den_name # Scrub already processed. edit_exact uc_name _den_name # Scrub already waiting. if [ ${#_den_name[@]} -gt 0 ] # Anything new? then uc_name=( ${uc_name[@]} ${_den_name[@]} ) pend_func expand_input_name ${_den_lmt} _trace_log[${#_trace_log[@]}]='## Added '${#_den_name[@]}' unchecked name(s). ##' fi return 0 } # Réponse de délégation analysez-le-vous-même # L'entrée est la liste chk_address. # detail_each_address <indirection_limit> detail_each_address() { [ ${#chk_address[@]} -gt 0 ] || return 0 unique_lines chk_address chk_address edit_exact known_address chk_address if [ ${#chk_address[@]} -gt 0 ] then known_address=( ${known_address[@]} ${chk_address[@]} ) unset chk_address[@] fi return 0 } ## Fonctions de sortie spécifiques à l'application ## # Affiche joliment les pairs connues. report_pairs() { echo echo 'Known network pairs.' col_print known_pair 2 5 30 if [ ${#auth_chain[@]} -gt 0 ] then echo echo 'Known chain of authority.' col_print auth_chain 2 5 30 55 fi if [ ${#reverse_pair[@]} -gt 0 ] then echo echo 'Known reverse pairs.' col_print reverse_pair 2 5 55 fi return 0 } # Vérifie une adresse contre la liste des serveurs #+ faisant partie de la liste noire. # Un bon endroit pour capturer avec GraphViz : # address->status(server(reports)) # check_lists <ip_address> check_lists() { [ $# -eq 1 ] || return 1 local -a _cl_fwd_addr local -a _cl_rev_addr local -a _cl_reply local -i _cl_rc local -i _ls_cnt local _cl_dns_addr local _cl_lkup split_ip ${1} _cl_fwd_addr _cl_rev_addr _cl_dns_addr=$(dot_array _cl_rev_addr)'.' _ls_cnt=${#list_server[@]} echo ' Checking address '${1} for (( _cl = 0 ; _cl < _ls_cnt ; _cl++ )) do _cl_lkup=${_cl_dns_addr}${list_server[${_cl}]} if short_text ${_cl_lkup} _cl_reply then if [ ${#_cl_reply[@]} -gt 0 ] then echo ' Records from '${list_server[${_cl}]} address_hits[${#address_hits[@]}]=${1}' '${list_server[${_cl}]} _hs_RC=2 for (( _clr = 0 ; _clr < ${#_cl_reply[@]} ; _clr++ )) do echo ' '${_cl_reply[${_clr}]} done fi fi done return 0 } ## La colle habituelle de l'application ## # Qui l'a fait ? credits() { echo echo "Guide d'écriture avancée des scripts Bash : is_spammer.bash, v2, 2004-msz" } # Comment l'utiliser ? # (Voir aussi, "Quickstart" à la fin de ce script.) usage() { cat <<-'_usage_statement_' Le script is_spammer.bash requiert un ou deux arguments. arg 1) Pourrait être : a) Un nom de domaine b) Une adresse IPv4 c) Le nom d'un fichier avec des noms et adresses mélangés, un par ligne. arg 2) Pourrait être : a) Un nom de domaine d'un serveur Blacklist b) Le nom d'un fichier contenant une liste de noms de domaine Blacklist, un domaine par ligne. c) Si non présent, une liste par défaut de serveurs Blacklist (libres) est utilisée. d) Si un fichier vide, lisible, est donné, la recherche de serveurs Blacklist est désactivée. Toutes les sorties du script sont écrites sur stdout. Codes de retour: 0 -> Tout est OK, 1 -> Échec du script, 2 -> Quelque chose fait partie de la liste noire. Requiert le programme externe 'dig' provenant des programmes DNS de 'bind-9' Voir http://www.isc.org La limite de la profondeur de recherche du nom de domaine est par défaut de deux niveaux. Initialisez la variable d'environnement SPAMMER_LIMIT pour modifier ceci. SPAMMER_LIMIT=0 signifie 'illimité' La limite peut aussi être initialisée sur la ligne de commande. Si arg#1 est un entier, la limite utilise cette valeur puis les règles d'arguments ci-dessus sont appliquées. Initialiser la variable d'environnemnt 'SPAMMER_DATA' à un nom de fichier demandera au script d'écrire un fichier graphique GraphViz. Pour la version de développement ; Initialiser la variable d'environnement 'SPAMMER_TRACE' avec un nom de fichier demandera au moteur d'exécution de tracer tous les appels de fonction. _usage_statement_ } # La liste par défaut des serveurs Blacklist : # Plusieurs choix, voir : http://www.spews.org/lists.html declare -a default_servers # Voir : http://www.spamhaus.org (Conservateur, bien maintenu) default_servers[0]='sbl-xbl.spamhaus.org' # Voir : http://ordb.org (Relais mail ouverts) default_servers[1]='relays.ordb.org' # Voir : http://www.spamcop.net/ (Vous pouvez rapporter les spammeurs ici) default_servers[2]='bl.spamcop.net' # Voir : http://www.spews.org (Un système de détection rapide) default_servers[3]='l2.spews.dnsbl.sorbs.net' # Voir : http://www.dnsbl.us.sorbs.net/using.shtml default_servers[4]='dnsbl.sorbs.net' # Voir : http://dsbl.org/usage (Différentes listes de relai de mail) default_servers[5]='list.dsbl.org' default_servers[6]='multihop.dsbl.org' default_servers[7]='unconfirmed.dsbl.org' # Argument utilisateur #1 setup_input() { if [ -e ${1} ] && [ -r ${1} ] # Nom d'un fichier lisible then file_to_array ${1} uc_name echo 'Using filename >'${1}'< as input.' else if is_address ${1} # Adresse IP ? then uc_address=( ${1} ) echo 'Starting with address >'${1}'<' else # Doit être un nom. uc_name=( ${1} ) echo 'Starting with domain name >'${1}'<' fi fi return 0 } # Argument utilisateur #2 setup_servers() { if [ -e ${1} ] && [ -r ${1} ] # Nom d'un fichier lisible then file_to_array ${1} list_server echo 'Using filename >'${1}'< as blacklist server list.' else list_server=( ${1} ) echo 'Using blacklist server >'${1}'<' fi return 0 } # Variable d'environnement utilisateur SPAMMER_TRACE live_log_die() { if [ ${SPAMMER_TRACE:=} ] # Journal de trace ? then if [ ! -e ${SPAMMER_TRACE} ] then if ! touch ${SPAMMER_TRACE} 2>/dev/null then pend_func echo $(printf '%q\n' \ 'Unable to create log file >'${SPAMMER_TRACE}'<') pend_release exit 1 fi _log_file=${SPAMMER_TRACE} _pend_hook_=trace_logger _log_dump=dump_log else if [ ! -w ${SPAMMER_TRACE} ] then pend_func echo $(printf '%q\n' \ 'Unable to write log file >'${SPAMMER_TRACE}'<') pend_release exit 1 fi _log_file=${SPAMMER_TRACE} echo '' > ${_log_file} _pend_hook_=trace_logger _log_dump=dump_log fi fi return 0 } # Variable d'environnement utilisateur SPAMMER_DATA data_capture() { if [ ${SPAMMER_DATA:=} ] # Tracer les données ? then if [ ! -e ${SPAMMER_DATA} ] then if ! touch ${SPAMMER_DATA} 2>/dev/null then pend_func echo $(printf '%q]n' \ 'Unable to create data output file >'${SPAMMER_DATA}'<') pend_release exit 1 fi _dot_file=${SPAMMER_DATA} _dot_dump=dump_dot else if [ ! -w ${SPAMMER_DATA} ] then pend_func echo $(printf '%q\n' \ 'Unable to write data output file >'${SPAMMER_DATA}'<') pend_release exit 1 fi _dot_file=${SPAMMER_DATA} _dot_dump=dump_dot fi fi return 0 } # Réunir les arguments spécifiés par l'utilisateur. do_user_args() { if [ $# -gt 0 ] && is_number $1 then indirect=$1 shift fi case $# in # L'utilisateur nous traite-t'il correctement? 1) if ! setup_input $1 # Vérification des erreurs. then pend_release $_log_dump exit 1 fi list_server=( ${default_servers[@]} ) _list_cnt=${#list_server[@]} echo 'Using default blacklist server list.' echo 'Search depth limit: '${indirect} ;; 2) if ! setup_input $1 # Vérification des erreurs. then pend_release $_log_dump exit 1 fi if ! setup_servers $2 # Vérification des erreurs. then pend_release $_log_dump exit 1 fi echo 'Search depth limit: '${indirect} ;; *) pend_func usage pend_release $_log_dump exit 1 ;; esac return 0 } # Un outil à but général de déboguage. # list_array <array_name> list_array() { [ $# -eq 1 ] || return 1 # Un argument requis. local -a _la_lines set -f local IFS=${NO_WSP} eval _la_lines=\(\ \$\{$1\[@\]\}\ \) echo echo "Element count "${#_la_lines[@]}" array "${1} local _ln_cnt=${#_la_lines[@]} for (( _i = 0; _i < ${_ln_cnt}; _i++ )) do echo 'Element '$_i' >'${_la_lines[$_i]}'<' done set +f return 0 } ## Code 'Chez le spammeur' ## pend_init # Initialisation du moteur à pile. pend_func credits # Dernière chose à afficher. ## Gérer l'utilisateur ## live_log_die # Initialiser le journal de trace de #+ déboguage. data_capture # Initialiser le fichier de capture de #+ données. echo do_user_args $@ ## N'a pas encore quitté - Il y a donc un peu d'espoir ## # Groupe de découverte - Le moteur d'exécution est LIFO - queue en ordre # inverse d'exécution. _hs_RC=0 # Code de retour de Chassez le spammeur pend_mark pend_func report_pairs # Paires nom-adresse rapportées. # Les deux detail_* sont des fonctions mutuellement récursives. # Elles mettent en queue les fonctions expand_* functions si nécessaire. # Ces deux (les dernières de ???) sortent de la récursion. pend_func detail_each_address # Obtient toutes les ressources #+ des adresses. pend_func detail_each_name # Obtient toutes les ressources #+ des noms. # Les deux expand_* sont des fonctions mutuellement récursives, #+ qui mettent en queue les fonctions detail_* supplémentaires si #+ nécessaire. pend_func expand_input_address 1 # Étend les noms en entrées par des #+ adresses. pend_func expand_input_name 1 # Étend les adresses en entrées par des #+ noms. # Commence avec un ensemble unique de noms et d'adresses. pend_func unique_lines uc_address uc_address pend_func unique_lines uc_name uc_name # Entrée mixe séparée de noms et d'adresses. pend_func split_input pend_release ## Paires rapportées -- Liste unique d'adresses IP trouvées echo _ip_cnt=${#known_address[@]} if [ ${#list_server[@]} -eq 0 ] then echo 'Blacklist server list empty, none checked.' else if [ ${_ip_cnt} -eq 0 ] then echo 'Known address list empty, none checked.' else _ip_cnt=${_ip_cnt}-1 # Start at top. echo 'Checking Blacklist servers.' for (( _ip = _ip_cnt ; _ip >= 0 ; _ip-- )) do pend_func check_lists $( printf '%q\n' ${known_address[$_ip]} ) done fi fi pend_release $_dot_dump # Fichier graphique $_log_dump # Trace d'exécution echo ######################################### # Exemple de sortie provenant du script # ######################################### :<<-'_is_spammer_outputs_' ./is_spammer.bash 0 web4.alojamentos7.com Starting with domain name >web4.alojamentos7.com< Using default blacklist server list. Search depth limit: 0 .:....::::...:::...:::.......::..::...:::.......:: Known network pairs. 66.98.208.97 web4.alojamentos7.com. 66.98.208.97 ns1.alojamentos7.com. 69.56.202.147 ns2.alojamentos.ws. 66.98.208.97 alojamentos7.com. 66.98.208.97 web.alojamentos7.com. 69.56.202.146 ns1.alojamentos.ws. 69.56.202.146 alojamentos.ws. 66.235.180.113 ns1.alojamentos.org. 66.235.181.192 ns2.alojamentos.org. 66.235.180.113 alojamentos.org. 66.235.180.113 web6.alojamentos.org. 216.234.234.30 ns1.theplanet.com. 12.96.160.115 ns2.theplanet.com. 216.185.111.52 mail1.theplanet.com. 69.56.141.4 spooling.theplanet.com. 216.185.111.40 theplanet.com. 216.185.111.40 www.theplanet.com. 216.185.111.52 mail.theplanet.com. Checking Blacklist servers. Checking address 66.98.208.97 Records from dnsbl.sorbs.net "Spam Received See: http://www.dnsbl.sorbs.net/lookup.shtml?66.98.208.97" Checking address 69.56.202.147 Checking address 69.56.202.146 Checking address 66.235.180.113 Checking address 66.235.181.192 Checking address 216.185.111.40 Checking address 216.234.234.30 Checking address 12.96.160.115 Checking address 216.185.111.52 Checking address 69.56.141.4 Advanced Bash Scripting Guide: is_spammer.bash, v2, 2004-msz _is_spammer_outputs_ exit ${_hs_RC} ############################################################### # Le script ignore tout ce qui se trouve entre ici et la fin # #+ à cause de la commande 'exit' ci-dessus. # ############################################################### Quickstart ========== Prérequis Bash version 2.05b ou 3.00 (bash --version) Une version de Bash supportant les tableaux. Le support des tableaux est inclus dans les configurations par défaut de Bash. 'dig,' version 9.x.x (dig $HOSTNAME, voir la première ligne en sortie) Une version de dig supportant les options +short. Voir dig_wrappers.bash pour les détails. Prérequis optionnels 'named', un programme de cache DNS local. N'importe lequel conviendra. Faites deux fois : dig $HOSTNAME Vérifier près de la fin de la sortie si vous voyez: SERVER: 127.0.0.1#53 Ceci signifie qu'il fonctionne. Support optionnel des graphiques 'date', un outil standard *nix. (date -R) dot un programme pour convertir le fichier de description graphique en un diagramme. (dot -V) Fait partie de l'ensemble des programmes Graph-Viz. Voir [http://www.research.att.com/sw/tools/graphviz||GraphViz] 'dotty', un éditeur visuel pour les fichiers de description graphique. Fait aussi partie de l'ensemble des programmes Graph-Viz. Quick Start Dans le même répertoire que le script is_spammer.bash; Lancez : ./is_spammer.bash Détails d'utilisation 1. Choix de serveurs Blacklist. (a) Pour utiliser les serveurs par défaut, liste intégrée : ne rien faire. (b) Pour utiliser votre propre liste : i. Créez un fichier avec un seul serveru Blacklist par ligne. ii. Indiquez ce fichier en dernier argument du script. (c) Pour utiliser un seul serveur Blacklist : Dernier argument de ce script. (d) Pour désactiver les recherches Blacklist : i. Créez un fichier vide (touch spammer.nul) Le nom du fichier n'a pas d'importance. ii. Indiquez ce nom en dernier argument du script. 2. Limite de la profondeur de recherche. (a) Pour utiliser la valeur par défaut de 2 : ne rien faire. (b) Pour configurer une limite différente : Une limite de 0 signifie illimitée. i. export SPAMMER_LIMIT=1 ou tout autre limite que vous désirez. ii. OU indiquez la limite désirée en premier argument de ce script. 3. Journal de trace de l'exécution (optionnel). (a) Pour utiliser la configuration par défaut (sans traces) : ne rien faire. (b) Pour écrire dans un journal de trace : export SPAMMER_TRACE=spammer.log ou tout autre nom de fichier que vous voulez. 4. Fichier de description graphique optionnel. (a) Pour utiliser la configuration par défaut (sans graphique) : ne rien faire. (b) Pour écrire un fichier de description graphique Graph-Viz : export SPAMMER_DATA=spammer.dot ou tout autre nom de fichier que vous voulez. 5. Où commencer la recherche. (a) Commencer avec un simple nom de domaine : i. Sans limite de recherche sur la ligne de commande : Premier argument du script. ii. Avec une limite de recherche sur la ligne de commande : Second argument du script. (b) Commencer avec une simple adresse IP : i. Sans limite de recherche sur la ligne de commande : Premier argument du script. ii. Avec une limite de recherche sur la ligne de commande : Second argument du script. (c) Commencer avec de nombreux noms et/ou adresses : Créer un fichier avec un nom ou une adresse par ligne. Le nom du fichier n'a pas d'importance. i. Sans limite de recherche sur la ligne de commande : Fichier comme premier argument du script. ii. Avec une limite de recherche sur la ligne de commande : Fichier comme second argument du script. 6. Que faire pour l'affichage en sortie. (a) Pour visualiser la sortie à l'écran : ne rien faire. (b) Pour sauvegarder la sortie dans un fichier : rediriger stdout vers un fichier. (c) Pour désactiver la sortie : rediriger stdout vers /dev/null. 7. Fin temporaire de la phase de décision. appuyez sur RETURN attendez (sinon, regardez les points et les virgules). 8. De façon optionnelle, vérifiez le code de retour. (a) Code de retour 0: Tout est OK (b) Code de retour 1: Échec du script de configuration (c) Code de retour 2: Quelque chose était sur la liste noire. 9. Où est mon graphe (diagramme) ? Le script ne produit pas directement un graphe (diagramme). Il produit seulement un fichier de description graphique. Vous pouvez utiliser ce fichier qui a été créé par le programme 'dot'. Jusqu'à l'édition du fichier de description pour décrire les relations que vous souhaitez montrer, tout ce que vous obtenez est un ensemble de noms et de noms d'adresses. Toutes les relations découvertes par le script font partie d'un bloc en commentaires dans le fichier de description graphique, chacun ayant un en-tête descriptif. L'édition requise pour tracer une ligne entre une paire de noeuds peut se faire avec un éditeur de texte à partir des informations du fichier descripteur. Avec ces lignes quelque part dans le fichier descripteur : # Known domain name nodes N0000 [label="guardproof.info."] ; N0002 [label="third.guardproof.info."] ; # Known address nodes A0000 [label="61.141.32.197"] ; /* # Known name->address edges NA0000 third.guardproof.info. 61.141.32.197 # Known parent->child edges PC0000 guardproof.info. third.guardproof.info. */ Modifiez ceci en les lignes suivantes après avoir substitué les identifiants de noeuds avec les relations : # Known domain name nodes N0000 [label="guardproof.info."] ; N0002 [label="third.guardproof.info."] ; # Known address nodes A0000 [label="61.141.32.197"] ; # PC0000 guardproof.info. third.guardproof.info. N0000->N0002 ; # NA0000 third.guardproof.info. 61.141.32.197 N0002->A0000 ; /* # Known name->address edges NA0000 third.guardproof.info. 61.141.32.197 # Known parent->child edges PC0000 guardproof.info. third.guardproof.info. */ Lancez le programme 'dot' et vous avez votre premier diagramme réseau. En plus des formes graphiques habituelles, le fichier de description inclut des paires/données de format similaires, décrivant les services, les enregistrements de zones (sous-graphe ?), des adresses sur liste noire et d'autres choses pouvant être intéressante à inclure dans votre graphe. Cette information supplémentaire pourrait être affichée comme différentes formes de noeuds, couleurs, tailles de lignes, etc. Le fichier de description peut aussi être lu et édité par un script Bash (bien sûr). Vous devez être capable de trouver la plupart des fonctions requises à l'intérieur du script "is_spammer.bash". # Fin de Quickstart. Note Supplémentaire ==== ============== Michael Zick indique qu'il existe un "makeviz.bash" interactif sur le site Web rediris.es. Impossible de donner le lien complet car ce n'est pas un site accessible publiquement.
Un autre script anti-spam.
Exemple A.30. Chasse aux spammeurs
#!/bin/bash # whx.sh : recherche d'un spammeur via "whois" # Auteur: Walter Dnes # Révisions légères (première section) par l'auteur du guide ABS. # Utilisé dans le guide ABS avec sa permission. # Nécessite la version 3.x ou ultérieure de Bash pour fonctionner #+ (à cause de l'utilisation de l'opérateur =~). # Commenté par l'auteur du script et par l'auteur du guide ABS. E_MAUVAISARGS=65 # Argument manquant en ligne de commande. E_SANSHOTE=66 # Hôte introuvable. E_DELAIDEPASSE=67 # Délai dépassée pour la recherche de l'hôte. E_NONDEF=68 # D'autres erreurs (non définies). ATTENTEHOTE=10 # Spécifiez jusqu'à 10 secondes pour la réponse à la requête. # L'attente réelle pourrait être un peu plus longue. FICHIER_RESULTAT=whois.txt # Fichier en sortie. PORT=4321 if [ -z "$1" ] # Vérification de l'argument (requis) en ligne de commande. then echo "Usage: $0 nom de domaine ou adresse IP" exit $E_MAUVAISARGS fi if [[ "$1" =~ "[a-zA-Z][a-zA-Z]$" ]] # Se termine avec deux caractères alphabetiques ? then # C'est un nom de domaine et nous devons faire une recherche d'hôte. ADR_IP=$(host -W $ATTENTEHOTE $1 | awk '{print $4}') # Recherche d'hôte pour récupérer l'adresse IP. # Extraction du champ final. else ADR_IP="$1" # L'argument en ligne de commande était une adresse IP. fi echo; echo "L'adresse IP est "ADR_IP""; echo if [ -e "$FICHIER_RESULTAT" ] then rm -f "$FICHIER_RESULTAT" echo "Ancien fichier résultat \"$FICHIER_RESULTAT\" supprimé."; echo fi # Vérification. # (Cette section nécessite plus de travail.) # ========================================== if [ -z "$ADR_IP" ] # Sans réponse. then echo "Hôte introuvable !" exit $E_SANSHOTE # Quitte. fi if [[ "$ADR_IP" =~ "^[;;]" ]] # ;; connection timed out; no servers could be reached then echo "Délai de recherche dépassé !" exit $E_DELAIDEPASSE # On quitte. fi if [[ "$ADR_IP" =~ "[(NXDOMAIN)]$" ]] # Host xxxxxxxxx.xxx not found: 3(NXDOMAIN) then echo "Hôte introuvable !" exit $E_SANSHOTE # On quitte. fi if [[ "$ADR_IP" =~ "[(SERVFAIL)]$" ]] # Host xxxxxxxxx.xxx not found: 2(SERVFAIL) then echo "Hôte introuvable !" exit $E_SANSHOTE # On quitte. fi # ======================== Corps principal du script ======================== AFRINICquery() { # Définit la fonction qui envoit la requête à l'AFRINIC. #+ Affiche une notification à l'écran, puis exécute la requête #+ en redirigeant la sortie vers $FICHIER_RESULTAT. echo "Recherche de $ADR_IP dans whois.afrinic.net" whois -h whois.afrinic.net "$ADR_IP" > $FICHIER_RESULTAT # Vérification de la présence de la référence à un rwhois. # Avertissement sur un serveur rwhois.infosat.net non fonctionnel #+ et tente une requête rwhois. if grep -e "^remarks: .*rwhois\.[^ ]\+" "$FICHIER_RESULTAT" then echo " " >> $FICHIER_RESULTAT echo "***" >> $FICHIER_RESULTAT echo "***" >> $FICHIER_RESULTAT echo "Avertissement : rwhois.infosat.net ne fonctionnait pas le 2005/02/02" >> $FICHIER_RESULTAT echo " lorsque ce script a été écrit." >> $FICHIER_RESULTAT echo "***" >> $FICHIER_RESULTAT echo "***" >> $FICHIER_RESULTAT echo " " >> $FICHIER_RESULTAT RWHOIS=`grep "^remarks: .*rwhois\.[^ ]\+" "$FICHIER_RESULTAT" | tail -n 1 |\ sed "s/\(^.*\)\(rwhois\..*\)\(:4.*\)/\2/"` whois -h ${RWHOIS}:${PORT} "$ADR_IP" >> $FICHIER_RESULTAT fi } APNICquery() { echo "Recherche de $ADR_IP dans whois.apnic.net" whois -h whois.apnic.net "$ADR_IP" > $FICHIER_RESULTAT # Just about every country has its own internet registrar. # I don't normally bother consulting them, because the regional registry #+ usually supplies sufficient information. # There are a few exceptions, where the regional registry simply #+ refers to the national registry for direct data. # These are Japan and South Korea in APNIC, and Brasil in LACNIC. # The following if statement checks $FICHIER_RESULTAT (whois.txt) for the presence #+ of "KR" (South Korea) or "JP" (Japan) in the country field. # If either is found, the query is re-run against the appropriate #+ national registry. if grep -E "^country:[ ]+KR$" "$FICHIER_RESULTAT" then echo "Recherche de $ADR_IP dans whois.krnic.net" whois -h whois.krnic.net "$ADR_IP" >> $FICHIER_RESULTAT elif grep -E "^country:[ ]+JP$" "$FICHIER_RESULTAT" then echo "Recherche de $ADR_IP dans whois.nic.ad.jp" whois -h whois.nic.ad.jp "$ADR_IP"/e >> $FICHIER_RESULTAT fi } ARINquery() { echo "Recherche de $ADR_IP dans whois.arin.net" whois -h whois.arin.net "$ADR_IP" > $FICHIER_RESULTAT # Several large internet providers listed by ARIN have their own #+ internal whois service, referred to as "rwhois". # A large block of IP addresses is listed with the provider #+ under the ARIN registry. # To get the IP addresses of 2nd-level ISPs or other large customers, #+ one has to refer to the rwhois server on port 4321. # I originally started with a bunch of "if" statements checking for #+ the larger providers. # This approach is unwieldy, and there's always another rwhois server #+ that I didn't know about. # A more elegant approach is to check $FICHIER_RESULTAT for a reference #+ to a whois server, parse that server name out of the comment section, #+ and re-run the query against the appropriate rwhois server. # The parsing looks a bit ugly, with a long continued line inside #+ backticks. # But it only has to be done once, and will work as new servers are added. #@ ABS Guide author comment: it isn't all that ugly, and is, in fact, #@+ an instructive use of Regular Expressions. if grep -E "^Comment: .*rwhois.[^ ]+" "$FICHIER_RESULTAT" then RWHOIS=`grep -e "^Comment:.*rwhois\.[^ ]\+" "$FICHIER_RESULTAT" | tail -n 1 |\ sed "s/^\(.*\)\(rwhois\.[^ ]\+\)\(.*$\)/\2/"` echo "Recherche de $ADR_IP dans ${RWHOIS}" whois -h ${RWHOIS}:${PORT} "$ADR_IP" >> $FICHIER_RESULTAT fi } LACNICquery() { echo "Recherche de $ADR_IP dans whois.lacnic.net" whois -h whois.lacnic.net "$ADR_IP" > $FICHIER_RESULTAT # The following if statement checks $FICHIER_RESULTAT (whois.txt) for the presence of #+ "BR" (Brasil) in the country field. # If it is found, the query is re-run against whois.registro.br. if grep -E "^country:[ ]+BR$" "$FICHIER_RESULTAT" then echo "Recherche de $ADR_IP dans whois.registro.br" whois -h whois.registro.br "$ADR_IP" >> $FICHIER_RESULTAT fi } RIPEquery() { echo "Recherche de $ADR_IP dans whois.ripe.net" whois -h whois.ripe.net "$ADR_IP" > $FICHIER_RESULTAT } # Initialise quelques variables. # * slash8 est l'octet le plus significatif # * slash16 consiste aux deux octets les plus significatifs # * octet2 est le deuxième octet le plus significatif slash8=`echo $IPADDR | cut -d. -f 1` if [ -z "$slash8" ] # Encore une autre vérification. then echo "Undefined error!" exit $E_UNDEF fi slash16=`echo $IPADDR | cut -d. -f 1-2` # ^ Point spécifié comme délimiteur pour cut. if [ -z "$slash16" ] then echo "Undefined error!" exit $E_UNDEF fi octet2=`echo $slash16 | cut -d. -f 2` if [ -z "$octet2" ] then echo "Undefined error!" exit $E_UNDEF fi # Vérification de différentes étrangetés. # Il n'y a pas d'intérêts à chercher ces adresses. if [ $slash8 == 0 ]; then echo $ADR_IP est l\'espace '"This Network"' \; Pas de requêtes elif [ $slash8 == 10 ]; then echo $ADR_IP est l\'espace RFC1918 \; Pas de requêtes elif [ $slash8 == 14 ]; then echo $ADR_IP est l\'espace '"Public Data Network"' \; Pas de requêtes elif [ $slash8 == 127 ]; then echo $ADR_IP est l\'espace loopback \; Pas de requêtes elif [ $slash16 == 169.254 ]; then echo $ADR_IP est l\'espace link-local \; Pas de requêtes elif [ $slash8 == 172 ] && [ $octet2 -ge 16 ] && [ $octet2 -le 31 ];then echo $ADR_IP est l\'espace RFC1918 \; Pas de requêtes elif [ $slash16 == 192.168 ]; then echo $ADR_IP est l\'espace RFC1918 \; Pas de requêtes elif [ $slash8 -ge 224 ]; then echo $ADR_IP est l\'espace Multicast ou réservé \; Pas de requêtes elif [ $slash8 -ge 200 ] && [ $slash8 -le 201 ]; then LACNICquery "$ADR_IP" elif [ $slash8 -ge 202 ] && [ $slash8 -le 203 ]; then APNICquery "$ADR_IP" elif [ $slash8 -ge 210 ] && [ $slash8 -le 211 ]; then APNICquery "$ADR_IP" elif [ $slash8 -ge 218 ] && [ $slash8 -le 223 ]; then APNICquery "$ADR_IP" # Si nous sommes arrivés ici sans prendre de décision, demander à l'ARIN. # Si une référence est trouvée dans $FICHIER_RESULTAT à l'APNIC, l'AFRINIC, LACNIC ou RIPE, #+ alors envoyez une requête au serveur whois approprié. else ARINquery "$ADR_IP" if grep "whois.afrinic.net" "$FICHIER_RESULTAT"; then AFRINICquery "$ADR_IP" elif grep -E "^OrgID:[ ]+RIPE$" "$FICHIER_RESULTAT"; then RIPEquery "$ADR_IP" elif grep -E "^OrgID:[ ]+APNIC$" "$FICHIER_RESULTAT"; then APNICquery "$ADR_IP" elif grep -E "^OrgID:[ ]+LACNIC$" "$FICHIER_RESULTAT"; then LACNICquery "$ADR_IP" fi fi #@ --------------------------------------------------------------- # Essayez aussi : # wget http://logi.cc/nw/whois.php3?ACTION=doQuery&DOMAIN=$ADR_IP #@ --------------------------------------------------------------- # Nous avons fini maintenant toutes les requêtes. # Affiche une copie du résultat final à l'écran. cat $FICHIER_RESULTAT # Ou "less $FICHIER_RESULTAT" . . . exit 0 #@ Commentaires de l'auteur du guide ABS : #@ Rien de particulièrement intéressant ici, #@+ mais quand même un outil très utile pour chasser les spammeurs. #@ Bien sûr, le script peut être un peu nettoyé et il est encore un peu bogué #@+ (exercice pour le lecteur) mais, en fait, c'est un joli code de #@+ Walter Dnes. #@ Merci !
L'interface de « Little Monster » pour wget.
Exemple A.31. Rendre wget plus facile à utiliser
#!/bin/bash # wgetter2.bash # Auteur : Little Monster [monster@monstruum.co.uk] # ==> Utilisé dans le guide ABS avec la permission de l'auteur du script. # ==> Ce script a toujours besoin de débogage et de corrections (exercice # ==> laissé au lecteur). # ==> Il pourrait aussi bénéficier de meilleurs commentaires. # Ceci est wgetter2 -- #+ un script Bash rendant wget un peu plus facile à utiliser #+ et évitant de la frappe clavier. # Écrit avec attention par Little Monster. # Plus ou moins complet le 02/02/2005. # Si vous pensez que ce script est améliorable, #+ envoyez-moi un courrier électronique à : monster@monstruum.co.uk # ==> et mettez en copie l'auteur du guide ABS. # Ce script est sous licence GPL. # Vous êtes libre de le copier, modifier, ré-utiliser, #+ mais, s'il-vous-plait, ne dites pas que vous l'avez écrit. # À la place, indiquez vos changements ici. # ======================================================================= # journal des modifications : # 07/02/2005. Corrections par Little Monster. # 02/02/2005. Petits ajouts de Little Monster. # (Voir après # +++++++++++ ) # 29/01/2005. Quelques petites modifications de style et nettoyage de l'auteur # du guide ABS. # Ajout des codes d'erreur. # 22/11/2004. Fin de la version initiale de la seconde version de wgetter : # wgetter2 est né. # 01/12/2004. Modification de la fonction 'runn' de façon à ce qu'il # fonctionne de deux façons -- # soit en demandant le nom d'un fichier soit en le récupérant sur # la ligne de commande. # 01/12/2004. Gestion sensible si aucune URL n'est fournie. # 01/12/2004. Boucle des options principales, de façon à ne pas avoir à # rappeller wgetter 2 tout le temps. # À la place, fonctionne comme une session. # 01/12/2004. Ajout d'une boucle dans la fonction 'runn'. # Simplifié et amélioré. # 01/12/2004. Ajout de state au paramètrage de récursion. # Active la ré-utilisation de la valeur précédente. # 05/12/2004. Modification de la routine de détection de fichiers dans la # fonction 'runn' de façon à ce qu'il ne soit pas gêné par des # valeurs vides et pour qu'il soit plus propre. # 01/02/2004. Ajout de la routine de récupération du cookie à partir de # la dernière version (qui n'est pas encore prête), de façon à ne # pas avoir à codé en dur les chemins. # ======================================================================= # Codes d'erreur pour une sortie anormale. E_USAGE=67 # Message d'usage, puis quitte. E_SANS_OPTS=68 # Aucun argument en ligne de commande. E_SANS_URLS=69 # Aucune URL passée au script. E_SANS_FICHIERSAUVEGARDE=70 # Aucun nom de fichier de sortie passé au script. E_SORTIE_UTILISATEUR=71 # L'utilisateur a décidé de quitter. # Commande wget par défaut que nous voulons utiliser. # C'est l'endroit où la changer, si nécessaire. # NB: si vous utilisez un proxy, indiquez http_proxy = yourproxy dans .wgetrc. # Sinon, supprimez --proxy=on, ci-dessous. # ==================================================================== CommandeA="wget -nc -c -t 5 --progress=bar --random-wait --proxy=on -r" # ==================================================================== # -------------------------------------------------------------------- # Initialisation de quelques autres variables avec leur explications. pattern=" -A .jpg,.JPG,.jpeg,.JPEG,.gif,.GIF,.htm,.html,.shtml,.php" # Options de wget pour ne récupérer que certain types de #+ fichiers. Mettre en commentaire si inutile today=`date +%F` # Utilisé pour un nom de fichier. home=$HOME # Utilise HOME pour configurer une variable interne. # Au cas où d'autres chemins sont utilisés, modifiez cette #+ variable. depthDefault=3 # Configure un niveau de récursion sensible. Depth=$depthDefault # Sinon, le retour de l'utilisateur ne sera pas intégré. RefA="" # Configure la page blanche de référence. Flag="" # Par défaut, ne sauvegarde rien, #+ ou tout ce qui pourrait être voulu dans le futur. lister="" # Utilisé pour passer une liste d'url directement à wget. Woptions="" # Utilisé pour passer quelques options à wget. inFile="" # Utilisé pour la fonction run. newFile="" # Utilisé pour la fonction run. savePath="$home/w-save" Config="$home/.wgetter2rc" # Quelques variables peuvent être stockées, #+ si elles sont modifiées en permanence à l'intérieur de ce #+ script. Cookie_List="$home/.cookielist" # Pour que nous sachions où sont conservés les cookies... cFlag="" # Une partie de la routine de sélection du cookie. # Définissez les options disponibles. Lettres faciles à modifier ici si #+ nécessaire. # Ce sont les options optionnelles ; vous n'avez pas besoin d'attendre #+ qu'elles vous soient demandées. save=s # Sauvegarde la commande au lieu de l'exécuter. cook=c # Modifie le cookie pour cette session. help=h # Guide d'usage. list=l # Passe à wget l'option -i et la liste d'URL. runn=r # Lance les commandes sauvegardées comme argument de l'option. inpu=i # Lance les commandes sauvegardées de façon interactive. wopt=w # Autorise la saisie d'options à passer directement à wget. # -------------------------------------------------------------------- if [ -z "$1" ]; then # Soyons sûr de donner quelque chose à manger à wget. echo "Vous devez entrer au moins une RLS ou une option!" echo "-$help pour l'utilisation." exit $E_SANS_OPTS fi # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # ajout ajout ajout ajout ajout ajout ajout ajout ajout ajout ajout ajout if [ ! -e "$Config" ]; then # Vérification de l'existence du fichier de #+ configuration. echo "Création du fichier de configuration, $Config" echo "# Ceci est le fichier de configuration pour wgetter2" > "$Config" echo "# Vos paramètres personnalisés seront sauvegardés dans ce fichier" \ >> "$Config" else source $Config # Import des variables que nous avons initialisé #+ en dehors de ce script. fi if [ ! -e "$Cookie_List" ]; then # Configure une liste de cookie, si elle n'existe pas. echo "Recherche des cookies..." find -name cookies.txt >> $Cookie_List # Crée une liste des cookies. fi # Isole ceci dans sa propre instruction 'if', #+ au cas où nous serions interrompu durant la recherche. if [ -z "$cFlag" ]; then # Si nous n'avons pas encore fait ceci... echo # Ajoute un espacement après l'invite de la commande. echo "Il semble que vous n'avez pas encore configuré votre source de cookies." n=0 # S'assure que le compteur ne contient pas de valeurs. while read; do Cookies[$n]=$REPLY # Place les cookies que nous avons trouvé dans un #+ tableau. echo "$n) ${Cookies[$n]}" # Crée un menu. n=$(( n + 1 )) # Incrémente le comteur. done < $Cookie_List # Remplit l'instruction read. echo "Saisissez le nombre de cookies que vous souhaitez utiliser." echo "Si vous ne voulez pas utiliser de cookie, faites simplement RETURN." echo echo "Je ne vous demanderais plus ceci. Éditez $Config" echo "si vous décidez de le changer ultérieurement" echo "ou utilisez l'option -${cook} pour des modifications sur une session." read if [ ! -z $REPLY ]; then # L'utilisateur n'a pas seulement faire ENTER. Cookie=" --load-cookies ${Cookies[$REPLY]}" # Initialise la variable ici ainsi que dans le fichier de configuration. echo "Cookie=\" --load-cookies ${Cookies[$REPLY]}\"" >> $Config fi echo "cFlag=1" >> $Config # Pour que nous nous rappelions de ne pas le #+ demander de nouveau. fi # fin section ajoutée fin section ajoutée fin section ajoutée # +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Une autre variable. # Celle-ci pourrait être ou pas sujet à variation. # Un peu comme le petit affichage. CookiesON=$Cookie # echo "cookie file is $CookiesON" # Pour débogage. # echo "home is ${home}" # Pour débogage. Faites attention à celui-ci! wopts() { echo "Entrer les options à fournir à wget." echo "Il est supposé que vous savez ce que vous faites." echo echo "Vous pouvez passer leurs arguments ici aussi." # C'est-à-dire que tout ce qui est saisi ici sera passé à wget. read Wopts # Lire les options à donner à wget. Woptions=" $Wopts" # ^ Pourquoi cet espace initial ? # Affecter à une autre variable. # Pour le plaisir, ou pour tout autre chose... echo "options ${Wopts} fournies à wget" # Principalement pour du débogage. # Est joli. return } save_func() { echo "Les paramètres vont être sauvegardés." if [ ! -d $savePath ]; then # Vérifie si le répertoire existe. mkdir $savePath # Crée le répertoire pour la sauvegarde #+ si ce dernier n'existe pas. fi Flag=S # Indique au dernier bout de code ce qu'il faut faire. # Positionne un drapeau car le boulot est effectué dans la partie principale. return } usage() # Indique comment cela fonctionne. { echo "Bienvenue dans wgetter. C'est une interface pour wget." echo "Il lancera en permanence wget avec ces options :" echo "$CommandeA" echo "et le modèle de correspondance: $modele (que vous pouvez changer en" echo "haut du script)." echo "Il vous demandera aussi une profondeur de récursion depth et si vous" echo "souhaitez utiliser une page de référence." echo "Wgetter accepte les options suivantes :" echo "" echo "-$help : Affiche cette aide." echo "-$save : Sauvegarde la commande dans un fichier" echo "$savePath/wget-($today) au lieu de l'exécuter." echo "-$runn : Exécute les commandes wget sauvegardées au lieu d'en" echo "commencer une nouvelle --" echo "Saisissez le nom du fichier comme argument de cette option." echo "-$inpu : Exécute les commandes wget sauvegardées, de façon" echo "interactive -- " echo "Le script vous demandera le nom du fichier." echo "-$cook : Modifie le fichier des cookies pour cette session." echo "-$list : Indique à wget d'utiliser les URL à partir d'une liste" echo "plutôt que sur la ligne de commande." echo "-$wopt : Passe toute autre option directement à wget." echo "" echo "Voir la page man de wget pour les options supplémentaires que vous" echo "pouvez lui passer." echo "" exit $E_USAGE # Fin ici. Ne rien exécuter d'autre. } list_func() # Donne à l'utilisateur l'option pour utiliser l'option -i de wget, #+ et une liste d'URL. { while [ 1 ]; do echo "Saisissez le nom du fichier contenant les URL (appuyez sur q si vous" echo "avez changé d'idée)." read urlfile if [ ! -e "$urlfile" ] && [ "$urlfile" != q ]; then # Recherche un fichier ou l'option de sortie. echo "Ce fichier n'existe pas!" elif [ "$urlfile" = q ]; then # Vérifie l'option de sortie. echo "N'utilise pas de liste d'URL." return else echo "Utilisation de $urlfile." echo "Si vous m'avez fourni des URL sur la ligne de commandes," echo "je les utiliserais en premier." # Indique le comportement standard de wget à l'utilisateur. lister=" -i $urlfile" # C'est ce que nous voulons fournir à wget. return fi done } cookie_func() # Donne à l'utilisateur l'option d'utiliser un fichier #+ cookie différent. { while [ 1 ]; do echo "Modification du fichier cookie. Appuyez sur return si vous ne voulez " echo "pas le changer." read Cookies # NB: Ceci n'est pas la même chose que Cookie, un peu plus tôt. # Il y a un 's' à la fin. if [ -z "$Cookies" ]; then # Clause d'échappement. return elif [ ! -e "$Cookies" ]; then echo "Le fichier n'existe pas. Essayez de nouveau." # On continue... else CookiesON=" --load-cookies $Cookies" # Le fichier est bon -- utilisons-le! return fi done } run_func() { if [ -z "$OPTARG" ]; then # Teste pour voir si nous utilisons les options en ligne ou la requête. if [ ! -d "$savePath" ]; then # Au cas où le répertoire n'existe pas... echo "$savePath ne semble pas exister." echo "Merci de fournir un chemin et un nom de fichiers pour les commandes" echo "wget sauvegardées :" read newFile until [ -f "$newFile" ]; do # Continue jusqu'à ce que nous obtenions #+ quelque chose. echo "Désolé, ce fichier n'existe pas. Essayez de nouveau." # Essaie réellement d'avoir quelque chose. read newFile done # ------------------------------------------------------------------------- # if [ -z ( grep wget ${newfile} ) ]; then # Suppose qu'ils n'ont pas encore le bon fichier. # echo "Désolé, ce fichier ne contient pas de commandes wget. # echo "Annulation." # exit # fi # # Ce code est bogué. # Il ne fonctionne réellement pas. # Si vous voulez le corriger, n'hésitez pas ! # ------------------------------------------------------------------------- filePath="${newFile}" else echo "Le chemin de sauvegarde est $savePath" echo "Merci de saisir le nom du fichier que vous souhaitez utiliser." echo "Vous avez le choix entre :" ls $savePath # Leur donne un choix. read inFile until [ -f "$savePath/$inFile" ]; do # Continuez jusqu'à obtention. if [ ! -f "${savePath}/${inFile}" ]; then # Si le fichier n'existe pas. echo "Désolé, ce fichier n'existe pas." echo " Faites votre choix à partir de :" ls $savePath # Si une erreur est faite. read inFile fi done filePath="${savePath}/${inFile}" # En faire une variable... fi else filePath="${savePath}/${OPTARG}" # qui peut être beaucoup de choses... fi if [ ! -f "$filePath" ]; then # Si nous obtenons un fichier bogué. echo "Vous n'avez pas spécifié un fichier convenable." echo "Lancez tout d'abord ce script avec l'option -${save}." echo "Annulation." exit $E_SANS_FICHIERSAUVEGARDE fi echo "Utilisation de : $filePath" while read; do eval $REPLY echo "Fin : $REPLY" done < $filePath # Remplit le fichier que nous utilisons avec une boucle while. exit } # Récupération de toute option que nous utilisons pour ce script. # Ceci est basé sur la démo de "Learning The Bash Shell" (O'Reilly). while getopts ":$save$cook$help$list$runn:$inpu$wopt" opt do case $opt in $save) save_func;; # Sauvegarde de quelques sessions wgetter pour plus # tard. $cook) cookie_func;; # Modifie le fichier cookie. $help) usage;; # Obtient de l'aide. $list) list_func;; # Autorise wget à utiliser une liste d'URL. $runn) run_func;; # Utile si vous appelez wgetter à partir d'un script #+ cron par exemple. $inpu) run_func;; # Lorsque vous ne connaissez pas le nom des fichiers. $wopt) wopts;; # Passe les options directement à wget. \?) echo "Option invalide." echo "Utilisez -${wopt} si vous voulez passer les options " echo "directement à to wget," echo "ou -${help} pour de l'aide";; # Récupère quelque chose. esac done shift $((OPTIND - 1)) # Opérations magiques avec $#. if [ -z "$1" ] && [ -z "$lister" ]; then # Nous devrions laisser au moins une URL sur la #+ ligne de commande à moins qu'une liste ne soit #+ utilisée - récupère les lignes de commandes vides. echo "Aucune URL fournie ! Vous devez les saisir sur la même ligne " echo "que wgetter2." echo "Par exemple, wgetter2 http://somesite http://anothersite." echo "Utilisez l'option $help pour plus d'informations." exit $E_SANS_URLS # Quitte avec le bon code d'erreur. fi URLS=" $@" # Utilise ceci pour que la liste d'URL puisse être modifié si nous restons dans #+ la boucle d'option. while [ 1 ]; do # C'est ici que nous demandons les options les plus utilisées. # (Pratiquement pas changées depuis la version 1 de wgetter) if [ -z $curDepth ]; then Current="" else Current=" La valeur courante est $curDepth" fi echo "A quelle profondeur dois-je aller ? " echo "(entier: valeur par défaut $depthDefault.$Current)" read Depth # Récursion -- A quelle profondeur allons-nous ? inputB="" # Réinitialise ceci à rien sur chaque passe de la boucle. echo "Saisissez le nom de la page de référence (par défaut, aucune)." read inputB # Nécessaire pour certains sites. echo "Voulez-vous que la sortie soit tracée sur le terminal" echo "(o/n, par défaut, oui) ?" read noHide # Sinon, wget le tracera simplement dans un fichier. case $noHide in # Maintenant, vous me voyez, maintenant, vous ne me voyez plus. o|O ) hide="";; n|N ) hide=" -b";; * ) hide="";; esac if [ -z ${Depth} ]; then # L'utilisateur a accepté la valeur par #+ défaut ou la valeur courante, #+ auquel cas Depth est maintenant vide. if [ -z ${curDepth} ]; then # Vérifie si Depth a été configuré #+ sur une précédente itération. Depth="$depthDefault" # Configure la profondeur de récursion #+ par défaut si rien de défini #+ sinon, l'utilise. else Depth="$curDepth" # Sinon, utilisez celui configuré #+ précédemment. fi fi Recurse=" -l $Depth" # Initialise la profondeur. curDepth=$Depth # Se rappeler de ce paramètrage la #+ prochaine fois. if [ ! -z $inputB ]; then RefA=" --referer=$inputB" # Option à utiliser pour la page de #+ référence. fi WGETTER="${CommandeA}${modele}${hide}${RefA}${Recurse}${CookiesON}${lister}${Woptions}${URLS}" # Crée une chaîne contenant le lot complet... # NB: pas d'espace imbriqués. # Ils sont dans les éléments individuels si aucun n'est vide, #+ nous n'obtenons pas d'espace supplémentaire. if [ -z "${CookiesON}" ] && [ "$cFlag" = "1" ] ; then echo "Attention -- impossible de trouver le fichier cookie." # Ceci pourrait changer, au cas où l'utilisateur aurait choisi de ne #+ pas utiliser les cookies. fi if [ "$Flag" = "S" ]; then echo "$WGETTER" >> $savePath/wget-${today} # Crée un nom de fichier unique pour aujourd'hui #+ ou y ajoute les informations s'il existe déjà. echo "$inputB" >> $savePath/site-list-${today} # Crée une liste pour qu'il soit plus simple de s'y référer plus tard, #+ car la commande complète est un peu confuse. echo "Commande sauvegardée dans le fichier $savePath/wget-${today}" # Indication pour l'utilisateur. echo "URL de la page de référence sauvegardé dans le fichier " echo "$savePath/site-list-${today}" # Indication pour l'utilisateur. Saver=" avec les options sauvegardées" # Sauvegarde ceci quelque part, de façon à ce qu'il apparaisse dans la #+ boucle si nécessaire. else echo "**********************" echo "*****Récupération*****" echo "**********************" echo "" echo "$WGETTER" echo "" echo "**********************" eval "$WGETTER" fi echo "" echo "Continue avec$Saver." echo "Si vous voulez stopper, appuyez sur q." echo "Sinon, saisissez des URL :" # Laissons-les continuer. Indication sur les options sauvegardées. read case $REPLY in # Nécessaire de changer ceci par une clause 'trap'. q|Q ) exit $E_SORTIE_UTILISATEUR;; # Exercice pour le lecteur ? * ) URLS=" $REPLY";; esac echo "" done exit 0
Exemple A.32. Un script de « podcasting »
#!/bin/bash # bashpodder.sh: # Par Linc 10/1/2004 # Trouve le dernier script sur : # http://linc.homeunix.org:8080/scripts/bashpodder # Dernière révision 14/12/2004 - Beaucoup de contributeurs ! # Si vous l'utilisez et y avez ajouté quelques améliorations ou commentaires, #+ envoyez-moi un courrier électronique (linc POINT fessenden CHEZ gmail POINT com) # J'apprécierais beaucoup ! # ==> Commentaires supplémentaires du guide ABS. # ==> L'auteur de ce script a donné gentimment sa permission # ==>+ pour son ajout dans le guide ABS. # ==> ################################################################ # # ==> Qu'est-ce que "podcasting" ? # ==> C'est l'envoi d'émissions de radio sur Internet. # ==> Ces émissions peuvent être écoutées sur des iPod ainsi que sur #+==> d'autres lecteurs de fichiers musicaux. # ==> Ce script rend ceci possible. # ==> Voir la documentation sur le site de l'auteur du script. # ==> ################################################################ # Rend ce script compatible avec crontab : cd $(dirname $0) # ==> Change de répertoire par celui où ce script réside. # repdonnees est le répertoire où les fichiers podcasts ont été sauvegardés : repdonnees=$(date +%Y-%m-%d) # ==> Créera un répertoire de nom : YYYY-MM-DD # Vérifie et crée repdonnees si nécessaire : if test ! -d $repdonnees then mkdir $repdonnees fi # Supprime tout fichier temporaire : rm -f temp.log # Lit le fichier bp.conf et récupère toute URL qui ne se trouve pas dans le fichier podcast.log : while read podcast do # ==> L'action principale suit. fichier=$(wget -q $podcast -O - | tr '\r' '\n' | tr \' \" | \ sed -n 's/.*url="\([^"]*\)".*/\1/p') for url in $fichier do echo $url >> temp.log if ! grep "$url" podcast.log > /dev/null then wget -q -P $repdonnees "$url" fi done done < bp.conf # Déplace le journal créé dynamiquement dans le journal permanent : cat podcast.log >> temp.log sort temp.log | uniq > podcast.log rm temp.log # Crée une liste musicale m3u : ls $repdonnees | grep -v m3u > $repdonnees/podcast.m3u exit 0 ################################################# Pour une approche différente de l'écriture de script pour le Podcasting, voir l'article de Phil Salkie, "Internet Radio to Podcast with Shell Tools" dans le numéro de septembre 2005 du LINUX JOURNAL, http://www.linuxjournal.com/article/8171 #################################################
Exemple A.33. Sauvegarde de nuit pour un disque firewire
#!/bin/bash # nightly-backup.sh # http://www.richardneill.org/source.php#nightly-backup-rsync # Copyright (c) 2005 Richard Neill <backup@richardneill.org>. # Ce logiciel libre est sous licence GNU GPL. # ==> Inclus dans le guide ABS avec l'aimable autorisation de l'auteur du script. # ==> (Merci !) # Ceci réalise une sauvegarde de l'ordinateur hôte vers un disque dur firewire #+ connecté localement en utilisant rsync et ssh. # Il exécute ensuite une rotation des sauvegardes. # Exécutez-la via cron tous les jours à 5h du matin. # Cela ne sauvegarde que le répertoire principal. # Si le propriétaire (autre que l'utilisateur) doit être conservé, #+ alors exécutez le processus rsync en tant que root (et ajoutez le -o). # Nous sauvegardons tous les jours pendant sept jours, #+ puis chaque semaine pendant quatre semaines, #+ puis chaque mois pendant trois mois. # Voir http://www.mikerubel.org/computers/rsync_snapshots/ #+ pour plus d'informations sur la théorie. # À sauvegarder sous : $HOME/bin/nightly-backup_firewire-hdd.sh # Bogues connus : # --------------- # i) Idéalement, nous voulons exclure ~/.tmp et les caches du navigateur. # ii) Si l'utilisateur est devant son ordinateur à 5h du matin #+ et que les fichiers sont modifiés alors que le rsync est en cours, #+ alors la branche SAUVEGARDE_AUCASOU est appelée. # D'une certaine façon, c'est une fonctionnalité #+ mais cela cause aussi une "fuite d'espace disque". ##### DÉBUT DE LA SECTION DE CONFIGURATION ################################### UTILISATEUR_LOCAL=rjn # Utilisateur dont le répertoire principal sera #+ sauvegardé. POINT_MONTAGE=/backup # Point de montage du répertoire de sauvegarde. # Pas de slash à la fin ! # Il doit être unique #+ (par exemple en utilisant un lien symbolique udev) REP_SOURCE=/home/$UTILISATEUR_LOCAL # Pas de slash à la fin - important pour rsync. REP_DEST_SAUVE=$POINT_MONTAGE/backup/`hostname -s`.${UTILISATEUR_LOCAL}.nightly_backup ESSAI_A_BLANC=false # Si vrai, appelle rsync avec -n, réalisant un test. # Commentez ou configurez à faux pour une utilisation #+ normale. VERBEUX=false # Si vrai, rend rsync verbeux. # Commentez ou configurez à faux sinon. COMPRESSIONION=false # Si vrai, compresse. # Bon pour internet, mauvais sur LAN. # Commentez ou configurez à faux sinon. ### Codes d'erreur ### E_VAR_NON_CONF=64 E_LIGNECOMMANDE=65 E_ECHEC_MONTAGE=70 E_PASREPSOURCE=71 E_NONMONTE=72 E_SAUVE=73 ##### FIN DE LA SECTION DE CONFIGURATION ##################################### # Vérifie que toutes les variables importantes sont configurées : if [ -z "$UTILISATEUR_LOCAL" ] || [ -z "$REP_SOURCE" ] || [ -z "$POINT_MONTAGE" ] || [ -z "$REP_DEST_SAUVE" ] then echo "Une des variables n'est pas configurée ! Modifiez le fichier $0. ÉCHEC DE LA SAUVEGARDE." exit $E_VAR_NON_CONF fi if [ "$#" != 0 ] # Si des paramètres en ligne de commande... then # Document(ation) en ligne. cat <<-FINDUTEXTE Sauvegarde quotienne automatique exécutée par cron. Lisez les sources pour plus de détails : $0 Le répertoire de sauvegarde est $REP_DEST_SAUVE . Il sera créé si nécessaire ; une initialisation est inutile. ATTENTION : le contenu de $REP_DEST_SAUVE est l'objet de rotation. Les répertoires nommés 'backup.\$i' seront éventuellement supprimés. Nous conservons des répertoires pour chaque jour sur sept jours (1-8), puis pour chaque semaine sur quatre semaines (9-12), puis pour chaque mois sur trois mois (13-15). Vous pouvez ajouter ceci à votre crontab en utilisant 'crontab -e' # Fichiers sauvegardés : $REP_SOURCE dans $REP_DEST_SAUVE #+ chaque nuit à 3:15 du matin 15 03 * * * /home/$UTILISATEUR_LOCAL/bin/nightly-backup_firewire-hdd.sh N'oubliez pas de vérifier que les sauvegardes fonctionnent, surtout si vous ne lisez pas le mail de cron !" FINDUTEXTE exit $E_LIGNECOMMANDE fi # Analyse des options. # ==================== if [ "$ESSAI_A_BLANC" == "true" ]; then ESSAI_A_BLANC="-n" echo "ATTENTION" echo "CECI EST UN TEST SIMPLE !" echo "Aucune donnée ne sera réellement transférée !" else ESSAI_A_BLANC="" fi if [ "$VERBEUX" == "true" ]; then VERBEUX="-v" else VERBEUX="" fi if [ "$COMPRESSION" == "true" ]; then COMPRESSION="-z" else COMPRESSION="" fi # Chaque semaine (en fait tous les huit jours) et chaque mois, #+ des sauvegardes supplémentaires seront effectuées. JOUR_DU_MOIS=`date +%d` # Jour du mois (01..31). if [ $JOUR_DU_MOIS = 01 ]; then # Premier du mois. DEBUTMOIS=true elif [ $JOUR_DU_MOIS = 08 \ -o $JOUR_DU_MOIS = 16 \ -o $JOUR_DU_MOIS = 24 ]; then # Jour 8,16,24 # (on utilise 8 et non pas 7 pour mieux gérer les mois à 31 jours) DEBUTSEMAINE=true fi # Vérifie que le disque est monté. # En fait, vérifie que *quelque chose* est monté ici ! # Nous pouvons utiliser quelque chose d'unique sur le périphérique #+ plutôt que de simplement deviner l'ID SCSI en utilisant la bonne règle udev #+ dans /etc/udev/rules.d/10-rules.local #+ et en plaçant une entrée adéquate dans /etc/fstab. # Par exemple, cette règle udev : # BUS="scsi", KERNEL="sd*", SYSFS{vendor}="WDC WD16", # SYSFS{model}="00JB-00GVA0 ", NAME="%k", SYMLINK="lacie_1394d%n" if mount | grep $POINT_MONTAGE >/dev/null; then echo "Le point de montage $POINT_MONTAGE est déjà utilisé. OK" else echo -n "Tentative de montage de $POINT_MONTAGE..." # S'il n'est pas monté, essaie de le monter. sudo mount $POINT_MONTAGE 2>/dev/null if mount | grep $POINT_MONTAGE >/dev/null; then DEMONTE_APRES=TRUE echo "OK" # Note : s'assure qu'il sera aussi démonté #+ si nous quittons prématurément avec erreur. else echo "ÉCHEC" echo -e "Rien n'est monté sur $POINT_MONTAGE. ÉCHEC DE LA SAUVEGARDE!" exit $E_ECHEC_MONTAGE fi fi # Vérifie que le répertoire source existe et est lisible. if [ ! -r $REP_SOURCE ] ; then echo "$REP_SOURCE n'existe pas ou ne peut être lu. ÉCHEC DE LA SAUVEGARDE." exit $E_PASREPSOURCE fi # Vérifie que la structure du répertoire de sauvegarde est bonne. # Sinon, il la crée. # Crée les sous-répertoires. # Notez que backup.0 sera créé si nécessaire par rsync. for ((i=1;i<=15;i++)); do if [ ! -d $REP_DEST_SAUVE/backup.$i ]; then if /bin/mkdir -p $REP_DEST_SAUVE/backup.$i ; then # ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pas de tests entre crochets. Pourquoi ? echo "Attention : le répertoire $REP_DEST_SAUVE/backup.$i n'existe pas" echo "ou n'a pas été initialisé. (Re-)creation du répertoire." else echo "ERREUR : le répertoire $REP_DEST_SAUVE/backup.$i" echo "n'existe pas et n'a pas pu être créé." if [ "$DEMONTE_APRES" == "TRUE" ]; then # Avant de quitter, démonte le point de montage si nécessaire. cd sudo umount $POINT_MONTAGE && echo "Démontage de $POINT_MONTAGE. Abandon." fi exit $E_NONMONTE fi fi done # Configure les droits à 700 pour de la sécurité #+ sur un système multi-utilisateur. if ! /bin/chmod 700 $REP_DEST_SAUVE ; then echo "ERREUR : n'a pas pu configurer les droits du répertoire $REP_DEST_SAUVE à 700." if [ "$DEMONTE_APRES" == "TRUE" ]; then # Avant de quitter, démonte le point de montage si nécessaire. cd ; sudo umount $POINT_MONTAGE && echo "Démontage de $POINT_MONTAGE. Abandon." fi exit $E_NONMONTE fi # Création du lien symbolique : current -> backup.1 si nécessaire. # Un échec ici n'est pas critique. cd $REP_DEST_SAUVE if [ ! -h current ] ; then if ! /bin/ln -s backup.1 current ; then echo "Attention : n'a pas pu créer le lien symbolique current -> backup.1" fi fi # Maintenant, exécute le rsync. echo "Sauvegarde en cours avec rsync..." echo "Répertoire source : $REP_SOURCE" echo -e "Répertoire destination : $REP_DEST_SAUVE\n" /usr/bin/rsync $ESSAI_A_BLANC $VERBEUX -a -S --delete --modify-window=60 \ --link-dest=../backup.1 $REP_SOURCE $REP_DEST_SAUVE/backup.0/ # Avertit seulement, plutôt que de quitter, si rsync a échoué, #+ car cela pourrait n'être qu'un problème mineur. # Par exemple, si un fichier n'est pas lisible, rsync échouera. # Ceci ne doit pas empêcher la rotation. # Ne pas utiliser, par exemple, `date +%a` car ces répertoires #+ sont plein de liens et ne consomment pas *tant* d'espace. if [ $? != 0 ]; then SAUVEGARDE_AUCASOU=backup.`date +%F_%T`.justincase echo "ATTENTION : le processus rsync n'a pas complètement réussi." echo "Quelque chose s'est mal passé. Sauvegarde d'une copie supplémentaire dans : $SAUVEGARDE_AUCASOU" echo "ATTENTION : si cela arrive fréquemment, BEAUCOUP d'espace sera utilisé," echo "même si ce ne sont que des liens !" fi # Ajoute un fichier readme dans le répertoire principal de la sauvegarde. # En sauvegarde un autre dans le sous-répertoire recent. echo "La sauvegarde de $REP_SOURCE sur `hostname` a été exécuté le \ `date`" > $REP_DEST_SAUVE/README.txt echo "Cette sauvegarde de $REP_SOURCE sur `hostname` a été créé le \ `date`" > $REP_DEST_SAUVE/backup.0/README.txt # Si nous n'avons pas fait un test, exécute une rotation des sauvegardes. [ -z "$ESSAI_A_BLANC" ] && # Vérifie l'espace occupé du disque de sauvegarde. # Avertissement si 90%. # Si 98% voire plus, nous échouerons probablement, donc abandon. # (Note : df peut afficher plus d'une ligne.) # Nous le testons ici plutôt qu'avant pour donner une chance à rsync. DISK_FULL_PERCENT=`/bin/df $REP_DEST_SAUVE | tr "\n" ' ' | awk '{print $12}' | grep -oE [0-9]+ ` echo "Vérification de l'espace disque sur la partition de sauvegarde \ remplie à $POINT_MONTAGE $DISK_FULL_PERCENT%." if [ $DISK_FULL_PERCENT -gt 90 ]; then echo "Attention : le disque est rempli à plus de 90%." fi if [ $DISK_FULL_PERCENT -gt 98 ]; then echo "Erreur : le disque est rempli complètement ! Abandon." if [ "$DEMONTE_APRES" == "TRUE" ]; then # Avant de quitter, démonte le point de montage si nécessaire. cd; sudo umount $POINT_MONTAGE && echo "Démontage de $POINT_MONTAGE. Abandon." fi exit $E_NONMONTE fi # Crée une sauvegarde supplémentaire. # Si cette copie échoue, abandonne. if [ -n "$SAUVEGARDE_AUCASOU" ]; then if ! /bin/cp -al $REP_DEST_SAUVE/backup.0 $REP_DEST_SAUVE/$SAUVEGARDE_AUCASOU then echo "ERREUR : échec lors de la création de la copie de sauvegarde \ $REP_DEST_SAUVE/$SAUVEGARDE_AUCASOU" if [ "$DEMONTE_APRES" == "TRUE" ]; then # Avant de quitter, démonte le point de montage si nécessaire. cd ;sudo umount $POINT_MONTAGE && echo "Démontage de $POINT_MONTAGE. Abandon." fi exit $E_NONMONTE fi fi # Au début du mois, exécute une rotation des huit plus anciens. if [ "$DEBUTMOIS" == "true" ]; then echo -e "\nDébut du mois. \ Suppression de l'ancienne sauvegarde : $REP_DEST_SAUVE/backup.15" && /bin/rm -rf $REP_DEST_SAUVE/backup.15 && echo "Rotation mensuelle, sauvegardes hebdomadaires : \ $REP_DEST_SAUVE/backup.[8-14] -> $REP_DEST_SAUVE/backup.[9-15]" && /bin/mv $REP_DEST_SAUVE/backup.14 $REP_DEST_SAUVE/backup.15 && /bin/mv $REP_DEST_SAUVE/backup.13 $REP_DEST_SAUVE/backup.14 && /bin/mv $REP_DEST_SAUVE/backup.12 $REP_DEST_SAUVE/backup.13 && /bin/mv $REP_DEST_SAUVE/backup.11 $REP_DEST_SAUVE/backup.12 && /bin/mv $REP_DEST_SAUVE/backup.10 $REP_DEST_SAUVE/backup.11 && /bin/mv $REP_DEST_SAUVE/backup.9 $REP_DEST_SAUVE/backup.10 && /bin/mv $REP_DEST_SAUVE/backup.8 $REP_DEST_SAUVE/backup.9 # Au début de la semaine, exécute une rotation des quatre seconds plus anciens. elif [ "$DEBUTSEMAINE" == "true" ]; then echo -e "\nDébut de semaine. \ Suppression de l'ancienne sauvegarde hebdomadaire : $REP_DEST_SAUVE/backup.12" && /bin/rm -rf $REP_DEST_SAUVE/backup.12 && echo "Rotation des sauvegardes hebdomadaires : \ $REP_DEST_SAUVE/backup.[8-11] -> $REP_DEST_SAUVE/backup.[9-12]" && /bin/mv $REP_DEST_SAUVE/backup.11 $REP_DEST_SAUVE/backup.12 && /bin/mv $REP_DEST_SAUVE/backup.10 $REP_DEST_SAUVE/backup.11 && /bin/mv $REP_DEST_SAUVE/backup.9 $REP_DEST_SAUVE/backup.10 && /bin/mv $REP_DEST_SAUVE/backup.8 $REP_DEST_SAUVE/backup.9 else echo -e "\nSuppression de l'ancienne sauvegarde quotidienne : $REP_DEST_SAUVE/backup.8" && /bin/rm -rf $REP_DEST_SAUVE/backup.8 fi && # Chaque jour, rotation de huit plus anciens. echo "Rotation des sauvegardes quotidiennes : \ $REP_DEST_SAUVE/backup.[1-7] -> $REP_DEST_SAUVE/backup.[2-8]" && /bin/mv $REP_DEST_SAUVE/backup.7 $REP_DEST_SAUVE/backup.8 && /bin/mv $REP_DEST_SAUVE/backup.6 $REP_DEST_SAUVE/backup.7 && /bin/mv $REP_DEST_SAUVE/backup.5 $REP_DEST_SAUVE/backup.6 && /bin/mv $REP_DEST_SAUVE/backup.4 $REP_DEST_SAUVE/backup.5 && /bin/mv $REP_DEST_SAUVE/backup.3 $REP_DEST_SAUVE/backup.4 && /bin/mv $REP_DEST_SAUVE/backup.2 $REP_DEST_SAUVE/backup.3 && /bin/mv $REP_DEST_SAUVE/backup.1 $REP_DEST_SAUVE/backup.2 && /bin/mv $REP_DEST_SAUVE/backup.0 $REP_DEST_SAUVE/backup.1 && SUCCES=true if [ "$DEMONTE_APRES" == "TRUE" ]; then # Démonte le point de montage s'il n'était pas monté au début. cd ; sudo umount $POINT_MONTAGE && echo "$POINT_MONTAGE de nouveau démonté." fi if [ "$SUCCES" == "true" ]; then echo 'SUCCÈS !' exit 0 fi # Nous devrions avoir déjà quitté si la sauvegarde a fonctionné. echo 'ÉCHEC DE LA SAUVEGARDE ! Est-ce un test ? Le disque est-il plein ?) ' exit $E_SAUVE
Exemple A.34. Une commande cd étendue
############################################################################ # # cdll # par Phil Braham # # ########################################################### # La dernière version de ce script est disponible à partir de # http://freshmeat.net/projects/cd/ # ########################################################### # # .cd_new # # Une amélioration de la commande Unix cd # # Il y a une pile illimitée d'entrées et d'entrées spéciales. Les # entrées de la pile conservent les cd_maxhistory derniers répertoires # qui ont été utilisés. Les entrées spéciales peuvent être affectées aux # répertoires fréquemment utilisés. # # Les entrées spéciales pourraient être préaffectées en configurant les # variables d'environnement CDSn ou en utilisant la commande -u ou -U. # # Ce qui suit est une suggestion pour le fichier .profile : # # . cdll # Configure la commande cd # alias cd='cd_new' # Remplace la commande cd # cd -U # Charge les entrées pré-affectées pour # #+ la pile et les entrées spéciales # cd -D # Configure le mode pas par défaut # alias @="cd_new @" # Autorise l'utilisation de @ pour récupérer # #+ l'historique # # Pour une aide, saisissez : # # cd -h ou # cd -H # # ############################################################################ # # Version 1.2.1 # # Écrit par Phil Braham - Realtime Software Pty Ltd # (realtime@mpx.com.au) # Merci d'envoyer vos suggestions ou améliorations à l'auteur # (phil@braham.net) # ############################################################################ cd_hm () { ${PRINTF} "%s" "cd [dir] [0-9] [@[s|h] [-g [<dir>]] [-d] [-D] [-r<n>] [dir|0-9] [-R<n>] [<dir>|0-9] [-s<n>] [-S<n>] [-u] [-U] [-f] [-F] [-h] [-H] [-v] <dir> Se place sous le répertoire 0-n Se place sous le répertoire précedent (0 est le précédent, 1 est l'avant-dernier, etc) n va jusqu'au bout de l'historique (par défaut, 50) @ Liste les entrées de l'historique et les entrées spéciales @h Liste les entrées de l'historique @s Liste les entrées spéciales -g [<dir>] Se place sous le nom littéral (sans prendre en compte les noms spéciaux) Ceci permet l'accès aux répertoires nommés '0','1','-h' etc -d Modifie l'action par défaut - verbeux. (Voir note) -D Modifie l'action par défaut - silencieux. (Voir note) -s<n> Se place sous l'entrée spéciale <n>* -S<n> Se place sous l'entrée spéciale <n> et la remplace avec le répertoire en cours* -r<n> [<dir>] Se place sous le répertoire <dir> and then put it on special entry <n>* -R<n> [<dir>] Se place sous le répertoire <dir> et place le répertoire en cours dans une entrée spéciale <n>* -a<n> Autre répertoire suggéré. Voir la note ci-dessous. -f [<file>] Fichier des entrées <file>. -u [<file>] Met à jour les entrées à partir de <file>. Si aucun nom de fichier n'est fourni, utilise le fichier par défaut (${CDPath}${2:-"$CDFile"}) -F et -U sont les versions silencieuses -v Affiche le numéro de version -h Aide -H Aide détaillée *Les entrées spéciales (0 - 9) sont conservées jusqu'à la déconnexion, remplacées par une autre entrée ou mises à jour avec la commande -u Autres répertoires suggérés : Si un répertoire est introuvable, alors CD suggèrera des possibilités. Ce sont les répertoires commençant avec les mêmes lettres et si des résultats sont disponibles, ils sont affichés avec le préfixe -a<n> où <n> est un numéro. Il est possible de se placer dans le répertoire en saisissant cd -a<n> sur la ligne de commande. Le répertoire pour -r<n> ou -R<n> pourrait être un numéro. Par exemple : $ cd -r3 4 Se place dans le répertoire de l'entrée 4 de l'historique et la place sur l'entrée spéciale 3 $ cd -R3 4 Place le répertoire en cours sur l'entrée spéciale 3 et se déplace dans l'entrée 4 de l'historique $ cd -s3 Se déplace dans l'entrée spéciale 3 Notez que les commandes R,r,S et s pourraient être utilisées sans numéro et faire ainsi référence à 0: $ cd -s Se déplace dans l'entrée spéciale 0 $ cd -S Se déplace dans l'entrée spéciale 0 et fait de l'entrée spéciale 0 le répertoire courant $ cd -r 1 Se déplace dans l'entrée spéciale 1 et la place sur l'entrée spéciale 0 $ cd -r Se déplace dans l'entrée spéciale 0 et la place sur l'entrée spéciale 0 " if ${TEST} "$CD_MODE" = "PREV" then ${PRINTF} "$cd_mnset" else ${PRINTF} "$cd_mset" fi } cd_Hm () { cd_hm ${PRINTF} "%s" " Les répertoires précédents (0-$cd_maxhistory) sont stockés dans les variables d'environnement CD[0] - CD[$cd_maxhistory] De façon similaire, les répertoires spéciaux S0 - $cd_maxspecial sont dans la variable d'environnement CDS[0] - CDS[$cd_maxspecial] et pourraient être accédés à partir de la ligne de commande Le chemin par défaut pour les commandes -f et -u est $CDPath Le fichier par défaut pour les commandes est -f et -u est $CDFile Configurez les variables d'environnement suivantes : CDL_PROMPTLEN - Configuré à la longueur de l'invite que vous demandez. La chaîne de l'invite est configurée suivant les caractères de droite du répertoire en cours. Si non configuré, l'invite n'est pas modifiée. CDL_PROMPT_PRE - Configuré avec la chaîne pour préfixer l'invite. La valeur par défaut est: standard: \"\\[\\e[01;34m\\]\" (couleur bleu). root: \"\\[\\e[01;31m\\]\" (couleur rouge). CDL_PROMPT_POST - Configuré avec la chaîne pour suffixer l'invite. La valeur par défaut est: standard: \"\\[\\e[00m\\]$\" (réinitialise la couleur et affiche $). root: \"\\[\\e[00m\\]#\" (réinitialise la couleur et affiche #). CDPath - Configure le chemin par défaut des options -f & -u. Par défaut, le répertoire personnel de l'utilisateur CDFile - Configure le fichier par défaut pour les options -f & -u. Par défaut, cdfile " cd_version } cd_version () { printf "Version: ${VERSION_MAJOR}.${VERSION_MINOR} Date: ${VERSION_DATE}\n" } # # Tronque à droite. # # params: # p1 - chaîne # p2 - longueur à tronquer # # renvoit la chaîne dans tcd # cd_right_trunc () { local tlen=${2} local plen=${#1} local str="${1}" local diff local filler="<--" if ${TEST} ${plen} -le ${tlen} then tcd="${str}" else let diff=${plen}-${tlen} elen=3 if ${TEST} ${diff} -le 2 then let elen=${diff} fi tlen=-${tlen} let tlen=${tlen}+${elen} tcd=${filler:0:elen}${str:tlen} fi } # # Trois versions de l'historique do : # cd_dohistory - empile l'historique et les spéciaux côte à côte # cd_dohistoryH - Affiche seulement l'historique # cd_dohistoryS - Affiche seulement les spéciaux # cd_dohistory () { cd_getrc ${PRINTF} "Historique :\n" local -i count=${cd_histcount} while ${TEST} ${count} -ge 0 do cd_right_trunc "${CD[count]}" ${cd_lchar} ${PRINTF} "%2d %-${cd_lchar}.${cd_lchar}s " ${count} "${tcd}" cd_right_trunc "${CDS[count]}" ${cd_rchar} ${PRINTF} "S%d %-${cd_rchar}.${cd_rchar}s\n" ${count} "${tcd}" count=${count}-1 done } cd_dohistoryH () { cd_getrc ${PRINTF} "Historique :\n" local -i count=${cd_maxhistory} while ${TEST} ${count} -ge 0 do ${PRINTF} "${count} %-${cd_flchar}.${cd_flchar}s\n" ${CD[$count]} count=${count}-1 done } cd_dohistoryS () { cd_getrc ${PRINTF} "Spéciaux :\n" local -i count=${cd_maxspecial} while ${TEST} ${count} -ge 0 do ${PRINTF} "S${count} %-${cd_flchar}.${cd_flchar}s\n" ${CDS[$count]} count=${count}-1 done } cd_getrc () { cd_flchar=$(stty -a | awk -F \; '/rows/ { print $2 $3 }' | awk -F \ '{ print $4 }') if ${TEST} ${cd_flchar} -ne 0 then cd_lchar=${cd_flchar}/2-5 cd_rchar=${cd_flchar}/2-5 cd_flchar=${cd_flchar}-5 else cd_flchar=${FLCHAR:=75} # cd_flchar is used for for the @s & @h history cd_lchar=${LCHAR:=35} cd_rchar=${RCHAR:=35} fi } cd_doselection () { local -i nm=0 cd_doflag="TRUE" if ${TEST} "${CD_MODE}" = "PREV" then if ${TEST} -z "$cd_npwd" then cd_npwd=0 fi fi tm=$(echo "${cd_npwd}" | cut -b 1) if ${TEST} "${tm}" = "-" then pm=$(echo "${cd_npwd}" | cut -b 2) nm=$(echo "${cd_npwd}" | cut -d $pm -f2) case "${pm}" in a) cd_npwd=${cd_sugg[$nm]} ;; s) cd_npwd="${CDS[$nm]}" ;; S) cd_npwd="${CDS[$nm]}" ; CDS[$nm]=`pwd` ;; r) cd_npwd="$2" ; cd_specDir=$nm ; cd_doselection "$1" "$2";; R) cd_npwd="$2" ; CDS[$nm]=`pwd` ; cd_doselection "$1" "$2";; esac fi if ${TEST} "${cd_npwd}" != "." -a "${cd_npwd}" != ".." -a "${cd_npwd}" -le ${cd_maxhistory} >>/dev/null 2>&1 then cd_npwd=${CD[$cd_npwd]} else case "$cd_npwd" in @) cd_dohistory ; cd_doflag="FALSE" ;; @h) cd_dohistoryH ; cd_doflag="FALSE" ;; @s) cd_dohistoryS ; cd_doflag="FALSE" ;; -h) cd_hm ; cd_doflag="FALSE" ;; -H) cd_Hm ; cd_doflag="FALSE" ;; -f) cd_fsave "SHOW" $2 ; cd_doflag="FALSE" ;; -u) cd_upload "SHOW" $2 ; cd_doflag="FALSE" ;; -F) cd_fsave "NOSHOW" $2 ; cd_doflag="FALSE" ;; -U) cd_upload "NOSHOW" $2 ; cd_doflag="FALSE" ;; -g) cd_npwd="$2" ;; -d) cd_chdefm 1; cd_doflag="FALSE" ;; -D) cd_chdefm 0; cd_doflag="FALSE" ;; -r) cd_npwd="$2" ; cd_specDir=0 ; cd_doselection "$1" "$2";; -R) cd_npwd="$2" ; CDS[0]=`pwd` ; cd_doselection "$1" "$2";; -s) cd_npwd="${CDS[0]}" ;; -S) cd_npwd="${CDS[0]}" ; CDS[0]=`pwd` ;; -v) cd_version ; cd_doflag="FALSE";; esac fi } cd_chdefm () { if ${TEST} "${CD_MODE}" = "PREV" then CD_MODE="" if ${TEST} $1 -eq 1 then ${PRINTF} "${cd_mset}" fi else CD_MODE="PREV" if ${TEST} $1 -eq 1 then ${PRINTF} "${cd_mnset}" fi fi } cd_fsave () { local sfile=${CDPath}${2:-"$CDFile"} if ${TEST} "$1" = "SHOW" then ${PRINTF} "Saved to %s\n" $sfile fi ${RM} -f ${sfile} local -i count=0 while ${TEST} ${count} -le ${cd_maxhistory} do echo "CD[$count]=\"${CD[$count]}\"" >> ${sfile} count=${count}+1 done count=0 while ${TEST} ${count} -le ${cd_maxspecial} do echo "CDS[$count]=\"${CDS[$count]}\"" >> ${sfile} count=${count}+1 done } cd_upload () { local sfile=${CDPath}${2:-"$CDFile"} if ${TEST} "${1}" = "SHOW" then ${PRINTF} "Chargement de %s\n" ${sfile} fi . ${sfile} } cd_new () { local -i count local -i choose=0 cd_npwd="${1}" cd_specDir=-1 cd_doselection "${1}" "${2}" if ${TEST} ${cd_doflag} = "TRUE" then if ${TEST} "${CD[0]}" != "`pwd`" then count=$cd_maxhistory while ${TEST} $count -gt 0 do CD[$count]=${CD[$count-1]} count=${count}-1 done CD[0]=`pwd` fi command cd "${cd_npwd}" 2>/dev/null if ${TEST} $? -eq 1 then ${PRINTF} "Répertoire inconnu : %s\n" "${cd_npwd}" local -i ftflag=0 for i in "${cd_npwd}"* do if ${TEST} -d "${i}" then if ${TEST} ${ftflag} -eq 0 then ${PRINTF} "Suggest:\n" ftflag=1 fi ${PRINTF} "\t-a${choose} %s\n" "$i" cd_sugg[$choose]="${i}" choose=${choose}+1 fi done fi fi if ${TEST} ${cd_specDir} -ne -1 then CDS[${cd_specDir}]=`pwd` fi if ${TEST} ! -z "${CDL_PROMPTLEN}" then cd_right_trunc "${PWD}" ${CDL_PROMPTLEN} cd_rp=${CDL_PROMPT_PRE}${tcd}${CDL_PROMPT_POST} export PS1="$(echo -ne ${cd_rp})" fi } ################################################################################# # # # Initialisation ici # # # ################################################################################# # VERSION_MAJOR="1" VERSION_MINOR="2.1" VERSION_DATE="24 MAI 2003" # alias cd=cd_new # # Configuration des commandes RM=/bin/rm TEST=test PRINTF=printf # Utilise le printf interne ################################################################################# # # # Modifiez ceci pour modifier les chaînes préfixe et suffixe de l'invite. # # Elles ne prennent effet que si CDL_PROMPTLEN est configuré. # # # ################################################################################# if ${TEST} ${EUID} -eq 0 then # CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="$HOSTNAME@"} CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="\\[\\e[01;31m\\]"} # Root est en rouge CDL_PROMPT_POST=${CDL_PROMPT_POST:="\\[\\e[00m\\]#"} else CDL_PROMPT_PRE=${CDL_PROMPT_PRE:="\\[\\e[01;34m\\]"} # Les utilisateurs sont en bleu CDL_PROMPT_POST=${CDL_PROMPT_POST:="\\[\\e[00m\\]$"} fi ################################################################################# # # cd_maxhistory définit le nombre max d'entrées dans l'historique. typeset -i cd_maxhistory=50 ################################################################################# # # cd_maxspecial définit le nombre d'entrées spéciales. typeset -i cd_maxspecial=9 # # ################################################################################# # # cd_histcount définit le nombre d'entrées affichées dans la commande historique. typeset -i cd_histcount=9 # ################################################################################# export CDPath=${HOME}/ # Modifiez-les pour utiliser un chemin et un nom de fichier # #+ différent de la valeur par défaut # export CDFile=${CDFILE:=cdfile} # pour les commandes -u et -f # # ################################################################################# # typeset -i cd_lchar cd_rchar cd_flchar # Ceci est le nombre de caractères pour que # cd_flchar=${FLCHAR:=75} #+ cd_flchar puisse être autorisé pour l'historique de @s & @h # typeset -ax CD CDS # cd_mset="\n\tLe mode par défaut est maintenant configuré - saisir cd sans paramètre correspond à l'action par défaut\n\tUtilisez cd -d ou -D pour que cd aille au répertoire précédent sans paramètres\n" cd_mnset="\n\tL'autre mode est maintenant configuré - saisir cd sans paramètres est identique à saisir cd 0\n\tUtilisez cd -d ou -D pour modifier l'action par défaut de cd\n" # ==================================================================== # : <<DOCUMENTATION Écrit par Phil Braham. Realtime Software Pty Ltd. Sortie sous licence GNU. Libre à utiliser. Merci de passer toutes modifications ou commentaires à l'auteur Phil Braham: realtime@mpx.com.au =============================================================================== cdll est un remplacement pour cd et incorpore des fonctionnalités similaires aux commandes pushd et popd de bash mais est indépendent. Cette version de cdll a été testée sur Linux en utilisant Bash. Il fonctionnera sur la plupart des versions Linux mais ne fonctionnera probablement pas sur les autres shells sans modification. Introduction ============ cdll permet un déplacement facile entre les répertoires. En allant dans un autre répertoire, celui en cours est placé automatiquement sur une pile. Par défaut, 50 entrées sont conservées mais c'est configurable. Les répertoires spéciaux peuvent être gardés pour un accès facile - par défaut jusqu'à 10, mais ceci est configurable. Les entrées les plus récentes de la pile et les entrées spéciales peuvent être facilement visualisées. La pile de répertoires et les entrées spéciales peuvent être sauvegardées dans un fichier ou chargées à partir d'un fichier. Ceci leur permet d'être initialisé à la connexion, sauvegardé avant la fin de la session ou déplacé en passant de projet à projet. En plus, cdll fournit une invite flexible permettant, par exemple, un nom de répertoire en couleur, tronqué à partir de la gauche s'ilest trop long. Configurer cdll =============== Copiez cdll soit dans votre répertoire personnel soit dans un répertoire central comme /usr/bin (ceci requiert un accès root). Copiez le fichier cdfile dans votre répertoie personnel. Il requèrera un accès en lecture et écriture. Ceci est un fichier par défaut contenant une pile de répertoires et des entrées spéciales. Pour remplacer la commande cd, vous devez ajouter les commandes à votre script de connexion. Le script de connexion fait partie de : /etc/profile ~/.bash_profile ~/.bash_login ~/.profile ~/.bashrc /etc/bash.bashrc.local Pour configurer votre connexion, ~/.bashrc est recommandé, pour la configuration globale (et de root), ajoutez les commandes à /etc/bash.bashrc.local Pour configurer la connexion, ajoutez la commande : . <dir>/cdll Par exemple, si cdll est dans votre répertoire personnel : . ~/cdll Si dans /usr/bin, alors : . /usr/bin/cdll Si vous voulez utiliser ceci à la place de la commande cd interne, alors ajoutez : alias cd='cd_new' Nous devrions aussi recommander les commandes suivantes : alias @='cd_new @' cd -U cd -D Si vous utilisez la capacité de l'invite de cdll, alors ajoutez ce qui suit : CDL_PROMPTLEN=nn Quand nn est un nombre décrit ci-dessous. Initialement, 99 serait un nombre convenable. Du coup, le script ressemble à ceci : ###################################################################### # CD Setup ###################################################################### CDL_PROMPTLEN=21 # Autorise une longueur d'invite d'un maximum # de 21 caractères . /usr/bin/cdll # Initialise cdll alias cd='cd_new' # Remplace la commande cd interne alias @='cd_new @' # Autorise @ sur l'invite pour affiche l'historique cd -U # Recharge le répertoire cd -D # Configure l'action par défaut en non posix ###################################################################### La signification complète de ces commandes deviendra claire plus tard. Voici quelques astuces. Si un autre programme modifie le répertoire sans appeler cdll, alors le répertoire ne sera pas placé sur la pile et aussi si la fonctionnalité de l'invite est utilisée, alors ceci ne sera pas mise à jour. Deux programmes qui peuvent faire ceci sont pushd et popd. Pour mettre à jour l'invite et la pile, saisissez simplement : cd . Notez que si l'entrée précédente sur la pile est le répertoire en cours, alors la pile n'est pas mise à jour. Usage ===== cd [dir] [0-9] [@[s|h] [-g <dir>] [-d] [-D] [-r<n>] [dir|0-9] [-R<n>] [<dir>|0-9] [-s<n>] [-S<n>] [-u] [-U] [-f] [-F] [-h] [-H] [-v] <dir> Se place sous le répertoire 0-n Se place sous le répertoire précedent (0 est le précédent, 1 est l'avant-dernier, etc) n va jusqu'au bout de l'historique (par défaut, 50) @ Liste les entrées de l'historique et les entrées spéciales @h Liste les entrées de l'historique @s Liste les entrées spéciales -g [<dir>] Se place sous le nom littéral (sans prendre en compte les noms spéciaux) Ceci permet l'accès aux répertoires nommés '0','1','-h' etc -d Modifie l'action par défaut - verbeux. (Voir note) -D Modifie l'action par défaut - silencieux. (Voir note) -s<n> Se place sous l'entrée spéciale <n>* -S<n> Se place sous l'entrée spéciale <n> et la remplace avec le répertoire en cours* -r<n> [<dir>] Se place sous le répertoire <dir> and then put it on special entry <n>* -R<n> [<dir>] Se place sous le répertoire <dir> et place le répertoire en cours dans une entrée spéciale <n>* -a<n> Autre répertoire suggéré. Voir la note ci-dessous. -f [<file>] Fichier des entrées <file>. -u [<file>] Met à jour les entrées à partir de <file>. Si aucun nom de fichier n'est fourni, utilise le fichier par défaut (${CDPath}${2:-"$CDFile"}) -F et -U sont les versions silencieuses -v Affiche le numéro de version -h Aide -H Aide détaillée Exemples ======== Ces exemples supposent que le mode autre que celui par défaut est configuré (qui est, cd sans paramètres ira sur le répertoire le plus récent de la pile), que les alias ont été configurés pour cd et @ comme décrits ci-dessus et que la fonctionnalité de l'invite de cd est active et la longueur de l'invite est de 21 caractères. /home/phil$ @ # Liste les entrées avec le @ History: # Affiche la commande @ ..... # Laissé ces entrées pour être bref 1 /home/phil/ummdev S1 /home/phil/perl # Les deux entrées les plus récentes de l'historique 0 /home/phil/perl/eg S0 /home/phil/umm/ummdev # et deux entrées spéciales sont affichées /home/phil$ cd /home/phil/utils/Cdll # Maintenant, modifie les répertoires /home/phil/utils/Cdll$ @ # L'invite reflète le répertoire. History: # Nouvel historique ..... 1 /home/phil/perl/eg S1 /home/phil/perl # L'entrée 0 de l'historique a été déplacé dans 1 0 /home/phil S0 /home/phil/umm/ummdev # et la plus récente a été entrée Pour aller dans une entrée de l'historique : /home/phil/utils/Cdll$ cd 1 # Va dans l'entrée 1 de l'historique. /home/phil/perl/eg$ # Le répertoire en cours est maintenant celui du 1 Pour aller dans une entrée spéciale : /home/phil/perl/eg$ cd -s1 # Va dans l'entrée spéciale 1 /home/phil/umm/ummdev$ # Le répertoire en cours est S1 Pour aller dans un répertoire nommé, par exemple, 1 : /home/phil$ cd -g 1 # -g ignore la signification spéciale de 1 /home/phil/1$ Pour placer le répertoire en cours sur la liste spéciale en tant que S1 : cd -r1 . # OU cd -R1 . # Elles ont le même effet si le répertoire est #+ . (le répertoire en cours) Pour aller dans un répertoire et l'ajouter comme entrée spéciale Le répertoire pour -r<n> ou -R<n> pourrait être un nombre. Par exemple : $ cd -r3 4 Va dans l'entrée 4 de l'historique et placez-la dans l'entrée spéciale 3 $ cd -R3 4 Placez le répertoire en cours sur l'entrée spéciale 3 et allez dans l'entrée spéciale 4 $ cd -s3 Allez dans l'entrée spéciale 3 Notez que les commands R,r,S et s pourraient être utilisées sans un numéro et faire référence à 0 : $ cd -s Va dans l'entrée spéciale 0 $ cd -S Va dans l'entrée spéciale 0 et fait de l'entrée spéciale 0 le répertoire en cours $ cd -r 1 Va dans l'entrée 1 de l'historique et la place sur l'entrée spéciale 0 $ cd -r Va dans l'entrée 0 de l'historique et la place sur l'entrée spéciale 0 Autres répertoires suggérés : Si un répertoire est introuvable, alors CD suggèrera toute possibilité. Il s'agit des répertoires commençant avec les mêmes lettres et si des correspondances sont trouvées, ils sont affichés préfixés avec -a<n> où <n> est un numéro. Il est possible d'aller dans un répertoire de saisir cd -a<n> sur la ligne de commande. Utilisez cd -d ou -D pour modifier l'action par défaut de cd. cd -H affichera l'action en cours. Les entrées de l'historique (0-n) sont stockées dans les variables d'environnement CD[0] - CD[n] De façon similaire, les répertoires spéciaux S0 - 9 sont dans la variable d'environnement CDS[0] - CDS[9] et pourraient être accédés à partir de la ligne de commande, par exemple : ls -l ${CDS[3]} cat ${CD[8]}/file.txt Le chemin par défaut pour les commandes -f et -u est ~ Le nom du fichier par défaut pour les commandes -f et -u est cdfile Configuration ============= Les variables d'environnement suivantes peuvent être configurées : CDL_PROMPTLEN - Configuré à la longueur de l'invite que vous demandez. La chaîne de l'invite est configurée suivant les caractères de droite du répertoire en cours. Si non configuré, l'invite n'est pas modifiée. Notez que ceci est le nombre de caractères raccourcissant le répertoire, pas le nombre de caractères total dans l'invite. CDL_PROMPT_PRE - Configure une chaîne pour préfixer l'invite. Default is: non-root: "\\[\\e[01;34m\\]" (initialise la couleur à bleu). root: "\\[\\e[01;31m\\]" (initialise la couleur à rouge). CDL_PROMPT_POST - Configure une chaîne pour suffixer l'invite. Default is: non-root: "\\[\\e[00m\\]$" (réinitialise la couleur et affiche $). root: "\\[\\e[00m\\]#" (réinitialise la couleur et affiche #). Note: CDL_PROMPT_PRE & _POST only t CDPath - Configure le chemin par défaut pour les options -f & -u. La valeur par défaut est le répertoire personnel CDFile - Configure le nom du fichier pour les options -f & -u. La valeur par défaut est cdfile Il existe trois variables définies dans le fichier cdll qui contrôle le nombre d'entrées stockées ou affichées. Elles sont dans la sectioon labellées 'Initialisation ici' jusqu'à la fin du fichier. cd_maxhistory - Le nombre d'entrées stockées dans l'historique. Par défaut, 50. cd_maxspecial - Le nombre d'entrées spéciale autorisées. Par défaut, 9. cd_histcount - Le nombre d'entrées de l'historique et d'entrées spéciales affichées. Par défaut, 9. Notez que cd_maxspecial devrait être >= cd_histcount pour afficher des entrées spéciales qui ne peuvent pas être initialisées. Version: 1.2.1 Date: 24-MAY-2003 DOCUMENTATION
Exemple A.35. Un script de configuration d'une carte son
#!/bin/bash # soundcard-on.sh # Auteur du script : Mkarcher # http://www.thinkwiki.org/wiki ... # /Script pour la configuration du composant CS4239 en mode PnP. # L'auteur du guide ABS a réalisé quelques modifications mineures #+ et ajouté des commentaires. # N'a pas pu contacter l'auteur du script pour demander une autorisation de #+ publication mais... le script était disponible sous licence FDL, #+ donc son utilisation ici ne devrait pas poser de problèmes légaux ou éthiques. # Sound-via-pnp-script pour Thinkpad 600E #+ et aussi pour d'autres ordinateurs disposant du CS4239/CS4610 #+ qui ne fonctionne pas avec le piltoe PCI #+ et ne sont pas reconnus par le code PnP de snd-cs4236. # Aussi pour certains Thinkpads 770, comme le 770x. # À exécuter en tant que root bien sûr. # # Ce sont des portables vieux et largement obsolètes, #+ mais ce script particulier est très instructif #+ car il montre comment configurer et modifier des fichiers périphériques. # Recherche du périphérique correspondant à la carte son PnP : for dev in /sys/bus/pnp/devices/* do grep CSC0100 $dev/id > /dev/null && WSSDEV=$dev grep CSC0110 $dev/id > /dev/null && CTLDEV=$dev done # Sur 770x : # WSSDEV = /sys/bus/pnp/devices/00:07 # CTLDEV = /sys/bus/pnp/devices/00:06 # Ce sont des liens symboliques vers /sys/devices/pnp0/ ... # Activation des périphériques : # Thinkpad démarre avec les périphériques désactivés #+ sauf si le démarrage rapide est désactivé (dans le BIOS). echo activate > $WSSDEV/resources echo activate > $CTLDEV/resources # Analyse du paramétrage de la ressource. { read # Ignore "state = active" (voir ci-dessous). read bla port1 read bla port2 read bla port3 read bla irq read bla dma1 read bla dma2 # Les "bla" sont des labels dans le premier champ : "io", "state", etc. # Ils sont ignorés. # Hack : avec PnPBIOS : les ports sont : port1: WSS, port2: #+ OPL, port3: sb (unneeded) # avec ACPI-PnP : les ports sont: port1: OPL, port2: sb, port3: WSS # (le BIOS ACPI semble être erroné ici, le code de la carte PnP dans snd-cs4236.c #+ utilise l'ordre des ports de PnPBIOS) # Détecter l'ordre des ports en utilisant le port OPL fixe comme référence. if [ ${port2%%-*} = 0x388 ] # ^^^^ Supprime tout ce qui suit le trait d'union dans l'adresse du port. # Donc, si port1 vaut 0x530-0x537 #+ il nous reste 0x530 -- l'adresse de début du port. then # PnPBIOS : ordre habituel port=${port1%%-*} oplport=${port2%%-*} else # ACPI : ordre mixe port=${port3%%-*} oplport=${port1%%-*} fi } < $WSSDEV/resources # Pour voir ce qu'il se passe ici : # --------------------------------- # cat /sys/devices/pnp0/00:07/resources # # state = active # io 0x530-0x537 # io 0x388-0x38b # io 0x220-0x233 # irq 5 # dma 1 # dma 0 # ^^^ labels "bla" dans le premier champ (ignoré). { read # Ignore la première ligne, comme ci-dessus. read bla port1 cport=${port1%%-*} # ^^^^ # On veut seulement l'adresse de _début_ du port. } < $CTLDEV/resources # Chargement du module : modprobe --ignore-install snd-cs4236 port=$port cport=$cport\ fm_port=$oplport irq=$irq dma1=$dma1 dma2=$dma2 isapnp=0 index=0 # Voir la page man de modprobe. exit $?
Exemple A.36. Localise les paragraphes de division dans un fichier texte
#!/bin/bash # find-splitpara.sh # Recherche les paragraphes de séparation dans un fichier texte #+ et numérote les lignes. NB_ARG=1 # Un seul argument attendu. E_MAUVAISARG=65 fichier="$1" # Cible le nom du fichier. no_ligne=1 # Numéro de ligne. Commence à 1. Drapeau=0 # Blank ligne Drapeau. if [ $# -ne "$NB_ARG" ] then echo "Usage: `basename $0` NOMFICHIER" exit $E_MAUVAISARG fi # Parcourt le fichier à la recherche d'un modèle puis affiche la ligne. lecture_fichier () { while read ligne do if [[ "$ligne" =~ ^[a-z] && $Drapeau -eq 1 ]] then # ligne commence avec un caractère alphabétique, précédée d'une ligne blanche. echo -n "$no_ligne:: " echo "$line" fi if [[ "$ligne" =~ "^$" ]] then # Si ligne vide, Drapeau=1 #+ Drapeau activé. else Drapeau=0 fi ((no_ligne++)) done } < $fichier # Redirige fichier dans le stdin de la fonction. lecture_fichier exit $? # ---------------------------------------------------------------- Ceci est la ligne une du paragraphe exemple, bla, bla, bla. Ceci est la ligne deux et la ligne trois doit suivre mais... une ligne blanche sépare les deux parties du paragraphe. # ---------------------------------------------------------------- Exécuter ce script sur un fichier contenant le paragraphe ci-dessus ramène : 4:: une ligne blanche sépare les deux parties du paragraphe. Il y aura d'autres affichages pour tous les paragraphes séparées contenus dans le fichier cible.
Exemple A.37. Tri d'insertion
#!/bin/bash # insertion-sort.bash: Implémentation du tri d'insertion dans Bash # Grosse utilisation des fonctionnalités tableau de Bash : #+ découpage (chaîne), assemblage, etc # URL: http://www.lugmen.org.ar/~jjo/jjotip/insertion-sort.bash.d #+ /insertion-sort.bash.sh # # Auteur : JuanJo Ciarlante <jjo@irrigacion.gov.ar> # Légèrement reformaté par l'auteur du guide ABS. # Licence : GPLv2 # Utilisé dans le guide ABS avec la permission de l'auteur (merci !). # # Testez avec ./insertion-sort.bash -t # Ou : bash insertion-sort.bash -t # Ce qui suit *ne fonctionne pas* : # sh insertion-sort.bash -t # Pourquoi pas ? Astuce : quelles fonctionnalités spécifiques de Bash sont #+ désactivées quand un script est exécuté par 'sh script.sh'? # : ${DEBUG:=0} # Debug, surchargé avec : DEBUG=1 ./nomscript . . . # Substitution de paramètres -- configurer DEBUG à 0 si non initialisé auparavant. # Tableau global : "liste" typeset -a liste # Chargement de nombres séparés par des espaces blancs à partir de stdin. if [ "$1" = "-t" ]; then DEBUG=1 read -a liste < <( od -Ad -w24 -t u2 /dev/urandom ) # Liste aléatoire. # ^ ^ substitution de processus else read -a liste fi numelem=${#liste[*]} # Affiche la liste, marquant l'élément dont l'index est $1 #+ en la surchargeant avec les deux caractères passés à $2. # La ligne est préfixée par $3. afficherliste() { echo "$3"${liste[@]:0:$1} ${2:0:1}${liste[$1]}${2:1:1} ${liste[@]:$1+1}; } # Boucle _pivot_ -- à partir du second élément jusqu'à la fin de la liste. for(( i=1; i<numelem; i++ )) do ((DEBUG))&&showlist i "[]" " " # À partir du _pivot_ actuel, retour au premier élément. for(( j=i; j; j-- )) do # Recherche du premier élément inférieur au "pivot" actuel... [[ "${list[j-1]}" -le "${list[i]}" ]] && break done (( i==j )) && continue ## Aucune insertion n'était nécessaire pour cet élément. # . . . Déplacer liste[i] (pivot) à la gauche de liste[j] : liste=(${liste[@]:0:j} ${liste[i]} ${liste[j]}\ # {0,j-1} {i} {j} ${liste[@]:j+1:i-(j+1)} ${liste[@]:i+1}) # {j+1,i-1} {i+1,last} ((DEBUG))&&afficherliste j "<>" "*" done echo echo "------" echo $'Résultat :\n'${liste[@]} exit $?
Exemple A.38. Un générateur de fichiers pad pour les auteurs de shareware
#!/bin/bash # pad.sh ###################################################### # PAD (xml) file creator #+ Written by Mendel Cooper <thegrendel@theriver.com>. #+ Released to the Public Domain. # # Generates a "PAD" descriptor file for shareware #+ packages, according to the specifications #+ of the ASP. # http://www.asp-shareware.org/pad ###################################################### # Accepts (optional) save filename as a command-line argument. if [ -n "$1" ] then savefile=$1 else savefile=save_file.xml # Default save_file name. fi # ===== PAD file headers ===== HDR1="<?xml version=\"1.0\" encoding=\"Windows-1252\" ?>" HDR2="<XML_DIZ_INFO>" HDR3="<MASTER_PAD_VERSION_INFO>" HDR4="\t<MASTER_PAD_VERSION>1.15</MASTER_PAD_VERSION>" HDR5="\t<MASTER_PAD_INFO>Portable Application Description, or PAD for short, is a data set that is used by shareware authors to disseminate information to anyone interested in their software products. To find out more go to http://www.asp-shareware.org/pad</MASTER_PAD_INFO>" HDR6="</MASTER_PAD_VERSION_INFO>" # ============================ fill_in () { if [ -z "$2" ] then echo -n "$1? " # Get user input. else echo -n "$1 $2? " # Additional query? fi read var # May paste to fill in field. # This shows how flexible "read" can be. if [ -z "$var" ] then echo -e "\t\t<$1 />" >>$savefile # Indent with 2 tabs. return else echo -e "\t\t<$1>$var</$1>" >>$savefile return ${#var} # Return length of input string. fi } check_field_length () # Check length of program description fields. { # $1 = maximum field length # $2 = actual field length if [ "$2" -gt "$1" ] then echo "Warning: Maximum field length of $1 characters exceeded!" fi } clear # Clear screen. echo "PAD File Creator" echo "--- ---- -------" echo # Write File Headers to file. echo $HDR1 >$savefile echo $HDR2 >>$savefile echo $HDR3 >>$savefile echo -e $HDR4 >>$savefile echo -e $HDR5 >>$savefile echo $HDR6 >>$savefile # Company_Info echo "COMPANY INFO" CO_HDR="Company_Info" echo "<$CO_HDR>" >>$savefile fill_in Company_Name fill_in Address_1 fill_in Address_2 fill_in City_Town fill_in State_Province fill_in Zip_Postal_Code fill_in Country # If applicable: # fill_in ASP_Member "[Y/N]" # fill_in ASP_Member_Number # fill_in ESC_Member "[Y/N]" fill_in Company_WebSite_URL clear # Clear screen between sections. # Contact_Info echo "CONTACT INFO" CONTACT_HDR="Contact_Info" echo "<$CONTACT_HDR>" >>$savefile fill_in Author_First_Name fill_in Author_Last_Name fill_in Author_Email fill_in Contact_First_Name fill_in Contact_Last_Name fill_in Contact_Email echo -e "\t</$CONTACT_HDR>" >>$savefile # END Contact_Info clear # Support_Info echo "SUPPORT INFO" SUPPORT_HDR="Support_Info" echo "<$SUPPORT_HDR>" >>$savefile fill_in Sales_Email fill_in Support_Email fill_in General_Email fill_in Sales_Phone fill_in Support_Phone fill_in General_Phone fill_in Fax_Phone echo -e "\t</$SUPPORT_HDR>" >>$savefile # END Support_Info echo "</$CO_HDR>" >>$savefile # END Company_Info clear # Program_Info echo "PROGRAM INFO" PROGRAM_HDR="Program_Info" echo "<$PROGRAM_HDR>" >>$savefile fill_in Program_Name fill_in Program_Version fill_in Program_Release_Month fill_in Program_Release_Day fill_in Program_Release_Year fill_in Program_Cost_Dollars fill_in Program_Cost_Other fill_in Program_Type "[Shareware/Freeware/GPL]" fill_in Program_Release_Status "[Beta, Major Upgrade, etc.]" fill_in Program_Install_Support fill_in Program_OS_Support "[Win9x/Win2k/Linux/etc.]" fill_in Program_Language "[English/Spanish/etc.]" echo; echo # File_Info echo "FILE INFO" FILEINFO_HDR="File_Info" echo "<$FILEINFO_HDR>" >>$savefile fill_in Filename_Versioned fill_in Filename_Previous fill_in Filename_Generic fill_in Filename_Long fill_in File_Size_Bytes fill_in File_Size_K fill_in File_Size_MB echo -e "\t</$FILEINFO_HDR>" >>$savefile # END File_Info clear # Expire_Info echo "EXPIRE INFO" EXPIRE_HDR="Expire_Info" echo "<$EXPIRE_HDR>" >>$savefile fill_in Has_Expire_Info "Y/N" fill_in Expire_Count fill_in Expire_Based_On fill_in Expire_Other_Info fill_in Expire_Month fill_in Expire_Day fill_in Expire_Year echo -e "\t</$EXPIRE_HDR>" >>$savefile # END Expire_Info clear # More Program_Info echo "ADDITIONAL PROGRAM INFO" fill_in Program_Change_Info fill_in Program_Specific_Category fill_in Program_Categories fill_in Includes_JAVA_VM "[Y/N]" fill_in Includes_VB_Runtime "[Y/N]" fill_in Includes_DirectX "[Y/N]" # END More Program_Info echo "</$PROGRAM_HDR>" >>$savefile # END Program_Info clear # Program Description echo "PROGRAM DESCRIPTIONS" PROGDESC_HDR="Program_Descriptions" echo "<$PROGDESC_HDR>" >>$savefile LANG="English" echo "<$LANG>" >>$savefile fill_in Keywords "[comma + space separated]" echo echo "45, 80, 250, 450, 2000 word program descriptions" echo "(may cut and paste into field)" # It would be highly appropriate to compose the following #+ "Char_Desc" fields with a text editor, #+ then cut-and-paste the text into the answer fields. echo echo " |---------------45 characters---------------|" fill_in Char_Desc_45 check_field_length 45 "$?" echo fill_in Char_Desc_80 check_field_length 80 "$?" fill_in Char_Desc_250 check_field_length 250 "$?" fill_in Char_Desc_450 fill_in Char_Desc_2000 echo "</$LANG>" >>$savefile echo "</$PROGDESC_HDR>" >>$savefile # END Program Description clear echo "Done."; echo; echo echo "Save file is: \""$savefile"\"" exit 0
Pour finir cette section, une revue des bases... et plus encore.
Exemple A.39. Basics Reviewed
#!/bin/bash # basics-reviewed.bash # Extension du fichier == *.bash == spécifique à Bash # Copyright (c) Michael S. Zick, 2003; All rights reserved. # License: Use in any form, for any purpose. # Revision: $ID$ # # Édité pour la présentation par M.C. # (auteur du "Guide d'écriture avancée des scripts Bash") # Ce script a été testé sous Bash version 2.04, 2.05a et #+ 2.05b. # Il pourrait ne pas fonctionner avec les versions précédentes. # Ce script de démonstration génère une erreur "command not found" #+ --intentionnelle--. Voir ligne 394. # Le mainteneur actuel de Bash maintainer, Chet Ramey, a corrigé les éléments #+ notés pour une future version de Bash. ###-------------------------------------------### ### Envoyez la sortie de ce script à 'more' ### ###+ sinon cela dépassera la page. ### ### ### ### Vous pouvez aussi rediriger sa sortie ### ###+ vers un fichier pour l'examiner. ### ###-------------------------------------------### # La plupart des points suivants sont décrit en détail dans #+ le guide d'écriture avancé du script Bash. # Ce script de démonstration est principalement une présentation réorganisée. # -- msz # Les variables ne sont pas typées sauf cas indiqués. # Les variables sont nommées. Les noms doivent contenir un caractère qui #+ n'est pas un chiffre. # Les noms des descripteurs de fichiers (comme dans, par exemple, 2>&1) #+ contiennent UNIQUEMENT des chiffres. # Les paramètres et les éléments de tavbleau Bash sont numérotés. # (Les paramètres sont très similaires aux tableaux Bash.) # Un nom de variable pourrait être indéfini (référence nulle). unset VarNullee # Un nom de variable pourrait être défini mais vide (contenu nul). VarVide='' # Deux guillemets simples, adjacents. # Un nom de variable pourrait être défini et non vide. VarQuelquechose='Littéral' # Une variable pourrait contenir: # * Un nombre complet, entier signé sur 32-bit (voire plus) # * Une chaîne # Une variable pourrait aussi être un tableau. # Une chaîne pourrait contenir des espaces et pourrait être traitée #+ comme s'il s'agissait d'un nom de fonction avec des arguments optionnelles. # Les noms des variables et les noms des functions sont dans différents #+ espaces de noms. # Une variable pourrait être défini comme un tableau Bash soit explicitement #+ soit implicitement par la syntaxe de l'instruction d'affectation. # Explicite: declare -a VarTableau # La commande echo est intégrée. echo $VarQuelquechose # La commande printf est intégrée. # Traduire %s comme "Format chaîne" printf %s $VarQuelquechose # Pas de retours chariot spécifiés, #+ aucune sortie. echo # Par défaut, seulement un retour chariot. # L'analyseur de mots de Bash s'arrête sur chaque espace blanc mais son #+ manquement est significatif. # (Ceci reste vrai en général ; Il existe évidemment des exceptions.) # Traduire le signe SIGNE_DOLLAR comme Contenu-de. # Syntaxe étendue pour écrire Contenu-de : echo ${VarQuelquechose} # La syntaxe étendue ${ ... } permet de spécifier plus que le nom de la #+ variable. # En général, $VarQuelquechose peut toujours être écrit ${VarQuelquechose}. # Appelez ce script avec des arguments pour visualiser l'action de ce qui suit. # En dehors des doubles guillemets, les caractères spéciaux @ et * #+ spécifient un comportement identique. # Pourrait être prononcé comme Tous-Éléments-De. # Sans spécifier un nom, ils réfèrent un paramètre prédéfini Bash-Array. # Références de modèles globaux echo $* # Tous les paramètres du script ou de la fonction echo ${*} # Pareil # Bash désactive l'expansion de nom de fichier pour les modèles globaux. # Seuls les caractères correspondants sont actifs. # Références de Tous-Éléments-De echo $@ # Identique à ci-dessus echo ${@} # Identique à ci-dessus # À l'intérieur des guillemets doubles, le comportement des références de #+ modèles globaux dépend du paramètrage de l'IFS (Input Field Separator, soit #+ séparateur de champ d'entrée). # À l'intérieur des guillemets doubles, les références à Tous-Éléments-De #+ se comportent de façon identique. # Spécifier uniquement le nom de la variable contenant une chaîne réfère tous #+ les éléments (caractères) d'une chaîne. # Spécifier un élément (caractère) d'une chaîne, #+ la notation de référence de syntaxe étendue (voir ci-dessous) POURRAIT être #+ utilisée. # Spécifier uniquement le nom d'un tableau Bash référence l'élément 0, #+ PAS le PREMIER DÉFINI, PAS le PREMIER AVEC CONTENU. # Une qualification supplémentaire est nécessaire pour référencer d'autres #+ éléments, ce qui signifie que la référence DOIT être écrite dans la syntaxe #+ étendue. La forme générale est ${nom[indice]}. # Le format de chaîne pourrait aussi être utilisé ${nom:indice} #+ pour les tableaux Bash lors de la référence de l'élément zéro. # Les tableaux Bash sont implémentés en interne comme des listes liés, #+ pas comme une aire fixe de stockage comme le font certains langages de #+ programmation. # Caractéristiques des tableaux Bash (Bash-Arrays): # ------------------------------------------------ # Sans autre indication, les indices des tableaux Bash #+ commencent avec l'indice numéro 0. Littéralement : [0] # Ceci s'appelle un indice base 0. ### # Sans autre indication, les tableaux Bash ont des indices continus #+ (indices séquentielles, sans trou/manque). ### # Les indices négatifs ne sont pas autorisés. ### # Les éléments d'un tableau Bash n'ont pas besoin de tous être du même type. ### # Les éléments d'un tableau Bash pourraient être indéfinis (référence nulle). # C'est-à-dire qu'un tableau Bash pourrait être "subscript sparse." ### # Les éléments d'un tableau Bash pourraient être définis et vides #+ (contenu nul). ### # Les éléments d'un tableau Bash pourraient être : # * Un entier codé sur 32 bits (ou plus) # * Une chaîne # * Une chaîne formattée de façon à ce qu'elle soit en fait le nom d'une # fonction avec des arguments optionnelles ### # Les éléments définis d'un tableau Bash pourraient ne pas être définis # (unset). # C'est-à-dire qu'un tableau Bash à indice continu pourrait être modifié # en un tableau Bash à indice disparate. ### # Des éléments pourraient être ajoutés dans un tableau Bash en définissant un # élément non défini précédemment. ### # Pour ces raisons, je les ai appelé des tableaux Bash ("Bash-Arrays"). # Je retourne maintenant au terme générique "tableau". # -- msz # Maintenant, la démo -- initialise VarTableau précédemment déclaré comme #+ tableau à indice disparate. # (Le 'unset ... ' est ici simplement pour documentation.) unset VarTableau[0] # Juste pour la cellule VarTableau[1]=un # Littéral sans guillemets VarTableau[2]='' # Défini et vide unset VarTableau[3] # Juste pour la cellule VarTableau[4]='quatre' # Littérale entre guillemets # Traduit le format %q en : Quoted-Respecting-IFS-Rules. echo echo '- - En dehors des guillemets doubles - -' ### printf %q ${VarTableau[*]} # Tous-Éléments-De du modèle global echo echo 'echo commande:'${VarTableau[*]} ### printf %q ${VarTableau[@]} # Tous-Éléments-De echo echo 'echo commande:'${VarTableau[@]} # L'utilisation des guillemets doubles pourrait être traduit par: #+ Enable-Substitution. # Il existe cinq cas reconnus par le paramètrage de l'IFS. echo echo "- - À l'intérieur des guillemets doubles - IFS par défaut à espace-tabulation-nouvelle ligne - -" IFS=$'\x20'$'\x09'$'\x0A' # Ces trois octets, #+ dans cet ordre exact. printf %q "${VarTableau[*]}" # Tous-Éléments-De modèle global echo echo 'echo commande:'"${VarTableau[*]}" ### printf %q "${VarTableau[@]}" # Tous-Éléments-De echo echo 'echo commande:'"${VarTableau[@]}" echo echo "- - À l'intérieur des guillemets doubles - le premier caractère de l'IFS est ^ - -" # Tout caractère affichable, qui n'est pas une espace blanche, devrait réagir de #+ la même façon. IFS='^'$IFS # ^ + espace tabulation nouvelle ligne ### printf %q "${VarTableau[*]}" # Tous-Éléments-De modèle global echo echo 'echo commande:'"${VarTableau[*]}" ### printf %q "${VarTableau[@]}" # Tous-Éléments-De echo echo 'echo commande:'"${VarTableau[@]}" echo echo "- - À l'intérieur de guillemets doubles - Sans les espaces blancs dans IFS - -" IFS='^:%!' ### printf %q "${VarTableau[*]}" # Tous-Éléments-De modèle global echo echo 'echo commande:'"${VarTableau[*]}" ### printf %q "${VarTableau[@]}" # Tous-Éléments-De echo echo 'echo commande:'"${VarTableau[@]}" echo echo "- - À l'intérieur des guillemets doubles - IFS configuré et vide - -" IFS='' ### printf %q "${VarTableau[*]}" # Tous-Éléments-De modèle global echo echo 'echo commande:'"${VarTableau[*]}" ### printf %q "${VarTableau[@]}" # Tous-Éléments-De echo echo 'echo commande:'"${VarTableau[@]}" echo echo "- - À l'intérieur de guillemets doubles - IFS non défini - -" unset IFS ### printf %q "${VarTableau[*]}" # Tous-Éléments-De modèle global echo echo 'echo commande:'"${VarTableau[*]}" ### printf %q "${VarTableau[@]}" # Tous-Éléments-De echo echo 'echo commande:'"${VarTableau[@]}" # Remettre la valeur par défaut d'IFS. # Par défaut, il s'agit exactement de ces trois octets. IFS=$'\x20'$'\x09'$'\x0A' # Dans cet ordre. # Interprétation des affichages précédents : # Un modèle global est de l'entrée/sortie ; le paramètrage de l'IFS est pris en compte. ### # Un Tous-Éléments-De ne prend pas en compte le paramètrage de l'IFS. ### # Notez les affichages différents en utilisant la commande echo et l'opérateur #+ de format entre guillemets de la commande printf. # Rappel : # Les paramètres sont similaires aux tableaux et ont des comportements similaires. ### # Les exemples ci-dessous démontrent les variantes possibles. # Pour conserver la forme d'un tableau à indice non continu, un supplément au script #+ est requis. ### # Le code source de Bash dispose d'une routine d'affichage du format #+ d'affectation [indice]=valeur . # Jusqu'à la version 2.05b, cette routine n'est pas utilisée #+ mais cela pourrait changer dans les versions suivantes. # La longueur d'une chaîne, mesurée en éléments non nuls (caractères) : echo echo '- - Références sans guillemets - -' echo 'Nombre de caractères non nuls : '${#VarQuelquechose}' caractères.' # test='Lit'$'\x00''eral' # $'\x00' est un caractère nul. # echo ${#test} # Vous avez remarqué ? # La longueur d'un tableau, mesurée en éléments définis, #+ ceci incluant les éléments à contenu nul. echo echo 'Nombre de contenu défini : '${#VarTableau[@]}' éléments.' # Ce n'est PAS l'indice maximum (4). # Ce n'est PAS l'échelle des indices (1...4 inclus). # C'EST la longueur de la liste chaînée. ### # L'indice maximum et l'échelle d'indices pourraient être trouvées avec #+ un peu de code supplémentaire. # La longueur d'une chaîne, mesurée en éléments non nuls (caractères): echo echo '- - Références du modèle global, entre guillemets - -' echo 'Nombre de caractères non nuls : '"${#VarQuelquechose}"'.' # La longueur d'un tableau, mesuré avec ses éléments définis, #+ ceci incluant les éléments à contenu nul. echo echo "Nombre d'éléments définis: '"${#VarTableau[*]}"' éléments." # Interprétation : la substitution n'a pas d'effet sur l'opération ${# ... }. # Suggestion : # Toujours utiliser le caractère Tous-Éléments-De #+ si cela correspond au comportement voulu (indépendence par rapport à l'IFS). # Définir une fonction simple. # J'inclus un tiret bas dans le nom pour le distinguer des exemples ci-dessous. ### # Bash sépare les noms de variables et les noms de fonctions #+ grâce à des espaces de noms différents. # The Mark-One eyeball isn't that advanced. ### _simple() { echo -n 'FonctionSimple'$@ # Les retours chariots disparaissent dans le résultat. } # La notation ( ... ) appelle une commande ou une fonction. # La notation $( ... ) est prononcée Résultat-De. # Appelle la fonction _simple echo echo '- - Sortie de la fonction _simple - -' _simple # Essayez de passer des arguments. echo # or (_simple) # Essayez de passer des arguments. echo echo "- Existe-t'il une variable de ce nom ? -" echo $_simple indéfinie # Aucune variable de ce nom. # Appelle le résultat de la fonction _simple (message d'erreur attendu) ### $(_simple) # Donne un message d'erreur : # line 394: FonctionSimple: command not found # --------------------------------------- echo ### # Le premier mot du résultat de la fonction _simple #+ n'est ni une commande Bash valide ni le nom d'une fonction définie. ### # Ceci démontre que la sortie de _simple est sujet à évaluation. ### # Interprétation : # Une fonction peut être utilisée pour générer des commandes Bash en ligne. # Une fonction simple où le premier mot du résultat EST une commande Bash : ### _print() { echo -n 'printf %q '$@ } echo '- - Affichage de la fonction _print - -' _print parm1 parm2 # Une sortie n'est PAS une commande. echo $(_print parm1 parm2) # Exécute : printf %q parm1 parm2 # Voir ci-dessus les exemples IFS #+ pour les nombreuses possibilités. echo $(_print $VarQuelquechose) # Le résultat prévisible. echo # Variables de fonctions # ---------------------- echo echo '- - Variables de fonctions - -' # Une variable pourrait représenter un entier signé, une chaîne ou un tableau. # Une chaîne pourrait être utilisée comme nom de fonction avec des arguments optionnelles. # set -vx # À activer si désiré declare -f funcVar #+ dans l'espace de noms des fonctions funcVar=_print # Contient le nom de la fonction. $funcVar parm1 # Identique à _print à ce moment. echo funcVar=$(_print ) # Contient le résultat de la fonction. $funcVar # Pas d'entrée, pas de sortie. $funcVar $VarQuelquechose # Le résultat prévisible. echo funcVar=$(_print $VarQuelquechose) # $VarQuelquechose remplacé ICI. $funcVar # L'expansion fait parti du contenu echo #+ des variables. funcVar="$(_print $VarQuelquechose)" # $VarQuelquechose remplacé ICI. $funcVar # L'expansion fait parti du contenu echo #+ des variables. # La différence entre les versions sans guillemets et avec double guillemets #+ ci-dessus est rencontrée dans l'exemple "protect_literal.sh". # Le premier cas ci-dessus est exécuté comme deux mots Bash sans guillemets. # Le deuxième cas est exécuté comme un mot Bash avec guillemets. # Remplacement avec délai # ----------------------- echo echo '- - Remplacement avec délai - -' funcVar="$(_print '$VarQuelquechose')" # Pas de remplacement, simple mot Bash. eval $funcVar # $VarQuelquechose remplacé ICI. echo VarQuelquechose='NouvelleChose' eval $funcVar # $VarQuelquechose remplacé ICI. echo # Restaure la configuration initiale. VarQuelquechose=Literal # Il existe une paire de fonctions démontrées dans les exemples #+ "protect_literal.sh" et "unprotect_literal.sh". # Il s'agit de fonctions à but général pour des littérales à remplacements avec délai #+ contenant des variables. # REVUE : # ------ # Une chaîne peut être considérée comme un tableau classique d'éléments de type #+ caractère. # Une opération sur une chaîne s'applique à tous les éléments (caractères) de #+ la chaîne (enfin, dans son concept). ### # La notation ${nom_tableau[@]} représente tous les éléments du tableau Bash #+ nom_tableau. ### # Les opérations sur les chaînes de syntaxe étendue sont applicables à tous les #+ éléments d'un tableau. ### # Ceci peut être pensé comme une boucle For-Each sur un vecteur de chaînes. ### # Les paramètres sont similaires à un tableau. # L'initialisation d'un paramètre de type tableau pour un script #+ et d'un paramètre de type tableau pour une fonction diffèrent seulement #+ dans l'initialisation de ${0}, qui ne change jamais sa configuration. ### # L'indice zéro du tableau, paramètre d'un script, contient le nom du script. ### # L'indice zéro du tableau, paramètre de fonction, NE CONTIENT PAS le nom de la #+ fonction. # Le nom de la fonction courante est accédé par la variable $NOM_FONCTION. ### # Une liste rapide et revue suit (rapide mais pas courte). echo echo '- - Test (mais sans changement) - -' echo '- référence nulle -' echo -n ${VarNulle-'NonInitialisée'}' ' # NonInitialisée echo ${VarNulle} # NewLine only echo -n ${VarNulle:-'NonInitialisée'}' ' # NonInitialisée echo ${VarNulle} # Newline only echo '- contenu nul -' echo -n ${VarVide-'Vide'}' ' # Seulement l'espace echo ${VarVide} # Nouvelle ligne seulement echo -n ${VarVide:-'Vide'}' ' # Vide echo ${VarVide} # Nouvelle ligne seulement echo '- contenu -' echo ${VarQuelquechose-'Contenu'} # Littéral echo ${VarQuelquechose:-'Contenu'} # Littéral echo '- Tableau à indice non continu -' echo ${VarTableau[@]-'non initialisée'} # Moment ASCII-Art # État O==oui, N==non # - :- # Non initialisé O O ${# ... } == 0 # Vide N O ${# ... } == 0 # Contenu N N ${# ... } > 0 # Soit la première partie des tests soit la seconde pourrait être une chaîne #+ d'appel d'une commande ou d'une fonction. echo echo '- - Test 1 pour indéfini - -' declare -i t _decT() { t=$t-1 } # Référence nulle, initialisez à t == -1 t=${#VarNulle} # Résultats en zéro. ${VarNulle- _decT } # La fonction s'exécute, t vaut maintenant -1. echo $t # Contenu nul, initialisez à t == 0 t=${#VarVide} # Résultats en zéro. ${VarVide- _decT } # Fontion _decT NON exécutée. echo $t # Contenu, initialisez à t == nombre de caractères non nuls VarQuelquechose='_simple' # Initialisez avec un nom de fonction valide. t=${#VarQuelquechose} # longueur différente de zéro ${VarQuelquechose- _decT } # Fonction _simple exécutée. echo $t # Notez l'action Append-To. # Exercice : nettoyez cet exemple. unset t unset _decT VarQuelquechose=Literal echo echo '- - Test et modification - -' echo '- Affectation si référence nulle -' echo -n ${VarNulle='NonInitialisée'}' ' # NonInitialisée NonInitialisée echo ${VarNulle} unset VarNulle echo '- Affectation si référence nulle -' echo -n ${VarNulle:='NonInitialisée'}' ' # NonInitialisée NonInitialisée echo ${VarNulle} unset VarNulle echo "- Pas d'affectation si contenu nul -" echo -n ${VarVide='Vide'}' ' # Espace seulement echo ${VarVide} VarVide='' echo "- Affectation si contenu nul -" echo -n ${VarVide:='Vide'}' ' # Vide Vide echo ${VarVide} VarVide='' echo "- Aucun changement s'il a déjà un contenu -" echo ${VarQuelquechose='Contenu'} # Littéral echo ${VarQuelquechose:='Contenu'} # Littéral # Tableaux Bash à indice non continu ### # Les tableaux Bash ont des indices continus, commençant à zéro #+ sauf indication contraire. ### # L'initialisation de VarTableau était une façon de le "faire autrement". #+ Voici un autre moyen : ### echo declare -a TableauNonContinu TableauNonContinu=( [1]=un [2]='' [4]='quatre' ) # [0]=référence nulle, [2]=contenu nul, [3]=référence nulle echo '- - Liste de tableaux à indice non continu - -' # À l'intérieur de guillemets doubles, IFS par défaut, modèle global IFS=$'\x20'$'\x09'$'\x0A' printf %q "${TableauNonContinu[*]}" echo # Notez que l'affichage ne distingue pas entre "contenu nul" et "référence nulle". # Les deux s'affichent comme des espaces blancs échappés. ### # Notez aussi que la sortie ne contient PAS d'espace blanc échappé #+ pour le(s) "référence(s) nulle(s)" avant le premier élément défini. ### # Ce comportement des versions 2.04, 2.05a et 2.05b a été rapporté et #+ pourrait changer dans une prochaine version de Bash. # Pour afficher un tableau sans indice continu et maintenir la relation #+ [indice]=valeur sans changement requiert un peu de programmation. # Un bout de code possible : ### # local l=${#TableauNonContinu[@]} # Nombre d'éléments définis # local f=0 # Nombre d'indices trouvés # local i=0 # Indice à tester ( # Fonction anonyme en ligne for (( l=${#TableauNonContinu[@]}, f = 0, i = 0 ; f < l ; i++ )) do # 'si défini alors...' ${TableauNonContinu[$i]+ eval echo '\ ['$i']='${TableauNonContinu[$i]} ; (( f++ )) } done ) # Le lecteur arrivant au fragment de code ci-dessus pourrait vouloir voir #+ la liste des commandes et les commandes multiples sur une ligne dans le texte #+ du guide de l'écriture avancée de scripts shell Bash. ### # Note : # La version "read -a nom_tableau" de la commande "read" commence à remplir #+ nom_tableau à l'indice zéro. # TableauNonContinu ne définit pas de valeur à l'indice zéro. ### # L'utilisateur ayant besoin de lire/écrire un tableau non contigu pour soit #+ un stockage externe soit une communication par socket doit inventer une paire #+ de code lecture/écriture convenant à ce but. ### # Exercice : nettoyez-le. unset TableauNonContinu echo echo '- - Alternative conditionnel (mais sans changement)- -' echo "- Pas d'alternative si référence nulle -" echo -n ${VarNulle+'NonInitialisee'}' ' echo ${VarNulle} unset VarNulle echo "- Pas d'alternative si référence nulle -" echo -n ${VarNulle:+'NonInitialisee'}' ' echo ${VarNulle} unset VarNulle echo "- Alternative si contenu nul -" echo -n ${VarVide+'Vide'}' ' # Vide echo ${VarVide} VarVide='' echo "- Pas d'alternative si contenu nul -" echo -n ${VarVide:+'Vide'}' ' # Espace seul echo ${VarVide} VarVide='' echo "- Alternative si contenu déjà existant -" # Alternative littérale echo -n ${VarQuelquechose+'Contenu'}' ' # Contenu littéral echo ${VarQuelquechose} # Appelle une fonction echo -n ${VarQuelquechose:+ $(_simple) }' ' # Littéral FonctionSimple echo ${VarQuelquechose} echo echo '- - Tableau non contigu - -' echo ${VarTableau[@]+'Vide'} # Un tableau de 'vide'(s) echo echo '- - Test 2 pour indéfini - -' declare -i t _incT() { t=$t+1 } # Note: # C'est le même test utilisé dans le fragment de code #+ pour le tableau non contigu. # Référence nulle, initialisez : t == -1 t=${#VarNulle}-1 # Les résultats dans moins-un. ${VarNulle+ _incT } # Ne s'exécute pas. echo $t' Null reference' # Contenu nul, initialisez : t == 0 t=${#VarVide}-1 # Les résultats dans moins-un. ${VarVide+ _incT } # S'exécute. echo $t' Null content' # Contenu, initialisez : t == (nombre de caractères non nuls) t=${#VarQuelquechose}-1 # longueur non nul moins un ${VarQuelquechose+ _incT } # S'exécute. echo $t' Contents' # Exercice : nettoyez cet exemple. unset t unset _incT # ${name?err_msg} ${name:?err_msg} # Ceci suit les mêmes règles mais quitte toujours après #+ si une action est spécifiée après le point d'interrogation. # L'action suivant le point d'interrogation pourrait être un littéral #+ ou le résultat d'une fonction. ### # ${nom?} ${nom:?} sont seulement des tests, le retour peut être testé. # Opérations sur les éléments # --------------------------- echo echo '- - Sélection du sous-élément de queue - -' # Chaînes, tableaux et paramètres de position # Appeler ce script avec des arguments multiples #+ pour voir les sélections du paramètre. echo '- Tous -' echo ${VarQuelquechose:0} # tous les caractères non nuls echo ${VarTableau[@]:0} # tous les éléments avec contenu echo ${@:0} # tous les paramètres avec contenu # ignore paramètre[0] echo echo '- Tous après -' echo ${VarQuelquechose:1} # tous les non nuls après caractère[0] echo ${VarTableau[@]:1} # tous après élément[0] avec contenu echo ${@:2} # tous après param[1] avec contenu echo echo '- Intervalle après -' echo ${VarQuelquechose:4:3} # ral # trois caractères après # caractère[3] echo '- Sparse array gotch -' echo ${VarTableau[@]:1:2} # quatre - le premier élément avec contenu. # Deux éléments après (s'ils existent). # le PREMIER AVEC CONTENU #+ (le PREMIER AVEC CONTENU doit être #+ considéré comme s'il s'agissait de #+ l'indice zéro). # Éxécuté comme si Bash considère SEULEMENT les éléments de tableau avec CONTENU # printf %q "${VarTableau[@]:0:3}" # Essayez celle-ci # Dans les versions 2.04, 2.05a et 2.05b, #+ Bash ne gère pas les tableaux non contigu comme attendu avec cette notation. # # Le mainteneur actuel de Bash, Chet Ramey, a corrigé ceci pour une future #+ version de Bash. echo '- Tableaux contigus -' echo ${@:2:2} # Deux paramètres suivant paramètre[1] # Nouvelles victimes des exemples de vecteurs de chaînes : chaineZ=abcABC123ABCabc tableauZ=( abcabc ABCABC 123123 ABCABC abcabc ) noncontiguZ=( [1]='abcabc' [3]='ABCABC' [4]='' [5]='123123' ) echo echo ' - - Chaîne victime - -'$chaineZ'- - ' echo ' - - Tableau victime - -'${tableauZ[@]}'- - ' echo ' - - Tableau non contigu - -'${noncontiguZ[@]}'- - ' echo ' - [0]==réf. nulle, [2]==réf. nulle, [4]==contenu nul - ' echo ' - [1]=abcabc [3]=ABCABC [5]=123123 - ' echo ' - nombre de références non nulles : '${#noncontiguZ[@]}' elements' echo echo "- - Suppression du préfixe d'un sous élément - -" echo '- - la correspondance de modèle globale doit inclure le premier caractère. - -' echo "- - Le modèle global doit être un littéral ou le résultat d'une fonction. - -" echo # Fonction renvoyant un modèle global simple, littéral _abc() { echo -n 'abc' } echo '- Préfixe court -' echo ${chaineZ#123} # Non modifié (pas un préfixe). echo ${chaineZ#$(_abc)} # ABC123ABCabc echo ${tableauZ[@]#abc} # Appliqué à chaque élément. # Corrigé par Chet Ramey pour une future version de Bash. # echo ${noncontiguZ[@]#abc} # Version-2.05b quitte avec un « core dump ». # Le -it serait sympa- Premier-Indice-De # echo ${#noncontiguZ[@]#*} # Ce n'est PAS du Bash valide. echo echo '- Préfixe le plus long -' echo ${chaineZ##1*3} # Non modifié (pas un préfixe) echo ${chaineZ##a*C} # abc echo ${tableauZ[@]##a*c} # ABCABC 123123 ABCABC # Corrigé par Chet Ramey pour une future version de Bash. # echo ${noncontiguZ[@]##a*c} # Version-2.05b quitte avec un « core dump ». echo echo '- - Suppression du sous-élément suffixe - -' echo '- - La correspondance du modèle global doit inclure le dernier caractère. - -' echo '- - Le modèle global pourrait être un littéral ou un résultat de fonction. - -' echo echo '- Suffixe le plus court -' echo ${chaineZ%1*3} # Non modifié (pas un suffixe). echo ${chaineZ%$(_abc)} # abcABC123ABC echo ${tableauZ[@]%abc} # Appliqué à chaque élément. # Corrigé par Chet Ramey pour une future version de Bash. # echo ${noncontiguZ[@]%abc} # Version-2.05b quitte avec un « core dump ». # Le -it serait sympa- Dernier-Indice-De # echo ${#noncontiguZ[@]%*} # Ce n'est PAS du Bash valide. echo echo '- Suffixe le plus long -' echo ${chaineZ%%1*3} # Non modifié (pas un suffixe) echo ${chaineZ%%b*c} # a echo ${tableauZ[@]%%b*c} # a ABCABC 123123 ABCABC a # Corrigé par Chet Ramey pour une future version de Bash. # echo ${noncontiguZ[@]%%b*c} # Version-2.05b quitte avec un « core dump ». echo echo '- - Remplacement de sous-élements - -' echo "- - Sous-élément situé n'importe où dans la chaîne. - -" echo '- - La première spécification est un modèle global. - -' echo '- - Le modèle global pourrait être un littéral ou un résultat de fonction de modèle global. - -' echo '- - La seconde spécification pourrait être un littéral ou un résultat de fonction. - -' echo '- - La seconde spécification pourrait être non spécifiée. Prononcez-ça comme :' echo ' Remplace-Avec-Rien (Supprime) - -' echo # Fonction renvoyant un modèle global simple, littéral _123() { echo -n '123' } echo '- Remplace la première occurrence -' echo ${chaineZ/$(_123)/999} # Modifié (123 est un composant). echo ${chaineZ/ABC/xyz} # xyzABC123ABCabc echo ${tableauZ[@]/ABC/xyz} # Appliqué à chaque élément. echo ${noncontiguZ[@]/ABC/xyz} # Fonctionne comme attendu. echo echo '- Supprime la première first occurrence -' echo ${chaineZ/$(_123)/} echo ${chaineZ/ABC/} echo ${tableauZ[@]/ABC/} echo ${noncontiguZ[@]/ABC/} # Le remplacement ne doit pas être un littéral, #+ car le résultat de l'appel d'une fonction est permis. # C'est général pour toutes les formes de remplacement. echo echo '- Remplace la première occurence avec Résultat-De -' echo ${chaineZ/$(_123)/$(_simple)} # Fonctionne comme attendu. echo ${tableauZ[@]/ca/$(_simple)} # Appliqué à chaque élément. echo ${noncontiguZ[@]/ca/$(_simple)} # Fonctionne comme attendu. echo echo '- Remplace toutes les occurrences -' echo ${chaineZ//[b2]/X} # X-out b et 2 echo ${chaineZ//abc/xyz} # xyzABC123ABCxyz echo ${tableauZ[@]//abc/xyz} # Appliqué à chaque élément. echo ${noncontiguZ[@]//abc/xyz} # Fonctionne comme attendu. echo echo '- Supprime toutes les occurrences -' echo ${chaineZ//[b2]/} echo ${chaineZ//abc/} echo ${tableauZ[@]//abc/} echo ${noncontiguZ[@]//abc/} echo echo '- - Remplacement du sous-élément préfixe - -' echo '- - La correspondance doit inclure le premier caractère. - -' echo echo '- Remplace les occurrences du préfixe -' echo ${chaineZ/#[b2]/X} # Non modifié (n'est pas non plus un préfixe). echo ${chaineZ/#$(_abc)/XYZ} # XYZABC123ABCabc echo ${tableauZ[@]/#abc/XYZ} # Appliqué à chaque élément. echo ${noncontiguZ[@]/#abc/XYZ} # Fonctionne comme attendu. echo echo '- Supprime les occurrences du préfixe -' echo ${chaineZ/#[b2]/} echo ${chaineZ/#$(_abc)/} echo ${tableauZ[@]/#abc/} echo ${noncontiguZ[@]/#abc/} echo echo '- - Remplacement du sous-élément suffixe - -' echo '- - La correspondance doit inclure le dernier caractère. - -' echo echo '- Remplace les occurrences du suffixe -' echo ${chaineZ/%[b2]/X} # Non modifié (n'est pas non plus un suffixe). echo ${chaineZ/%$(_abc)/XYZ} # abcABC123ABCXYZ echo ${tableauZ[@]/%abc/XYZ} # Appliqué à chaque élément. echo ${noncontiguZ[@]/%abc/XYZ} # Fonctionne comme attendu. echo echo '- Supprime les occurrences du suffixe -' echo ${chaineZ/%[b2]/} echo ${chaineZ/%$(_abc)/} echo ${tableauZ[@]/%abc/} echo ${noncontiguZ[@]/%abc/} echo echo '- - Cas spéciaux du modèle global nul - -' echo echo '- Tout préfixe -' # modèle de sous-chaîne nul, signifiant 'préfixe' echo ${chaineZ/#/NEW} # NEWabcABC123ABCabc echo ${tableauZ[@]/#/NEW} # Appliqué à chaque élément. echo ${noncontiguZ[@]/#/NEW} # Aussi appliqué au contenu nul. # Cela semble raisonnable. echo echo '- Tout suffixe -' # modèle de sous-chaîne nul, signifiant 'suffixe' echo ${chaineZ/%/NEW} # abcABC123ABCabcNEW echo ${tableauZ[@]/%/NEW} # Appliqué à chaque élément. echo ${noncontiguZ[@]/%/NEW} # Aussi appliqué au contenu nul. # Cela semble raisonnable. echo echo '- - Cas spécial pour le modèle global For-Each - -' echo '- - - - Ceci est un rêve - - - -' echo _GenFunc() { echo -n ${0} # Illustration seulement. # Actuellement, ce serait un calcul arbitraire. } # Toutes les occurrences, correspondant au modèle NImporteQuoi. # Actuellement, //*/ n'établit pas une correspondance avec un modèle nul #+ ainsi qu'avec une référence nulle. # /#/ et /%/ correspondent à un contenu nul mais pas à une référence nulle. echo ${noncontiguZ[@]//*/$(_GenFunc)} # Une syntaxe possible placerait la notation du paramètre utilisé #+ à l'intérieur du moyen de construction. # ${1} - L'élément complet # ${2} - Le préfixe, S'il existe, du sous-élément correspondant # ${3} - Le sous-élément correspondant # ${4} - Le suffixe, S'il existe, du sous-élément correspondant # # echo ${noncontiguZ[@]//*/$(_GenFunc ${3})} # Pareil que ${1}, ici. # Cela sera peut-être implémenté dans une future version de Bash. exit 0