Filtrar o canalizar ciertas secciones de un archivo

14

Tengo un archivo de entrada con algunas secciones que están delimitadas con etiquetas de inicio y fin, por ejemplo:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

Quiero aplicar una transformación a este archivo de modo que las líneas X, Y, Z se filtren a través de algún comando ( nlpor ejemplo), pero el resto de las líneas pasan sin cambios. Observe que nl(líneas de números) acumula el estado a través de las líneas, por lo que no se trata de una transformación estática que se aplica a cada una de las líneas X, Y, Z. ( Editar : se señaló que nlpuede funcionar en un modo que no requiere un estado acumulado, pero solo estoy usandonl como ejemplo para simplificar la pregunta. En realidad, el comando es un script personalizado más complejo. Lo que realmente estoy buscando porque es una solución genérica al problema de aplicar un filtro estándar a una subsección de un archivo de entrada )

La salida debería verse así:

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D

Puede haber varias secciones de este tipo en el archivo que requieren la transformación.

Actualización 2 No especifiqué originalmente qué debería suceder si hay más una sección, por ejemplo:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
 @@inline-code-start
line L
line M
line N
@@inline-code-end

Mi expectativa sería que ese estado solo necesitaría mantenerse dentro de una sección dada, dando:

line A
line B
     1 line X
     2 line Y
     3 line Z
line C
line D
     1 line L
     2 line M
     3 line N

pero creo que interpretar que el problema requiere que el estado se mantenga en todas las secciones es válido y útil en muchos contextos.

Fin de la actualización 2

Mi primer pensamiento es construir una máquina de estado simple que rastree en qué sección estamos:

#!/usr/bin/bash
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
  echo $line | nl
  else
    # output
    echo $line
  fi
done

Con el que corro:

cat test-inline-codify | ./inline-codify

Esto no funciona ya que cada llamada a nles independiente, por lo que los números de línea no aumentan:

line A
line B
     1  line X
     1  line Y
     1  line Z
line C
line D

Mi siguiente intento fue usar un fifo:

#!/usr/bin/bash
mkfifo myfifo
nl < myfifo &
while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
  elif [[ $active = true ]]
  then
    # pipe
    echo $line > myfifo
  else
    # output
    echo $line
  fi
done
rm myfifo

Esto da la salida correcta, pero en el orden incorrecto:

line A
line B
line C
line D
     1  line 1
     2  line 2
     3  line 3

Probablemente hay algo de almacenamiento en caché.

¿Voy a hacer todo esto mal? Esto parece un problema bastante genérico. Siento que debería haber una tubería simple que resolvería esto.

James Scriven
fuente
nlNo tiene que acumular estado . Mire nl -dy comprobar su man/ infopáginas de información sobre nl's sección delimitador .
mikeserv
nl es solo un ejemplo. En mi caso, estoy ejecutando un script personalizado en lugar de nl.
James Scriven
En ese caso, aclare qué está haciendo su secuencia de comandos.
terdon
Aclaré en la pregunta que solo estoy usando nlun filtro de ejemplo. Pensé que simplificaría la pregunta al pasar por alto los detalles de lo que estaba haciendo exactamente el filtro, pero probablemente solo causé más confusión. De hecho, estoy filtrando la subsección a través de un resaltador de código, para un generador de blog estático de cosecha propia. En este momento estoy usando gnu source-highlight, pero eso podría cambiar, y podría agregar más filtros, como un formateador también.
James Scriven

Respuestas:

7

Estoy de acuerdo con usted, probablemente sea un problema genérico. Sin embargo, algunas utilidades comunes tienen algunas facilidades para manejarlo.


nl

nl, por ejemplo, separa la entrada en páginas lógicas como -deliminadas por un delimitador de sección de dos caracteres . Tres apariciones en una línea indican el comienzo de un encabezado , dos el cuerpo y uno el pie de página . Reemplaza cualquiera de estos que se encuentran en la entrada con una línea en blanco en la salida, que son las únicas líneas en blanco que imprime

Modifiqué tu ejemplo para incluir otra sección y ponerlo ./infile. Entonces se ve así:

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D
@@start
line M
line N
line O
@@end

Luego ejecuté lo siguiente:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end$/@@/'  <infile |
nl -d@@ -ha -bn -w1

