sed: lea todo el archivo en el espacio del patrón sin fallar en la entrada de una sola línea

9

Leer un archivo completo en el espacio de patrones es útil para sustituir nuevas líneas, & c. y hay muchas instancias que aconsejan lo siguiente:

sed ':a;N;$!ba; [commands...]'

Sin embargo, falla si la entrada contiene solo una línea.

Como ejemplo, con dos entradas de línea, cada línea está sujeta al comando de sustitución:

$ echo $'abc\ncat' | sed ':a;N;$!ba; s/a/xxx/g'
xxxbc
cxxxt

Pero, con una sola entrada de línea, no se realiza ninguna sustitución:

$ echo 'abc' | sed ':a;N;$!ba; s/a/xxx/g'
abc

¿Cómo se escribe un sedcomando para leer todas las entradas a la vez y no tener este problema?

dicktyr
fuente
Edité su pregunta para que contenga una pregunta real. Puede esperar otras respuestas si lo desea, pero eventualmente marca la mejor respuesta como aceptada (vea el botón de canalización a la izquierda de la respuesta, justo debajo de los botones de flecha arriba-abajo).
John1024
@ John1024 Gracias, es bueno tener un ejemplo. Encontrar este tipo de cosas tiende a recordarme que "todo está mal", pero me alegra que algunos de nosotros no nos demos por vencidos. :}
dicktyr
2
¡Hay una tercera opción! Use la sed -zopción de GNU . Si su archivo no tiene nulo, ¡se leerá hasta el final del archivo! Encontrado en esto: stackoverflow.com/a/30049447/582917
CMCDragonkai

Respuestas:

13

Hay todo tipo de razones por las cuales leer un archivo completo en el espacio de patrones puede salir mal. El problema lógico en la pregunta que rodea la última línea es común. Está relacionado con sedel ciclo de línea de - cuando no hay más líneas y sedencuentra EOF a través - termina el procesamiento. Entonces, si está en la última línea y le indica sedque obtenga otra, se detendrá allí y no hará más.

Dicho esto, si realmente necesita leer un archivo completo en el espacio de patrones, entonces probablemente valga la pena considerar otra herramienta de todos modos. El hecho es que sedes el mismo nombre del editor de flujo , está diseñado para trabajar una línea, o un bloque de datos lógico, a la vez.

Hay muchas herramientas similares que están mejor equipadas para manejar bloques de archivos completos. edy ex, por ejemplo, puede hacer mucho de lo que sedpuede hacer y con una sintaxis similar, y mucho más, pero en lugar de operar solo en una secuencia de entrada mientras se transforma en salida sed, también mantienen archivos de respaldo temporales en el sistema de archivos . Su trabajo se almacena en el disco según sea necesario, y no se cierra abruptamente al final del archivo (y tiende a explotar con mucha menos frecuencia bajo la tensión del búfer) . Además, ofrecen muchas funciones útiles que sedno lo hacen, del tipo que simplemente no tiene sentido en un contexto de flujo, como marcas de línea, deshacer, búferes con nombre, unirse y más.

sedLa fortaleza principal es su capacidad para procesar datos tan pronto como los lee, de manera rápida, eficiente y en tiempo real. Cuando sorbe un archivo, lo tira y tiende a encontrarse con dificultades de caso límite como el problema de la última línea que menciona, desbordamientos de búfer y rendimiento abismal, a medida que los datos que analiza crecen en longitud el tiempo de procesamiento de un motor de expresiones regulares al enumerar coincidencias aumenta exponencialmente .

Con respecto a este último punto, por cierto: si bien entiendo que el s/a/A/gcaso de ejemplo es muy probable que sea solo un ejemplo ingenuo y probablemente no sea el guión real para el que desea recopilar una entrada, es posible que valga la pena familiarizarse con y///. Si a menudo te encuentras gsustituyendo a nivel mundial un solo personaje por otro, entonces ypodría ser muy útil para ti. Es una transformación en lugar de una sustitución y es mucho más rápido, ya que no implica una expresión regular. Este último punto también puede ser útil cuando se intenta preservar y repetir //direcciones vacías porque no las afecta pero puede verse afectada por ellas. En cualquier caso, y/a/A/es un medio más simple de lograr lo mismo, y los intercambios también son posibles como:y/aA/Aa/ que intercambiarían todas las mayúsculas / minúsculas como en una línea entre sí.

También debe tener en cuenta que el comportamiento que describe realmente no es lo que se supone que debe suceder de todos modos.

