Wie grep eine bestimmte Zeile _und_ die erste Zeile einer Datei?


76

Angenommen, ein einfaches Grep wie:

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

Dies liefert viele Informationen, aber da die erste Zeile des Befehls ps fehlt, gibt es keinen Kontext für die Informationen. Ich würde es vorziehen, wenn auch die erste Zeile von ps angezeigt wird:

$ 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

Natürlich könnte ich einen regulären Ausdruck für grep speziell für ps hinzufügen:

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

Ich würde jedoch eine allgemeinere Lösung vorziehen, da es andere Fälle gibt, in denen ich auch die erste Zeile haben möchte.

Dies scheint ein guter Anwendungsfall für einen "stdmeta" -Dateideskriptor zu sein .


9
Die Komplexität, die diese Antworten erfordern, zeigt, wie die Unix - Philosophie "Mach eins und mach es gut" manchmal versagt, gemessen am Maßstab der Benutzerfreundlichkeit: Wenn wir alle diese Befehle gut genug kennen, um sie auf dieses häufige Problem anzuwenden (Filterprozessinfo) und immer noch die Spaltenbeschriftungen zu sehen) zeigt die Kehrseite des Ansatzes: Manchmal passen die Dinge nicht sehr sauber zusammen. Aus diesem Grund Tools wie ackso nützlich sind, und warum perlgeschnellt Vergangenheit sed, awketc. in der Popularität: Es ist wichtig , dass die Teile zu einem kohärenten Ganzen zusammenzufassen.
Iconoclast

3
Für dieses Beispiel könnten Sie natürlich das -CArgument to verwenden, psund Sie müssten es nicht in grep umleiten. zB ps u -C someAppoder sogarps u -C app1 -C app2 -C app3
cas

1
@iconoclast: Natürlich wäre die Unixy-Lösung ein Tool, mit dem mehrere Zeilen gemultiplext werden können, die jeweils durch verschiedene Filtersätze gefiltert werden. Irgendwie eine verallgemeinerte Version ps aux | { head -1; grep foo; }von @Nahuel Fouilleul (dies ist wahrscheinlich die einzige Lösung, an die ich mich bei Bedarf sofort erinnern kann)
Lie Ryan

@iconoclast: Mangelnde Erfahrung mit und Kenntnisse über die Tools, was die Tools wirklich gut machen, werden immer völlig nutzlos erscheinen. Wenn man weiß, dass ein Befehl gut ist, ist das nicht der Punkt, an dem es um Benutzerfreundlichkeit geht. Es ist der Punkt, an dem man das Handbuch lesen und üben muss. Diese Tools gibt es schon seit Jahrzehnten. Sie arbeiten und passen sehr gut (und sauber) zusammen.
Ярослав Рахматуллин

@ ЯрославРахматуллин: Ich denke, Sie haben das, was ich gesagt habe, möglicherweise völlig falsch verstanden. (Vielleicht, weil Englisch nicht Ihre Muttersprache ist?) "Benutzerfreundlichkeit" bezieht sich auf UX ("Benutzererfahrung"), nicht auf Dienstprogramm (oder "Nützlichkeit"). Wenn eine einfache Operation so komplex ist, schadet sie der Bedienbarkeit, heißt das NICHT, dass die Werkzeuge unbrauchbar sind. Ganz offensichtlich sind sie nicht nutzlos. Niemand, der bei Verstand ist, würde sagen, dass sie nutzlos sind.
Iconoclast

Antworten:


67

Gute Möglichkeit

Normalerweise können Sie dies nicht mit grep tun, aber Sie können andere Tools verwenden. AWK wurde bereits erwähnt, kann aber auch sedwie folgt verwendet werden:

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