nlse le puede decir que acumule estado en las páginas lógicas, pero no lo hace de manera predeterminada. En su lugar, numerará las líneas de su entrada de acuerdo con los estilos y por sección . Entonces -hasignifica numerar todas las líneas de encabezado y -bnsignifica que no hay líneas de cuerpo , ya que comienza en un cuerpo estado de .

Hasta que aprendí esto, solía usarlo nlpara cualquier entrada, pero después de darme cuenta de que nlpodría distorsionar la salida de acuerdo con su -deliminador predeterminado \:, aprendí a ser más cuidadoso con él y comencé a usarlo grep -nF ''para la entrada no probada. Pero otra lección aprendida ese día fue que nlse puede aplicar de manera muy útil en otros aspectos, como este, si solo modifica un poco su entrada, como hago consed anterior.

SALIDA

  line A
  line B

1       line X
2       line Y
3       line Z

  line C
  line D

1       line M
2       line N
3       line O

Aquí hay más información nl: ¿observan cómo todas las líneas, excepto las numeradas, comienzan con espacios? Cuando las nllíneas de números inserta un cierto número de caracteres en la cabeza de cada uno. Para esas líneas no -wnumera , incluso espacios en blanco, siempre coincide con la sangría insertando ( idth count + -separator len) * espacios en la parte superior de las líneas sin numerar. Esto le permite reproducir el contenido no numerado exactamente comparándolo con el contenido numerado, y con poco esfuerzo. Cuando considere que nldividirá su entrada en secciones lógicas para usted y que puede insertar -strings arbitrarios en la cabecera de cada línea que numere, entonces se vuelve bastante fácil manejar su salida:

sed 's/^@@.*start$/@@@@@@/
     s/^@@.*end/@@/; t
     s/^\(@@\)\{1,3\}$/& /' <infile |
nl -d@@ -ha -bn -s' do something with the next line!
'

Las impresiones anteriores ...

                                        line A
                                        line B

 1 do something with the next line!
line X
 2 do something with the next line!
line Y
 3 do something with the next line!
line Z

                                        line C
                                        line D

 1 do something with the next line!
line M
 2 do something with the next line!
line N
 3 do something with the next line!
line O

ÑU sed

Si nlno es su aplicación de destino, un GNU sedpuede eejecutar un comando de shell arbitrario en función de una coincidencia.

sed '/^@@.*start$/!b
     s//nl <<\\@@/;:l;N
     s/\(\n@@\)[^\n]*end$/\1/
Tl;e'  <infile

Arriba sedrecoge la entrada en el espacio del patrón hasta que tenga suficiente para pasar con éxito el Test de sustitución y dejar de bvolver a la :lgranja. Cuando lo hace, se eejecuta nlcon una entrada representada como un<< documento aquí para todo el resto de su espacio de patrones.

El flujo de trabajo es así:

  1. /^@@.*start$/!b
    • Si una ^línea entera $no !no /coincide con /el patrón anterior, entonces se branched fuera del guión y autoprinted - por lo que a partir de ahora sólo estamos trabajando con una serie de líneas que se inició con el patrón.
  2. s//nl <<\\@@/
    • el s//campo vacío /representa la última dirección que sedintentó coincidir, por lo que este comando sustituye la @@.*startlínea completa en su nl <<\\@@lugar.
  3. :l;N
    • El :comando define una etiqueta de rama: aquí configuro uno llamado :label. El Ncomando ext agrega la siguiente línea de entrada al espacio de patrón seguido de un \ncarácter de línea de flujo. Esta es una de las pocas formas de obtener un \newline en un sedespacio de patrón: el \ncarácter ewline es un delimitador seguro para un sedder que lo ha estado haciendo durante un tiempo.
  4. s/\(\n@@\)[^\n]*end$/\1/
    • esta s///ubicación solo puede tener éxito después de encontrar un inicio y solo en la primera aparición posterior de una línea final . Solo actuará en un espacio de patrón en el que la línea de \new final es seguida inmediatamente @@.*endmarcando el final $del espacio de patrón. Cuando actúa, reemplaza toda la cadena coincidente con el \1primer \(grupo \), o \n@@.
  5. Tl
    • el Tcomando est se bifurca a una etiqueta (si se proporciona) si no se ha producido una sustitución exitosa desde la última vez que se introdujo una línea de entrada en el espacio del patrón (como lo hago w / N) . Esto significa que cada vez que \nse agrega una línea ew al espacio de patrón que no coincide con su delimitador final, el Tcomando est falla y se bifurca de nuevo al :label, lo que resulta en sedtirar de la Nlínea ext y hacer un bucle hasta que tenga éxito.
  6. e

    • Cuando la sustitución de la coincidencia final sea exitosa y la secuencia de comandos no se ramifique para una prueba fallida T, sedse eejecutará un comando que ltenga este aspecto:

      nl <<\\@@\nline X\nline Y\nline Z\n@@$

