¿Cómo grep una línea específica _y_ la primera línea de un archivo?

76

Asumiendo un grep simple como:

$ psa aux | grep someApp
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp

Esto proporciona mucha información, pero como falta la primera línea del comando ps no hay contexto para la información. Preferiría que también se muestre la primera línea de ps:

$ psa aux | someMagic someApp
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp

Por supuesto, podría agregar una expresión regular a grep específicamente para ps:

$ ps aux | grep -E "COMMAND|someApp"

Sin embargo, preferiría una solución más general ya que hay otros casos en los que me gustaría tener la primera línea también.

Parece que este sería un buen caso de uso para un descriptor de archivo "stdmeta" .

dotancohen
fuente
99
La complejidad requerida por estas respuestas muestra cómo la filosofía de Unix de "hacer una cosa y hacerlo bien" a veces nos falla cuando se mide por el criterio de usabilidad: conocer todos estos comandos lo suficientemente bien como para aplicarlos a este problema común (información del proceso de filtrado y aún viendo las etiquetas de las columnas) muestra la desventaja del enfoque: a veces las cosas no encajan muy bien. Esta es la razón por herramientas como ackson tan útiles, y por perlúltimo se dispararon sed, awk, etc en popularidad: es importante que las partes en suma en un todo coherente.
iconoclasta
3
por supuesto, para este ejemplo en particular, podría usar el -Cargumento psay no necesitaría canalizarlo a grep. por ejemplo, ps u -C someAppo inclusops u -C app1 -C app2 -C app3
cas
1
@iconoclast: por supuesto, la solución Unixy sería una herramienta que puede multiplexar múltiples líneas cada una para filtrarlas a través de diferentes conjuntos de filtros. Una versión generalizada de la ps aux | { head -1; grep foo; }mencionada por @Nahuel Fouilleul a continuación (la suya es probablemente la única solución que podría recordar en el acto si fuera necesario)
Lie Ryan
@iconoclast: Al carecer de experiencia y conocimiento de las herramientas, lo que realmente hacen bien las herramientas siempre parecerá completamente inútil. Conocer bien un comando no está en el palo de la usabilidad, está en el palo de leer el excelente manual y practicar. Estas herramientas han existido por décadas. Funcionan y encajan muy bien (y limpiamente).
Ярослав Рахматуллин
@ ЯрославРахматуллин: Creo que es posible que hayas entendido mal lo que dije. (¿Quizás porque el inglés no es su primer idioma?) La "usabilidad" está relacionada con UX ("experiencia de usuario"), no con la utilidad (o "utilidad"). Señalar que cuando una operación simple es tan compleja que perjudica la usabilidad NO es lo mismo que decir que las herramientas son inútiles. Obviamente no son inútiles. Nadie en su sano juicio diría que son inútiles.
iconoclasta

Respuestas:

67

Buen camino

Normalmente no puede hacer esto con grep pero puede usar otras herramientas. AWK ya se mencionó, pero también puede usarlo sedasí:

sed -e '1p' -e '/youpattern/!d'

Cómo funciona:

  1. La utilidad Sed funciona en cada línea individualmente, ejecutando comandos específicos en cada una de ellas. Puede tener múltiples comandos, especificando varias -eopciones. Podemos anteponer cada comando con un parámetro de rango que especifica si este comando debe aplicarse a una línea específica o no.

  2. "1p" es un primer comando. Utiliza un pcomando que normalmente imprime todas las líneas. Pero lo anteponemos con un valor numérico que especifica el rango al que debe aplicarse. Aquí, usamos lo 1que significa primera línea. Si desea imprimir más líneas, puede usar x,ypdónde xestá la primera línea para imprimir, yes la última línea para imprimir. Por ejemplo, para imprimir las primeras 3 líneas, usaría1,3p

  3. El siguiente comando es el dque normalmente elimina todas las líneas del búfer. Antes de este comando ponemos yourpatternentre dos /caracteres. Esta es la otra forma (primero fue especificar en qué líneas como hicimos con el pcomando) de direccionar las líneas en las que el comando debería estar ejecutándose. Esto significa que el comando solo funcionará para las líneas que coinciden yourpattern. Excepto, usamos el !carácter antes del dcomando que invierte su lógica. Entonces ahora eliminará todas las líneas que no coinciden con el patrón especificado.

  4. Al final, sed imprimirá todas las líneas que quedan en el búfer. Pero eliminamos las líneas que no coinciden del búfer, por lo que solo se imprimirán las líneas coincidentes.