De GNU info seden la sección ERRORES COMUNES REPORTADOS :

  • N comando en la última línea

    • La mayoría de las versiones de sedexit sin imprimir nada cuando el Ncomando se emite en la última línea de un archivo. GNU sedimprime el espacio del patrón antes de salir a menos que, por supuesto, -nse haya especificado el interruptor de comando. Esta elección es por diseño.

    • Por ejemplo, el comportamiento de sed N foo bardependería de si foo tiene un número par o impar de líneas. O, al escribir un script para leer las siguientes líneas después de una coincidencia de patrones, las implementaciones tradicionales de sedte obligarían a escribir algo así en /foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }lugar de solo /foo/{ N;N;N;N;N;N;N;N;N; }.

    • En cualquier caso, la solución más simple es usar $d;Nscripts que se basen en el comportamiento tradicional, o establecer la POSIXLY_CORRECTvariable en un valor no vacío.

La POSIXLY_CORRECTvariable de entorno se menciona porque POSIX especifica que si sedencuentra EOF al intentarlo N, debe salir sin salida, pero la versión GNU rompe intencionalmente con el estándar en este caso. Tenga en cuenta también que, aunque el comportamiento se justifica por encima de la suposición, es que el caso de error es uno de edición de flujo, no de arrastrar un archivo completo a la memoria.

El estándar define Nel comportamiento de la siguiente manera:

  • N

    • Agregue la siguiente línea de entrada, menos su línea de \new final , al espacio del patrón, utilizando una \nlínea de ew incrustada para separar el material adjunto del material original. Tenga en cuenta que el número de línea actual cambia.

    • Si no hay disponible la siguiente línea de entrada, el Nverbo de comando se bifurcará hasta el final del script y se cerrará sin comenzar un nuevo ciclo o copiar el espacio del patrón a la salida estándar.

En esa nota, hay otros GNU-ismos demostrados en la pregunta, particularmente el uso de la :etiqueta, brancho y {corchetes de contexto de función }. Como regla general, sedse entiende que cualquier comando que acepte un parámetro arbitrario se delimita en una línea \nelectrónica en el script. Entonces los comandos ...

:arbitrary_label_name; ...
b to_arbitrary_label_name; ...
//{ do arbitrary list of commands } ...

... es muy probable que funcionen de manera errática dependiendo de la sedimplementación que los lea. Portablemente deben escribirse:

...;:arbitrary_label_name
...;b to_arbitrary_label_name
//{ do arbitrary list of commands
}

Lo mismo es cierto para r, w, t, a, i, y c (y, posiblemente, un poco más que yo estoy olvidando por el momento) . En casi todos los casos, también podrían escribirse:

sed -e :arbitrary_label_name -e b\ to_arbitary_label_name -e \
    "//{ do arbitrary list of commands" -e \}

... donde la nueva -einstrucción xecution representa el \ndelimitador de la línea ew. Entonces, cuando el infotexto de GNU sugiere que una implementación tradicional sedlo obligaría a hacer :

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N }

... más bien debería ser ...

/foo/{ $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N; $!N
}

... por supuesto, eso tampoco es cierto. Escribir el guión de esa manera es un poco tonto. Hay medios mucho más simples para hacer lo mismo, como:

printf %s\\n foo . . . . . . |
sed -ne 'H;/foo/h;x;//s/\n/&/3p;tnd
         //!g;x;$!d;:nd' -e 'l;$a\' \
     -e 'this is the last line' 

... que imprime:

foo
.
.
.
foo\n.\n.\n.$
.$
this is the last line

... porque el tcomando est, como la mayoría de los sedcomandos, depende del ciclo de línea para actualizar su registro de retorno y aquí el ciclo de línea puede realizar la mayor parte del trabajo. Esa es otra compensación que realiza cuando sorbe un archivo: el ciclo de la línea no se actualiza nunca más y muchas pruebas se comportarán de manera anormal.

El comando anterior no se arriesga a una entrada exagerada porque solo hace algunas pruebas simples para verificar lo que lee mientras lo lee. Con Hantiguo, todas las líneas se agregan al espacio de espera, pero si una línea coincide /foo/, sobrescribe el hespacio antiguo. Los búferes se xcambian a continuación, y s///se intenta una sustitución condicional si el contenido del búfer coincide con el //último patrón abordado. En otras palabras, //s/\n/&/3pintenta reemplazar la tercera nueva línea en el espacio de espera consigo mismo e imprime los resultados si el espacio de espera coincide actualmente /foo/. Si eso tiene téxito, el guión se ramifica a la etiqueta not delete, que hace un look y termina el guión.