Puedes ver esto por ti mismo editando la última línea para que se vea Tl;l;e.

Imprime:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
     1  line M
     2  line N
     3  line O

while ... read

Una última forma de hacer esto, y quizás la más simple, es usar un while readbucle, pero por una buena razón. El shell - (más especialmente un bashshell) - es típicamente bastante abismal al manejar la entrada en grandes cantidades o en flujos estables. Esto también tiene sentido: el trabajo del shell es manejar el ingreso de caracteres por carácter y llamar otros comandos que pueden manejar las cosas más grandes.

Pero lo más importante acerca de su función es que el shell no debe read sobrepasar la entrada: se especifica que no amortigua la entrada o salida hasta el punto que consume tanto o no se retransmite lo suficiente como para que falten los comandos que llama. - al byte. Por readlo tanto, es una excelente prueba de entrada :return obtener información sobre si hay entrada restante y debe llamar al siguiente comando para leerla, pero por lo general, no es la mejor manera de hacerlo.

Aquí hay un ejemplo, sin embargo, de cómo uno podría usar read y otros comandos para procesar la entrada sincronizada:

while   IFS= read -r line        &&
case    $line in (@@*start) :;;  (*)
        printf %s\\n "$line"
        sed -un "/^@@.*start$/q;p";;
esac;do sed -un "/^@@.*end$/q;=;p" |
        paste -d: - -
done    <infile

Lo primero que sucede para cada iteración son los readtirones en una línea. Si tiene éxito, significa que el bucle aún no ha llegado a EOF y, por lo tanto, casesi coincide con un delimitador de inicio, el dobloque se ejecuta inmediatamente. Si no, printfimprime el $lineque ready sedse llama.

sedse print cada línea hasta que se encuentra con el inicio marcador - cuando qUITS de entrada por completo. El -uconmutador nbuffered es necesario para GNU sedporque de lo contrario puede almacenarse en búfer con bastante avidez, pero, según las especificaciones, otros POSIX seddeberían funcionar sin ninguna consideración especial, siempre que <infilesea ​​un archivo normal.

Cuando se sed qinicia por primera vez, el shell ejecuta el dobloque del bucle, que llama a otro sedque imprime cada línea hasta que encuentra el marcador final . Canaliza su salida a paste, porque imprime los números de línea cada uno en su propia línea. Me gusta esto:

1
line M
2
line N
3
line O

pasteluego los pega juntos en los :caracteres, y toda la salida se ve así:

line A
line B
1:line X
2:line Y
3:line Z
line C
line D
1:line M
2:line N
3:line O

Estos son solo ejemplos: aquí se puede hacer cualquier cosa en la prueba o en los bloques, pero la primera utilidad no debe consumir demasiada información.

Todas las utilidades involucradas leen la misma entrada, e imprimen sus resultados, cada una a su vez. Este tipo de cosas puede ser difícil de conseguir la caída de - debido a diferentes utilidades serán amortiguar más que otros - pero en general se puede confiar en dd, heady sedhacer lo correcto (aunque, para GNU sed, se necesita el cli-switch) y siempre debe poder confiar read, porque es, por naturaleza, muy lento . Y es por eso que el ciclo anterior lo llama solo una vez por bloque de entrada.

mikeserv
fuente
Probé el segundo sedejemplo que diste, y funciona, pero REALMENTE tengo problemas para asimilar la sintaxis. (mi sed es bastante débil y generalmente se limita a s / findthis / replacethis / g. Tendré que hacer un esfuerzo para sentarme y entender realmente sed.)
James Scriven
@JamesScriven: acabo de editar para explicarlo mejor. Avísame si no ayuda. También cambié mucho el comando: ahora está en piezas más pequeñas y más sensibles.
mikeserv
4

