TP : awk(1) et sed(1)
Transcription
TP : awk(1) et sed(1)
TP : awk(1) et sed(1) Alexandra Volanschi [email protected] Pascal Cabaud [email protected] En reprenant le fréquenceur précédemment étudié, écrire un script qui affche le pourcentage d'apparitions des mots. $ cat -n freq-pct.sh 1 #!/bin/sh 2 tr -cs "[:alpha:]" "\n" <$1 |\ 3 sort |\ 4 uniq -c |\ 5 awk '{ printf "%.2f %s\n", 100*$1/n, $2 }' \ 6 n=$(wc -w $1 | egrep -o "[0-9]+") Les premières lignes sont triviales à ce stade du cours. La dernière ligne permet d'indiquer à awk(1) le nombre total de mots. En effet, wc(1) renvoie une ligne comme : 63 file.txt egrep(1) extrait alors le nombre recherché, qui est passé à la variable n. Cette valeur est utilisée dans la formule 100*$1/n. Pour fnir, printf() et les formats ont été évoqués en cours. Le script s'utilise comme suit (après avoir réglé le droit d'exécution sur le fchier) : $ ./freq-pct.sh fichier.txt Écrire un script qui cherche une expression régulière dans un fot de texte et le présente en contexte en HTML. Par exemple, si motif est recherché, on veut obtenir : ... <tr><td>avant</td><td>motif</td><td>après</td></tr> ... Le script suivant fait (à peu près) le job : #!/bin/sh regex=$1 sed -e "/$regex/s/\($regex\)/<\/td><td>\1<\/td><td>/; \ /$regex/s/^/<tr><td>/; \ /$regex/s/$/<\/td><\/tr>/; \ /$regex/!d" $2 Il s'utilise comme suit (après avoir réglé le droit d'exécution sur le fchier) : $ ./concord-html.sh ”r[e].ex” file.txt La dernière ligne du script supprime toutes les lignes qui ne matchent pas. Les deux précédentes griment le début et la fn de la ligne. La première commande sed(1) se charge de mettre en valeur le motif recherché. Une optimisation évidente consiste à commencer par supprimer les lignes qui ne matchent pas : ainsi, les autres transformations ne s'appliqueront qu'aux lignes utiles. Sur des fots importants (corpus de millions de lignes), cela peut infuencer lourdement sur le temps de traitement. Pour améliorer, on pourrait aussi spécifer le nombre de caractères voire même de mots avant et après le motif. Pour cela, le lecteur assidu pourrait commencer par transformer le fchier en un fot d'une seule ligne. Si on voulait ne spécifer que le nombre de lignes avant et après, l'usage de grep -$3 (avec comme troisième argument le nombre de lignes donc) paraît simple : il sufft alors de mettre sur une ligne ce qui est compris entre les lignes ne contenant que --. Tout cela est laissé en exercice au lecteur. Écrire un script qui cherche une expression régulière dans un fchier ODT passé en argument et affche le contexte. Choisissons un fchier ODT et observons son contenu dans un répertoire temporaire : $ mkdir odt-obs && cd odt-obs $ cp /path/to/file.odt f.zip && unzip f.zip On note la présence d'un fchier nommé content.xml. Un rapide coup d'œil nous montre qu'il s'agit du texte du document… en XML et sur une seule ligne. Pour une rapide entrée en matière sur XML, voir par exemple http://fr.wikipedia.org/wiki/XML. Donc, nous devons : • supprimer les balises XML. • n'affcher que N caractères ou mots avant et après le motif, Avec sed(1), ces balises disparaissent ainsi : $ sed -e "s/<[^>]*>//g" content.xml On supprime ici tout les caractères entre les chevrons < et >. Ces caractères ne peuvent pas être un chevron ouvrant sans quoi le fchier XML est invalide. De plus, nous excluons les chevrons fermants en défnissant la classe [^>] et l'étoile engloutie alors tous les caractères compris entre ces deux bornes. Il reste alors à rechercher et affcher le motif. Si on veut indiquer un nombre de mots avant et après, il sufft de transformer le fot pour avoir un mot par ligne puis d'utiliser grep(1) avec l'option -N (où N sera le nombre de mots et passé en argument au script fnal) et remettre en forme les lignes entre --. On pourrait aussi vouloir travailler sur des phrases mais — contrairement à la question 2 du TP sur sed(1) — on va rencontrer des cas où la séparation entre les phrases n'est pas si claire. En effet, on a supprimé le balisage XML… qui marquait les sauts de lignes, paragraphes, etc. La commande : $ sed -e "s/<[^>]*>//g" content.xml | tr -sc "[[:alnum:]]" "\n" tokenise les mots. Appliquons par exemple grep -2 et (pour simuler la recherche du motif trivial et) à ce fot et nous obtenons : ... -first words et shifted words -serait cassé et le fichier ... Pour remettre en forme, appliquons tr "\n" " "|sed -e "s/-- /\n/g" pour tout mettre sur une seule ligne puis transformer les chaînes "-- " en un saut de ligne. Le script demandé pourrait donc être : $ cat -n grep-odt.sh 1 #!/bin/sh 2 3 d=$(pwd) 4 tmpdir=$0.$$ 5 6 g=$(basename $2 | sed -e "s/\.odt/.zip/") 7 8 mkdir /tmp/$tmpdir && cd /tmp/$tmpdir 9 cp $d/$2 $g 10 11 unzip $g 12 13 sed -e "s/<[^>]*>//g;" content.xml |\ 14 tr -sc "[[:alnum:]]" "\n" |\ 15 grep -$3 "$1" |\ 16 tr "\n" " "|\ 17 sed -e "s/-- /\n/g" 18 19 cd $d 20 rm -rf /tmp/$tmpdir À la ligne 3, on mémorise le répertoire courant pour pouvoir y retourner (lignes 19 et 20). La ligne 4 utilise la variable $$ qui correspond au numéro du processus courant (donc celui du shell qui interprètera ce script) ; ce numéro est unique à tout instant. Ainsi, nous sommes assurés de l'unicité de notre répertoire de travail (lignes 8 et 9) : ne pas oublier qu'un système Unix est multiutilisateurs et que /tmp est accessible pour tout un chacun. Enfn, la ligne 6 utilise la commande basename(1)1 ; cette dernière affche le nom du fchier passé en argument en ayant supprimé ceux des éventuels répertoires parents. Exemples : $ basename /usr/local/bin/detox detox $ echo /usr/local/bin/detox | sed -e "s/[^\/]*\///g" detox Enfn, les lignes 11 à 17 reprennent ce qui a été dit ci-dessus. Pour fnir, le script grep-odt.sh s'utilise comme suit (après avoir réglé le droit d'exécution sur le fchier) : $ ./grep-odt.sh motif fichier.odt num où num est le nombre de mots à affcher avant et après le motif (utilisé ligne 15). 1 Voir aussi dirname(1).