¿Qué requiere POSIX sed para `1d; 1,2d` donde un rango de direcciones comienza desde una línea ya eliminada?


11

En los comentarios a esta pregunta, surgió un caso en el que varias implementaciones de sed no estaban de acuerdo con un programa bastante simple, y nosotros (o al menos yo) no pudimos determinar lo que la especificación realmente requiere para ello.

El problema es el comportamiento de un rango que comienza en una línea eliminada:

1d;1,2d

¿Debería eliminarse la línea 2 aunque se eliminó el inicio del rango antes de llegar a ese comando? Mi expectativa inicial era "no" en línea con BSD sed, mientras que GNU sed dice "sí", y verificar el texto de la especificación no resuelve completamente el asunto.

Coincidiendo con mis expectativas son (al menos) macOS y Solaris sed, y BSD sed. No estamos de acuerdo (al menos) GNU y Busybox sed, y muchas personas aquí. Los dos primeros están certificados por SUS, mientras que los otros probablemente están más extendidos. ¿Qué comportamiento es correcto?


El texto de especificación para rangos de dos direcciones dice:

La utilidad sed aplicará en secuencia todos los comandos cuyas direcciones seleccionen ese espacio de patrón, hasta que un comando comience el siguiente ciclo o se cierre.

y

Un comando de edición con dos direcciones seleccionará el rango inclusivo desde el primer espacio de patrón que coincide con la primera dirección hasta el siguiente espacio de patrón que coincide con el segundo. [...] Comenzando en la primera línea que sigue al rango seleccionado, sed buscará nuevamente la primera dirección. A partir de entonces, el proceso se repetirá.

Podría decirse que la línea 2 está dentro del "rango inclusivo desde el primer espacio de patrón que coincide con la primera dirección hasta el siguiente espacio de patrón que coincide con el segundo", independientemente de si se ha eliminado el punto de inicio. Por otro lado, esperaba que el primero dpasara al siguiente ciclo y no le diera al rango la oportunidad de comenzar. Las implementaciones certificadas por UNIX ™ hacen lo que esperaba, pero potencialmente no lo que exige la especificación.

Siguen algunos experimentos ilustrativos, pero la pregunta clave es: ¿qué debe sed hacer cuando comienza un rango en una línea eliminada?


Experimentos y ejemplos

Una demostración simplificada del problema es esta, que imprime copias adicionales de líneas en lugar de eliminarlas:

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

Esto proporciona seddos líneas de entrada, ay b. El programa hace dos cosas:

  1. Elimina la primera línea con 1d. El dcomando se

    Elimine el espacio del patrón y comience el siguiente ciclo. y

  2. Seleccione el rango de líneas de 1 a 2 e imprímalas explícitamente, además de la impresión automática que recibe cada línea. Una línea incluida en el rango debería aparecer dos veces.

Mi expectativa era que esto debería imprimir

b

solo, con el rango que no se aplica porque 1,2nunca se alcanza durante la línea 1 (porque ya se dpasó al siguiente ciclo / línea) y, por lo tanto, la inclusión del rango nunca comienza, mientras que ase ha eliminado. Los Unix sedcompatibles de macOS y Solaris 10 producen esta salida, al igual que los que no son POSIX seden Solaris y BSD seden general.

GNU sed, por otro lado, imprime

b
b

indicando que ha interpretado el rango. Esto ocurre tanto en modo POSIX como no. El sed de Busybox tiene el mismo comportamiento (pero no un comportamiento idéntico siempre, por lo que no parece ser el resultado de un código compartido).

Más experimentación con

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'

encuentra que parece tratar un rango que comienza en una línea eliminada como si comenzara en la siguiente línea. Esto es visible porque /c/no coincide para finalizar el rango. Usar /b/para iniciar el rango no se comporta igual que 2.


El ejemplo de trabajo inicial que estaba usando era

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

como una forma de eliminar todas las líneas hasta la primera /a/coincidencia, incluso si eso está en la primera línea (para lo que GNU sed usaría 0,/a/d; esto fue un intento de interpretación compatible con POSIX de eso).

Se ha sugerido que esto debería eliminar hasta la segunda coincidencia de /a/si la primera línea coincide (o el archivo completo si no hay una segunda coincidencia), lo que parece plausible, pero de nuevo, solo GNU sed hace eso. Tanto macOS sed como los sed de Solaris producen

b
c
d
e

para eso, como esperaba (GNU sed produce la salida vacía de la eliminación del rango no terminado; Busybox sed imprime solo dy e, lo cual es claramente incorrecto sin importar qué). En general, supondría que haber pasado las pruebas de conformidad de certificación significa que su comportamiento es correcto, pero suficientes personas han sugerido lo contrario que no estoy seguro, el texto de la especificación no es completamente convincente y el conjunto de pruebas no puede ser perfectamente comprensivo

Claramente, no es prácticamente portátil escribir ese código hoy dada la inconsistencia, pero en teoría debería ser equivalente en todas partes con un significado u otro. Creo que esto es un error, pero no sé contra qué implementación (es) informarlo. Mi opinión actual es que el comportamiento de GNU y Busybox sed es inconsistente con la especificación, pero podría estar equivocado al respecto.

¿Qué requiere POSIX aquí?


Como solución temporal, ¿escribir en un archivo temporal y procesarlo con POSIX ed, omitiendo por sedcompleto?
D. Ben Knoble

Respuestas:


9