Una posibilidad es hacer esto con el editor de texto vim. Puede canalizar secciones arbitrarias a través de comandos de shell.

Una forma de hacerlo es mediante los números de línea, usando :4,6!nl. Este comando ex ejecutará nl en las líneas 4-6 inclusive, logrando lo que desea en su entrada de ejemplo.

Otra forma más interactiva es seleccionar las líneas apropiadas usando el modo de selección de línea (shift-V) y las teclas de flecha o buscar, y luego usar :!nl. Una secuencia de comando completa para su entrada de ejemplo podría ser

/@@inline-code-start
jV/@@inline-code-end
k:!nl

Esto no es muy adecuado para la automatización (las respuestas que utilizan, por ejemplo, sed son mejores para eso), pero para las ediciones únicas es muy útil no tener que recurrir a shellscripts de 20 líneas.

Si no está familiarizado con vi (m), al menos debe saber que después de estos cambios puede guardar el archivo usando :wq.

marcelm
fuente
¡Sí, vim es increíble! Pero, en este caso, estoy buscando una solución programable.
James Scriven
@JamesScriven, cualquiera que diga que vim no es programable en una determinación insuficiente. Primero cree un directorio de proyecto y en ese directorio copie todos los archivos de inicio de vim de su directorio de inicio (ln -s funciona bien, excepto .vimrc que estamos a punto de modificar y .viminfo que puede estar lleno de ruido). Agregue la definición de función que hará el trabajo al nuevo archivo .vimrc y luego llame a vim como HOME=$(pwd) vim -c 'call Mf()' f. Si está usando xargs, es posible que desee usar gvim en un servidor x dedicado para evitar corromper su tty (vnc es independiente de la tarjeta de video y se puede monitorear).
hildred
@hildred Hmmm ... ¿No podría simplemente usar [XSendEvent] ( tronche.com/gui/x/xlib/event-handling/XSendEvent.html ) para simular clics del mouse en vim?
James Scriven el
2

La solución más simple que se me ocurre es no usar nlsino contar las líneas usted mismo:

#!/usr/bin/env bash
while read line
do
    if [[ $line == @@inline-code-start* ]]
    then
        active=true
    elif [[ $line == @@inline-code-end* ]]
    then
        active=false
    elif [[ $active = true ]]
    then
        ## Count the line number
        let num++;
        printf "\t%s %s\n" "$num" "$line"
    else
        # output
        printf "%s\n" "$line"
    fi
done

Luego lo ejecuta en el archivo:

$ foo.sh < file
line A
line B
    1 line X
    2 line Y
    3 line Z
line C
line D
terdon
fuente
Gracias terdon. Actualicé la pregunta para aclarar que estoy buscando una solución genérica para filtrar una subsección de una entrada, en lugar del ejemplo específico de líneas de numeración. quizás un mejor ejemplo de comando hubiera sido "tac" (líneas inversas)
James Scriven
2

Si su objetivo es enviar el bloque de código completo a una sola instancia de proceso, entonces podría acumular las líneas y retrasar la tubería hasta llegar al final del bloque de código:

#!/bin/bash

acc=""

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    acc=""
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    # Act on entire block of code
    echo "${acc:1}" | nl  # Chops off first leading new-line character using ${VAR:1}
  elif [[ $active = true ]]
  then
    acc=$( printf "%s\n%s" "$acc" "$line" )
  else
    # output
    echo $line
  fi
done

Esto produce lo siguiente para un archivo de entrada que repite el caso de prueba tres veces:

line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D
line A
line B
     1  line X
     2  line Y
     3  line Z
line C
line D

Para hacer algo más con el bloque de código, por ejemplo, y luego revertir el número, simplemente canalizarla a través de algo más: echo -E "${acc:1}" | tac | nl. Resultado:

line A
line B
     1  line Z
     2  line Y
     3  line X
line C
line D

O recuento de palabras echo -E "${acc:1}" | wc:

line A
line B
      3       6      21
line C
line D
Supr
fuente
2

Editar agregó una opción para definir un filtro proporcionado por el usuario

