Ici et maintenant, les gars.
-- Aldous Huxley, Islande
Un document en ligne est un bloc de code à usage spécial. Il utilise une forme de redirection d'E/S pour fournir une liste de commande à un programme ou une commande interactif, tel que ftp, cat ou l'éditeur de texte ex.
COMMANDE <<DesEntreesIci ... DesEntreesIci
Une chaîne de caractères de limite encadre la liste de commandes. Le symbole spécial << désigne la chaîne de caractères de limite. Ceci a pour effet de rediriger la sortie d'un fichier vers le stdin d'un programme ou d'une commande. Ceci est similaire à programme-interactif < fichier-commandes, où fichier-commandes contient
commande n°1 commande n°2 ...
L'alternative au document en ligne ressemble à ceci :
#!/bin/bash programme-interactif <<ChaineLimite commande #1 commande #2 ... ChaineLimite
Choisissez une chaîne de caractères de limite suffisamment inhabituelle pour qu'elle ne soit pas présente où que ce soit dans la liste de commandes afin qu'aucune confusion ne puisse survenir.
Notez que les documents en ligne peuvent parfois être utilisés correctement avec des utilitaires et des commandes non interactifs, tels que wall.
Exemple 18.1. broadcast : envoie des messages à chaque personne connectée
#!/bin/bash wall <<zzz23EndOfMessagezzz23 Envoyez par courrier électronique vos demandes de pizzas à votre administrateur système. (Ajoutez un euro supplémentaire pour les anchois et les champignons.) # Un message texte supplémentaire vient ici. # Note: Les lignes de commentaires sont affichées par 'wall'. zzz23EndOfMessagezzz23 # Peut se faire plus efficacement avec # wall <fichier-message # Néanmoins, intégrer un message modèle dans un script #+ est une solution rapide bien que sale exit 0
Même de si improbables candidats comme vi tendent eux-même aux documents en ligne.
Exemple 18.2. fichierstupide : Crée un fichier stupide de deux lignes
#!/bin/bash # Utilisation non interactive de 'vi' pour éditer un fichier. # Émule 'sed'. E_MAUVAISARGS=65 if [ -z "$1" ] then echo "Usage: `basename $0` nomfichier" exit $E_MAUVAISARGS fi FICHIERCIBLE=$1 # Insère deux lignes dans le fichier et le sauvegarde. #--------Début document en ligne-----------# vi $FICHIERCIBLE <<x23LimitStringx23 i Ceci est la ligne 1 du fichier exemple. Ceci est la ligne 2 du fichier exemple. ^[ ZZ x23LimitStringx23 #--------Fin document en ligne-----------# # Notez que ^[ ci-dessus est un échappement littéral, saisi avec #+ Control-V <Esc>. # Bram Moolenaar indique que ceci pourrait ne pas fonctionner avec 'vim', #+ à cause de problèmes possibles avec l'interaction du terminal. exit 0
Le script ci-dessus pourrait avoir été implémenté aussi efficacement avec ex, plutôt que vi. Les documents en ligne contenant une liste de commandes ex sont assez courants pour disposer de leur propre catégorie, connue sous le nom de scripts ex.
#!/bin/bash # Remplace toutes les instances de "Smith" avec "Jones" #+ dans les fichiers avec extension ".txt". ORIGINAL=Smith REMPLACEMENT=Jones for mot in $(fgrep -l $ORIGINAL *.txt) do # ------------------------------------- ex $mot <<EOF :%s/$ORIGINAL/$REMPLACEMENT/g :wq EOF # :%s est la commande de substitution d'"ex". # :wq est un raccourci pour deux commandes : sauvegarde puis quitte. # ------------------------------------- done
Les « scripts cat » sont analogues aux scripts ex.
Exemple 18.3. Message multi-lignes en utilisant cat
#!/bin/bash # 'echo' est bien pour afficher des messages sur une seule ligne #+ mais il est parfois problématique pour des blocs de message. # Un document en ligne style 'cat' permet de surpasser cette limitation. cat <<Fin-du-message ------------------------------------- Ceci est la ligne 1 du message. Ceci est la ligne 2 du message. Ceci est la ligne 3 du message. Ceci est la ligne 4 du message. Ceci est la dernière ligne du message. ------------------------------------- Fin-du-message # le remplacement de la ligne 7, ci-dessus, par #+ cat > $NouveauFichier <<Fin-du-message #+ ^^^^^^^^^^ #+ écrit la sortie vers le fichier $NouveauFichier, au lieu de stdout. exit 0 #-------------------------------------------- # Le code ci-dessous est désactivé à cause du "exit 0" ci-dessus. # S.C. indique que ce qui suit fonctionne aussi. echo "------------------------------------- Ceci est la ligne 1 du message. Ceci est la ligne 2 du message. Ceci est la ligne 3 du message. Ceci est la ligne 4 du message. Ceci est la dernière ligne du message. -------------------------------------" # Néanmoins, le texte ne pourrait pas inclure les doubles guillemets sauf #+ s'ils sont échappés.
L'option - marquant la chaîne de caractères de limite d'un document en ligne (<<-ChaineLimite) supprime les tabulations du début (mais pas les espaces) lors de la sortie. Ceci est utile pour réaliser un script plus lisible.
Exemple 18.4. Message multi-lignes, aves les tabulations supprimées
#!/bin/bash # Identique à l'exemple précédent, mais... # L'option - pour un document en ligne <<- # supprime les tabulations du début dans le corps du document, #+ mais *pas* les espaces. cat <<-FINDUMESSAGE Ceci est la ligne 1 du message. Ceci est la ligne 2 du message. Ceci est la ligne 3 du message. Ceci est la ligne 4 du message. Ceci est la dernière ligne du message. FINDUMESSAGE # La sortie du script sera poussée vers la gauche. # Chaque tabulation de chaque ligne ne s'affichera pas. # Les cinq lignes du "message" sont préfacées par une tabulation, et non des espaces, # Les espaces ne sont pas affectés par <<- . # Notez que cette option n'a aucun effet sur les tabulations *intégrées*. exit 0
Un document en ligne supporte la substitution de paramètres et de commandes. Il est donc possible de passer différents paramètres dans le corps du document en ligne, en changeant la sortie de façon appropriée.
Exemple 18.5. Document en ligne avec une substitution de paramètre
#!/bin/bash # Autre document en ligne 'cat' utilisant la substitution de paramètres. # Essayez-le sans arguments, ./scriptname # Essayez-le avec un argument, ./scriptname Mortimer # Essayez-le avec deux arguments entre guillemets, # ./scriptname "Mortimer Jones" CMDLINEPARAM=1 # Attendez au moins un paramètre en ligne de commande. if [ $# -ge $CMDLINEPARAM ] then NOM=$1 # Si plus d'un paramètre en ligne de commande, prendre #+ seulement le premier. else NOM="John Doe" # Par défaut, s'il n'y a pas de paramètres. fi INTERLOCUTEUR="l'auteur de ce joli script" cat <<FinDuMessage Salut, $NOM. Bienvenue à toi, $NOM, de la part de $INTERLOCUTEUR. # Ce commentaire s'affiche dans la sortie (pourquoi ?). FinDuMessage # Notez que les lignes blanches s'affichent. Ainsi que le commentaire. exit 0
Voici un script utile contenant un document intégré avec une substitution de paramètres.
Exemple 18.6. Télécharger un ensemble de fichiers dans le répertoire de récupération Sunsite
#!/bin/bash # upload.sh # Téléchargement de fichiers par paires (Fichier.lsm, Fichier.tar.gz) #+ pour le répertoire entrant de Sunsite (metalab.unc.edu). # Fichier.tar.gz est l'archive tar elle-même. # Fichier.lsm est le fichier de description. # Sunsite requiert le fichier "lsm", sinon cela retournera les contributions. E_ERREURSARGS=65 if [ -z "$1" ] then echo "Usage: `basename $0` fichier_à_télécharger" exit $E_ERREURSARGS fi NomFichier=`basename $1` # Supprime le chemin du nom du fichier. Serveur="ibiblio.org" Repertoire="/incoming/Linux" # Ils n'ont pas besoin d'être codés en dur dans le script, #+ mais peuvent être changés avec un argument en ligne de commande. MotDePasse="votre.adresse.courriel" # A changer suivant vos besoins. ftp -n $Serveur <<Fin-De-Session # L'option -n désactive la connexion automatique user anonymous "$MotDePasse" binary bell # Sonne après chaque transfert de fichiers. cd $Repertoire put "$NomFichier.lsm" put "$NomFichier.tar.gz" bye Fin-De-Session exit 0
Mettre entre guillemets, ou échapper la « chaîne de caractères de limite » au début du document intgr, désactive la substitution de paramètres en son corps.
Exemple 18.7. Substitution de paramètres désactivée
#!/bin/bash # Un document en ligne 'cat', mais avec la substitution de paramètres #+ désactivée. NOM="John Doe" INTERLOCUTEUR="l'auteur de ce joli script" cat <<'FinDuMessage' Salut, $NOM. Bienvenue à toi, $NOM, de la part de $INTERLOCUTEUR. FinDuMessage # Remplacement de la ligne 7, ci-dessus, avec #+ cat > $Nouveaufichier <<Fin-du-message #+ ^^^^^^^^^^ #+ écrit la sortie dans le fichier $Nouveaufichier, plutôt que sur stdout. # Pas de substitution de paramètres lorsque la chaîne de fin est entre #+ guillemets ou échappée. # L'une des deux commandes ci-dessous à l'entête du document en ligne aura le #+ le même effet. # cat <<"FinDuMessage" # cat <<\FinDuMessage exit 0
Désactiver la substitution de paramètres permet d'afficher le texte littéral. Générer des scripts, ou même du code, en est une des utilités principales.
Exemple 18.8. Un script générant un autre script
#!/bin/bash # generate-script.sh # Basé sur une idée d'Albert Reiner. FICHIER_SORTIE=genere.sh # Nom du fichier à générer. # ----------------------------------------------------------- # 'Document en ligne contenant le corps du script généré. ( cat <<'EOF' #!/bin/bash echo "Ceci est un script shell généré" # Notez que, comme nous sommes dans un sous-shell, #+ nous ne pouvons pas accéder aux variables du script "externe". # Prouvez-le... echo "Le fichier généré aura pour nom : $FICHIER_SORTIE" # La ligne ci-dessus ne fonctionnera pas comme on pourrait s'y attendre #+ parce que l'expansion des paramètres a été désactivée. # A la place, le résultat est une sortie littérale. a=7 b=3 let "c = $a * $b" echo "c = $c" exit 0 EOF ) > $FICHIER_SORTIE # ----------------------------------------------------------- # Mettre entre guillemets la chaîne limite empêche l'expansion de la variable #+ à l'intérieur du corps du document en ligne ci-dessus. # Ceci permet de sortir des chaînes littérales dans le fichier de sortie. if [ -f "$FICHIER_SORTIE" ] then chmod 755 $FICHIER_SORTIE # Rend le fichier généré exécutable. else echo "Problème lors de la création du fichier: \"$FICHIER_SORTIE\"" fi # Cette méthode est aussi utilisée pour générer des programmes C, Perl, Python, #+ Makefiles et d'autres. exit 0
Il est possible d'initialiser une variable à partir de la sortie d'un document en ligne. En fait, il s'agit d'une forme dévié de substitution de commandes.
variable=$(cat <<SETVAR Cette variable est sur plusieurs lignes. SETVAR) echo "$variable"
Un document en ligne peut donner une entrée à une fonction du même script.
Exemple 18.9. Documents en ligne et fonctions
#!/bin/bash # here-function.sh ObtientDonneesPersonnelles () { read prenom read nom read adresse read ville read etat read codepostal } # Ceci ressemble vraiment à une fonction interactive, mais... # Apporter l'entrée à la fonction ci-dessus. ObtientDonneesPersonnelles <<ENREG001 Bozo Bozeman 2726 Nondescript Dr. Baltimore MD 21226 RECORD001 echo echo "$prenom $nom" echo "$adresse" echo "$ville, $etat $codepostal" echo exit 0
Il est possible d'utiliser : comme commande inactive acceptant une sortie d'un document en ligne. Cela crée un document en ligne « anonyme ».
Exemple 18.10. Document en ligne « anonyme »
#!/bin/bash : <<TESTVARIABLES ${HOSTNAME?}${USER?}${MAIL?} # Affiche un message d'erreur #+ si une des variables n'est pas configurée. TESTVARIABLES exit 0
Une variante de la technique ci-dessus permet de « supprimer les commentaires » de blocs de code.
Exemple 18.11. Décommenter un bloc de code
#!/bin/bash # commentblock.sh : <<BLOC_COMMENTAIRE echo "Cette ligne n'est pas un echo." C'est une ligne de commentaire sans le préfixe "#". Ceci est une autre ligne sans le préfixe "#". &*@!!++= La ligne ci-dessus ne causera aucun message d'erreur, Parce que l'interpréteur Bash l'ignorera. BLOC_COMMENTAIRE echo "La valeur de sortie du \"BLOC_COMMENTAIRE\" ci-dessus est $?." # 0 # Pas d'erreur. echo # La technique ici-dessus est aussi utile pour mettre en commentaire un bloc #+ de code fonctionnel pour des raisons de déboguage. # Ceci permet d'éviter de placer un "#" au début de chaque ligne, et d'avoir #+ ensuite à les supprimer. echo "Juste avant le bloc de code commenté." # Les lignes de code entre les lignes de soulignés doubles ne s'exécuteront pas. # ============================================================================== : <<DEBUGXXX for fichier in * do cat "$fichier" done DEBUGXXX # ============================================================================== echo "Juste après le bloc de code commenté." exit 0 ###################################################################### # Notez, néanmoins, que si une variable entre crochets est contenu #+ dans un bloc de code commenté, cela pourrait poser problème. # Par exemple : #/!/bin/bash : <<BLOC_COMMENTAIRE echo "Cette ligne ne s'affichera pas." &*@!!++= ${foo_bar_bazz?} $(rm -rf /tmp/foobar/) $(touch mon_repertoire_de_construction/cups/Makefile) BLOC_COMMENTAIRE $ sh commented-bad.sh commented-bad.sh: line 3: foo_bar_bazz: parameter null or not set # Le remède pour ceci est de placer le BLOC_COMMENTAIRE #+ entre guillemets simples à la ligne 48, ci-dessus. : <<'COMMENTBLOCK' # Merci de nous l'avoir indiqué, Kurt Pfeifle.
Exemple 18.12. Un script auto-documenté
#!/bin/bash # self-document.sh : script auto-documenté # Modification de "colm.sh". DEMANDE_DOC=70 if [ "$1" = "-h" -o "$1" = "--help" ] # Demande de l'aide. then echo; echo "Usage: $0 [nom-repertoire]"; echo sed --silent -e '/DOCUMENTATIONXX$/,/^DOCUMENTATIONXX$/p' "$0" | sed -e '/DOCUMENTATIONXX$/d'; exit $DEMANDE_DOC; fi : <<DOCUMENTATIONXX Liste les statistiques d'un répertoire spécifié dans un format de tabulations. ------------------------------------------------------------------------------ Le paramètre en ligne de commande donne le répertoire à lister. Si aucun répertoire n'est spécifié ou que le répertoire spécifié ne peut être lu, alors liste le répertoire courant. DOCUMENTATIONXX if [ -z "$1" -o ! -r "$1" ] then repertoire=. else repertoire="$1" fi echo "Liste de "$repertoire":"; echo (printf "PERMISSIONS LIENS PROP GROUPE TAILLE MOIS JOUR HH:MM NOM-PROG\n" \ ; ls -l "$repertoire" | sed 1d) | column -t exit 0
Utiliser un script cat est une autre façon d'accomplir ceci.
REQUETE_DOC=70 if [ "$1" = "-h" -o "$1" = "--help" ] # Demande d'aide. then # Utilise un "script cat"... cat <<DOCUMENTATIONXX Liste les statistiques d'un répertoire spécifié au format de tableau. --------------------------------------------------------------------- Le paramètre en ligne de commande indique le répertoire à lister. Si aucun répertoire n'est spécifié ou si le répertoire spécifié ne peut pas être lu, alors liste le répertoire courant. DOCUMENTATIONXX exit $REQUETE_DOC fi
Voir aussi l'Exemple A.30, « Identification d'un spammer » , Exemple A.40, « Pétales autour d'une rose », et Exemple A.41, « Quacky : un jeu de mots de type Perquackey » pour plus d'exemples de script auto-documenté.
Les documents en ligne créent des fichiers temporaires mais ces fichiers sont supprimés après avoir été ouverts et ne sont plus accessibles par aucun autre processus.
bash$ bash -c 'lsof -a -p $$ -d0' << EOF > EOF lsof 1213 bozo 0r REG 3,5 0 30386 /tmp/t1213-0-sh (deleted)
Quelques utilitaires ne fonctionneront pas à l'intérieur d'un document en ligne.
La chaîne de limite fermante, à la ligne finale d'un document en ligne, doit commencer à la position du tout premier caractère. Il ne peut pas y avoir d'espace blanc devant. Les espaces de fin après la chaîne de limite cause un comportement inattendu. L'espace blanc empêche la chaîne de limite d'être reconnu.
#!/bin/bash echo "----------------------------------------------------------------------" cat <<ChaineLimite echo "Ligne 1 du document en ligne." echo "Ligne 2 du document en ligne." echo "Ligne finale du document en ligne." ChaineLimite #^^^^Chaîne de limite indentée. Erreur! Ce script ne va pas se comporter comme #+ on s'y attend. echo "----------------------------------------------------------------------" # Ces commentaires sont en dehors du document en ligne et ne devraient pas #+ s'afficher. echo "En dehors du document en ligne." exit 0 echo "Cette ligne s'affiche encore moins." # Suit une commande 'exit'.
Pour ces tâches trop complexes pour un « document en ligne », considérez l'utilisation du langage de scripts expect, qui est conçu spécifiquement pour alimenter l'entrée de programmes interactifs.
Une chaîne en ligne peut être considéré comme une forme minimale du document en ligne. Il consiste simplement en la chaîne COMMANDE <<<$MOT où $MOT est étendu et est initialisé via l'entrée standard (stdin) de COMMANDE.
Comme exemple de base, considérez cette alternative à la construction echo-grep.
# Au lieu de : if echo "$VAR" | grep -q txt # if [[ $VAR = *txt* ]] # etc. # Try: if grep -q "txt" <<< "$VAR" then echo "$VAR contient la sous-chaîne \"txt\"" fi # Merci pour la suggestion, Sebastian Kaminski.
Ou en combinaison avec read :
Chaine="Ceci est une chaîne de mots." read -r -a Mots <<< "$Chaine" # L'option -a pour "lire" affecte les valeurs résultants #+ aux membres d'un tableau. echo "Le premier mot de Chaine est : ${Mots[0]}" # Ceci echo "Le deuxième mot de Chaine est : ${Mots[1]}" # est echo "Le troisième mot de Chaine est : ${Mots[2]}" # une echo "Le quatrième mot de Chaine est : ${Mots[3]}" # chaîne echo "Le cinquième mot de Chaine est : ${Mots[4]}" # de echo "Le sixième mot de Chaine est : ${Mots[5]}" # mots. echo "Le septième mot de Chaine est : ${Mots[6]}" # (null) # On dépasse la fin de $Chaine. # Merci à Francisco Lobo pour sa suggestion.
Exemple 18.13. Ajouter une ligne au début d'un fichier
#!/bin/bash # prepend.sh: Ajoute du texte au début d'un fichier. # # Exemple contribué par Kenny Stauffer, #+ et légèrement modifié par l'auteur du document. E_FICHIERINEXISTANT=65 read -p "Fichier : " fichier # argument -p pour que 'read' affiche l'invite. if [ ! -e "$fichier" ] then # Quitte si le fichier n'existe pas. echo "Fichier $fichier introuvable." exit $E_FICHIERINEXISTANT fi read -p "Titre : " titre cat - $fichier <<<$titre > $fichier.nouveau echo "Le fichier modifié est $fichier.nouveau" exit 0 # provenant de 'man bash' # Chaînes en ligne # Une variante des documents en ligne, le format est : # # <<<mot # # Le mot est étendu et fourni à la commande sur son entrée standard.
Exemple 18.14. Analyser une boîte mail
#!/bin/bash # Script par Francisco Lobo, #+ et légèrement modifié par l'auteur du guide ABS. # Utilisé avec sa permission dans le guide ABS (Merci !). # Ce script ne fonctionnera pas avec les versions de Bash antérieures à la 3.0. E_ARGS_MANQUANTS=67 if [ -z "$1" ] then echo "Usage: $0 fichier-mailbox" exit $E_ARGS_MANQUANTS fi mbox_grep() # Analyse le fichier mailbox. { declare -i corps=0 correspondance=0 declare -a date emetteur declare mail entete valeur while IFS= read -r mail # ^^^^ Réinitialise $IFS. # Sinon, "read" supprimera les espaces devant et derrière sa cible. do if [[ $mail =~ "^From " ]] # correspondance du champ "From" dans le message. then (( corps = 0 )) # Variables ré-initialisées. (( correspondance = 0 )) unset date elif (( corps )) then (( correspondance )) # echo "$mail" # Décommentez la ligne ci-dessus si vous voulez afficher #+ le corps entier du message. elif [[ $mail ]]; then IFS=: read -r entete valeur <<< "$mail" # ^^^ "chaîne intégrée" case "$entete" in [Ff][Rr][Oo][Mm] ) [[ $valeur =~ "$2" ]] && (( correspondance++ )) ;; # correspondance de la ligne "From". [Dd][Aa][Tt][Ee] ) read -r -a date <<< "$valeur" ;; # ^^^ # correspondance de la ligne "Date". [Rr][Ee][Cc][Ee][Ii][Vv][Ee][Dd] ) read -r -a sender <<< "$valeur" ;; # ^^^ # correspondance de l'adresse IP (pourrait être f). esac else (( corps++ )) (( correspondance )) && echo "MESSAGE ${date:+of: ${date[*]} }" # Tableau entier $date ^ echo "IP address of sender: ${sender[1]}" # Second champ de la ligne "Received"^ fi done < "$1" # Redirige le stdout du fichier dans une boucle. } mbox_grep "$1" # Envoie le fichier mailbox. exit $? # Exercices : # ---------- # 1) Cassez la seule fonction, ci-dessus, dans plusieurs fonctions. # 2) Ajoutez des analyses supplémentaires dans le script, en vérifiant plusieurs mots-clés. $ mailbox_grep.sh scam_mail MESSAGE of Thu, 5 Jan 2006 08:00:56 -0500 (EST) IP address of sender: 196.3.62.4
Exercice : trouver d'autres utilisations des chaînes en ligne.