¿Hay alguna alternativa a los conmutadores grep -A -B -C (para imprimir algunas líneas antes y después)?

10
grep -A 2 -B 3 

imprime 2 líneas después de la cadena grep e imprime 3 líneas antes.

grep -C 3

imprime 3 líneas antes y 3 líneas después

Desafortunadamente, el grepque estoy usando no admite estas opciones. ¿Hay algún comando o script alternativo disponible para simular esto? ¿Usando sed/ awk/ perl/ scripts de shell?

Prashant Bhate
fuente
+1 No sabía que había un -Ccambio.
Lazer
1
Instala grep de GNU. En términos más generales, cuando una nueva máquina Sun llegó aquí, el primer paso en la configuración fue lo que alguien llamó GNU > /usr/local. Los programas GNU tienen muchas extensiones muy útiles, y están diseñados para evitar restricciones arbitrarias (pero se paga caro en tamaño y, a veces, en rendimiento). Muchos sistemas propietarios tienen repositorios de paquetes "no oficiales" con GNU y otras herramientas. El "socio" no le informará acerca de ellos, incluso cuando sean administrados por el proveedor ...
vonbrand

Respuestas:

6

Una forma moderadamente fea de hacerlo es

grep -v pattern file >file.tmp; diff -c file.tmp file

o sustituir -ccon -C NUMde NUMlíneas de contexto. Sin embargo, producirá una salida extra. (Si sus diffsoportes -u/ -U NUM, será más limpio).

Si diffno tiene -c/ -C/ -u, todavía hay formas de hacerlo, pero son bastante feas. Por otro lado, un sistema diffque ni siquiera es compatible -cprobablemente tampoco tenga Perl.

geekosaur
fuente
Esto es genial, funciona de maravilla, aunque tuve que usar la opción -bitw con esto para que funcione para archivos generados por Windows.
Prashant Bhate
Puede enviar stdin a diff y omitir el temporal:grep -v pattern file | diff -c - file
Cascabel
5

ACK sólo requiere Perl, e incluye -A, -By -Clas opciones que funcionan como grep. Utiliza la sintaxis de expresiones regulares de Perl en lugar de la de grep, y la forma en que selecciona los archivos para buscar es bastante diferente. Es posible que desee probar la -fopción cuando la use (que imprime los archivos que buscará sin buscar nada).

Se puede instalar como un script único que no requiere módulos no básicos. Simplemente colóquelo en su ~/bindirectorio (o en cualquier otro lugar de su RUTA al que tenga acceso de escritura) y asegúrese de que sea chmodejecutable.

cjm
fuente
Su caja de la producción y por desgracia no tiene privilegios suficientes para instalar cualquier cosa, y no puedo correr el riesgo, sin embargo, gracias por este consejo me lo instalarán y probar en mi portátil en casa
Prashant Bhate
@Prashant, no necesita root para instalarlo ackpara su propio uso.
cjm
Sí, pero aún no puedo usarlo allí, aunque es seguro que este script permanecerá para siempre en mi ~ / bin :)
Prashant Bhate
@Prashant: ¿Por qué no puedes usarlo? Es solo un guión perl.
intuido
1
Su caja de PRODUCCIÓN, necesita tener permisos especiales aprobaciones bla bla bla ... hacer cualquier cosa en él. y todo sale mal en mi cabeza;) y no vale la pena :)
Prashant Bhate
5

Este simple script perl emula grep -Ahasta cierto punto

#!/usr/bin/perl

$pattern=shift; #patthern to search
$lines=shift; # number of lines to print

$n = 0;
while (<>) {
  $n = $lines if /$pattern/; # reset counting
  if ($n) { print; $n-- } # print if within
  $n = 0 if eof; # don't leak across file boundaries
}

Tenga en cuenta que puede agregar una declaración de uso para que el script sea legible y utilizable;)

USAGE:    $./grep-A.pl <pattern> <numLines> <filename> 
Vijay Anant
fuente
Bien, ¿qué versión de perl necesito para ejecutar esto?
Prashant Bhate
Yo uso v5.10.1, supongo que perl 5 es bastante común en estos días.
Vijay Anant
ya es 5.8.8 y funciona, genial, pero necesito un script que haga lo que -B hace
Prashant Bhate
Bueno. Sin embargo, cambiaría el orden de los argumentos; grep-A 3 foose ve mucho más natural que grep-A foo 3. :-)
musiphil
3

Simplemente puede instalar GNU grep o Ack (escrito en Perl, comprende muchas de las opciones de GNU grep y más).

Si prefiere apegarse a las herramientas estándar más un poco de secuencias de comandos, aquí hay una secuencia de comandos awk que emula el comportamiento de grep -Ay -Bopciones de GNU . Mínimamente probado

