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

76

En supposant un simple grep 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 l'information. 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 un regex à grep spécifiquement pour ps:

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

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

Cela semble être un bon cas d’utilisation pour un descripteur de fichier "stdmeta" .

dotancohen
la source
9
La complexité requise par ces réponses montre comment la philosophie Unix "faire une chose et bien le faire" nous manque parfois quand elle est mesurée en termes de facilité d'utilisation: connaître toutes ces commandes suffisamment bien pour les appliquer à ce problème courant (informations de filtrage (voir toujours les étiquettes de colonne) montre les inconvénients de l’approche: parfois, les choses ne s’emboîtent pas parfaitement. Voilà pourquoi des outils tels acksont si utiles, et pourquoi perlpassé sont montés en flèche sed, awketc. en popularité: il est important pour les parties à résumer en un tout cohérent.
iconoclaste
3
bien sûr, pour cet exemple particulier, vous pouvez utiliser l' -Cargument de pset vous n'avez pas besoin de le canaliser dans grep. par exemple ps u -C someAppou mêmeps u -C app1 -C app2 -C app3
cas
1
@iconoclast: bien entendu, la solution Unixy serait un outil permettant de multiplexer plusieurs lignes, chacune devant être filtrée à travers différents ensembles de filtres. Un peu comme une version généralisée de ps aux | { head -1; grep foo; }mentionnée par @Nahuel Fouilleul ci-dessous (c'est probablement la seule solution que je pourrais rappeler sur place si nécessaire)
Lie Ryan le
@iconoclast: Manquant d'expérience et de connaissance des outils, ce qui est vraiment efficace apparaît toujours comme totalement inutile. Connaître une commande bien n’est nulle part sur le bâton de la facilité d’utilisation, c’est sur le bâton de la cour, de lire le manuel détaillé et de s’exercer Ces outils existent depuis des décennies. Ils travaillent et s'emboîtent très bien (et proprement).
рослав Рахматуллин
@ ЯрославРахматуллин: Je pense que vous avez peut-être complètement mal compris ce que j'ai dit. (Peut-être parce que l'anglais n'est pas votre langue maternelle?) La "convivialité" est liée à UX ("expérience utilisateur") et non à l'utilité (ou "l'utilité"). Signaler que lorsqu'une opération simple est aussi complexe, sa facilité d'utilisation n'est pas la même chose que d'affirmer que les outils sont inutiles. Bien évidemment, ils ne sont pas inutiles. Personne dans leur esprit ne dirait qu'ils sont inutiles.
iconoclaste

Réponses:

67

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, en exécutant des commandes spécifiées sur chacune d'entre elles. Vous pouvez avoir plusieurs commandes, en spécifiant plusieurs -eoptions. Nous pouvons ajouter à chaque commande un paramètre de plage spécifiant si cette commande doit être appliquée à une ligne spécifique ou non.

  2. "1p" est une première commande. Il utilise une pcommande qui affiche normalement toutes les lignes. Mais nous le préfixons avec une valeur numérique qui spécifie la plage à laquelle il doit être appliqué. Ici, nous utilisons 1ce qui signifie première ligne. Si vous souhaitez imprimer plus de lignes, vous pouvez utiliser x,ypxest la première ligne pour imprimer, yest la dernière ligne pour imprimer. Par exemple, pour imprimer les 3 premières lignes, vous utiliseriez1,3p

  3. La commande suivante est celle dqui supprime normalement toutes les lignes du tampon. Avant cette commande, nous mettons yourpatternentre deux /caractères. C’est l’autre façon (en premier lieu de spécifier les lignes que nous avons utilisées avec la pcommande) d’adressage des 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 dcommande qui inverse sa logique. Alors maintenant, toutes les lignes qui ne correspondent pas au motif spécifié seront supprimées .

  4. À la fin, sed imprimera toutes les lignes laissées en mémoire 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. Reste des lignes sont imprimées (donc seulement des lignes qui font correspondre au modèle).

Problème de première ligne

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

  1. Ajout de 1dcommande après 1p. Comme je l'ai déjà mentionné, la dcommande supprime les lignes du tampon et nous spécifions sa plage par le numéro 1, ce qui signifie qu'elle supprimera uniquement la 1ère ligne. Donc, la commande seraitsed -e '1p' -e '1d' -e '/youpattern/!d'

  2. Utiliser la 1bcommande au lieu de 1p. C'est un tour bCette commande nous permet de passer à une autre commande spécifiée par une étiquette (certaines commandes peuvent ainsi être omises). Mais si cette étiquette n'est pas spécifiée (comme dans notre exemple), il saute simplement à la fin des commandes, ignorant le reste des commandes pour notre ligne. Donc, dans notre cas, la dernière dcommande ne supprimera pas cette ligne du tampon.

Exemple complet:

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

Utiliser le point-virgule

Certaines sedimplémentations peuvent vous épargner une certaine frappe en utilisant un point-virgule pour séparer des commandes au lieu d'utiliser plusieurs -eoptions. Donc, si vous ne vous souciez pas d'être portable, la commande le serait ps aux | sed '1b;/syslog/!d'. Cela fonctionne au moins dans GNU sedet les busyboximplémentations.

Façon folle

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

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

Comment ça fonctionne

  1. Tout d'abord, nous utilisons l' -noption 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 les lignes vides. 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 \|un caractère spécial qui fonctionne comme OR. Nous faisons donc correspondre 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 cela pose un problème, nous pouvons les supprimer avec cut, comme ceci:

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

-doption spécifie le délimiteur, -fspécifie les champs (ou colonnes) que nous voulons imprimer. Nous voulons donc couper chaque ligne sur chaque :caractère et imprimer uniquement la deuxième colonne 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.

Krzysztof Adamski
la source
4
La numérotation des lignes peut également être utilisée cat -net sera plus claire qu'avec un grep abusé pour cela.
Alfe
1
nlne compte pas les lignes vides (mais les affiche sans numéro de ligne), cat -nformate la numérotation avec les espaces précédents, grep -n .supprime les lignes vides et ajoute deux points. Tous ont leurs ...
eus
2
Réponse bien écrite très pédagogique. J'ai essayé de remplacer "Pretend" (presque au début) par "Prepend" pour vous, mais il voulait plus de changements et je n'avais pas envie de changer des conneries aléatoires dans votre message, alors vous voudrez peut-être y remédier.
Bill K
2
ps aux | sed '1p;/pattern/!d'imprimera la première ligne deux fois si elle correspond au motif . Le mieux est de utilisé la bcommande: ps aux | sed -e 1b -e '/pattern/!d'. cat -nn'est pas POSIX. grep -n '^'numéroter chaque ligne (pas un problème pour la sortie ps qui n'a pas de lignes vides). nl -ba -d $'\n'numérote chaque ligne.
Stéphane Chazelas le
2
Notez que ce 1b;...n'est pas portable ni POSIX, il ne peut y avoir aucune autre commande après "b", vous avez donc besoin d'une nouvelle ligne ou d'une autre expression -e.
Stéphane Chazelas
58