#!/usr/bin/perl -s
use IPC::Open2;
our $p;
$p = "nl" unless $p;    ## default filter

$/ = "\@\@inline-code-end\n";
while(<>) { 
   chomp;
   s/\@\@inline-code-start\n(.*)/pipeit($1,$p)/se;
   print;
}

sub pipeit{my($text,$pipe)=@_;
  open2(my $R, my $W,$pipe) || die("can open2");
  local $/ = undef;
  print $W $text;
  close $W;
  return <$R>;
}

Por defecto el filtro es "nl". Para cambiar el filtro, use la opción "-p" con algún comando proporcionado por el usuario:

codify -p="wc" file

o

codify -p="sed -e 's@^@ ║ @; 1s@^@ ╓─\n@; \$s@\$@\n ╙─@'" file

Este último filtro generará:

line A
line B
 ╓─
  line X
  line Y
  line Z
 ╙─
line C
line D

Actualización 1 El uso de IPC :: Open2 tiene problemas de escala: si se excede el tamaño del búfer, puede bloquearse. (en mi máquina, el tamaño de la tubería se amortigua si 64K corresponden a 10_000 x "línea Y").

Si necesitamos cosas más grandes (si necesitamos más la "línea Y" 10000):

(1) instalar y usar use Forks::Super 'open2';

(2) o sustituya la función pipeit por:

sub pipeit{my($text,$pipe)=@_;
  open(F,">","/tmp/_$$");
  print F $text;
  close F;
  my $out = `$pipe < /tmp/_$$ `;
  unlink "/tmp/_$$";
  return $out;
}
JJoao
fuente
Eso es realmente genial. Supongo que los trucos son que no estás procesando línea por línea (redifinando $/y marcando s), y el uso de la ebandera para hacer la llamada real al comando externo. ¡Realmente me gusta el segundo ejemplo (arte ascii)!
James Scriven
Sin embargo, lo que noté es que esto no parece escalar más allá de un par de miles de líneas en la subsección. Sospecho que esto tiene que ver con tratar la subsección como un gran bloque de texto.
James Scriven
Gracias. Sí: `/ e` = eval; /s= ("." significa (.|\n)); $/redefine el separador de registros.
JJoao
@JamesScriven, tienes razón (la tubería está bloqueando). Déjame probar lo que está pasando ...
JJoao
@JamesScriven, mira mi actualización ...
JJoao
1

Ese es un trabajo para awk.

#!/usr/bin/awk -f
$0 == "@@inline-code-start" {pipe = 1; next}
$0 == "@@inline-code-end" {pipe = 0; close("nl"); next}
pipe {print | "nl"}
!pipe {print}

Cuando el script ve el marcador de inicio, observa que debería comenzar a conectarse nl. Cuando la pipevariable es verdadera (distinta de cero), la salida se canaliza al nlcomando; cuando la variable es falsa (sin establecer o cero), la salida se imprime directamente. El comando canalizado se bifurca la primera vez que se encuentra la construcción de tubería para cada cadena de comando. Las evaluaciones posteriores del operador de tubería con la misma cadena reutilizan la tubería existente; un valor de cadena diferente crearía una tubería diferente. La closefunción cierra la tubería para la cadena de comando dada.


Esta es esencialmente la misma lógica que su script de shell que usa una tubería con nombre, pero es mucho más fácil de deletrear, y la lógica de cierre es correcta. Debe cerrar la tubería en el momento adecuado para que el nlcomando salga y vacíe sus búferes. Su script realmente cierra la tubería demasiado pronto: la tubería se cierra tan pronto como la primera echo $line >myfifotermina de ejecutarse. Sin embargo, el nlcomando solo ve el final del archivo si obtiene un intervalo de tiempo antes de la próxima vez que se ejecute el script echo $line >myfifo. Si tenía un gran volumen de datos, o si agrega sleep 1después de escribir myfifo, verá que nlsolo procesa la primera línea o el primer grupo rápido de líneas, luego sale porque se ve el final de su entrada.

Usando su estructura, necesitaría mantener la tubería abierta hasta que ya no la necesite. Debe tener una única redirección de salida en la tubería.

