web-dev-qa-db-fra.com

Comment grep une ligne spécifique _et_ la première ligne d'un fichier?

En supposant un grep simple tel que:

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

Cela fournit beaucoup d'informations, mais comme la première ligne de la commande ps est manquante, il n'y a pas de contexte pour les informations. Je préférerais que la première ligne de ps soit également affichée:

$ 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

Bien sûr, je pourrais ajouter une expression régulière à grep spécifiquement pour ps:

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

Cependant, je préférerais une solution plus générale car il y a d'autres cas dans lesquels j'aimerais également avoir la première ligne.

Il semble que ce serait un bon cas d'utilisation pour n descripteur de fichier "stdmeta" .

77
dotancohen

Bonne façon

Normalement, vous ne pouvez pas faire cela avec grep mais vous pouvez utiliser d'autres outils. AWK a déjà été mentionné mais vous pouvez également utiliser sed, comme ceci:

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

Comment ça fonctionne:

  1. L'utilitaire Sed fonctionne sur chaque ligne individuellement, exécutant les commandes spécifiées sur chacune d'elles. Vous pouvez avoir plusieurs commandes, en spécifiant plusieurs -e options. Nous pouvons ajouter à chaque commande un paramètre de plage qui spécifie si cette commande doit être appliquée à une ligne spécifique ou non.

  2. "1p" est une première commande. Il utilise la commande p qui imprime normalement toutes les lignes. Mais nous lui ajoutons une valeur numérique qui spécifie la plage à laquelle il doit être appliqué. Ici, nous utilisons 1 qui signifie première ligne. Si vous souhaitez imprimer plus de lignes, vous pouvez utiliser x,ypx est la première ligne à imprimer, y est la dernière ligne à imprimer. Par exemple, pour imprimer les 3 premières lignes, vous utiliseriez 1,3p

  3. La commande suivante est d qui supprime normalement toutes les lignes du tampon. Avant cette commande, nous mettons yourpattern entre deux / personnages. C'est l'autre façon (la première était de spécifier les lignes comme nous l'avons fait avec la commande p) d'adresser les lignes sur lesquelles la commande devrait être exécutée. Cela signifie que la commande ne fonctionnera que pour les lignes qui correspondent à yourpattern. Sauf que nous utilisons ! caractère avant d commande qui inverse sa logique. Alors maintenant, il supprimera toutes les lignes qui ne pas correspondent au modèle spécifié.

  4. À la fin, sed imprimera toutes les lignes qui restent dans le tampon. Mais nous avons supprimé les lignes qui ne correspondent pas du tampon afin que seules les lignes correspondantes soient imprimées.

Pour résumer: nous imprimons la 1ère ligne, puis nous supprimons toutes les lignes qui ne correspondent pas à notre modèle d'entrée. Les autres lignes sont imprimées (donc seules les lignes qui correspondent au motif).

Problème de première ligne

Comme mentionné dans les commentaires, il y a un problème avec cette approche. Si le motif spécifié correspond également à la première ligne, il sera imprimé deux fois (une fois par la commande p et une fois en raison d'une correspondance). Nous pouvons éviter cela de deux manières:

  1. Ajouter 1d commande après 1p. Comme je l'ai déjà mentionné, la commande d supprime les lignes du tampon et nous spécifions sa plage par le numéro 1, ce qui signifie qu'elle ne supprimera que la 1ère ligne. La commande serait donc sed -e '1p' -e '1d' -e '/youpattern/!d'

  2. En utilisant 1b commande, au lieu de 1p. C'est un truc. La commande b nous permet de passer à une autre commande spécifiée par une étiquette (de cette façon, certaines commandes peuvent être omises). Mais si cette étiquette n'est pas spécifiée (comme dans notre exemple), elle saute juste à la fin des commandes, ignorant le reste des commandes de notre ligne. Donc, dans notre cas, la dernière commande d ne supprimera pas cette ligne du tampon.

Exemple complet:

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

Utilisation du point-virgule

Certaines implémentations sed peuvent vous faire économiser de la frappe en utilisant un point-virgule pour séparer les commandes au lieu d'utiliser plusieurs -e options. Donc, si vous ne vous souciez pas d'être portable, la commande serait ps aux | sed '1b;/syslog/!d'. Cela fonctionne au moins dans GNU sed et busybox implémentations.

Façon folle

Voici, cependant, une façon assez folle de le faire avec grep. Ce n'est certainement pas optimal, je le poste juste à des fins d'apprentissage, mais vous pouvez l'utiliser par exemple, si vous n'avez pas d'autre outil dans votre système:

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

Comment ça fonctionne

  1. Tout d'abord, nous utilisons -n option pour ajouter des numéros de ligne avant chaque ligne. Nous voulons numéroter toutes les lignes auxquelles nous correspondons .* - n'importe quoi, même une ligne vide. Comme suggéré dans les commentaires, nous pouvons également faire correspondre "^", le résultat est le même.

  2. Ensuite, nous utilisons des expressions régulières étendues pour pouvoir utiliser \| caractère spécial qui fonctionne comme OU. Donc, nous correspondons si la ligne commence par 1: (première ligne) ou contient notre modèle (dans ce cas, son syslog).

Problème de numéros de ligne

Maintenant, le problème est que nous obtenons ces numéros de ligne laids dans notre sortie. Si c'est un problème, nous pouvons les supprimer avec cut, comme ceci:

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

-d option spécifie le délimiteur, -f spécifie les champs (ou colonnes) que nous voulons imprimer. Nous voulons donc couper chaque ligne sur chaque : caractère et imprime uniquement la 2e et toutes les colonnes suivantes. Cela supprime efficacement la première colonne avec son délimiteur et c'est exactement ce dont nous avons besoin.

68
Krzysztof Adamski

Que pensez-vous de l'utilisation de awk au lieu 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: Nombre d'enregistrements == 1; c'est à dire. la première ligne
  • ||: ou:
  • /syslogd/: Modèle à rechercher

Il peut également être utile de consulter pgrep, bien que ce soit plus pour les scripts que pour les sorties destinées aux utilisateurs. Cela évite cependant que la commande grep elle-même n'apparaisse dans la sortie.

chopper:~> pgrep -l syslogd
19 syslogd
58
mrb
ps aux | { read line;echo "$line";grep someApp;}

EDIT: après commentaires

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

Je pensais que head -1 lirait toutes les entrées, mais après l'avoir testé, cela fonctionne aussi.

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

la sortie est

this is a test
this line should be ok
31
Nahuel Fouilleul

Ps prend en charge le filtre interne,

Supposons que vous recherchez un processus bash:

ps -C bash -f

Répertorie tous les processus nommés bash.

14
daisy

J'ai tendance à envoyer l'en-tête à stderr :

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

Ceci est généralement suffisant pour la lecture humaine. par exemple.:

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

La partie entre crochets pourrait entrer dans son propre script pour une utilisation générale.

Il y a une commodité supplémentaire en ce que la sortie peut être davantage canalisée (vers sort etc.) et l'en-tête restera en haut.

6
antak

Vous pouvez également utiliser tee et head:

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

Notez cependant que tant que tee est incapable d'ignorer les signaux SIGPIPE (voir par exemple discussion ici ) cette approche a besoin d'une solution de contournement pour être fiable. La solution de contournement est d'ignorer les signaux SIGPIPE, cela peut par exemple être fait comme ceci dans des shells comme des shells:

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

Notez également que l'ordre de sortie n'est pas garanti .

5
Thor

Peut-être que deux commandes ps seraient plus faciles.

$ 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
4
emcconville

Vous pouvez utiliser pidstat avec:

pidstat -C someApp
or
pidstat -p <PID>

Exemple:

# 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

Plus d'informations: http://linux.die.net/man/1/pidstat

4
harp

Mettez d'abord ce qui suit dans votre fichier .bashrc ou copiez/collez dans Shell pour les tests.

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

Utilisation: psls [motif 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

Assurez-vous de vous procurer votre .bashrc (ou .bash_profile si vous le placez à la place):

source ~/.bashrc

La fonction sera même complétée automatiquement sur la ligne de commande Shell. Comme vous l'avez indiqué dans une autre réponse, vous pouvez diriger la première ligne vers un fichier pour enregistrer un appel à ps.

4
taco

trier mais garder la ligne d'en-tête en haut

# 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"
    "$@"
}

Et utilisez-le comme ça

$ 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
3
Mikel

Merci surtout à Janis Papanagnou dans comp.unix.Shell, j'utilise la fonction suivante:

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

Cela présente un certain nombre d'avantages:

  • Fonctionne avec bash, zsh et probablement ksh
  • Il s'agit d'un remplacement direct pour grep, vous pouvez donc continuer à utiliser les indicateurs de votre choix: -i pour une correspondance insensible à la casse, -E pour les expressions rationnelles étendues, etc.
  • Donne toujours le même code de sortie que grep, au cas où vous voudriez déterminer par programme si des lignes correspondent réellement
  • N'imprime rien si l'entrée était vide

Exemple d'utilisation:

$ 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
3
bdesham

Une autre façon avec gnu ed:

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

ou, si le shell prend en charge la substitution de processus:

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

c'est:

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

Plus portable, sans gnu'!' ou substitution Shell - en utilisant uniquement ed intégré r à rlire la sortie de ps aux dans le tampon, puis supprimez les lignes non correspondantes dans le 2,$ rangez et imprimez le résultat:

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

Et puisque les commandes sed dans la réponse acceptée affichent également la ligne qui correspond, avec un sed qui prend en charge -f- et un shell qui prend en charge la substitution de processus, je lancerais:

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

qui fait à peu près la même chose que les commandes ed précédentes.

2
don_crissti

La manière Perl:

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

Bien plus facile à lire que sed, plus rapide, pas de risque de choisir des lignes indésirables.

1
emazep

Si ce n'est que pour les processus de grepping avec des en-têtes complets, je développerais la suggestion 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 -fp obtiendra le même résultat mais sans sous-shell. Si un autre formatage est requis:

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