Wie es funktioniert:

  1. Das Dienstprogramm Sed arbeitet in jeder Zeile einzeln und führt für jede Zeile bestimmte Befehle aus. Sie können mehrere Befehle haben und mehrere -eOptionen angeben. Wir können jedem Befehl einen Bereichsparameter voranstellen, der angibt, ob dieser Befehl auf eine bestimmte Zeile angewendet werden soll oder nicht.

  2. "1p" ist ein erster Befehl. Es wird ein pBefehl verwendet, der normalerweise alle Zeilen druckt. Wir stellen ihm jedoch einen numerischen Wert voran, der den Bereich angibt, auf den er angewendet werden soll. Hier verwenden wir 1die erste Zeile. Wenn Sie mehr Zeilen drucken möchten, können Sie angeben, x,ypwo xdie erste Zeile gedruckt werden ysoll und wo die letzte Zeile gedruckt werden soll. Um beispielsweise die ersten 3 Zeilen zu drucken, würden Sie verwenden1,3p

  3. Der nächste Befehl dlöscht normalerweise alle Zeilen aus dem Puffer. Vor diesem Befehl setzen wir yourpatternzwischen zwei /Zeichen. Dies ist der andere Weg (zuerst mussten wir angeben, welche Zeilen wir mit dem pBefehl ausgeführt haben) , um die Zeilen zu adressieren, auf denen der Befehl ausgeführt werden soll. Dies bedeutet, dass der Befehl nur für die übereinstimmenden Zeilen funktioniert yourpattern. Es wird jedoch ein !Zeichen vor dem dBefehl verwendet, der seine Logik umkehrt. Jetzt werden alle Linien entfernt, die nicht mit dem angegebenen Muster übereinstimmen.

  4. Am Ende druckt sed alle im Puffer verbleibenden Zeilen. Wir haben jedoch Zeilen aus dem Puffer entfernt, die nicht übereinstimmen, sodass nur übereinstimmende Zeilen gedruckt werden.

Zusammenfassend: Wir drucken die erste Zeile und löschen dann alle Zeilen, die nicht zu unserem Muster passen, aus der Eingabe. Die restlichen Zeilen werden gedruckt (also nur die Zeilen, die dem Muster entsprechen).

Problem in der ersten Zeile

Wie in den Kommentaren erwähnt, liegt bei diesem Ansatz ein Problem vor. Wenn das angegebene Muster auch mit der ersten Zeile übereinstimmt, wird es zweimal gedruckt (einmal per pBefehl und einmal aufgrund einer Übereinstimmung). Wir können dies auf zwei Arten vermeiden:

  1. 1dBefehl hinzufügen nach 1p. Wie bereits erwähnt, dlöscht der Befehl Zeilen aus dem Puffer und wir geben den Bereich durch Nummer 1 an, was bedeutet, dass nur die erste Zeile gelöscht wird. Der Befehl wäre alsosed -e '1p' -e '1d' -e '/youpattern/!d'

  2. Verwenden des 1bBefehls anstelle von 1p. Es ist ein Trick. bBefehl ermöglicht es uns, zu einem anderen Befehl zu springen, der durch eine Beschriftung angegeben ist (auf diese Weise können einige Befehle weggelassen werden). Wenn dieses Label nicht angegeben wird (wie in unserem Beispiel), springt es nur zum Ende der Befehle und ignoriert den Rest der Befehle für unsere Zeile. In unserem Fall entfernt der letzte dBefehl diese Zeile nicht aus dem Puffer.

Vollständiges Beispiel:

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

Semikolon verwenden

Bei einigen sedImplementierungen können Sie Tipparbeit sparen, indem Sie Befehle durch Semikolon trennen, anstatt mehrere -eOptionen zu verwenden. Wenn Sie sich also nicht dafür interessieren, portabel zu sein, wäre der Befehl ps aux | sed '1b;/syslog/!d'. Es funktioniert zumindest in GNU sedund busyboxImplementierungen.

Verrückter Weg

Hier ist jedoch ein ziemlich verrückter Weg, dies mit grep zu tun. Es ist definitiv nicht optimal, ich poste dies nur zu Lernzwecken, aber Sie können es zum Beispiel verwenden, wenn Sie kein anderes Tool in Ihrem System haben:

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

Wie es funktioniert

  1. Zunächst verwenden wir die -nOption, um Zeilennummern vor jeder Zeile hinzuzufügen. Wir wollen alle Zeilen nummerieren, die wir abgleichen .*- alles, auch leere Zeilen. Wie in den Kommentaren vorgeschlagen, können wir auch mit '^' übereinstimmen, das Ergebnis ist dasselbe.

  2. Dann verwenden wir erweiterte reguläre Ausdrücke, damit wir \|Sonderzeichen verwenden können, die als ODER funktionieren. Wir passen also an, ob die Zeile mit 1:(erste Zeile) beginnt oder unser Muster enthält (in diesem Fall sein syslog).