nl <myfifo &
exec 3>&1
while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    exec >myfifo
  elif [[ $line == @@inline-code-end* ]]
  then
    exec >&3
  else
    printf '%s\n' "$line"
  fi
done

(También aproveché la oportunidad para agregar citas correctas y demás ; consulte ¿Por qué mi script de shell se ahoga en espacios en blanco u otros caracteres especiales? )

Si está haciendo eso, podría usar una tubería en lugar de una tubería con nombre.

while IFS= read -r line
do
  if [[ $line == @@inline-code-start* ]]
  then
    while IFS= read -r line && [[ $line != @@inline-code-end* ]] do
      printf '%s\n' "$line"
    done | nl
  else
    printf '%s\n' "$line"
  fi
done
Gilles 'SO- deja de ser malvado'
fuente
¡tu solución awk es realmente agradable! Creo que es, con mucho, la solución más concisa (pero muy legible). ¿Está garantizado el comportamiento del awk de reutilizar la tubería a nl, o podría awk decidir, "oye, ya has conectado lo suficiente por ahora ... voy a cerrar esta tubería y abrir una nueva"? Su solución de "tubería" también es realmente agradable. Originalmente descarté un enfoque con bucles while integrados, ya que pensé que podría ser un poco confuso, pero creo que lo que tienes es genial. Falta un punto y coma antes del do. (No tengo el representante aquí para hacer una pequeña edición.)
James Scriven
1
... No pude hacer funcionar su solución de tubería nombrada. Parece que hay una condición de carrera, de modo que la sección canalizada a nl a veces se pierde por completo. Además, si hay una segunda sección @@ inline-code-start / end, siempre se pierde.
James Scriven
0

OK, primero que nada; Entiendo que no está buscando una forma de numerar las líneas en las secciones de su archivo. Como no ha dado un ejemplo real de lo que podría ser su filtro (que no sea nl), supongamos que es

tr "[[:lower:]]" "[[:upper:]]"

es decir, convertir texto a mayúsculas; entonces, para una entrada de

line A
line B
@@inline-code-start
line X
line Y
line Z
@@inline-code-end
line C
line D

quieres una salida de

line A
line B
LINE X
LINE Y
LINE Z
line C
line D

Aquí está mi primera aproximación de una solución:

#!/bin/sh
> file0
> file1
active=0
nl -ba "$@" | while IFS= read -r line
do
        case "$line" in
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-start")
                active=1
                ;;
            ([\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9][\ 0-9]"        @@inline-code-end")
                active=0
                ;;
            (*)
                printf "%s\n" "$line" >> file$active
        esac
done
(cat file0; tr "[[:lower:]]" "[[:upper:]]" < file1) | sort | sed 's/^[ 0-9]\{6\}        //'

donde los espacios antes de las @@cadenas, y cerca del final de la última línea, son pestañas. Tenga en cuenta que estoy usando nl para mis propios fines . (Por supuesto, lo estoy haciendo para resolver su problema, pero no para darle una salida numerada por línea).

Esto numera las líneas de la entrada para que podamos separarlas en los marcadores de sección y saber cómo volver a unirlas más tarde. El cuerpo principal del bucle se basa en su primer intento, teniendo en cuenta el hecho de que los marcadores de sección tienen números de línea. Divide la entrada en dos archivos: file0(inactivo; no en una sección) y file1(activo; en una sección). Así es como se ven para la entrada anterior:

file0:
     1  line A
     2  line B
     8  line C
     9  line D

file1:
     4  line X
     5  line Y
     6  line Z

Luego ejecutamos file1(que es la concatenación de todas las líneas en sección) a través del filtro de mayúsculas; combine eso con las líneas fuera de sección sin filtrar; ordenar, volver a ponerlos en su orden original; y luego quitar los números de línea. Esto produce el resultado que se muestra cerca de la parte superior de mi respuesta.

Esto supone que su filtro deja solo los números de línea. Si no lo hace (p. Ej., Si inserta o elimina caracteres al comienzo de la línea), entonces, creo, este enfoque general todavía se puede usar, pero requerirá una codificación un poco más complicada.

Scott
fuente
nlya hace la mayor parte del trabajo allí, para eso está su -dopción de eliminación.
mikeserv
0

Un script de shell que utiliza sed para generar fragmentos de líneas no delimitadas y alimentar fragmentos de líneas demarcadas en un programa de filtro:

