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 d
pasara 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 sed
dos líneas de entrada, a
y b
. El programa hace dos cosas:
Elimina la primera línea con
1d
. Eld
comando seElimine el espacio del patrón y comience el siguiente ciclo. y
- 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,2
nunca se alcanza durante la línea 1 (porque ya se d
pasó al siguiente ciclo / línea) y, por lo tanto, la inclusión del rango nunca comienza, mientras que a
se ha eliminado. Los Unix sed
compatibles de macOS y Solaris 10 producen esta salida, al igual que los que no son POSIX sed
en Solaris y BSD sed
en 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 d
y 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í?
ed
, omitiendo porsed
completo?Respuestas:
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:
Y aquí está la parte relevante del resto del mensaje (por mí) que Geoff estaba citando:
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'
conseq 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
sed
implementació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:
las líneas 1, 2, 4 y 5 deberían salir, porque esta vez, es la dirección final que nunca encuentra el
1,3p
comando 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
sed
devuelve 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:
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 conbusybox sed
en: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 !
fuente
seq 10 | sed -n '1d;1,2p'
conseq 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.d
que 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!1d;1,2p
script el1,2p
comando 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'
1
y/1/
son ambas direcciones,1
es la dirección cuando el número de línea es 1,/1/
es la dirección cuando contiene el espacio del patrón1
, 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.