Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Supprimer les dernières lignes « vides » d'un fichier texte

5 réponses
Avatar
Francois Lafont
Bonjour à tous,

Tout est dans le titre. J'entends par ligne « vide » une ligne qui correspond à la regex /^[[:space:]]*$/. J'aimerais bien si possible me limiter à du sed par exemple ou éventuellement à du awk.

Je précise que :

sed '/^[[:space:]]*$/d' mon-fichier.txt

ne me convient pas car il supprime *toutes* les lignes vides du fichier, or je voudrais seulement supprimer les *dernières* lignes vides du fichier. C'est ce point là sur lequel je bloque et qui fait que je viens poster ici.

Merci d'avance.

--
François Lafont

5 réponses

Avatar
Loki Harfagr
Thu, 18 Jul 2013 18:27:37 +0200, Francois Lafont did cat :

Bonjour à tous,

Tout est dans le titre. J'entends par ligne « vide » une ligne qui correspond à la regex /^[[:space:]]*$/. J'aimerais bien si possible me limiter à du sed par exemple ou éventuellement à du awk.

Je précise que :

sed '/^[[:space:]]*$/d' mon-fichier.txt

ne me convient pas car il supprime *toutes* les lignes vides du fichier, or je voudrais seulement supprimer les *dernières* lignes vides du fichier. C'est ce point là sur lequel je bloque et qui fait que je viens poster ici.

Merci d'avance.



Bon, il y aurait beaucoup à dire sur ce genre de truc dans l'absolu
mais si l'on suppose qu'il s'agit de fichiers humains et
pas d'archives monofiche gigantesques et que les babasses ont
au moins la puissance d'un z80 ceci est ce que je ferais :
$ tac ton-fichier.txt | awk '!/^[[:space:]]*$/{a=1}; a' | tac

Si ta question n'était pas une "question pratique" mais une
jeu de l'été pour du codage tous styles on pourrait s'amuser
avec des trucs plus graves mais bon, là j'ai trop chaud ;D)
Avatar
Benoit Izac
Bonjour,

le 18/07/2013 à 18:27, Francois Lafont a écrit dans le message
<51e81779$0$3755$ :

Tout est dans le titre. J'entends par ligne « vide » une ligne qui
correspond à la regex /^[[:space:]]*$/. J'aimerais bien si possible me
limiter à du sed par exemple ou éventuellement à du awk.

Je précise que :

sed '/^[[:space:]]*$/d' mon-fichier.txt

ne me convient pas car il supprime *toutes* les lignes vides du
fichier, or je voudrais seulement supprimer les *dernières* lignes
vides du fichier. C'est ce point là sur lequel je bloque et qui fait
que je viens poster ici.



sed -n '1h;1!H;${x;s/[[:space:]]*$//;p}' mon-fichier.txt

1h;1!H ajoute le contenu de mon fichier dans le buffer additionnel

${...} le bloc ne sera exécuté que pour la dernière ligne

x échange le buffer additionnel et le buffer principal

s/[[:space:]]*$// supprime tout les espaces consécutifs à la fin du
fichier

p affiche le contenu du buffer principal

Plus concis en Perl :
perl -0777pe 's/s*$/n/' mon-fichier.txt

--
Benoit Izac
Avatar
Francois Lafont
Salut,

Déjà, merci à tous les deux pour vos réponses, toutes les deux super efficaces. J'aurais bien voulu avoir quelques précisions si c'est possible.

Loki a écrit :

$ tac ton-fichier.txt | awk '!/^[[:space:]]*$/{a=1}; a' | tac



Pas mal le coup du « tac-tac » (je ne connaissais pas cette commande). Je pense avoir compris le principe mais il y a juste la syntaxe :

... | awk '...;a'

qui me gêne un peu. Quand a vaut 1, sauf erreur de ma part on a :

awk 'a' <=> awk '1' <=> awk '1 { print $0 }'

Et là j'aimerais bien avoir une explication. En effet, je connais le classique « /regex/ { ... } », mais mettre juste 1 à la place de la regex je ne vois pas trop. Ça serait un peu comme dans certain langage, 1 est évalué comme différent de 0 et le test est positif (et donc on affiche) ? Et si a n'est pas définie, comme c'est le cas au début quand awk lit des lignes vides, alors a est évalué comme étant égal à une chaîne vide qui est évaluée à 0 et donc le test est négatif ? C'est un truc dans le genre ?

Benoît a écrit :

sed -n '1h;1!H;${x;s/[[:space:]]*$//;p}' mon-fichier.txt



La vache, au départ ça pique les yeux ! :-)

1h;1!H ajoute le contenu de mon fichier dans le buffer additionnel



Ok, après avoir consulté Google, j'ai compris que 1h met dans le hold buffer (le buffer additionnel) la ligne 1 et ensuite, avec 1!H, pour toutes les lignes sauf la ligne 1, on les *ajoute* (H) dans le hold buffer. Une fois la dernière ligne lue par sed, le hold buffer contient alors tout le fichier.