#!/bin/bash

usage(){
    echo "  usage: $0 <input file>"
}

# Check input file
if [ ! -f "$1" ]; then
    usage
    exit 1
fi

# Program to use for filtering
# e.g. FILTER='tr X -'
FILTER='./filter.sh'

# Generate arrays with starting/ending line numbers of demarcators
startposs=($(grep -n '^@@inline-code-start$' "$1" | cut -d: -f1))
endposs=($(grep -n '^@@inline-code-end$' "$1" | cut -d: -f1))

nums=${#startposs[*]}
nume=${#endposs[*]}

# Verify both line number arrays have the same number of elements
if (($nums != $nume)); then
    echo "Tag mismatch"
    exit 2
fi

lastline=1
i=0
while ((i < nums)); do
    # Exclude lines with code demarcators
    sprev=$((${startposs[$i]} - 1))
    snext=$((${startposs[$i]} + 1))
    eprev=$((${endposs[$i]} - 1))

    # Don't run this bit if the first demarcator is on the first line
    if ((sprev > 1)); then
        # Output lines leading up to start demarcator
        sed -n "${lastline},${sprev} p" "$1"
    fi

    # Filter lines between demarcators
    sed -n "${snext},${eprev} p" "$1" | $FILTER

    lastline=$((${endposs[$i]} + 1))
    let i++
done

# Output lines (if any) following last demarcator
sed -n "${lastline},$ p" "$1"

Escribí este script en un archivo llamado detagger.sh y lo utilizó como tal: ./detagger.sh infile.txt. Creé un archivo filter.sh separado para imitar la funcionalidad de filtrado en la pregunta:

#!/bin/bash
awk '{ print "\t" NR " " $0}'

Pero la operación de filtrado se puede cambiar en el código.

Intenté seguir la idea de una solución genérica con esto para que operaciones como las líneas de numeración no requieran un recuento adicional / interno. El script realiza algunas comprobaciones rudimentarias para ver que las etiquetas demarcadoras están en pares y no maneja las etiquetas anidadas con elegancia.

Caca
fuente
-1

Gracias por todas las grandes ideas. Se me ocurrió mi propia solución al hacer un seguimiento de la subsección en un archivo temporal y conectarlo todo de una vez a mi comando externo. Esto es muy similar a lo que sugirió Supr (pero con una variable de shell en lugar de un archivo temporal). Además, me gusta mucho la idea de usar sed, pero la sintaxis para este caso me parece un poco exagerada.

Mi solución:

(Yo uso nlsolo como un filtro de ejemplo)

#!/usr/bin/bash

while read line
do
  if [[ $line == @@inline-code-start* ]]
  then
    active=true
    tmpfile=$(mktemp)
    trap "rm -f $tmpfile" EXIT
  elif [[ $line == @@inline-code-end* ]]
  then
    active=false
    <$tmpfile nl
    rm $tmpfile
  elif [[ $active = true ]]
  then
    echo $line >> $tmpfile
  else
    echo $line
  fi
done

Preferiría no tener que lidiar con la administración de los archivos temporales, pero entiendo que las variables de shell pueden tener límites de tamaño bastante bajos, y no conozco ninguna construcción bash que funcione como un archivo temporal, pero desaparezca automáticamente cuando El proceso termina.

James Scriven
fuente
Pensé que quería ser capaz de “estado se acumulan a través de líneas”, por lo que, por ejemplo, el uso de los datos de prueba de Mike, líneas M, Ny Ose designará 4, 5y 6. Esto no hace eso. Mi respuesta sí (aparte del hecho de que, en su encarnación actual, no funciona nlcomo un filtro). Si esta respuesta le está dando la salida que desea, entonces, ¿qué quiso decir con "acumular estado entre líneas"? ¿Quiso decir que deseaba conservar el estado solo a través de cada sección, pero no entre secciones? (¿Por qué no pusiste un ejemplo de varias secciones en tu pregunta?)
Scott
@Scott: úsalo nl -ppara obtener M,N,O==4,5,6.
mikeserv
Actualicé la pregunta para aclarar que solo estoy interesado en mantener el estado dentro de la subsección, aunque creo que la otra interpretación es igualmente interesante.
James Scriven