Para resumir: imprimimos la primera línea, luego eliminamos todas las líneas que no coinciden con nuestro patrón de la entrada. Resto de las líneas se imprimen (tan sólo las líneas que hacen coincidir con el patrón).

Problema de primera línea

Como se mencionó en los comentarios, hay un problema con este enfoque. Si el patrón especificado coincide también con la primera línea, se imprimirá dos veces (una por pcomando y otra por una coincidencia). Podemos evitar esto de dos maneras:

  1. Agregar 1dcomando después 1p. Como ya mencioné, el dcomando elimina líneas del búfer y especificamos su rango con el número 1, lo que significa que solo eliminará la primera línea. Entonces el comando seríased -e '1p' -e '1d' -e '/youpattern/!d'

  2. Usando el 1bcomando, en lugar de 1p. Es un truco. bEl comando nos permite saltar a otro comando especificado por una etiqueta (de esta manera se pueden omitir algunos comandos). Pero si esta etiqueta no se especifica (como en nuestro ejemplo) simplemente salta al final de los comandos, ignorando el resto de los comandos para nuestra línea. Entonces, en nuestro caso, el último dcomando no eliminará esta línea del búfer.

Ejemplo completo:

ps aux | sed -e '1b' -e '/syslog/!d'

Usando punto y coma

Algunas sedimplementaciones pueden ahorrarle algo de tipeo usando punto y coma para separar comandos en lugar de usar múltiples -eopciones. Entonces, si no te importa ser portátil, el comando sería ps aux | sed '1b;/syslog/!d'. Funciona al menos en GNU sede busyboximplementaciones.

Manera loca

Aquí hay, sin embargo, una forma bastante loca de hacer esto con grep. Definitivamente no es óptimo, estoy publicando esto solo con fines de aprendizaje, pero puede usarlo, por ejemplo, si no tiene ninguna otra herramienta en su sistema:

ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog'

Cómo funciona

  1. Primero, usamos la -nopción para agregar números de línea antes de cada línea. Queremos numerar todas las líneas que estamos haciendo coincidir .*, cualquier cosa, incluso una línea vacía. Como se sugiere en los comentarios, también podemos hacer coincidir '^', el resultado es el mismo.

  2. Luego estamos usando expresiones regulares extendidas para que podamos usar \|caracteres especiales que funcionen como OR. Entonces, hacemos coincidir si la línea comienza con 1:(primera línea) o contiene nuestro patrón (en este caso, su syslog).

Problema de números de línea

Ahora el problema es que estamos obteniendo estos números de línea feos en nuestra salida. Si esto es un problema, podemos eliminarlos de cutesta manera:

ps aux | grep -n '.*' | grep -e '\(^1:\)\|syslog' | cut -d ':' -f2-

-dLa opción especifica delimitador, -fespecifica los campos (o columnas) que queremos imprimir. Por lo tanto, queremos cortar cada línea en cada :carácter e imprimir solo la segunda y todas las columnas posteriores. Esto elimina efectivamente la primera columna con su delimitador y esto es exactamente lo que necesitamos.

