2009-06-18 17 views
6

Tengo un archivo de texto que muestra una gran cantidad de apariciones aleatorias de la cadena @STRING_A, y me gustaría escribir un breve script que solo elimine algunos de ellos. En particular uno que explora el archivo y una vez que encuentra una línea que comienza con esta cadena comoBash o Python para ir hacia atrás?

@STRING_A 

entonces comprueba si 3 líneas hacia atrás hay otra ocurrencia de una línea que comienza con la misma cadena, como

@STRING_A 


@STRING_A 

y si sucede, para eliminar la aparición 3 líneas hacia atrás. Estaba pensando en bash, pero no sé cómo "ir hacia atrás" con él. Así que estoy seguro de que esto no es posible con bash. También pensé en Python, pero luego debería almacenar toda la información en la memoria para ir hacia atrás y luego, para archivos largos, sería inviable.

¿Qué opinas? ¿Es posible hacerlo en bash o python?

Gracias

Respuesta

-1

En bash se puede utilizar sort -r filename y tail -n filename para leer el archivo hacia atrás.

$LINES=`tail -n filename | sort -r` 
# now iterate through the lines and do your checking 
+0

Cómo en el mundo no clasificar un archivo alfabéticamente (sort -r) o la salida de las últimas n líneas de un archivo (-n) la cola de resolver este problema? –

+0

¿Qué OS estás usando? sort -r no ordena alfabéticamente en Linux. He editado para mayor claridad. http://www.thelinuxblog.com/linux-man-pages/1/sort http://www.thelinuxblog.com/linux-man-pages/1/tail –

+0

¿Y qué hay de 'tac'? – log0

1

¿Por qué no debería ser posible en bash? No es necesario que guarde todo el archivo en la memoria, solo las últimas tres líneas (si lo entendí correctamente) y escriba lo que es apropiado para el estándar. Redirija eso a un archivo temporal, verifique que todo funcionó como se esperaba y sobrescriba el archivo fuente con el temporal.

Lo mismo ocurre con Python.

Proporcionaría un script propio, pero eso no sería probado. ;-)

2

Por supuesto, Python también funcionará. Simplemente almacene las últimas tres líneas en una matriz y verifique si el primer elemento de la matriz es el mismo que el valor que está leyendo actualmente. Luego borre el valor e imprima la matriz actual. Luego se movería sobre sus elementos para dejar espacio para el nuevo valor y repetir. Por supuesto, cuando se llena la matriz, debe asegurarse de continuar moviendo los valores fuera de la matriz y colocar los valores recién leídos, deteniéndose para verificar cada vez que el primer valor de la matriz coincide con el valor que tiene. Actualmente leyendo.

1

Este código escaneará el archivo y eliminará las líneas que comiencen con el marcador. Sólo se mantiene sólo tres líneas en la memoria por defecto:

from collections import deque 

def delete(fp, marker, gap=3): 
    """Delete lines from *fp* if they with *marker* and are followed 
    by another line starting with *marker* *gap* lines after. 
    """ 
    buf = deque() 
    for line in fp: 
     if len(buf) < gap: 
      buf.append(line) 
     else: 
      old = buf.popleft() 
      if not (line.startswith(marker) and old.startswith(marker)): 
       yield old 
      buf.append(line) 
    for line in buf: 
     yield line 

He probado con:

>>> from StringIO import StringIO 
>>> fp = StringIO('''a 
... b 
... xxx 1 
... c 
... xxx 2 
... d 
... e 
... xxx 3 
... f 
... g 
... h 
... xxx 4 
... i''') 
>>> print ''.join(delete(fp, 'xxx')) 
a 
b 
xxx 1 
c 
d 
e 
xxx 3 
f 
g 
h 
xxx 4 
i 
+0

No me parece correcto. El OP no dijo nada sobre eliminar una región. Sí dijo: "" "ocurrencias aleatorias de la cadena @STRING_A, y me interesaría escribir un breve script que elimine solo algunos de ellos" "" y "" "borrar la ocurrencia 3 líneas hacia atrás" "". –

+0

Bueno, fue trivial actualizar el código para que coincida con la pregunta :-) –

+0

s/actualización trivial/reescritura/... y también todavía no tiene el punto de que el OP dijo que quería eliminar todas las apariciones del cadena, NO toda la línea. –

-2

yo consideraría usando sed. gnu sed admite la definición de rangos de línea. si sed fallara, entonces hay otra bestia - awk y estoy seguro de que puedes hacerlo con awk.

O.K. Siento que debería poner mi awk POC. No pude descifrar el uso de direcciones sed. No he probado la combinación de awk + sed, pero me parece que es exagerado.

mi script awk funciona de la siguiente manera:

  • Se lee las líneas y los almacena en el buffer de línea 3

  • patrón deseado una vez se encontró (/^data.*/ en mi caso), la memoria intermedia de 3-línea se busca para comprobar, si patrón deseado se ha visto hace tres líneas

  • si el patrón se ha visto, a continuación, 3 líneas están rayados