#!/bin/sh
# grep-ac: a grep-like awk script
# Arguments: pattern = awk regexp to search for
#            before = number of lines to print before a match
#            after = number of lines to print after a match
{ "exec" "awk" "-f" "$0" "$@"; }
# The array h contains the history of lines that haven't been printed
# but are eligible for being "before" lines.
# The variable until contains the number of the last "after" line to print.
match($0, pattern) {   # the current line matches
    for (i in h) {
        print h[i];    # print each remaining before line
        delete h[i];   # delete each line as it's printed
    }
    until=NR+after;    # record the last after line to print
}
{
    if (NR<=until) print $0;    # from a match to its last after line: print
    else h[NR]=$0;              # after that: save in history
    delete h[NR-before];        # remove line too old to be a before line
}
END {exit !until}               # exit status: 0 if there was a match, else 1

Ejecútelo como grep-ac -vpattern=PATTERN -vbefore=NBEFORE -vafter=NAFTERdónde PATTERNestá el patrón para buscar (una expresión regular extendida con algunas adiciones awk ), NBEFOREy NAFTERson los números de líneas para imprimir antes y después de una coincidencia respectivamente (por defecto a 0). Ejemplo:

<input_file grep-ac -vbefore=2 -vpattern='foo *bar'
Gilles 'SO- deja de ser malvado'
fuente
Cualquier solución que almacene datos en una matriz está fuera de discusión ... como mencioné anteriormente, el tamaño del archivo es bastante grande y puede desbordarse. También awk en este sistema no permite un tamaño de archivo de más de 3000 bytes.
Prashant Bhate
2
@Prashant: No entiendo tus objeciones. Este script elimina líneas una vez que no son elegibles para ser líneas anteriores. No usa más memoria de la que es inherentemente necesaria dados los requisitos, excepto que awk puede tener una sobrecarga más alta que un programa de propósito especial (pero menos que Perl, que también está considerando). El tamaño total del archivo es completamente irrelevante.
Gilles 'SO- deja de ser malvado'
2
{ "exec" "awk" "-f" "$0" "$@"; }: forma muy ingeniosa de sortear las limitaciones en el análisis de líneas shebang.
dubiousjim
2

Resulta que es bastante complicado emular -B, debido a los problemas que surgen cuando tienes líneas coincidentes que se siguen directamente. Esto prácticamente no permite el uso de ningún tipo de escaneo de archivos de paso único.

Me di cuenta de esto mientras jugaba con la siguiente aproximación:

perl -pe 'if(/search_term/) {print foreach @A; print ">"; $B=4}; shift @A if push(@A, $_)>7; $_ = "" unless ($B-- > 0);' target_file

Esto funcionará aproximadamente correctamente como grep -A7 -B3, con la advertencia descrita en el primer párrafo.

Una solución alternativa (también de un solo archivo) para este problema es usar perl para alimentar una cadena de comando:

sed -n `perl -pe '$_=(/search_term/?sprintf("%d,%dp;", $.-3,$.+4):"")' file` file
usuario455
fuente
bastante largo, pero este archivo es muy grande, por lo que insertar líneas en la matriz en este caso es una mala idea, ¿no?
Prashant Bhate
El shift @A if push(@A,$_)>7;bit solo mantiene una matriz de tamaño máximo 7 alrededor. (ese es su parámetro -A). La segunda opción mantiene un archivo increíblemente pequeño (solo ejecuta el perl sin la capa externa sed para ver lo que se genera allí), pero lee el archivo dos veces.
user455
0

Con el uso sed, primero puede obtener los números de línea de las líneas coincidentes, disminuir e incrementar un número de línea dado en un whilebucle y luego usar sed -n "n1,n2p"para imprimir líneas de contexto inicial ( n1) y final ( n2) (similar a la sedalternativa sugerida por el usuario455). Sin embargo, muchos procesos de lectura pueden conducir a un impacto en el rendimiento.

edpuede hacer referencia directa a las líneas anteriores y siguientes de una línea coincidente, pero falla si el rango de línea especificado no existe; por ejemplo, la línea coincidente es la línea número 2, pero deben imprimirse 5 líneas anteriores. Por edlo tanto, es necesario agregar un número apropiado de líneas (vacías) al principio y al final. (Sin edembargo, para archivos grandes puede que no sea la herramienta adecuada, consulte: bfs - escáner de archivos grandes ).

# sample code to match lines with number 5 plus previous & following line
# (using Bash)
printf '%s\n' {1..20} > num.txt

# sed
sed -n '/5/=' num.txt | while read num; do
   n1=$((num - 1))
   n2=$((num + 1))
   [[ $n1 -lt 1 ]] && n1=1
   sed -n "${n1},${n2}p" num.txt
   echo --
done | sed -e '${/^--$/d;}'

# ed
cat <<-'EOF' | ed -s num.txt | sed -e $'N;N;a\\\n--' | sed -e '${/^--$/d;}'
H
0i
beginning: added line one
.
$a
end: added line one
.
,g/5/km\
'm-1,'m+1p
q
EOF
larz
fuente