Question : au départ je me suis dit qu'on pouvait tout simplement mettre « H » à la place de « 1h;1!H ». Mais ça n'est pas tout à fait équivalent car je me retrouve avec une ligne vide ajoutée au tout début de la sortie de sed. Pourquoi ?

${...} le bloc ne sera exécuté que pour la dernière ligne



Ok et à ce moment là le hold buffer contient tout le fichier.

x échange le buffer additionnel et le buffer principal



Ok.

s/[[:space:]]*$// supprime tout les espaces consécutifs à la fin du
fichier



Et là je coince un peu. Normalement sed fonctionne ligne par ligne et là, sauf erreur de ma part, tout se passe comme si ton buffer principal était en un seul morceau (alors qu'il est bien constitué de plusieurs lignes) et comme si « [[:space:]]*$ » matchait les blancs avec les sauts de ligne compris.

1. Déjà, [[:space:]] ça matche aussi les sauts de lignes (autrement dit les sauts de lignes font partie des blancs) ?

2. Comment se fait-il que sed voit son buffer principal en un seul morceau alors que ce morceau fait plusieurs lignes ? Je pensais que sed fonctionnait ligne par ligne ?

Merci encore.


--
François Lafont
Avatar
Benoit Izac
Bonjour,

le 19/07/2013 à 04:53, Francois Lafont a écrit dans le message
<51e8aa20$0$2415$ :

$ tac ton-fichier.txt | awk '!/^[[:space:]]*$/{a=1}; a' | tac



Pas mal le coup du « tac-tac » (je ne connaissais pas cette commande).



Note que tac n'est pas standard (POSIX).

Je pense avoir compris le principe mais il y a juste la syntaxe :

... | awk '...;a'

qui me gêne un peu. Quand a vaut 1, sauf erreur de ma part on a :

awk 'a' <=> awk '1' <=> awk '1 { print $0 }'

Et là j'aimerais bien avoir une explication. En effet, je connais le
classique « /regex/ { ... } », mais mettre juste 1 à la place de la
regex je ne vois pas trop.



http://pubs.opengroup.org/onlinepubs/7908799/xcu/awk.html

Overall Program Structure
An awk program is composed of pairs of the form:

pattern { action }

Either the pattern or the action (including the enclosing brace
characters) can be omitted.

A missing pattern shall match any record of input, and a missing
action shall be equivalent to:

{ print }

Ça serait un peu comme dans certain langage, 1 est évalué comme
différent de 0 et le test est positif (et donc on affiche) ? Et si
a n'est pas définie, comme c'est le cas au début quand awk lit des
lignes vides, alors a est évalué comme étant égal à une chaîne vide
qui est évaluée à 0 et donc le test est négatif ? C'est un truc dans
le genre ?



Comme dans beaucoup de langage :
0 => faux
différent de 0 => vrai

sed -n '1h;1!H;${x;s/[[:space:]]*$//;p}' mon-fichier.txt



La vache, au départ ça pique les yeux ! :-)

1h;1!H ajoute le contenu de mon fichier dans le buffer additionnel



Ok, après avoir consulté Google, j'ai compris que 1h met dans le hold
buffer (le buffer additionnel) la ligne 1 et ensuite, avec 1!H, pour
toutes les lignes sauf la ligne 1, on les *ajoute* (H) dans le hold
buffer. Une fois la dernière ligne lue par sed, le hold buffer
contient alors tout le fichier.

Question : au départ je me suis dit qu'on pouvait tout simplement
mettre « H » à la place de « 1h;1!H ». Mais ça n'est pas tout à fait
équivalent car je me retrouve avec une ligne vide ajoutée au tout
début de la sortie de sed. Pourquoi ?



http://pubs.opengroup.org/onlinepubs/009695399/utilities/sed.html

[2addr]H
Append to the hold space a <newline> followed by the contents of the
pattern space.

${...} le bloc ne sera exécuté que pour la dernière ligne



Ok et à ce moment là le hold buffer contient tout le fichier.

x échange le buffer additionnel et le buffer principal



Ok.

s/[[:space:]]*$// supprime tout les espaces consécutifs à la fin du
fichier



Et là je coince un peu. Normalement sed fonctionne ligne par ligne et
là, sauf erreur de ma part, tout se passe comme si ton buffer
principal était en un seul morceau (alors qu'il est bien constitué de
plusieurs lignes) et comme si « [[:space:]]*$ » matchait les blancs
avec les sauts de ligne compris.

1. Déjà, [[:space:]] ça matche aussi les sauts de lignes (autrement
dit les sauts de lignes font partie des blancs) ?



Voir regex(7) qui mène wctype(3) pour finir sur isspace(3) :