para ser honesto, probablemente me iría con python también, dado que awk es realmente incómodo. el código AWK sigue:

 
function max(a, b) 
{ 
    if (a > b) 
     return a; 
    else 
     return b; 
} 

BEGIN { 
    w = 0; #write index 
    r = 0; #read index 
    buf[0, 1, 2]; #buffer 

} 

END { 
    # flush buffer 
    # start at read index and print out up to w index 
    for (k = r % 3; k r - max(r - 3, 0); k--) { 
     #search in 3 line history buf 
     if (match(buf[k % 3], /^data.*/) != 0) { 
      # found -> remove lines from history 
      # by rewriting them -> adjust write index 
      w -= max(r, 3); 
     } 
    } 
    buf[w % 3] = $0; 
    w++; 
} 

/^.*/ { 
    # store line into buffer, if the history 
    # is full, print out the oldest one. 
    if (w > 2) { 
     print buf[r % 3]; 
     r++; 
     buf[w % 3] = $0; 
    } 
    else { 
     buf[w] = $0; 
    } 
    w++; 
} 
+0

Puedes hacerlo en Brainfuck o INTERCAL, también. El truco está en el "cómo" ... – DevSolar

+0

awk probablemente pueda hacerlo solo ... pero sospecho que es más limpio usar realmente awk + sed, según mi solución anterior. – jkerian

+0

Martillo, cumple con el tornillo. – l0b0

1

Como dijo AlbertoPL, líneas de almacenar en un FIFO para su uso posterior - no "ir hacia atrás". Para esto, definitivamente usaría Python sobre bash + sed/awk/whatever.

me tomó unos minutos para codificar este fragmento arriba:

from collections import deque 
line_fifo = deque() 
for line in open("test"): 
    line_fifo.append(line) 
    if len(line_fifo) == 4: 
     # "look 3 lines backward"            
     if line_fifo[0] == line_fifo[-1] == "@STRING_A\n": 
      # get rid of that match 
      line_fifo.popleft() 
     else: 
      # print out the top of the fifo 
      print line_fifo.popleft(), 
# don't forget to print out the fifo when the file ends 
for line in line_fifo: print line, 
+0

El OP dice que solo quiere que se elimine "@STRING_A" del inicio de la línea ... "línea que comienza con", "borre la ocurrencia" ("occurrence" se usa en todas partes para referirse a esa cadena). Todos parecen estar asumiendo que toda la línea es (a) para ser probada contra (b) eliminada. Punto 2: ¿por qué enrollas tu fifo cuando hay un deque provisto? –

+0

@John: Creo que el OP podría haber hecho las cosas más precisas dando un ejemplo de cómo debería verse el archivo antes y después de la eliminación. Tanto mi código anterior como el código de Goger deberían ser suficientes para resolver el problema. –

+1

@John: IMO el primer punto es un detalle de implementación para el OP, tangencial a la carne de la pregunta. Su segundo punto con respecto al deque es bueno y he actualizado mi código. – goger

0

Mi awk-fu nunca ha sido tan bueno ... pero Los factores que pueden ofrecer lo que estás buscando en un bash- shell/shell-utility form:

sed `awk 'BEGIN{ORS=";"} 
/@STRING_A/ { 
    if(LAST!="" && LAST+3 >= NR) print LAST "d" 
    LAST = NR 
}' test_file` test_file 

Básicamente ... awk está produciendo un comando para que sed elimine ciertas líneas. Estoy seguro de que hay una manera relativamente fácil de hacer que todo el proceso se haga, pero parece que funciona.

¿La parte mala? Lee el archivo de prueba dos veces.

¿La parte buena? Es una implementación de utilidad bash/shell.

Editar: Alex Martelli señala que el archivo de muestra anterior podría haberme confundido. (Mi código borra toda la línea, en lugar de la bandera @STRING_A solamente)

Esto se remedia fácilmente mediante el ajuste de la orden de sed:

sed `awk 'BEGIN{ORS=";"} 
/@STRING_A/ { 
    if(LAST!="" && LAST+3 >= NR) print LAST "s/@STRING_A//" 
    LAST = NR 
}' test_file` test_file 
2

Aquí es una solución más divertido, usando dos iteradores con una de tres elementos compensado :)

from itertools import izip, chain, tee 
f1, f2 = tee(open("foo.txt")) 
for third, line in izip(chain(" ", f1), f2): 
    if not (third.startswith("@STRING_A") and line.startswith("@STRING_A")): 
     print line, 
+0