Eso fue planteado en la lista de correo del grupo de Austin en marzo de 2012. Aquí está el mensaje final al respecto (por Geoff Clare del Grupo de Austin (el organismo que mantiene POSIX), quien también fue el que planteó el problema en primer lugar). Aquí copiado de la interfaz NNTP gmane:

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

Stephane Chazelas <stephane_chazelas-Qt13gs6zZMY@public.gmane.org> 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/jkIBncuagvECLh61g@public.gmane.org>
The Open Group, Apex Plaza, Forbury Road, Reading, RG1 1AX, England

Y aquí está la parte relevante del resto del mensaje (por mí) que Geoff estaba citando:

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.

Entonces, (según Geoff) POSIX es claro que el comportamiento de GNU no es compatible.

Y es cierto que es menos consistente (comparar seq 10 | sed -n '1d;1,2p'con seq 10 | sed -n '1d;/^1$/,2p') incluso si es potencialmente menos sorprendente para las personas que no se dan cuenta de cómo se procesan los rangos (incluso Geoff inicialmente encontró el comportamiento conforme "extraño" ).

Nadie se molestó en reportarlo como un error a la gente de GNU. No estoy seguro de calificarlo como un error. Probablemente la mejor opción sería que la especificación POSIX se actualice para permitir que ambos comportamientos dejen en claro que no se puede confiar en ninguno.

Editar . Ahora he echado un vistazo a la sedimplementación original en Unix V7 de finales de los 70, y parece que ese comportamiento para las direcciones numéricas no fue intencionado o al menos no se pensó por completo allí.

Con la lectura de Geoff de la especificación (y mi interpretación original de por qué sucede), por el contrario, en:

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

las líneas 1, 2, 4 y 5 deberían salir, porque esta vez, es la dirección final que nunca encuentra el 1,3pcomando a distancia, como enseq 5 | sed -n '3d;/1/,/3/p'

Sin embargo, eso no sucede en la implementación original, ni en ninguna otra implementación que probé (busybox seddevuelve las líneas 1, 2 y 4, que se parece más a un error).

Si observa el código UNIX v7 , verifica el caso en el que el número de línea actual es mayor que la dirección final (numérica), y luego se sale del rango. El hecho de que no lo haga para la dirección de inicio se parece más a un descuido que a un diseño intencional.

Lo que eso significa es que no hay una implementación que realmente cumpla con esa interpretación de la especificación POSIX a ese respecto en este momento.

Otro comportamiento confuso con la implementación de GNU es:

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

Como se omitió la línea 2, 2,/3/se ingresa en la línea 3 (la primera línea cuyo número es> = 2). Pero como es la línea que nos hizo ingresar al rango, no se verifica la dirección final . Empeora con busybox seden:

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

Como se eliminaron las líneas 2 a 7, la línea 8 es la primera que es> = 2, ¡entonces se ingresa el rango 2,3 !


1
Por lo tanto, parece que el problema aún no se ha resuelto; estoy de acuerdo con su razonamiento de por qué está sucediendo, pero también que no está claro si eso era lo que quería, aunque también parece que Geoff estaba convencido por el texto citado de que las implementaciones de UNIX ™ fueron correctos ¿Esa es tu lectura también?
Michael Homer

1
@MichaelHomer, la idea es que (según Geoff) POSIX está claro que el comportamiento de GNU no es compatible. Y es cierto que es menos consistente (compárelo seq 10 | sed -n '1d;1,2p'con seq 10 | sed -n '1d;/^1$/,2p') incluso si es potencialmente menos sorprendente para las personas no se darían cuenta de cómo se procesan los rangos. Nadie se molestó en reportarlo como un error a la gente de GNU. No estoy seguro de calificarlo como un error, probablemente la mejor opción sería actualizar la especificación POSIX para permitir que ambos comportamientos dejen en claro que no se puede confiar en ninguno.
Stéphane Chazelas

2
En realidad, como la definición POSIX no establece que las direcciones deben "verse" para comenzar o finalizar un rango de direcciones, IMO la implementación de GNU sigue la redacción POSIX de manera más estricta (¡sorprendente para GNU!). Este es también el comportamiento deseado para la mayoría de los casos del mundo real que conozco. Pero, como usted señala, debería ser consistente. Y verificar cada línea para ver los patrones de rango, incluso después de dque no sea solo un problema de rendimiento, también conduce a problemas de implementación adicionales, ya que los patrones "invisibles" necesarios para los rangos no pueden tener efecto en patrones vacíos adicionales ... ¡un desastre!
Philippos el

@Philippos, en ese 1d;1,2pscript el 1,2pcomando no se ejecuta en la primera línea, por lo que la primera dirección no coincide con ningún espacio de patrón , que es una forma de interpretar ese texto. En cualquier caso, debería ser obvio que la evaluación de las direcciones debe hacerse en el momento en que se ejecuta el comando. Como ensed 's/./x/g; /xxx/,/xxx/d'
Stéphane Chazelas

2
@ Isaac, ese es el núcleo del problema. En el lenguaje POSIX 1y /1/son ambas direcciones, 1es la dirección cuando el número de línea es 1, /1/es la dirección cuando contiene el espacio del patrón 1, la pregunta es si ambos tipos de dirección deben tratarse de la misma manera o si los rangos de números de línea deben considerarse " en absoluto "independientemente de si coincidían. Vea también mi última edición para más contexto histórico.
Stéphane Chazelas
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.