Que pensez-vous d'utiliser awkau 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: Numéro d'enregistrement == 1; c'est à dire. la première ligne
  • ||: ou:
  • /syslogd/: Modèle à rechercher

Cela pourrait également valoir la peine d’être examiné pgrep, même s’il s’agit davantage de scripts que de sorties orientées vers les utilisateurs. Cela évite toutefois que la grepcommande elle-même apparaisse dans la sortie.

chopper:~> pgrep -l syslogd
19 syslogd
mrb
la source
Très gentil merci. Ceci est également joliment scriptable pour une expansion future.
dotancohen
J'ai besoin de m'apprendre un peu awk. très agréable.
user606723
30
ps aux | { read line;echo "$line";grep someApp;}

EDIT: après les commentaires

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

Bien que head -1je lise toutes les entrées, mais après les avoir testées, 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
Nahuel Fouilleul
la source
2
C'est l'idée énoncée directement dans Bash. Je voudrais donner plus d'un pouce pour cela. Je voudrais peut-être utiliser { IFS='' read line; ... }au cas où l'en-tête commence par des espaces.
Alfe
Cela ne exactement attaquer le problème directement. Agréable!
dotancohen
3
Je voudrais simplement utiliser à la head -1place du combo lecture / écho.
Chepner
1
Eh bien, ça marche avec head -n1ma bash. Cela peut probablement être spécifique à la mise en œuvre. Ma tête ne lit pas toute l'entrée dans ce cas, seulement la première ligne, laissant le reste d'entre eux dans le tampon d'entrée.
Krzysztof Adamski
2
head -n1est plus court, mais il semble que même la spécification POSIX ne précise pas quelle quantité de son entrée est autorisée à lire, elle est donc peut read line; echo $line- être plus portable après tout.
Chepner
14

Filtre interne de support ps,

Supposons que vous recherchiez un processus bash:

ps -C bash -f

Dresse la liste de tous les processus nommés bash.

Marguerite
la source
Merci, c'est bon à savoir. Cependant, il ne trouvera pas de scripts démarrés à partir de python, entre autres.
dotancohen
6

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 sens que la sortie peut être acheminée plus loin (vers sortetc.) et que l'en-tête restera au-dessus.

antak
la source
5

Vous pouvez également utiliser teeet head:

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

Notez cependant que tant qu’il teeest impossible d’ignorer les SIGPIPEsignaux (voir par exemple la discussion ici ), cette solution nécessite une solution de contournement pour être fiable. La solution de contournement consiste à ignorer les signaux SIGPIPE, ceci peut par exemple être fait comme ceci dans bash comme des obus:

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 .

