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 d
passe 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 sed
deux lignes d'entrée, a
et b
. Le programme fait deux choses:
Supprime la première ligne avec
1d
. Lad
commande seraSupprimez l'espace de motif et commencez le cycle suivant. et
- 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,2
n'est jamais atteinte pendant la ligne 1 (car elle est déjà d
passée au cycle / ligne suivant) et donc l'inclusion de la plage ne commence jamais, alors qu'elle a
a été supprimée. Les Unix conformes sed
de macOS et de Solaris 10 produisent cette sortie, tout comme les non-POSIX sed
dans Solaris et BSD sed
en 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 d
et 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?
ed
, en contournantsed
complètement?Réponses:
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:
Et voici la partie pertinente du reste du message (par moi) que Geoff citait:
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'
avecseq 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'
sed
implé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:
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,3p
commande à 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
sed
renvoie 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:
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 avecbusybox sed
: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 !
la source
seq 10 | sed -n '1d;1,2p'
avecseq 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.d
n'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!1d;1,2p
script, la1,2p
commande 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'
1
et/1/
sont les deux adresses,1
est l'adresse lorsque le numéro de ligne est 1,/1/
est l'adresse lorsque l'espace de modèle contient1
, 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.