Zeilennummern Problem

Jetzt ist das Problem, wir bekommen diese hässlichen Zeilennummern in unserer Ausgabe. Wenn dies ein Problem ist, können wir sie folgendermaßen entfernen cut:

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

-dDiese Option gibt das Trennzeichen und die zu -fdruckenden Felder (oder Spalten) an. Wir wollen also jede Zeile eines jeden :Zeichens ausschneiden und nur die 2. und alle nachfolgenden Spalten drucken. Dadurch wird die erste Spalte mit ihrem Begrenzer effektiv entfernt, und genau das ist erforderlich.


4
Die Zeilennummerierung kann auch mit durchgeführt cat -nwerden und würde klarer aussehen als mit einem Grep, der dafür missbraucht wird.
Alfe

1
nlZählt keine leeren Zeilen (sondern druckt sie ohne Zeilennummer), cat -nformatiert die Nummerierung mit vorangestellten Leerzeichen, entfernt überhaupt grep -n .leere Zeilen und fügt einen Doppelpunkt hinzu. Alle haben ihre ... äh ... Funktionen ;-)
Alfe

2
Sehr lehrreiche, gut geschriebene Antwort. Ich habe versucht, "Pretend" (Fast am Anfang) durch "Prepend" zu ersetzen, aber es wollte mehr Änderungen und ich hatte keine Lust, zufälligen Mist in Ihrem Beitrag zu ändern. Vielleicht möchten Sie das beheben.
Bill K

2
ps aux | sed '1p;/pattern/!d'druckt die erste Zeile zweimal, wenn sie mit dem Muster übereinstimmt . Am besten ist das zu verwendende bBefehl: ps aux | sed -e 1b -e '/pattern/!d'. cat -nist nicht POSIX. grep -n '^'würde jede Zeile nummerieren (kein Problem für ps-Ausgaben ohne Leerzeilen). nl -ba -d $'\n'nummeriert jede Zeile.
Stéphane Chazelas

2
Beachten Sie, dass 1b;...weder portierbar noch POSIX-fähig ist. Nach "b" kann kein anderer Befehl angegeben werden. Sie benötigen daher eine neue Zeile oder einen anderen -e-Ausdruck.
Stéphane Chazelas

58

Wie stehst du zu " awkstatt" 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: Anzahl der Datensätze == 1; dh die erste Zeile
  • ||: oder:
  • /syslogd/: Zu suchendes Muster

Es könnte sich auch lohnen, sich das anzuschauen pgrep, obwohl dies eher für Skripte als für benutzerbezogene Ausgaben gedacht ist. Es wird jedoch vermieden, dass der grepBefehl selbst in der Ausgabe angezeigt wird.

chopper:~> pgrep -l syslogd
19 syslogd

Sehr schön danke. Dies ist auch gut skriptfähig für zukünftige Erweiterungen.
Dotancohen

Ich muss mir ein bisschen awk beibringen. Sehr schön.
user606723

30
ps aux | { read line;echo "$line";grep someApp;}

EDIT: nach Kommentaren

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

Ich head -1würde zwar alle Eingaben lesen, aber nach dem Testen funktioniert es auch.

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

Ausgabe ist

this is a test
this line should be ok

2
Das ist die Idee, die direkt in bash formuliert wurde. Ich würde gerne mehr als einen Daumen hoch geben. Ich würde nur vielleicht verwenden, { IFS='' read line; ... }falls der Header mit Leerzeichen beginnt.
Alfe

Dies ist genau das Problem direkt angreifen. Nett!
Dotancohen

3
Ich würde nur head -1anstelle der Lese / Echo-Combo verwenden.
Chepner

1
Nun, es funktioniert mit head -n1meiner Bash. Dies kann wahrscheinlich implementierungsspezifisch sein. Mein Kopf liest in diesem Fall nicht die gesamte Eingabe, sondern nur die erste Zeile, und der Rest verbleibt im Eingabepuffer.
Krzysztof Adamski