Krzysztof Adamski
fuente
44
La numeración de líneas también se puede hacer cat -ny se vería más clara como con un grep abusado por esto.
Alfe
1
nlno cuenta líneas vacías (pero las imprime sin número de línea), cat -nformatea la numeración con espacios anteriores, grep -n .elimina las líneas vacías y agrega dos puntos. Todos tienen sus ... er ... características ;-)
Alfe
2
Respuesta muy bien escrita y educativa. Traté de reemplazar "Pretender" (Casi al principio) con "Preponer" para ti, pero quería más cambios y no tenía ganas de cambiar basura aleatoria en tu publicación, por lo que es posible que desees solucionarlo.
Bill K
2
ps aux | sed '1p;/pattern/!d'imprimirá la primera línea dos veces si coincide con el patrón . Lo mejor es utilizado el bcomando: ps aux | sed -e 1b -e '/pattern/!d'. cat -nNo es POSIX. grep -n '^'numeraría cada línea (no es un problema para la salida ps que no tiene líneas vacías). nl -ba -d $'\n'numera cada línea.
Stéphane Chazelas
2
Tenga en cuenta que 1b;...no es portátil ni POSIX, no puede haber ningún otro comando después de "b", por lo que necesita una nueva línea u otra expresión -e.
Stéphane Chazelas
58

¿Cómo te sientes acerca de usar en awklugar de grep?

chopper:~> ps aux | awk 'NR == 1 || /syslogd/'
USER              PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
root               19   0.0  0.0  2518684   1160   ??  Ss   26Aug12   1:00.22 /usr/sbin/syslogd
mrb               574   0.0  0.0  2432852    696 s006  R+    8:04am   0:00.00 awk NR == 1 || /syslogd/
  • NR == 1: Número de registro == 1; es decir. la primera linea
  • ||: o:
  • /syslogd/: Patrón para buscar

También podría valer la pena mirarlo pgrep, aunque esto es más para los scripts que para la salida orientada al usuario. Sin embargo, evita que el grepcomando en sí aparezca en la salida.

chopper:~> pgrep -l syslogd
19 syslogd
mrb
fuente
Muy bien, gracias. Esto también está bien programable para futuras expansiones.
dotancohen
Necesito aprender algo de awk. muy agradable.
user606723
30
ps aux | { read line;echo "$line";grep someApp;}

EDITAR: después de los comentarios

ps aux | { head -1;grep someApp;}

Pensé head -1que leería todas las entradas, pero después de probarlo, también funciona.

{ head -1;grep ok;} <<END
this is a test
this line should be ok
not this one
END

la salida es

this is a test
this line should be ok
Nahuel Fouilleul
fuente
2
Esa es la idea explicada directamente en bash. Me gustaría dar más de un pulgar hacia arriba por esto. Tal vez lo usaría { IFS='' read line; ... }en caso de que el encabezado comience con espacios.
Alfe
Esto ataca exactamente el problema directamente. ¡Agradable!
dotancohen
3
Simplemente usaría en head -1lugar del combo de lectura / eco.
chepner
1
Bueno, funciona head -n1en mi fiesta. Esto probablemente puede ser específico de la implementación. Mi cabeza no está leyendo toda la entrada en este caso, solo la primera línea, dejando el resto en el búfer de entrada.
Krzysztof Adamski el
2
head -n1es más corto, pero parece que incluso la especificación POSIX no dice cuánto de su entrada puede leer, por lo que quizás read line; echo $linesea ​​más portátil después de todo.
chepner
14

Ps admite filtro interno,

Supongamos que está buscando un proceso bash:

ps -C bash -f

Enumerará todos los procesos que nombraron bash.

margarita
fuente
Gracias, es bueno saberlo. Sin embargo, no encontrará scripts iniciados desde python, entre otros.
dotancohen
6

Tiendo a enviar el encabezado a stderr :

ps | (IFS= read -r HEADER; echo "$HEADER" >&2; cat) | grep ps

Esto suele ser suficiente para fines de lectura humana. p.ej:

  PID TTY          TIME CMD
 4738 pts/0    00:00:00 ps

La parte entre corchetes podría ir en su propio guión para uso general.

Hay una conveniencia adicional en que la salida se puede canalizar más (a sortetc.) y el encabezado permanecerá en la parte superior.

antak
fuente
5

También puedes usar teey head:

ps aux | tee >(head -n1) | grep syslog

Sin embargo, teetenga en cuenta que, mientras no pueda ignorar las SIGPIPEseñales (ver, por ejemplo, la discusión aquí ), este enfoque necesita una solución alternativa para ser confiable. La solución alternativa es ignorar las señales SIGPIPE, esto se puede hacer, por ejemplo, en bash como shells:

trap '' PIPE    # ignore SIGPIPE
ps aux | tee >(head -n1) 2> /dev/null | grep syslog
trap - PIPE     # restore SIGPIPE handling

También tenga en cuenta que el orden de salida no está garantizado .

Thor
fuente
No confiaría en que esto funcionara, la primera vez que lo ejecuté (zsh) produjo encabezados de columna por debajo de los resultados grep. La segunda vez estuvo bien.
Rqomey
1
No he visto aún, pero una forma de aumentar la fiabilidad es insertar un pequeño retraso en la tubería antes de la grep: | { sleep .5; cat }.
Thor
2
Agregar durmientes para evitar problemas de concurrencia siempre es un truco. Aunque esto podría funcionar, es un paso hacia el lado oscuro. -1 por esto.
Alfe
1
He tenido algunos otros problemas extraños al intentar esta respuesta, configuré una pregunta para verificar
Rqomey
Este es un uso interesante de tee, pero me parece poco confiable y, a menudo, solo imprime la línea de salida, pero no la línea de encabezado.
dotancohen
4

Quizás dos pscomandos serían los más fáciles.

$ ps aux | head -1 && ps aux | grep someApp
USER             PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
100         3304   0.0  0.2  2466308   6476   ??  Ss    2Sep12   0:01.75 /usr/bin/someApp
Emcconville
fuente
2
No me gusta esta solución, principalmente porque la situación podría cambiar entre la primera y la segunda ps auxllamada ... Y si solo quieres esa primera línea estática, ¿por qué no repetirla manualmente?
Shadur
1
Los cambios entre las dos llamadas no deben ser molestados en esta situación. El primero solo proporcionará el título que siempre se ajustará a la salida del segundo.
Alfe
2
No veo por qué esto fue rechazado, ciertamente es una opción viable. Votación a favor.
dotancohen
4

Podrías usar pidstat con:

pidstat -C someApp
or
pidstat -p <PID>

Ejemplo:

# pidstat -C java
Linux 3.0.26-0.7-default (hostname)    09/12/12        _x86_64_

13:41:21          PID    %usr %system  %guest    %CPU   CPU  Command
13:41:21         3671    0.07    0.02    0.00    0.09     1  java

Más información: http://linux.die.net/man/1/pidstat

arpa
fuente
Gracias, es bueno saberlo. Sin embargo, no encontrará scripts iniciados desde python, entre otros.
dotancohen
4

Ponga lo siguiente en su archivo .bashrc o copie / pegue en el shell primero, para probar.

function psls { 
ps aux|head -1 && ps aux|grep "$1"|grep -v grep;
}

Uso: psls [patrón grep]

$ psls someApp
USER             PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
root              21   0.0  0.0  2467312   1116   ??  Ss   Tue07PM   0:00.17 /sbin/someApp

Asegúrese de obtener su .bashrc (o .bash_profile si lo coloca allí):

source ~/.bashrc

La función incluso se completará automáticamente en la línea de comandos del shell. Como indicó en otra respuesta, puede canalizar la primera línea a un archivo para guardar una llamada a ps.

taco
fuente
1
Bien, he estado usando ese tipo de función durante años. Llamo a mi versiónpsl , que solo llama psy grepuna vez cada una (y no necesita head).
Adam Katz
3

ordenar pero mantener la línea de encabezado en la parte superior

# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
    IFS= read -r header
    printf '%s\n' "$header"
    "$@"
}

Y úsalo así

$ ps aux | body grep someApp
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
1000     11634 51.2  0.1  32824  9112 pts/1    SN+  13:24   7:49 someApp
Mikel
fuente
Gracias, algunas de esas respuestas discuten el caso general de esta pregunta. ¡Perfecto!
dotancohen
3

Gracias principalmente a Janis Papanagnou en comp.unix.shell, uso la siguiente función:

function grep1 {
    IFS= read -r header && printf "%s\n" "$header"; grep "$@"
}

