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 ( nl
por 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 nl
puede 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 nl
es 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.
fuente
nl
No tiene que acumular estado . Mirenl -d
y comprobar suman
/info
páginas de información sobrenl
's sección delimitador .nl
un 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 gnusource-highlight
, pero eso podría cambiar, y podría agregar más filtros, como un formateador también.Respuestas:
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-d
eliminadas 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 imprimeModifiqué tu ejemplo para incluir otra sección y ponerlo
./infile
. Entonces se ve así:Luego ejecuté lo siguiente:
nl
se 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-ha
significa numerar todas las líneas de encabezado y-bn
significa que no hay líneas de cuerpo , ya que comienza en un cuerpo estado de .Hasta que aprendí esto, solía usarlo
nl
para cualquier entrada, pero después de darme cuenta de quenl
podría distorsionar la salida de acuerdo con su-d
eliminador predeterminado\:
, aprendí a ser más cuidadoso con él y comencé a usarlogrep -nF ''
para la entrada no probada. Pero otra lección aprendida ese día fue quenl
se puede aplicar de manera muy útil en otros aspectos, como este, si solo modifica un poco su entrada, como hago consed
anterior.SALIDA
Aquí hay más información
nl
: ¿observan cómo todas las líneas, excepto las numeradas, comienzan con espacios? Cuando lasnl
líneas de números inserta un cierto número de caracteres en la cabeza de cada uno. Para esas líneas no-w
numera , incluso espacios en blanco, siempre coincide con la sangría insertando ( idth count +-s
eparator 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 quenl
dividirá su entrada en secciones lógicas para usted y que puede insertar-s
trings arbitrarios en la cabecera de cada línea que numere, entonces se vuelve bastante fácil manejar su salida:Las impresiones anteriores ...
ÑU
sed
Si
nl
no es su aplicación de destino, un GNUsed
puedee
ejecutar un comando de shell arbitrario en función de una coincidencia.Arriba
sed
recoge la entrada en el espacio del patrón hasta que tenga suficiente para pasar con éxito elT
est de sustitución y dejar deb
volver a la:l
granja. Cuando lo hace, see
ejecutanl
con una entrada representada como un<<
documento aquí para todo el resto de su espacio de patrones.El flujo de trabajo es así:
/^@@.*start$/!b
^
línea entera$
no!
no/
coincide con/
el patrón anterior, entonces seb
ranched 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.s//nl <<\\@@/
s//
campo vacío/
representa la última dirección quesed
intentó coincidir, por lo que este comando sustituye la@@.*start
línea completa en sunl <<\\@@
lugar.:l;N
:
comando define una etiqueta de rama: aquí configuro uno llamado:l
abel. ElN
comando ext agrega la siguiente línea de entrada al espacio de patrón seguido de un\n
carácter de línea de flujo. Esta es una de las pocas formas de obtener un\n
ewline en unsed
espacio de patrón: el\n
carácter ewline es un delimitador seguro para unsed
der que lo ha estado haciendo durante un tiempo.s/\(\n@@\)[^\n]*end$/\1/
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\n
ew final es seguida inmediatamente@@.*end
marcando el final$
del espacio de patrón. Cuando actúa, reemplaza toda la cadena coincidente con el\1
primer\(
grupo\)
, o\n@@
.Tl
T
comando 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\n
se agrega una línea ew al espacio de patrón que no coincide con su delimitador final, elT
comando est falla y se bifurca de nuevo al:l
abel, lo que resulta ensed
tirar de laN
línea ext y hacer un bucle hasta que tenga éxito.e
Cuando la sustitución de la coincidencia final sea exitosa y la secuencia de comandos no se ramifique para una prueba fallida
T
,sed
see
ejecutará un comando quel
tenga este aspecto:Puedes ver esto por ti mismo editando la última línea para que se vea
Tl;l;e
.Imprime:
while ... read
Una última forma de hacer esto, y quizás la más simple, es usar un
while read
bucle, pero por una buena razón. El shell - (más especialmente unbash
shell) - 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. Porread
lo 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:Lo primero que sucede para cada iteración son los
read
tirones en una línea. Si tiene éxito, significa que el bucle aún no ha llegado a EOF y, por lo tanto,case
si coincide con un delimitador de inicio, eldo
bloque se ejecuta inmediatamente. Si no,printf
imprime el$line
queread
ysed
se llama.sed
sep
rint cada línea hasta que se encuentra con el inicio marcador - cuandoq
UITS de entrada por completo. El-u
conmutador nbuffered es necesario para GNUsed
porque de lo contrario puede almacenarse en búfer con bastante avidez, pero, según las especificaciones, otros POSIXsed
deberían funcionar sin ninguna consideración especial, siempre que<infile
sea un archivo normal.Cuando se
sed
q
inicia por primera vez, el shell ejecuta eldo
bloque del bucle, que llama a otrosed
que imprime cada línea hasta que encuentra el marcador final . Canaliza su salida apaste
, porque imprime los números de línea cada uno en su propia línea. Me gusta esto:paste
luego los pega juntos en los:
caracteres, y toda la salida se ve así: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
,head
ysed
hacer lo correcto (aunque, para GNUsed
, se necesita el cli-switch) y siempre debe poder confiarread
, porque es, por naturaleza, muy lento . Y es por eso que el ciclo anterior lo llama solo una vez por bloque de entrada.fuente
sed
ejemplo 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.)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 serEsto 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
.fuente
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).La solución más simple que se me ocurre es no usar
nl
sino contar las líneas usted mismo:Luego lo ejecuta en el archivo:
fuente
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:
Esto produce lo siguiente para un archivo de entrada que repite el caso de prueba tres veces:
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:O recuento de palabras
echo -E "${acc:1}" | wc
:fuente
Editar agregó una opción para definir un filtro proporcionado por el usuario
Por defecto el filtro es "nl". Para cambiar el filtro, use la opción "-p" con algún comando proporcionado por el usuario:
o
Este último filtro generará:
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:
fuente
$/
y marcandos
), y el uso de lae
bandera para hacer la llamada real al comando externo. ¡Realmente me gusta el segundo ejemplo (arte ascii)!/s
= ("." significa(.|\n)
);$/
redefine el separador de registros.Ese es un trabajo para awk.
Cuando el script ve el marcador de inicio, observa que debería comenzar a conectarse
nl
. Cuando lapipe
variable es verdadera (distinta de cero), la salida se canaliza alnl
comando; 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. Laclose
funció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
nl
comando 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 primeraecho $line >myfifo
termina de ejecutarse. Sin embargo, elnl
comando solo ve el final del archivo si obtiene un intervalo de tiempo antes de la próxima vez que se ejecute el scriptecho $line >myfifo
. Si tenía un gran volumen de datos, o si agregasleep 1
después de escribirmyfifo
, verá quenl
solo 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.
(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.
fuente
do
. (No tengo el representante aquí para hacer una pequeña edición.)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 eses decir, convertir texto a mayúsculas; entonces, para una entrada de
quieres una salida de
Aquí está mi primera aproximación de una solución:
donde los espacios antes de las
@@
cadenas, y cerca del final de la última línea, son pestañas. Tenga en cuenta que estoy usandonl
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) yfile1
(activo; en una sección). Así es como se ven para la entrada anterior: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.
fuente
nl
ya hace la mayor parte del trabajo allí, para eso está su-d
opción de eliminación.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:
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: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.
fuente
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
nl
solo como un filtro de ejemplo)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.
fuente
M
,N
yO
se designará4
,5
y6
. Esto no hace eso. Mi respuesta sí (aparte del hecho de que, en su encarnación actual, no funcionanl
como 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?)nl -p
para obtenerM,N,O==4,5,6
.