2
head -n1ist kürzer, aber es sieht so aus, als ob sogar die POSIX-Spezifikation nicht angibt, wie viel von ihrer Eingabe gelesen werden darf, sodass sie möglicherweise read line; echo $linedoch portabler ist.
Chepner

14

Ps unterstützen internen Filter,

Angenommen, Sie suchen nach einem Bash-Prozess:

ps -C bash -f

Listet alle Prozesse mit dem Namen auf bash.


Danke, das ist schön zu wissen. Skripte, die unter anderem mit Python gestartet wurden, werden jedoch nicht gefunden.
Dotancohen

6

Ich neige dazu, den Header an stderr zu senden :

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

Dies ist in der Regel ausreichend für menschliche Lesezwecke. z.B:

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

Der in Klammern gesetzte Teil kann zur allgemeinen Verwendung in ein eigenes Skript eingefügt werden.

Ein zusätzlicher Vorteil besteht darin, dass die Ausgabe weiter geleitet werden kann (zu sortusw.) und der Header oben bleibt.


5

Sie könnten auch teeund verwenden head:

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

Beachten Sie jedoch, dass dieser Ansatz eine Problemumgehung erfordert, um zuverlässig zu sein , solange Signale teenicht ignoriert SIGPIPEwerden können (siehe z. B. die Diskussion hier ). Die Problemumgehung besteht darin, SIGPIPE-Signale zu ignorieren. Dies kann beispielsweise in bash-ähnlichen Shells erfolgen:

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

Beachten Sie auch, dass die Ausgabereihenfolge nicht garantiert wird .


Ich würde mich nicht darauf verlassen, dass dies funktioniert, wenn ich es zum ersten Mal ausführte (zsh), wurden Spaltenüberschriften unterhalb der grep-Ergebnisse erzeugt. Das zweite Mal war es in Ordnung.
Rqomey

1
Ich habe dies noch nicht gesehen, aber eine Möglichkeit , die Zuverlässigkeit zu erhöhen , ist eine kleine Verzögerung in der Pipeline vor dem einfügen grep: | { sleep .5; cat }.
Thor

2
Das Hinzufügen von Ruhezuständen, um Parallelitätsprobleme zu vermeiden, ist immer ein Hack. Obwohl dies funktionieren könnte, ist es ein Schritt in Richtung der dunklen Seite. -1 dafür.
Alfe

1
Ich habe ein paar andere seltsame Probleme hatte , während diese Antwort versuchen, ich einrichten Frage zu prüfen
Rqomey

Dies ist eine interessante Verwendung von tee, aber ich finde es unzuverlässig und druckt oft nur die Ausgabezeile, nicht aber die Kopfzeile.
Dotancohen

4

Vielleicht pswären zwei Befehle am einfachsten.

$ 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

2
Diese Lösung gefällt mir nicht, vor allem, weil sich die Situation zwischen dem ersten und dem zweiten ps auxAnruf ändern könnte ... Und wenn Sie nur diese statische erste Zeile wollen, warum nicht manuell wiederholen?
Shadur

1
Änderungen zwischen den beiden Anrufen sind in dieser Situation nicht zu stören . Die erste gibt nur die Überschrift an, die immer zur Ausgabe der zweiten passt.
Alfe

2
Ich verstehe nicht, warum dies abgelehnt wurde, es ist sicherlich eine praktikable Option. Upvoting.
Dotancohen

4

Sie könnten pidstat verwenden mit:

pidstat -C someApp
or
pidstat -p <PID>

Beispiel:

# 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

Weitere Informationen: http://linux.die.net/man/1/pidstat


Danke, das ist schön zu wissen. Skripte, die unter anderem mit Python gestartet wurden, werden jedoch nicht gefunden.
Dotancohen

4

Fügen Sie Folgendes zum Testen zuerst in Ihre .bashrc-Datei ein oder kopieren / fügen Sie es in die Shell ein.

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

Verwendung: psls [grep pattern]

$ 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

Stellen Sie sicher, dass Sie Ihre .bashrc-Datei (oder .bash_profile, wenn Sie sie stattdessen dort ablegen) als Quelle angeben:

source ~/.bashrc

