it-swarm.com.de

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

Angenommen, ein einfacher 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 Regex hinzufügen, um speziell für ps zu grep:

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

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

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

77
dotancohen

Gute Möglichkeit

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

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 die angegebenen Befehle aus. Sie können mehrere Befehle verwenden und dabei mehrere -e - Optionen 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 verwendet den Befehl p, 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 1, Was erste Zeile bedeutet. Wenn Sie mehr Zeilen drucken möchten, können Sie x,yp Verwenden, wobei x die erste zu druckende Zeile und y die letzte zu druckende Zeile ist. Zum Beispiel, um die ersten 3 Zeilen zu drucken, würden Sie 1,3p Verwenden.

  3. Der nächste Befehl ist d, der normalerweise alle Zeilen aus dem Puffer löscht. Vor diesem Befehl setzen wir yourpattern zwischen zwei / Zeichen. Dies ist die andere Möglichkeit (zuerst wurde angegeben, welche Zeilen wie mit dem Befehl p verwendet wurden), Zeilen zu adressieren, auf denen der Befehl ausgeführt werden soll. Dies bedeutet, dass der Befehl nur für die Zeilen funktioniert, die mit yourpattern übereinstimmen. Außer, wir verwenden das Zeichen ! Vor dem Befehl d, der seine Logik invertiert. Jetzt werden alle Zeilen entfernt, die nicht mit dem angegebenen Muster übereinstimmen.

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

Zusammenfassend: Wir drucken die erste Zeile und löschen dann alle Zeilen, die nicht mit unserem Muster übereinstimmen, aus der Eingabe. Die restlichen Zeilen werden gedruckt (daher stimmen nur Zeilen überein, die mit dem Muster übereinstimmen).

First Line Problem

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

  1. Hinzufügen des Befehls 1d Nach 1p. Wie bereits erwähnt, löscht der Befehl d Zeilen aus dem Puffer und wir geben den Bereich durch die Nummer 1 an, was bedeutet, dass nur die erste Zeile gelöscht wird. Der Befehl wäre also sed -e '1p' -e '1d' -e '/youpattern/!d'

  2. Verwenden Sie den Befehl 1b Anstelle von 1p. Es ist ein Trick. Mit dem Befehl b können wir zu einem anderen Befehl springen, der durch eine Bezeichnung angegeben wird (auf diese Weise können einige Befehle weggelassen werden). Wenn diese Bezeichnung jedoch nicht angegeben ist (wie in unserem Beispiel), springt sie nur zum Ende der Befehle und ignoriert die restlichen Befehle für unsere Zeile. In unserem Fall entfernt der letzte Befehl d diese Zeile nicht aus dem Puffer.

Vollständiges Beispiel:

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

Semikolon verwenden

Einige sed -Implementierungen können Ihnen das Schreiben ersparen, indem Sie Semikolon verwenden, um Befehle zu trennen, anstatt mehrere -e - Optionen zu verwenden. Wenn Sie sich also nicht dafür interessieren, portabel zu sein, lautet der Befehl ps aux | sed '1b;/syslog/!d'. Es funktioniert zumindest in GNU sed Und busybox Implementierungen.

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 beispielsweise verwenden, wenn Sie kein anderes Tool in Ihrem System haben:

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

Wie es funktioniert

  1. Zuerst verwenden wir die Option -n, Um vor jeder Zeile Zeilennummern hinzuzufügen. Wir wollen alle Zeilen nummerieren, mit denen wir übereinstimmen .* - alles, sogar leere Zeilen. Wie in den Kommentaren vorgeschlagen, können wir auch mit '^' übereinstimmen, das Ergebnis ist das gleiche.

  2. Dann verwenden wir erweiterte reguläre Ausdrücke, damit wir das Sonderzeichen \| Verwenden können, das als ODER fungiert. Wir stimmen also überein, wenn die Zeile mit 1: (Erste Zeile) beginnt oder unser Muster enthält (in diesem Fall syslog).

