J'avais un fichier TSV, dont certaines lignes étaient coupées par un retour chariot indésirable. Chaque ligne qui commençait par une tabulation devait donc être recollée à la ligne précédente. J'ai voulu le faire avec la commande sed, mais je me suis rendu compte que j'étais un peu rouillé pour faire plus qu'un simple s/foo/bar/
. Je le note donc ici pour ne plus perdre de temps, un jour, à retrouver comment sed fonctionne.
J'avais donc des lignes de ce style :
Il était une fois trois petits cochons qui vivaient avec leur maman dans une petite maison
Que je voulais remettre d'équerre comme ça :
Il était une fois trois petits cochons qui vivaient avec leur maman dans une petite maison
Il fallait donc que je demande à sed de regarder si la ligne courante (la ligne lue dans l'espace de travail, "pattern space" dans la doc en anglais) commence par une tabulation, et si c'est le cas, recoller avec la ligne précédente. Donc il fallait que je garde en permanence la ligne précédente dans le "hold space" (l'espace de rétention). Ce qui s'écrit en pseudo-algorithme :
x
)p
)x
)G
en majuscule)s/\n//
)
p
)
n
)
h
en minuscule)h
)
d
)$
, ne commence pas par une tabulation (pas le cas avec ce fichier) :p
)
Ce qui s'écrit en one-liner avec un peu d'espacement pour y voir clair :
sed -n '1{h;d}; /^\t/!{x;p}; /^\t/{x;G;s/\n//;p;n;h}; $p'
Je rappelle le contenu du fichier de départ, je numérote les lignes, et je décortique le fonctionnement ligne à ligne, un peu à la manière de sed --debug
:
1 Il était une fois 2 trois petits 3 cochons 4 qui vivaient avec leur maman 5 dans une 6 petite maison
ligne lue |
exécute |
pattern space |
hold space |
1 |
1{h;d} | Il était une fois | |
2 |
/^\t/!{x;p} | Il était une fois | trois petits |
3 |
/^\t/{x;G;s/\n//;p;n;h} |
trois petits cochons juste avant la commande n |
cochons juste avant la commande h |
4 déjà lue par la commande n | qui vivaient avec leur maman après la commande n, je n'affiche pas |
qui vivaient avec leur maman après la commande h |
|
5 |
/^\t/!{x;p} |
qui vivaient avec leur maman cette fois j'affiche | dans une |
6 |
/^\t/{x;G;s/\n//;p;n;h} |
dans une petite maison juste avant la commande n |
petite maison juste avant la commande h |
J'ai pourtant appris, dès que j'ai débuté, qu'il faut toujours RTFM… mais j'ai quand même passé quelques heures à mettre au point ma méthode avant de découvrir celle-ci qui est beaucoup plus simple, dans le manuel.
sed ':a ; $!N ; s/\n\t/\t/ ; ta ; P ; D'
N
) et s'il y a une tabulation juste après le retour chariot qui colle les deux lignes lues, il suffit de supprimer ce retour chariot et de laisser la tabulation avec la substitution s/\n\t/\t/
.t
permet de se brancher sur l'étiquette "a" seulement s'il y a eu une substitution.P
affiche l'espace de travail jusqu'au premier retour chariot, c'est-à-dire la ligne "précédente".D
dépend de si l'espace de travail contient des retours chariot ou pas.Bon, d'accord, sed c'est un peu barjot et j'aurais eu plus vite fait de le faire en Perl ou en PHP… Mais sed fait partie de la "culture Unix" donc c'est toujours un plaisir de l'utiliser 😉