isspace()
checks for white-space characters. In the "C" and "POSIX" locales,
these are: space, form-feed ('f'), newline ('n'), carriage
return ('r'), horizontal tab ('t'), and vertical tab ('v').

2. Comment se fait-il que sed voit son buffer principal en un seul
morceau alors que ce morceau fait plusieurs lignes ? Je pensais que
sed fonctionnait ligne par ligne ?



Sed _lit_ les données par lignes, ensuite 'n' est un caractère comme
les autres.

--
Benoit Izac
Avatar
Loki Harfagr
Fri, 19 Jul 2013 04:53:19 +0200, Francois Lafont did cat :

Salut,

Déjà, merci à tous les deux pour vos réponses, toutes les deux super efficaces. J'aurais bien voulu avoir quelques précisions si c'est possible.

Loki a écrit :

$ tac ton-fichier.txt | awk '!/^[[:space:]]*$/{a=1}; a' | tac



Pas mal le coup du « tac-tac » (je ne connaissais pas cette commande). Je pense avoir compris le principe mais il y a juste la syntaxe :

... | awk '...;a'

qui me gêne un peu. Quand a vaut 1, sauf erreur de ma part on a :

awk 'a' <=> awk '1' <=> awk '1 { print $0 }'



exact, l'action par défaut est le print du record en cours

Et là j'aimerais bien avoir une explication. En effet,
je connais le classique « /regex/ { ... } », mais mettre juste 1 à la
place de la regex je ne vois pas trop.
Ça serait un peu comme dans certain langage, 1 est évalué comme différent de 0 et
le test est positif (et donc on affiche) ?
Et si a n'est pas définie, comme c'est le cas au début quand awk lit des
lignes vides, alors a est évalué comme étant égal à une chaîne vide qui est
évaluée à 0 et donc le test est négatif ? C'est un truc dans le genre ?



presque, mais c'est plus simple encore :-)
d'abord, le principe de base du langage awk est pattern-->action
(et pas simplement regexp-->action comme tu semblais supposer,
la regexp est juste un cas particulier de "pattern" :-)

(
(note; si tu n'es pas hostile au globish le groupe comp.lang.awk et
diverses pistes que tu pourras y trouver sera plus adapté pour une réponse
plus exacte sur le sujet excitant des "paradigmes" de awk ;-)
)

à partir de là tu as déjà à peu près analysé le reste, je te le reformule
ci-dessous tel que je le sens et ça devrait éclairer tes derniers doutes ;-)

- tu nous as fourni une regexp décrivant précisément les "records" que tu
voulais éviter à condition qu'ils soient "les derniers" du fichier, j'en ai
inféré que le plus simple serait alors de partir de la fin et de ne commencer
à "imprimer" qu'une fois dépassée la partie évitable, cela indiquait donc
l'utilisation dans la "toolbox" d'un truc qui 'pousse' les fichiers d'entrée
en partant de la fin et en remontant, *tac* m'a semblé la meilleure idée pour
commencer.

- comme on lit en commençant par la fin l'algorithme devient simplement
"ne rien faire tant que" avec "tant que <==> autre chose que la regexp de refus est lu"

- en awk j'écris et pense cela directement comme "positionner un drapeau d'action"
dès que "autre chose que la regexp de refus est lu"

- une variable inconnue étant (en awk (et d'autres)) considérée comme ayant une
valeur nulle par defaut on peut donc s'en tenir à choisir de 'valuer' une variable
qui servira de drapeau lorsque "autre chose que la regexp de refus est lu"

- le *tac* de sortie remet les lignes/"records" dans l'ordre initial





Benoît a écrit :

sed -n '1h;1!H;${x;s/[[:space:]]*$//;p}' mon-fichier.txt



La vache, au départ ça pique les yeux ! :-)



ça peut, mais ça ne dure pas ;D)
C'est un peu ce que je sous-entendais dans mon post initial, j'ai répondu
avec une orientation "pratique", qui est finalement une "imitation" d'une
situation "pratique" du genre "rembobiner K7 en enregistrant dès que le
noise-gate détecte un niveau S/B correct puis passer l'enregistrement
généré en vitesse normale+reverse"

Mais dans l'absolu l'option "sed 'stocker tant que puis tout donner'"
est très bien, juste plus délicate à lire, plus dangereuse à écrire et
plus hasardeuse à relire six mois plus tard ;-)

en voici, pour exemple rapide, un quasi équivalent awk (en C-style) :
awk '!/^[[:space:]]*$/{a=NR}; {b[NR]=$0} END{for(i=1;i<=a;i++) print b[i]}'

mais après, la question de quand et pourquoi choisir quelle orientation
d'algo ne dépendra que d'options "systèmes et admin" (ex: outils existants/
dispos/interdits) ou "système et data" (ex: taille/perfsIO/RAMdispo)

Ce serait une autre histoire et probablement un autre "thread" dans
un autre groupe ;D)

...