sed: lire le fichier entier dans l'espace modèle sans échouer sur l'entrée sur une seule ligne


9

La lecture d'un fichier entier dans l'espace modèle est utile pour remplacer les retours à la ligne, etc. et il existe de nombreux cas qui conseillent ce qui suit:

sed ':a;N;$!ba; [commands...]'

Cependant, il échoue si l'entrée ne contient qu'une seule ligne.

Par exemple, avec une entrée sur deux lignes, chaque ligne est soumise à la commande de substitution:

$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g'
xxxbc
cxxxt

Mais, avec une entrée sur une seule ligne, aucune substitution n'est effectuée:

$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g'
abc

Comment écrire une sedcommande pour lire toutes les entrées en même temps et ne pas avoir ce problème?


J'ai modifié votre question pour qu'elle contienne une vraie question. Vous pouvez attendre d'autres réponses si vous le souhaitez, mais marquez éventuellement la meilleure réponse comme acceptée (voir le bouton du tuyau à gauche de la réponse, juste en dessous des boutons fléchés de haut en bas).
John1024

@ John1024 Merci, c'est bien d'avoir un exemple. Trouver ce genre de chose tend à me rappeler que "tout va mal" mais je suis content que certains d'entre nous n'abandonnent pas. :}
dicktyr

2
Il y a une troisième option! Utilisez l' sed -zoption de GNU . Si votre fichier n'est pas nul, il sera lu jusqu'à la fin du fichier! Trouvé à partir de ceci: stackoverflow.com/a/30049447/582917
CMCDragonkai

Réponses:


13

Il y a toutes sortes de raisons pour lesquelles la lecture d'un fichier entier dans l'espace modèle peut mal tourner. Le problème logique de la question entourant la dernière ligne est un problème courant. Il est lié au sedcycle de ligne de - lorsqu'il n'y a plus de lignes et qu'il sedrencontre EOF - il arrête le traitement. Et donc si vous êtes sur la dernière ligne et que vous demandez sedà en obtenir un autre, cela va s'arrêter là et ne rien faire de plus.

Cela dit, si vous avez vraiment besoin de lire un fichier entier dans l'espace modèle, il vaut probablement la peine d'envisager un autre outil de toute façon. Le fait est, sedest éponyme de l' éditeur de flux - il est conçu pour fonctionner une ligne - ou un bloc de données logique - à la fois.

Il existe de nombreux outils similaires qui sont mieux équipés pour gérer des blocs de fichiers complets. edet ex, par exemple, peut faire beaucoup de ce qui sedpeut faire et avec une syntaxe similaire - et bien plus encore - mais plutôt que de fonctionner uniquement sur un flux d'entrée tout en le transformant en sortie comme il le sedfait, ils conservent également des fichiers de sauvegarde temporaires dans le système de fichiers . Leur travail est mis en mémoire tampon sur le disque selon les besoins, et ils ne s'arrêtent pas brusquement à la fin du fichier (et ont tendance à imploser beaucoup moins souvent sous la pression du tampon) . De plus, ils offrent de nombreuses fonctions utiles qui sed- comme celles qui n'ont tout simplement pas de sens dans un contexte de flux - comme les marques de ligne, l'annulation, les tampons nommés, la jointure, etc.

sedLa principale force de la société est sa capacité à traiter les données dès qu'elles les lisent - rapidement, efficacement et en continu. Lorsque vous récupérez un fichier, vous le jetez et vous avez tendance à rencontrer des problèmes de casse comme le dernier problème de ligne que vous mentionnez, des dépassements de mémoire tampon et des performances épouvantables - à mesure que les données qu'il analyse augmentent en longueur, le temps de traitement d'un moteur d'expression régulière lors de l'énumération des correspondances augmente de façon exponentielle .

En ce qui concerne ce dernier point, soit dit en passant: même si je comprends que l'exemple s/a/A/gest très probablement un exemple naïf et n'est probablement pas le script réel que vous souhaitez rassembler dans une entrée, vous pourriez trouver utile de vous familiariser avec y///. Si vous vous retrouvez souvent en gtrain de substituer un seul caractère par un autre à un lob, cela ypourrait vous être très utile. C'est une transformation par opposition à une substitution et est beaucoup plus rapide car elle n'implique pas une expression rationnelle. Ce dernier point peut également être utile lors de la tentative de conservation et de répétition d' //adresses vides car il ne les affecte pas mais peut être affecté par celles-ci. Dans tous les cas, y/a/A/c'est un moyen plus simple d'accomplir la même chose - et les échanges sont également possibles comme:y/aA/Aa/ qui échangeraient tous les majuscules / minuscules comme sur une ligne les uns pour les autres.

