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).

Documents pareils