¡Muy bueno! :-) Usando la función tee de itertools ("T", como en el tipo de tubería que usa para dividir una tubería de agua en dos tuberías) puede obtener dos iteradores para el archivo y así evitar leer el archivo dos veces. No creo que importe mucho aquí ya que el sistema operativo almacenará el archivo de todos modos, pero es divertido jugar con iteradores :-) –

+0

La camiseta suena bien, actualizó el código. – truppo

4

es curioso que después de todos estos hora nadie ha dado todavía una solución al problema, ya que en realidad su enunciado (como señala @ John Machin en un comentario) - acaba de quitar el marcador de líder (si seguido de otro marcador 3 líneas abajo), no toda la línea reteniéndolo No es difícil, por supuesto - aquí hay un pequeño mod según sea necesario de la solución de la diversión de @ truppo, por ejemplo:

from itertools import izip, chain 
f = "foo.txt" 
for third, line in izip(chain(" ", open(f)), open(f)): 
    if third.startswith("@STRING_A") and line.startswith("@STRING_A"): 
     line = line[len("@STRING_A"):] 
    print line, 

Por supuesto, en la vida real, se podría utilizar un iterator.tee lugar de leer el archivo dos veces, tiene este código en una función, no repite la constante del marcador sin fin, & c ;-).

+0

¿Por qué volverías a publicar el código de truppo de esa manera? ¿Y por qué continúan quejándose de nuestras soluciones perfectas cuando la pregunta no es clara para empezar? –

+0

@Martin, estoy de acuerdo con John Machin sobre la probable interpretación de la pregunta, aunque tienes razón, es ligeramente ambigua, y las soluciones de tu o truppo estarían bien bajo una interpretación diferente, pensé que publicar la solución sería lo más probable la interpretación era mejor que dejar eso sin respuesta. Elegí la respuesta de truppo (¡con crédito completo, por supuesto!) Como base porque estoy de acuerdo con tu comentario acerca de que es genial, y no lo edité en el lugar porque eso violaría las pautas de edición. ¡Espero que esto ayude! –

-1

Esto puede ser lo que estás buscando?

lines = open('sample.txt').readlines() 

needle = "@string " 

for i,line in enumerate(lines): 
    if line.startswith(needle) and lines[i-3].startswith(needle): 
     lines[i-3] = lines[i-3].replace(needle, "") 
print ''.join(lines) 

Muestra esto:

string 0 extra text 
string 1 extra text 
string 2 extra text 
string 3 extra text 
--replaced -- 4 extra text 
string 5 extra text 
string 6 extra text 
@string 7 extra text 
string 8 extra text 
string 9 extra text 
string 10 extra text 
+0

Reemplaza la aguja en lugar de quitarla. Lee el archivo completo en la memoria y luego hace otra copia durante la instrucción de impresión al final. Escribe una nueva línea adicional al final de la salida. Se bloqueará (IndexError) si la aguja ocurre en las primeras 3 líneas. –

+0

Fácil de arreglar. Él puede hacer que el reemplazo sea "". Eso eliminará la aguja de la línea. No causa indexerror. No necesita imprimir al final; puede escribir directamente en otro archivo. Sin embargo, copia todo el archivo en la memoria. – sqram

+0

Se puede hacer que cause IndexError. Se puede hacer para masticar una línea inocente. Hacer el reemplazo "" no es suficiente. Ver la "respuesta" para la demostración. –

0

Esta "respuesta" es para Lyrae ... Voy a enmendar mi comentario anterior: si la aguja está en las 3 primeras líneas del archivo, la secuencia de comandos provocará un IndexError o accederá a una línea a la que no debería acceder, a veces con efectos secundarios interesantes.

Ejemplo de secuencia de comandos que causan IndexError:

>>> lines = "@string line 0\nblah blah\n".splitlines(True) 
>>> needle = "@string " 
>>> for i,line in enumerate(lines): 
...  if line.startswith(needle) and lines[i-3].startswith(needle): 
...   lines[i-3] = lines[i-3].replace(needle, "") 
... 
Traceback (most recent call last): 
    File "<stdin>", line 2, in <module> 
IndexError: list index out of range 

y este ejemplo demuestra no sólo que la Tierra es redonda, sino también por qué su "solución" al problema "no elimine toda la línea" debe tener utilizado .replace(needle, "", 1) o [len(needle):] en lugar de .replace(needle, "")

>>> lines = "NEEDLE x NEEDLE y\nnoddle\nnuddle\n".splitlines(True) 
>>> needle = "NEEDLE" 
>>> # Expected result: no change to the file 
... for i,line in enumerate(lines): 
...  if line.startswith(needle) and lines[i-3].startswith(needle): 
...   lines[i-3] = lines[i-3].replace(needle, "") 
... 
>>> print ''.join(lines) 
x y <<<=== whoops! 
noddle 
nuddle 
     <<<=== still got unwanted newline in here 
>>> 
+0

ahh gotcha. Gracias. – sqram

Cuestiones relacionadas