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?