Esto tiene una serie de ventajas:

  • Funciona con bash, zsh y probablemente ksh
  • Es un reemplazo directo para grep, por lo que puede continuar utilizando las banderas que desee: -ipara coincidencias que no distingan entre mayúsculas y minúsculas, -Epara expresiones regulares extendidas, etc.
  • Siempre produce el mismo código de salida que grep, en caso de que desee determinar mediante programación si alguna línea coincide realmente
  • No imprime nada si la entrada estaba vacía

Ejemplo de uso:

$ ps -rcA | grep1 databases
  PID TTY           TIME CMD

$ ps -rcA | grep1 -i databases
  PID TTY           TIME CMD
62891 ??         0:00.33 com.apple.WebKit.Databases
Bdesham
fuente
2

Otra forma con gnu ed:

ed -s '!ps aux' <<< $'2,$v/PATTERN/d\n,p\nq\n'

o, si el shell admite la sustitución del proceso:

printf '%s\n' '2,$v/PATTERN/d' ,p q | ed -s <(ps aux)

es decir:

2,$v/PATTERN/d  - remove all lines not matching pattern (ignore the header)
,p              - print the remaining lines
q               - quit

Más portátil, sin gnu '!' sustitución de shell, utilizando solo el edincorporado rpara renviar la salida ps auxal búfer y luego eliminar las líneas que no coinciden en el 2,$rango e imprimir el resultado:

printf '%s\n' 'r !ps aux' '2,$v/PATTERN/d' ,p q | ed -s

Y dado que los sedcomandos en la respuesta aceptada también muestran la línea que coinciden, con una sedque admite -f-y una shell que admite la sustitución de procesos, ejecutaría:

printf '%s\n' '2,${' '/PATTERN/!d' '}' | sed -f - <(ps aux)

que prácticamente hace lo mismo que los edcomandos anteriores .

revs don_crissti
fuente
1

El camino de Perl:

ps aux | perl -ne 'print if /pattern/ || $.==1'

Mucho más fácil de leer que sed, más rápido, sin riesgo de elegir líneas no deseadas.

emazep
fuente
Perl?!?
dotancohen
0

Si eso es solo para procesos grepping con encabezados completos, expandiría la sugerencia de @ mrb:

$ ps -f -p $(pgrep bash)
UID        PID  PPID  C STIME TTY      STAT   TIME CMD
nasha     2810  2771  0  2014 pts/6    Ss+    0:00 bash
...

pgrep bash | xargs ps -fpobtendrá el mismo resultado pero sin una subshell. Si se requiere otro formato:

$ pgrep bash | xargs ps fo uid,pid,stime,cmd -p
  UID   PID STIME CMD
    0  3599  2014 -bash
 1000  3286  2014 /bin/bash
 ...

fuente
-2

Si conoce los números de línea exactos, ¡es fácil con perl! Si desea obtener las líneas 1 y 5 de un archivo, diga / etc / passwd:

perl -e 'while(<>){if(++$l~~[1,5]){print}}' < /etc/passwd

Si desea obtener otras líneas también, simplemente agregue sus números en la matriz.

Dagelf
fuente
1
Gracias. Según el OP, sé algo del texto en la línea, pero no el número de línea.
dotancohen
Esto aparece como una respuesta en Google cuando se busca este caso de uso estrechamente relacionado con el OP, por lo que vale la pena señalarlo aquí.
Dagelf
1
Si ese es el caso, le sugiero que comience una nueva pregunta y la responda con esta respuesta. Está perfectamente bien responder sus propias preguntas sobre SE, especialmente en la situación que menciona. Siga adelante y enlace a su nueva pregunta en un comentario sobre el OP.
dotancohen
Hay tales preguntas, pero actualmente no aparecen en Google.
Dagelf
Dagelf, la conclusión es: su respuesta no responde la pregunta aquí. @dotancohen tiene razón: si esto aparece como una respuesta en Google cuando se busca este caso de uso estrechamente relacionado con el OP , haga una pregunta por separado, que detalle ese caso de uso estrechamente relacionado, y responda.
don_crissti