Sin /foo/embargo, en el caso de que ambas y una tercera línea nueva no puedan coincidir en el espacio de espera, //!gsobrescribirán el búfer si /foo/no coincide, o, si coincide, sobrescribirá el búfer si una línea \new no coincide (reemplazando así /foo/con en sí) . Esta pequeña prueba sutil evita que el búfer se llene innecesariamente durante largos períodos de no /foo/y garantiza que el proceso se mantenga ágil porque la entrada no se acumula. Continuando en un caso de no /foo/o //s/\n/&/3pfalla, los buffers se intercambian nuevamente y se eliminan todas las líneas, excepto la última.

Esa última, la última línea $!d, es una demostración simple de cómo sedse puede hacer un script de arriba hacia abajo para manejar múltiples casos fácilmente. Cuando su método general es eliminar los casos no deseados, comenzando por los más generales y trabajando hacia los más específicos, los casos límite se pueden manejar más fácilmente porque simplemente se les permite llegar hasta el final del script con sus otros datos deseados y cuándo todo se envuelve y te quedan solo los datos que deseas. Sin embargo, tener que recuperar estos casos extremos de un circuito cerrado puede ser mucho más difícil de hacer.

Y aquí está lo último que tengo que decir: si realmente debe extraer un archivo completo, entonces puede soportar hacer un poco menos de trabajo confiando en el ciclo de línea para hacerlo por usted. Normalmente se usaría Next y nextensión de búsqueda hacia delante - debido a que avanzan por delante del ciclo de línea. En lugar de implementar redundantemente un bucle cerrado dentro de un bucle, ya que el sedciclo de línea es solo un bucle de lectura simple de todos modos, si su propósito es solo reunir información indiscriminadamente, entonces probablemente sea más fácil de hacer:

sed 'H;1h;$!d;x;...'

... que reunirá todo el archivo o lo intentará.


una nota al margen sobre Nel comportamiento de la última línea ...

Si bien no tengo las herramientas disponibles para probar, tenga en cuenta que Ncuando la lectura y la edición in situ se comportan de manera diferente si el archivo editado es el archivo de script para la próxima lectura.

mikeserv
fuente
1
Poner lo incondicional Hprimero es encantador.
2015
@mikeserv Gracias por tu aporte. Puedo ver un beneficio potencial en mantener el ciclo de la línea, pero ¿cómo es menos trabajo?
dicktyr
@dicktyr bueno, la sintaxis toma algunos atajos :a;$!{N;ba}como mencioné anteriormente: es más fácil usar la forma estándar a largo plazo cuando intentas ejecutar expresiones regulares en sistemas desconocidos. Pero eso no era realmente lo que quise decir: implementas un ciclo cerrado, no puedes meterte tan fácilmente en el medio cuando lo desees como lo harías, ramificando, recortando datos no deseados y dejando que el ciclo suceda. Es como una cosa de arriba hacia abajo: todo lo que sedhace es un resultado directo de lo que acaba de hacer. Tal vez lo veas de manera diferente, pero si lo intentas, es posible que el script sea más fácil.
mikeserv 01 de
11

Falla porque el Ncomando viene antes de la coincidencia de patrón $!(no la última línea) y sed se cierra antes de realizar cualquier trabajo:

norte

Agregue una nueva línea al espacio del patrón, luego agregue la siguiente línea de entrada al espacio del patrón. Si no hay más entradas, sed sale sin procesar más comandos .

Esto también se puede solucionar fácilmente para que funcione con entrada de una sola línea (y, de hecho, para ser más claro en cualquier caso) simplemente agrupando los comandos Ny bdespués del patrón:

sed ':a;$!{N;ba}; [commands...]'

Funciona de la siguiente manera:

  1. :a crear una etiqueta llamada 'a'
  2. $! si no es la última línea, entonces
  3. Nagregue la siguiente línea al espacio del patrón (o salga si no hay una línea siguiente) y babifurque (vaya a) la etiqueta 'a'

Desafortunadamente, no es portátil (ya que se basa en extensiones GNU), pero la siguiente alternativa (sugerida por @mikeserv) es portátil:

sed 'H;1h;$!d;x; [commands...]'
dicktyr
fuente
Publiqué esto aquí porque no encontré la información en otro lugar y quería ponerla a disposición para que otros puedan evitar problemas con la difusión :a;N;$!ba;.
dicktyr
¡Gracias por publicar! Recuerde que aceptar su propia respuesta también está bien. Solo necesita esperar un poco antes de que el sistema le permita hacerlo.
terdon