Vous devez également noter que le comportement que vous décrivez n'est vraiment pas censé se produire de toute façon.

De GNU info seddans la section BOGUES RAPPORTS COMMUNS :

  • N commande sur la dernière ligne

    • La plupart des versions de sedexit n'impriment rien lorsque la Ncommande est émise sur la dernière ligne d'un fichier. GNU sedimprime l'espace de motif avant de quitter, à moins bien sûr que le -ncommutateur de commande n'ait été spécifié. Ce choix se fait par conception.

    • Par exemple, le comportement de sed N foo bardépendrait de si foo a un nombre pair ou impair de lignes. Ou, lors de l'écriture d'un script pour lire les quelques lignes suivantes après une correspondance de modèle, les implémentations traditionnelles de sedvous forceraient à écrire quelque chose comme /foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }au lieu de juste /foo/{ N;N;N;N;N;N;N;N;N; }.

    • Dans tous les cas, la solution de contournement la plus simple consiste à utiliser des $d;Nscripts reposant sur le comportement traditionnel ou à définir la POSIXLY_CORRECTvariable sur une valeur non vide.

La POSIXLY_CORRECTvariable d'environnement est mentionnée car POSIX spécifie que si sedrencontre EOF lors d'une tentative, Nelle doit se fermer sans sortie, mais la version GNU rompt intentionnellement avec la norme dans ce cas. Notez également que même si le comportement est justifié ci-dessus, l'hypothèse est que le cas d'erreur est celui de l'édition de flux - et non la fusion d'un fichier entier en mémoire.

La norme définit Nainsi le comportement de:

  • N

    • Ajoutez la ligne d'entrée suivante, moins sa ligne terminale \n, à l'espace de motif, en utilisant une ligne intégrée \npour séparer le matériau ajouté du matériau d'origine. Notez que le numéro de ligne actuel change.

    • Si aucune ligne d'entrée suivante n'est disponible, le Nverbe de commande doit se ramifier à la fin du script et quitter sans démarrer un nouveau cycle ou copier l'espace de modèle sur la sortie standard.

Sur cette note, il y a d'autres GNU-ismes démontrés dans la question - en particulier l'utilisation des crochets d' :étiquette, de branch et {de contexte de fonction }. En règle générale, toute sedcommande qui accepte un paramètre arbitraire est \ncensée être délimitée au niveau d'une ligne électronique dans le script. Donc les commandes ...

:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...

... sont tous très susceptibles de fonctionner de manière irrégulière en fonction de l' sedimplémentation qui les lit. Portablement, ils devraient être écrits:

...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}

La même chose vaut pour r, w, t, a, iet c (et peut - être un peu plus que je suis oublier pour le moment) . Dans presque tous les cas, ils pourraient également être écrits:

sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
    "//{ do arbitrary list of commands" -e \}

... où la nouvelle -einstruction xecution \nremplace le délimiteur ewline. Donc, là où le infotexte GNU suggère qu'une implémentation traditionnelle sedvous obligerait à faire :

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }

... ça devrait plutôt être ...

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}

... bien sûr, ce n'est pas vrai non plus. Écrire le script de cette façon est un peu idiot. Il existe des moyens beaucoup plus simples de faire de même, comme:

printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
         //!g;x;$!d;:nd' -e 'l;$a\' \
     -e 'this is the last line' 

... qui imprime:

foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line

... car la tcommande est - comme la plupart des sedcommandes - dépend du cycle de ligne pour rafraîchir son registre de retour et ici le cycle de ligne est autorisé à faire la plupart du travail. C'est un autre compromis que vous faites lorsque vous slurpez un fichier - le cycle de ligne ne se rafraîchit plus jamais, et de nombreux tests se comporteront anormalement.

La commande ci-dessus ne risque pas de dépasser la saisie car elle ne fait que des tests simples pour vérifier ce qu'elle lit en le lisant. Avec Hold, toutes les lignes sont ajoutées à l'espace d'attente, mais si une ligne correspond, /foo/elle remplace l' hancien espace. Les tampons sont ensuite xmodifiés et une s///substitution conditionnelle est tentée si le contenu du tampon correspond au //dernier motif adressé. En d'autres termes, //s/\n/&/3ptente de remplacer le troisième retour à la ligne dans l'espace d'attente par lui-même et d'imprimer les résultats si l' espace d'attente correspond actuellement /foo/. Si cela tréussit, le script se branche sur l' étiquette not delete - qui fait un tour let termine le script.