Zeilennummernproblem

Das Problem ist nun, dass wir diese hässlichen Zeilennummern in unserer Ausgabe erhalten. Wenn dies ein Problem ist, können wir sie mit cut wie folgt entfernen:

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

Die Option -d Gibt das Trennzeichen an. -f Gibt die Felder (oder Spalten) an, die gedruckt werden sollen. Wir wollen also jede Zeile in jedes : - Zeichen schneiden und nur die 2. und alle nachfolgenden Spalten drucken. Dadurch wird die erste Spalte mit ihrem Trennzeichen effektiv entfernt, und genau das benötigen wir.

68

Wie denkst du über die Verwendung von awk anstelle von 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/: Muster, nach dem gesucht werden soll

Es könnte sich auch lohnen, pgrep zu betrachten, obwohl dies eher für Skripte als für benutzerbezogene Ausgaben gilt. Es wird jedoch verhindert, dass der Befehl grep selbst in der Ausgabe angezeigt wird.

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

EDIT: nach Kommentaren

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

Ich dachte head -1 würde 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
31

Ps unterstützen internen Filter,

Angenommen, Sie suchen nach einem Bash-Prozess:

ps -C bash -f

Listet alle Prozesse mit dem Namen bash auf.

14
daisy

Ich neige dazu, den Header an stderr zu senden :

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

Dies ist normalerweise für menschliche Lesezwecke ausreichend. z.B.:

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

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

Es ist ein zusätzlicher Vorteil, dass die Ausgabe weiter geleitet werden kann (an sort usw.) und der Header oben bleibt.

6
antak

Sie können auch tee und head verwenden:

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

Beachten Sie jedoch, dass dieser Ansatz eine Problemumgehung benötigt, um zuverlässig zu sein, solange teeSIGPIPE Signale nicht ignorieren kann (siehe z. B. Diskussion hier ). Die Problemumgehung besteht darin, SIGPIPE-Signale zu ignorieren. Dies kann beispielsweise in Bash-ähnlichen Shells wie folgt erfolgen:

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

Beachten Sie auch, dass Ausgabereihenfolge nicht garantiert ist .

5
Thor

Vielleicht wären zwei ps 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
4
emcconville

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

4
harp

Fügen Sie zum Testen Folgendes in Ihre .bashrc-Datei ein oder kopieren/fügen Sie es zuerst in 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 Ihr .bashrc (oder .bash_profile, wenn Sie es stattdessen dort ablegen) als Quelle angeben:

source ~/.bashrc

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

4
taco

sortiere, aber halte 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"
    "[email protected]"
}

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

Vor allem dank Janis Papanagnou in comp.unix.Shell verwende ich die folgende Funktion:

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

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: -i für Matching ohne Berücksichtigung der Groß- und Kleinschreibung, -E für erweiterte reguläre Ausdrücke usw.
  • Gibt immer den gleichen Exit-Code wie grep aus, falls Sie programmgesteuert feststellen möchten, ob tatsächlich Zeilen übereinstimmen
  • Druckt nichts, 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
3
bdesham

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 Shell-Ersetzung - nur mit ed integriertem r, um r die Ausgabe von ps aux In den Puffer und löschen Sie dann nicht übereinstimmende Zeilen im Bereich 2,$ Und drucken Sie das Ergebnis:

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

Und da die Befehle sed in der akzeptierten Antwort auch die Zeile ausgeben, die mit sich selbst übereinstimmt, mit einem sed, das -f- Unterstützt, 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 Befehle ed.

2
don_crissti

Der Perl-Weg:

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

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

1
emazep

Wenn dies nur für Grepping-Prozesse mit vollständigen Headern gilt, würde ich den Vorschlag von @ mrb 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 -fp erhält das gleiche Ergebnis, jedoch ohne 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
 ...
0
user86969