Que requiert POSIX sed pour «1d; 1,2d» où une plage d'adresses commence à partir d'une ligne déjà supprimée?

11

Dans les commentaires de cette question, un cas est survenu où diverses implémentations sed n'étaient pas d'accord sur un programme assez simple, et nous (ou du moins je) n'avons pas été en mesure de déterminer ce que la spécification requiert réellement pour cela.

Le problème est le comportement d'une plage commençant à une ligne supprimée:

1d;1,2d

La ligne 2 doit-elle être supprimée même si le début de la plage a été supprimé avant d'atteindre cette commande? Mon attente initiale était "non" en ligne avec BSD sed, tandis que GNU sed dit "oui", et la vérification du texte de spécification ne résout pas entièrement le problème.

Correspondant à mes attentes sont (au moins) macOS et Solaris sed, et BSD sed. En désaccord sont (au moins) GNU et Busybox sed, et de nombreuses personnes ici. Les deux premiers sont certifiés SUS tandis que les autres sont probablement plus répandus. Quel comportement est correct?


Le texte de spécification pour les plages à deux adresses indique:

L' utilitaire sed doit ensuite appliquer en séquence toutes les commandes dont les adresses sélectionnent cet espace de modèle, jusqu'à ce qu'une commande démarre le cycle suivant ou se termine.

et

Une commande d'édition avec deux adresses doit sélectionner la plage inclusive du premier espace de modèle qui correspond à la première adresse au prochain espace de modèle qui correspond à la seconde. [...] À partir de la première ligne suivant la plage sélectionnée, sed recherchera à nouveau la première adresse. Par la suite, le processus doit être répété.

On peut soutenir que la ligne 2 est dans "la plage inclusive du premier espace de modèle qui correspond à la première adresse au prochain espace de modèle qui correspond à la seconde", que le point de départ ait été supprimé ou non. D'un autre côté, je m'attendais à ce que le premier dpasse au cycle suivant et ne donne pas à la gamme une chance de commencer. Les implémentations certifiées UNIX ™ font ce que j'attendais, mais potentiellement pas ce que la spécification exige.

Certaines expériences illustratives suivent, mais la question clé est la suivante : ce qui devrait sed faire quand une gamme commence sur une ligne supprimée?


Expériences et exemples

Voici une démonstration simplifiée du problème, qui imprime des copies supplémentaires de lignes plutôt que de les supprimer:

printf 'a\nb\n' | sed -e '1d;1,2p'

Cela fournit seddeux lignes d'entrée, aet b. Le programme fait deux choses:

  1. Supprime la première ligne avec 1d. La dcommande sera

    Supprimez l'espace de motif et commencez le cycle suivant. et

  2. Sélectionnez la plage de lignes de 1 à 2 et imprimez-les explicitement, en plus de l'impression automatique reçue par chaque ligne. Une ligne incluse dans la plage doit donc apparaître deux fois.

Je m'attendais à ce que cela imprime

b

uniquement, la plage ne s'appliquant pas car elle 1,2n'est jamais atteinte pendant la ligne 1 (car elle est déjà dpassée au cycle / ligne suivant) et donc l'inclusion de la plage ne commence jamais, alors qu'elle aa été supprimée. Les Unix conformes sedde macOS et de Solaris 10 produisent cette sortie, tout comme les non-POSIX seddans Solaris et BSD seden général.

GNU sed, d'autre part, imprime

b
b

indiquant qu'il a interprété la plage. Cela se produit à la fois en mode POSIX et non. Sed de Busybox a le même comportement (mais pas toujours un comportement identique, donc il ne semble pas être le résultat d'un code partagé).

Expérimentation supplémentaire avec

printf 'a\nb\nc\nd\ne\n' | sed -e '2d;2,/c/p'
printf 'a\nb\nc\nd\ne\n' | sed -e '2d;2,/d/p'

constate qu'il semble traiter une plage commençant à une ligne supprimée comme si elle commençait sur la ligne suivante . Ceci est visible car /c/ne correspond pas à la fin de la plage. Utiliser /b/pour démarrer la plage ne se comporte pas de la même manière que 2.


L'exemple de travail initial que j'utilisais était

printf '%s\n' a b c d e | sed -e '1{/a/d;};1,//d'

comme un moyen de supprimer toutes les lignes jusqu'à la première /a/correspondance, même si c'est sur la première ligne (ce que GNU sed utiliserait 0,/a/d- c'était une tentative de rendu compatible POSIX de cela).

Il a été suggéré que cela devrait à la place supprimer jusqu'à la deuxième correspondance de /a/si la première ligne correspond (ou le fichier entier s'il n'y a pas de deuxième correspondance), ce qui semble plausible - mais encore une fois, seul GNU sed le fait. MacOS sed et sed de Solaris produisent

b
c
d
e

pour cela, comme je m'y attendais (GNU sed produit la sortie vide en supprimant la plage non terminée; Busybox sed imprime juste det e, ce qui est clairement faux quoi qu'il arrive). En général, je suppose que le fait d'avoir réussi les tests de conformité de la certification signifie que leur comportement est correct, mais suffisamment de personnes ont suggéré sinon que je ne suis pas sûr, le texte de spécification n'est pas complètement convaincant et la suite de tests ne peut pas être parfaitement complet.