Thor
la source
Je ne compterais pas sur cela pour fonctionner, la première fois que je l'ai exécuté (zsh), il a généré des en-têtes de colonne sous les résultats de grep. Deuxième fois c'était bien.
Rqomey
1
Je ne l' ai pas vu encore, mais une façon d'augmenter la fiabilité est d'insérer un petit retard dans la canalisation avant le grep: | { sleep .5; cat }.
Thor
2
Ajouter des périodes de sommeil pour éviter les problèmes de concurrence est toujours un hack. Bien que cela puisse fonctionner, c'est un pas vers le côté obscur. -1 pour cela.
Alfe
1
J'ai eu quelques autres problèmes étranges en essayant cette réponse, j'ai mis en place une question à vérifier
Rqomey
C'est une utilisation intéressante de tee, mais je la trouve peu fiable et n'imprime souvent que la ligne de sortie, mais pas la ligne d'en-tête.
dotancohen
4

Peut-être que deux pscommandes seraient les 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
Emcconville
la source
2
Je n'aime pas cette solution, principalement parce que la situation pourrait changer entre le premier ps auxappel et le deuxième appel ... Et si vous voulez juste cette première ligne statique, pourquoi ne pas y revenir manuellement?
Shadur
1
Les changements entre les deux appels ne doivent pas être dérangés dans cette situation. Le premier ne fournira que le titre qui s'adaptera toujours à la sortie du second.
Alfe
2
Je ne vois pas pourquoi cela a été réduit, c'est certainement une option viable. Upvoting.
dotancohen
4

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

Informations complémentaires: http://linux.die.net/man/1/pidstat

harpe
la source
Merci, c'est bon à savoir. Cependant, il ne trouvera pas de scripts démarrés à partir de python, entre autres.
dotancohen
4

Placez ce qui suit dans votre fichier .bashrc ou copiez / collez d’abord dans un shell pour le tester.

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

Utilisation: psls [modèle 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 source votre .bashrc (ou .bash_profile si vous le mettez là à la place):

source ~/.bashrc

La fonction se complétera même automatiquement sur la ligne de commande du shell. Comme vous l'avez dit dans une autre réponse, vous pouvez diriger la première ligne vers un fichier pour enregistrer un appel sur ps.

taco
la source
1
Nice, j'utilise ce genre de fonction depuis des années. J'appelle ma versionpsl , qui n'appelle que pset grepune fois chacune (et dont on n'a pas besoin head).
Adam Katz
3

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 l'utiliser 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
Mikel
la source
Merci, certaines de ces réponses traitent du cas général de cette question. Parfait!
dotancohen
3

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 de nombreux avantages:

  • Fonctionne avec bash, zsh et probablement ksh
  • C'est un remplacement instantané de grep. Vous pouvez donc continuer à utiliser les indicateurs de votre choix : -ipour la correspondance sans -Edistinction de casse, pour les expressions rationnelles étendues, etc.
  • Génère toujours le même code de sortie que grep, au cas où vous souhaitiez déterminer par programme si des lignes correspondaient 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
Bdesham
la source
2

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 '!' substitution de shell, utilisez uniquement edintégré rpour rinsérer la sortie de ps auxdans la mémoire tampon, puis supprimez les lignes non correspondantes dans la 2,$plage et imprimez le résultat:

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

Et puisque les sedcommandes de la sortie de réponse acceptée aussi la ligne se correspondant, avec un sedqui prend en charge -f-et une coque qui prend en charge la substitution de processus que je courrais:

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

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

don_crissti
la source
1

La manière Perl:

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

Bien plus facile à lire que sed, plus rapidement, aucun risque de tracer des lignes indésirables.

emazep
la source
Perl?!?
dotancohen
0

Si cela ne concerne que les processus grepping avec des en-têtes complets, j'aimerais développer 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 -fpvous obtiendrez 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
 ...

la source
-2

Si vous connaissez les numéros de ligne exacts, c'est facile avec perl! Si vous voulez obtenir les lignes 1 et 5 d'un fichier, dites / etc / passwd:

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

Si vous souhaitez également obtenir d'autres lignes, ajoutez simplement leurs numéros dans le tableau.

Dagelf
la source
1
Je vous remercie. Selon le PO, je connais une partie du texte de la ligne, mais pas son numéro.
dotancohen
Ceci apparaît comme une réponse sur Google lors de la recherche de ce cas d'utilisation étroitement lié au PO, mérite donc d'être noté ici.
Dagelf
1
Si tel est le cas, je vous suggère fortement de commencer une nouvelle question et d'y répondre avec cette réponse. C'est parfaitement bien de répondre à vos propres questions sur la SE, en particulier dans la situation que vous avez mentionnée. Allez-y et faites un lien vers votre nouvelle question dans un commentaire sur le PO.
dotancohen
Il y a de telles questions, mais elles n'apparaissent pas actuellement sur Google.
Dagelf
Dagelf, l’essentiel est que votre réponse ne réponde pas à la question. @dotancohen a raison - si cela apparaît comme une réponse sur Google lorsque vous recherchez ce cas d'utilisation étroitement lié au PO, puis posez une question distincte - détaillant ce cas d'utilisation étroitement lié - et répondez-y.
don_crissti