Die Funktion wird sogar in der Shell-Befehlszeile automatisch vervollständigt. Wie Sie in einer anderen Antwort angegeben haben, können Sie die erste Zeile an eine Datei weiterleiten, um einen Anruf in ps zu speichern.


1
Gut, ich benutze diese Funktion schon seit Jahren. Ich rufe meine Version aufpsl , die nur psund jeweils grepeinmal aufruft (und nicht benötigt head).
Adam Katz

3

sortiere aber behalte die Kopfzeile oben

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

Und benutze es so

$ 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

Vielen Dank, einige dieser Antworten behandeln den allgemeinen Fall dieser Frage. Perfekt!
Dotancohen

3

Vor allem dank Janis Papanagnou in comp.unix.shell benutze ich folgende Funktion:

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

Dies hat eine Reihe von Vorteilen:

  • Funktioniert mit bash, zsh und wahrscheinlich ksh
  • Es ist ein Drop-In-Ersatz für grep, sodass Sie weiterhin die gewünschten Flags verwenden können: -ifür Übereinstimmungen ohne -EBerücksichtigung der Groß- und Kleinschreibung, für erweiterte reguläre Ausdrücke usw.
  • Ergibt immer den gleichen Exit-Code wie grep, falls Sie programmgesteuert feststellen möchten, ob Zeilen tatsächlich übereinstimmen
  • Gibt nichts aus, wenn die Eingabe leer war

Anwendungsbeispiel:

$ 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

2

Ein anderer Weg mit gnu ed:

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

oder, wenn die Shell die Prozessersetzung unterstützt:

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

das ist:

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

Tragbarer, ohne gnu '!' oder durch Shell-Substitution - Verwenden Sie nur die edintegrierte Funktion, um die Ausgabe von in den Puffer rzu rlesen, ps auxund löschen Sie dann nicht übereinstimmende Zeilen im 2,$Bereich, und drucken Sie das Ergebnis aus:

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

Und da die sedBefehle in der akzeptierten Antwort auch die selbst passende Zeile ausgeben, mit einem sed, der unterstützt, -f-und einer Shell, die die Prozessersetzung unterstützt, würde ich Folgendes ausführen:

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

Das macht so ziemlich das Gleiche wie die vorherigen edBefehle.


1

Der Perl Weg:

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

Viel einfacher zu lesen als sed, schneller, kein Risiko, unerwünschte Zeilen zu wählen.



0

Wenn das nur für Grepping-Prozesse mit vollständigen Überschriften ist, würde ich @ mrbs Vorschlag erweitern:

$ 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 -fpwird das gleiche Ergebnis erhalten, aber ohne eine Unterschale. Wenn eine andere Formatierung erforderlich ist:

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

-2

Wenn Sie die genauen Zeilennummern kennen, ist das mit Perl ganz einfach! Wenn Sie die Zeilen 1 und 5 aus einer Datei erhalten möchten, sagen Sie / etc / passwd:

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

Wenn Sie auch andere Zeilen erhalten möchten, fügen Sie einfach deren Nummern in das Array ein.


1
Danke. Laut OP kenne ich einen Teil des Textes in der Zeile, aber nicht die Zeilennummer.
Dotancohen

Dies erscheint als Antwort auf Google, wenn Sie nach diesem Anwendungsfall suchen, der eng mit dem OP zusammenhängt.
Dagelf

1
Wenn dies der Fall ist, empfehle ich dringend, dass Sie eine neue Frage beginnen und diese mit dieser Antwort beantworten. Es ist vollkommen in Ordnung, Ihre eigenen Fragen zu SE zu beantworten, insbesondere in der von Ihnen erwähnten Situation. Fahren Sie fort und verlinken Sie Ihre neue Frage in einem Kommentar zum OP.
Dotancohen

Es gibt solche Fragen, aber sie tauchen derzeit nicht bei Google auf.
Dagelf

Fazit: Ihre Antwort beantwortet die Frage hier nicht. @dotancohen ist richtig - wenn dies auf Google als Antwort auftaucht, wenn Sie nach diesem Anwendungsfall suchen, der eng mit dem OP zusammenhängt, stellen Sie eine separate Frage, in der dieser eng verwandte Anwendungsfall aufgeführt ist, und beantworten Sie sie.
don_crissti
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.