De toute évidence, il n'est pas pratiquement portable d'écrire ce code aujourd'hui étant donné l'incohérence, mais théoriquement, il devrait être équivalent partout avec une signification ou une autre. Je pense que c'est un bug, mais je ne sais pas contre quelle (s) implémentation (s) le signaler. Mon avis est actuellement que le comportement de GNU et de Busybox sed est incompatible avec la spécification, mais je peux me tromper à ce sujet.

Que requiert POSIX ici?

Michael Homer
la source
Comme solution temporaire, écrivez dans un fichier temporaire et traitez-le avec POSIX ed, en contournant sedcomplètement?
D. Ben Knoble

Réponses:

9

Cela a été soulevé sur la liste de diffusion du groupe Austin en mars 2012. Voici le dernier message à ce sujet (par Geoff Clare du groupe Austin (l'organisme qui maintient POSIX), qui est également celui qui a soulevé la question en premier lieu). Voici copié à partir de l'interface gmane NNTP:

Date: Fri, 16 Mar 2012 17:09:42 +0000
From: Geoff Clare <gwc-7882/[email protected]>
To: austin-group-l-7882/[email protected]
Newsgroups: gmane.comp.standards.posix.austin.general
Subject: Re: Strange addressing issue in sed

Stephane Chazelas <[email protected]> wrote, on 16 Mar 2012:
>
> 2012-03-16 15:44:35 +0000, Geoff Clare:
> > I've been alerted to an odd behaviour of sed on certified UNIX
> > systems that doesn't seem to match the requirements of the
> > standard.  It concerns an interaction between the 'n' command
> > and address matching.
> > 
> > According to the standard, this command:
> > 
> > printf 'A\nB\nC\nD\n' | sed '1,3s/A/B/;1,3n;1,3s/B/C/'
> > 
> > should produce the output:
> > 
> > B
> > C
> > C
> > D
> > 
> > GNU sed does produce this, but certified UNIX systems produce this:
> > 
> > B
> > B
> > C
> > D
> > 
> > However, if I change the 1,3s/B/C/ to 2,3s/B/C/ then they produce
> > the expected output (tested on Solaris and HP-UX).
> > 
> > Is this just an obscure bug from common ancestor code, or is there
> > some legitimate reason why this address change alters the behaviour?
> [...]
> 
> I suppose the idea is that for the second 1,3cmd, line "1" has
> not been seen, so the 1,3 range is not entered.

Ah yes, now it makes sense, and it looks like the standard does
require this slightly strange behaviour, given how the processing
of the "two addresses" case is specified:

    An editing command with two addresses shall select the inclusive
    range from the first pattern space that matches the first address
    through the next pattern space that matches the second.  (If the
    second address is a number less than or equal to the line number
    first selected, only one line shall be selected.) Starting at the
    first line following the selected range, sed shall look again for
    the first address. Thereafter, the process shall be repeated.

It's specified this way because the addresses can be BREs, but if
the same matching process is applied to the line numbers (even though
they can only match at most once), then the 1,3 range on that last
command is never entered.

-- 
Geoff Clare <g.clare-7882/[email protected]>
The Open Group, Apex Plaza, Forbury Road, Reading, RG1 1AX, England

Et voici la partie pertinente du reste du message (par moi) que Geoff citait:

I suppose the idea is that for the second 1,3cmd, line "1" has
not been seen, so the 1,3 range is not entered.

Same idea as in

printf '%s\n' A B C | sed -n '1d;1,2p'

whose behavior differ in traditional (heirloom toolchest at
least) and GNU.

It's unclear to me whether POSIX wants one behavior or the
other.

Donc, (selon Geoff) POSIX est clair que le comportement GNU n'est pas conforme.

Et c'est vrai que c'est moins cohérent (comparer seq 10 | sed -n '1d;1,2p'avec seq 10 | sed -n '1d;/^1$/,2p') même si potentiellement moins surprenant pour les gens qui ne réalisent pas comment les plages sont traitées (même Geoff a d'abord trouvé le comportement conforme "étrange" ).

Personne n'a pris la peine de le signaler comme un bug aux utilisateurs de GNU. Je ne suis pas sûr de le qualifier de bug. La meilleure option serait probablement la mise à jour de la spécification POSIX pour permettre aux deux comportements d'indiquer clairement que l'on ne peut pas compter sur l'un ou l'autre.

Modifier . J'ai maintenant jeté un coup d'œil à l' sedimplémentation originale dans Unix V7 de la fin des années 70, et il semble à peu près que ce comportement pour les adresses numériques n'était pas prévu ou du moins pas complètement pensé.

Avec la lecture de la spécification par Geoff (et mon interprétation originale des raisons pour lesquelles cela se produit), inversement, dans:

seq 5 | sed -n '3d;1,3p'

les lignes 1, 2, 4 et 5 doivent être sorties, car cette fois, c'est l'adresse de fin qui n'est jamais rencontrée par la 1,3pcommande à distance, comme dansseq 5 | sed -n '3d;/1/,/3/p'

Pourtant, cela ne se produit pas dans l'implémentation d'origine, ni dans aucune autre implémentation que j'ai essayée (busybox sedrenvoie les lignes 1, 2 et 4 qui ressemblent plus à un bug).

Si vous regardez le code UNIX v7 , il vérifie le cas où le numéro de ligne actuel est supérieur à l'adresse de fin (numérique) et sort alors de la plage. Le fait qu'il ne le fasse pas pour l'adresse de départ ressemble plus à un oubli qu'à une conception intentionnelle.

Cela signifie qu'il n'y a actuellement aucune implémentation conforme à cette interprétation de la spécification POSIX à cet égard.

Un autre comportement déroutant avec l'implémentation GNU est:

$ seq 5 | sed -n '2d;2,/3/p'
3
4
5

Puisque la ligne 2 a été sautée, le 2,/3/est entré sur la ligne 3 (la première ligne dont le numéro est> = 2). Mais comme c'est la ligne qui nous a fait entrer dans la plage, l' adresse de fin n'est pas vérifiée . Cela empire avec busybox sed:

$ seq 10 | busybox sed -n '2,7d; 2,3p'
8

Puisque les lignes 2 à 7 ont été supprimées, la ligne 8 est la première qui est> = 2, la plage 2,3 est alors entrée !

Stéphane Chazelas
la source
1
Il semble donc que le problème ne soit toujours pas résolu - je suis d'accord avec votre raisonnement pour expliquer pourquoi cela se produit, mais aussi qu'il n'est pas clair si c'était ce que vous vouliez - bien qu'il semble également que Geoff ait été convaincu par le texte cité que les implémentations UNIX ™ étaient corrects. Est-ce aussi votre lecture?
Michael Homer
1
@MichaelHomer, l'idée est que (selon Geoff) POSIX est clair que le comportement GNU n'est pas conforme. Et c'est vrai que c'est moins cohérent (comparer seq 10 | sed -n '1d;1,2p'avec seq 10 | sed -n '1d;/^1$/,2p') même si potentiellement moins surprenant pour les gens ne comprendraient pas comment les plages sont traitées. Personne n'a pris la peine de le signaler comme un bug aux utilisateurs de GNU. Je ne suis pas sûr de le qualifier de bogue, la meilleure option serait probablement de mettre à jour la spécification POSIX pour permettre aux deux comportements d'indiquer clairement que l'on ne peut pas compter sur l'un ou l'autre.
Stéphane Chazelas
2
En fait, comme la définition POSIX ne fait aucune déclaration selon laquelle les adresses doivent être "vues" pour démarrer ou terminer une plage d'adresses, IMO l'implémentation GNU suit plus strictement le libellé POSIX (surprenant pour GNU!). C'est également le comportement souhaité pour la plupart des cas réels que je connais. Mais, comme vous le faites remarquer, il faudrait que ce soit cohérent. Et vérifier chaque ligne pour les modèles de plage, même après, dn'est pas seulement un problème de performances, cela conduit à d'autres problèmes d'implémentation car les modèles "invisibles" nécessaires pour les plages ne sont pas autorisés à avoir un effet sur d'autres modèles vides ... un gâchis!
Philippos
@Philippos, dans ce 1d;1,2pscript, la 1,2pcommande n'est pas exécutée sur la première ligne, donc la première adresse ne correspond à aucun espace de modèle , ce qui est une façon d'interpréter ce texte. Dans tous les cas, il doit être évident que l'évaluation des adresses doit être effectuée au moment de l'exécution de la commande. Comme danssed 's/./x/g; /xxx/,/xxx/d'
Stéphane Chazelas
2
@Isaac, c'est le cœur du problème. Dans le langage POSIX 1et /1/sont les deux adresses, 1est l'adresse lorsque le numéro de ligne est 1, /1/est l'adresse lorsque l'espace de modèle contient 1, la question est de savoir si les deux types d'adresse doivent être traités de la même manière, ou si les plages de numéros de ligne doivent être considérées " dans l'absolu ", qu'ils correspondent ou non. Voir aussi ma dernière édition pour plus de contexte historique.
Stéphane Chazelas