Dans le cas où les deux /foo/et une troisième nouvelle ligne ne peuvent pas être appariés ensemble dans l'espace de retenue, alors, //!gils écraseront le tampon s'ils /foo/ne sont pas appariés, ou, s'ils sont appariés, ils écraseront le tampon si une \newline n'est pas appariée (remplaçant ainsi /foo/par lui-même) . Ce petit test subtil empêche le tampon de se remplir inutilement pendant de longues périodes de non /foo/et garantit que le processus reste accrocheur car l'entrée ne s'accumule pas. En cas de non /foo/ou d' //s/\n/&/3péchec, les tampons sont à nouveau échangés et chaque ligne, sauf la dernière, est supprimée.

Ce dernier - la dernière ligne $!d- est une simple démonstration de la façon dont un sedscript descendant peut être fait pour gérer facilement plusieurs cas. Lorsque votre méthode générale consiste à tailler les cas indésirables en commençant par les plus généraux et en travaillant vers les cas les plus spécifiques, les cas marginaux peuvent être plus facilement traités car ils sont simplement autorisés à passer à la fin du script avec vos autres données souhaitées et quand tout vous enveloppe avec les seules données que vous souhaitez. Cependant, il peut être beaucoup plus difficile de récupérer de tels cas de bord en boucle fermée.

Et voici donc la dernière chose que j'ai à dire: si vous devez vraiment extraire un fichier entier, vous pouvez vous tenir à faire un peu moins de travail en vous appuyant sur le cycle de ligne pour le faire pour vous. En règle générale, vous utiliseriez Next et next pour l' anticipation - car ils avancent avant le cycle de ligne. Plutôt que d'implémenter de manière redondante une boucle fermée dans une boucle - comme le sedcycle de ligne est de toute façon une simple boucle de lecture - si votre but est uniquement de collecter des entrées sans discernement, il est probablement plus facile de le faire:

sed 'H;1h;$!d;x;...'

... qui rassemblera l'intégralité du fichier ou fera faillite.


une note latérale sur Net le comportement de dernière ligne ...

même si je n'ai pas les outils à ma disposition pour tester, considérez que Nlors de la lecture et de l' édition sur place se comporte différemment si le fichier édité est le fichier de script pour la prochaine lecture.


1
Faire passer l'inconditionnel en Hpremier est beau.
2015

@mikeserv Merci pour votre contribution. Je peux voir un avantage potentiel à garder le cycle de ligne, mais comment cela fonctionne-t-il moins?
dicktyr

@dicktyr bien, la syntaxe prend quelques raccourcis :a;$!{N;ba}comme je le mentionne ci-dessus - il est plus facile d'utiliser le formulaire standard à long terme lorsque vous essayez d'exécuter des expressions rationnelles sur des systèmes inconnus. Mais ce n'était pas vraiment ce que je voulais dire: vous implémentez une boucle fermée - vous ne pouvez pas aussi facilement entrer au milieu de cela lorsque vous le souhaitez que vous le feriez plutôt en vous ramifiant - en élaguant les données indésirables - et en laissant le cycle se produire. C'est comme une chose descendante - tout ce sedqui se produit est le résultat direct de ce qu'il vient de faire. Peut-être que vous le voyez différemment - mais si vous l'essayez, vous trouverez peut-être que le script est plus facile.
mikeserv

11

Il échoue car la Ncommande vient avant la correspondance de modèle $!(pas la dernière ligne) et sed se ferme avant d'effectuer tout travail:

N

Ajoutez une nouvelle ligne à l'espace modèle, puis ajoutez la ligne d'entrée suivante à l'espace modèle. S'il n'y a plus d'entrée, sed quitte sans traiter d'autres commandes .

Cela peut être facilement corrigé pour fonctionner avec une entrée sur une seule ligne (et en fait pour être plus clair dans tous les cas) en regroupant simplement les commandes Net baprès le modèle:

sed ':a;$!{N;ba}; [commands...]'

Cela fonctionne comme suit:

  1. :a créer une étiquette nommée 'a'
  2. $! sinon la dernière ligne, alors
  3. Najoutez la ligne suivante à l'espace de motif (ou quittez s'il n'y a pas de ligne suivante) et babranchez (allez à) l'étiquette 'a'

Malheureusement, ce n'est pas portable (car il repose sur des extensions GNU), mais l'alternative suivante (suggérée par @mikeserv) est portable:

sed 'H;1h;$!d;x; [commands...]'

J'ai posté ceci ici parce que je n'ai pas trouvé les informations ailleurs et je voulais les rendre disponibles afin que d'autres puissent éviter des problèmes avec la diffusion :a;N;$!ba;.
dicktyr le

Merci d'avoir posté! N'oubliez pas qu'accepter votre propre réponse est également une bonne chose. Il vous suffit d'attendre un moment avant que le système ne vous le permette.
terdon
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.