Jak grepować konkretną linię _ i_ pierwszą linię pliku?

76

Zakładając prosty grep, taki jak:

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

Zapewnia to wiele informacji, ale ponieważ brakuje pierwszego wiersza polecenia ps, nie ma kontekstu dla informacji. Wolałbym, aby wyświetlana była również pierwsza linia 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

Oczywiście mogę dodać wyrażenie regularne do grep specjalnie dla ps:

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

Wolałbym jednak bardziej ogólne rozwiązanie, ponieważ istnieją inne przypadki, w których chciałbym mieć również pierwszą linię.

Wydaje się, że byłby to dobry przypadek użycia deskryptora pliku „stdmeta” .

dotancohen
źródło
9
Złożoność wymagana przez te odpowiedzi pokazuje, w jaki sposób uniksowa filozofia „rób jedną rzecz i rób to dobrze” czasami zawodzi, gdy mierzymy ją użytecznością: znajomość wszystkich tych poleceń wystarczająco dobrze, aby zastosować je do tego wspólnego problemu (filtrowanie informacji o procesie i wciąż widząc etykiety kolumn) pokazuje wadę tego podejścia: czasami rzeczy nie pasują do siebie bardzo dobrze. Dlatego narzędzia jak acksą tak użyteczne i dlaczego perlzdrożała przeszłość sed, awkitp popularność: to ważne dla części podsumować w spójną całość.
iconoclast
3
oczywiście w tym konkretnym przykładzie można użyć -Cargumentu do psi nie trzeba go było potokować do grep. np. ps u -C someApplub nawetps u -C app1 -C app2 -C app3
cas
1
@iconoclast: oczywiście rozwiązanie Unixy byłoby narzędziem, które może multipleksować wiele linii, z których każda ma być filtrowana przez inny zestaw filtrów. Tak jakby uogólniona wersja ps aux | { head -1; grep foo; }wspomnianego przez @Nahuela Fouilleula poniżej (jego prawdopodobnie jest to jedyne rozwiązanie, które mógłbym w razie potrzeby przypomnieć na miejscu)
Lie Ryan,
@iconoclast: Brak doświadczenia i wiedzy o narzędziach, to, co narzędzia naprawdę robią dobrze, zawsze będą wydawać się całkowicie bezużyteczne. Znając dobrze rozkaz, nie ma gdzie na podwórku użyteczności, to na podwórku przeczytania dobrego podręcznika i ćwiczenia. Te narzędzia są dostępne od dziesięcioleci. Działają i pasują do siebie bardzo ładnie (i czysto).
Ярослав Рахматуллин
@ ЯрославРахматуллин: Myślę, że mogłeś całkowicie nie zrozumieć, co powiedziałem. (Być może dlatego, że angielski nie jest twoim pierwszym językiem?) „Użyteczność” dotyczy UX („user experience”), a nie użyteczności (lub „użyteczności”). Wskazanie, że gdy prosta operacja jest tak złożona, szkodzi użyteczności, NIE jest tym samym, co stwierdzenie, że narzędzia są bezużyteczne. Z pewnością nie są one bezużyteczne. Nikt przy zdrowych zmysłach nie powiedziałby, że są bezużyteczni.
iconoclast

Odpowiedzi:

67

Dobry sposób

Zwykle nie możesz tego zrobić za pomocą grep, ale możesz użyć innych narzędzi. Wspomniano już o AWK, ale możesz także użyć sedtakiego:

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

Jak to działa:

  1. Narzędzie Sed działa indywidualnie dla każdej linii, uruchamiając określone polecenia na każdym z nich. Możesz mieć wiele poleceń, określając kilka -eopcji. Do każdego polecenia możemy dołączyć parametr zakresu określający, czy to polecenie powinno być zastosowane do określonej linii, czy nie.

  2. „1p” to pierwsze polecenie. Używa ppolecenia, które normalnie drukuje wszystkie linie. Ale poprzedzamy go wartością liczbową, która określa zakres, do którego należy ją zastosować. Tutaj używamy, 1co oznacza pierwszą linię. Jeśli chcesz wydrukować więcej linii, możesz użyć x,ypgdzie xpierwsza linia do wydrukowania, yostatnia linia do wydrukowania. Na przykład do wydrukowania pierwszych 3 wierszy użyłbyś1,3p

  3. Następne polecenie to dnormalnie usuwające wszystkie linie z bufora. Przed tym poleceniem umieszczamy yourpatternmiędzy dwoma /znakami. Jest to inny sposób (po pierwsze, aby określić, które linie, tak jak to zrobiliśmy z ppoleceniem), adresowania linii, w których polecenie powinno być uruchomione. Oznacza to, że polecenie będzie działać tylko dla pasujących linii yourpattern. Poza tym używamy !znaku przed dpoleceniem, które odwraca jego logikę. Teraz usunie wszystkie linie, które nie pasują do określonego wzorca.

  4. Na końcu sed wydrukuje wszystkie linie pozostawione w buforze. Ale usunęliśmy z bufora linie, które nie pasują, więc zostaną wydrukowane tylko pasujące linie.

Podsumowując: drukujemy 1. linię, a następnie usuwamy z linii wszystkie linie, które nie pasują do naszego wzorca. Reszta linii jest drukowana (więc tylko linie, które pasują do wzoru).

Problem z pierwszej linii

Jak wspomniano w komentarzach, istnieje problem z tym podejściem. Jeśli określony wzór pasuje również do pierwszego wiersza, zostanie wydrukowany dwukrotnie (raz na ppolecenie i raz z powodu dopasowania). Możemy tego uniknąć na dwa sposoby:

  1. Dodanie 1dpolecenia po 1p. Jak już wspomniałem, dpolecenie usuwa wiersze z bufora, a my określamy jego zakres liczbą 1, co oznacza, że ​​usunie tylko pierwszą linię. Tak byłoby poleceniesed -e '1p' -e '1d' -e '/youpattern/!d'

  2. Za pomocą 1bpolecenia zamiast 1p. To jest trik. bpolecenie pozwala nam przejść do innego polecenia określonego przez etykietę (w ten sposób niektóre polecenia można pominąć). Ale jeśli ta etykieta nie jest określona (jak w naszym przykładzie), po prostu przeskakuje na koniec poleceń, ignorując pozostałe polecenia dla naszej linii. W naszym przypadku ostatnie dpolecenie nie usunie tej linii z bufora.

Pełny przykład:

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

Używanie średnika

Niektóre sedimplementacje mogą zaoszczędzić ci trochę pisania, używając średnika do oddzielania poleceń zamiast wielu -eopcji. Jeśli więc nie zależy ci na przenośności, polecenie będzie takie ps aux | sed '1b;/syslog/!d'. Działa przynajmniej GNU sedi busyboximplementacje.

Szalony sposób

Oto jednak dość szalony sposób na zrobienie tego z grep. To zdecydowanie nie jest optymalne, publikuję to tylko w celach edukacyjnych, ale możesz go użyć na przykład, jeśli nie masz innego narzędzia w systemie:

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

Jak to działa

  1. Najpierw używamy -nopcji, aby dodać numery linii przed każdą linią. Chcemy numerować wszystkie linie, które pasujemy .*- cokolwiek, nawet pustą linię. Jak sugerujemy w komentarzach, możemy również dopasować „^”, wynik jest taki sam.

  2. Następnie używamy rozszerzonych wyrażeń regularnych, abyśmy mogli użyć \|znaku specjalnego, który działa jak OR. Dopasowujemy więc, jeśli linia zaczyna się od 1:(pierwsza linia) lub zawiera nasz wzorzec (w tym przypadku jest to syslog).

Problem z numerami linii

Teraz problem polega na tym, że otrzymujemy te brzydkie numery wierszy w naszych wynikach. Jeśli jest to problem, możemy je usunąć za pomocą cut:

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

-dopcja określa separator, -fokreśla pola (lub kolumny), które chcemy wydrukować. Dlatego chcemy wyciąć każdą linię na każdym :znaku i wydrukować tylko drugą i wszystkie kolejne kolumny. To skutecznie usuwa pierwszą kolumnę ze swoim separatorem i właśnie tego potrzebujemy.

Krzysztof Adamski
źródło
4
Numeracja linii może być również wykonana za pomocą cat -ni wyglądałaby wyraźniej, jak w przypadku nadużywania grep.
Alfe
1
nlnie zlicza pustych linii (ale drukuje je bez numeru linii), cat -nformatuje numerację z poprzedzającymi spacjami, grep -n .w ogóle usuwa puste linie i dodaje dwukropek. Wszystkie mają swoje ... eee ... cechy ;-)
Alfe
2
Bardzo pouczająca, dobrze napisana odpowiedź. Próbowałem zastąpić „Udawaj” (prawie na początku) słowem „Przygotuj” dla ciebie, ale chciałem więcej zmian i nie miałem ochoty zmieniać losowych bzdur w swoim poście, więc możesz to naprawić.
Bill K
2
ps aux | sed '1p;/pattern/!d'wydrukuje pierwszą linię dwa razy, jeśli pasuje do wzorca . Najlepiej jest używane bpolecenie: ps aux | sed -e 1b -e '/pattern/!d'. cat -nnie jest POSIX. grep -n '^'numerowałby każdą linię (nie jest to problem z wyjściem ps, który nie ma pustych linii). nl -ba -d $'\n'numeruje każdą linię.
Stéphane Chazelas,
2
Zauważ, że 1b;...nie jest przenośny ani POSIX, po "b" nie może być żadnych innych poleceń, więc potrzebujesz nowego wiersza lub innego wyrażenia -e.
Stéphane Chazelas,
58

Co myślisz o używaniu awkzamiast 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: Liczba rekordów == 1; to znaczy. pierwsza linia
  • ||: lub:
  • /syslogd/: Wzór do wyszukania

Warto też przyjrzeć się temu pgrep, choć dotyczy to raczej skryptów, a nie danych wyjściowych użytkownika. Pozwala to jednak uniknąć greppojawienia się samego polecenia na wyjściu.

chopper:~> pgrep -l syslogd
19 syslogd
mrb
źródło
Bardzo fajnie, dziękuję. Jest to również łatwe do skryptowania do przyszłej rozbudowy.
dotancohen,
Muszę nauczyć się trochę awk. bardzo dobrze.
user606723,
30
ps aux | { read line;echo "$line";grep someApp;}

EDYCJA: po komentarzach

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

Myślałem, że head -1przeczytam wszystkie dane wejściowe, ale po przetestowaniu to też działa.

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

wyjście jest

this is a test
this line should be ok
Nahuel Fouilleul
źródło
2
To jest pomysł napisany bezpośrednio w bash. Chciałbym dać za to więcej niż jedną aprobatę. Może bym użył { IFS='' read line; ... }na wypadek, gdyby nagłówek zaczynał się od spacji.
Alfe
To jest dokładnie zaatakować ten problem bezpośrednio. Miły!
dotancohen,
3
Po prostu użyłbym head -1zamiast kombinacji odczytu / echa.
chepner
1
Cóż, działa z head -n1moim bash. Prawdopodobnie może to być specyficzne dla implementacji. Moja głowa nie czyta w tym przypadku całego wejścia, tylko pierwszy wiersz, pozostawiając resztę w buforze wejściowym.
Krzysztof Adamski,
2
head -n1jest krótszy, ale wydaje się, że nawet specyfikacja POSIX milczy co do tego, ile jego danych wejściowych może odczytać, więc być może read line; echo $linejest w końcu bardziej przenośny.
chepner,
14

Ps obsługuje wewnętrzny filtr,

Załóżmy, że szukasz procesu bash:

ps -C bash -f

Wyświetla listę wszystkich procesów o tej nazwie bash.

stokrotka
źródło
Dzięki, miło to wiedzieć. Nie znajdzie jednak między innymi skryptów uruchamianych z Pythona.
dotancohen
6

Zwykle wysyłam nagłówek do stderr :

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

Zazwyczaj wystarcza to do czytania przez ludzi. na przykład:

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

Część w nawiasach może przejść do własnego skryptu do ogólnego użytku.

Dodatkową wygodą jest to, że dane wyjściowe można dalej potokować (do sortitp.), A nagłówek pozostanie na górze.

antak
źródło
5

Możesz także użyć teei head:

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

Należy jednak pamiętać, że dopóki teenie można zignorować SIGPIPEsygnałów (patrz np. Dyskusja tutaj ), takie podejście wymaga obejścia, aby być wiarygodnym. Obejściem tego problemu jest ignorowanie sygnałów SIGPIPE, można to na przykład zrobić w ten sposób w powłokach typu bash:

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

Należy również pamiętać, że kolejność wydruków nie jest gwarantowana .

Thor
źródło
Nie polegałbym na tym, aby zadziałało, kiedy pierwszy raz go uruchomiłem (zsh), wygenerował nagłówki kolumn poniżej wyników grep. Za drugim razem było dobrze.
Rqomey
1
Nie widziałem tego jeszcze, ale jednym ze sposobów na zwiększenie niezawodności jest umieszczenie małego opóźnienia w rurociągu przed grep: | { sleep .5; cat }.
Thor
2
Dodawanie uśpień w celu uniknięcia problemów z współbieżnością jest zawsze włamaniem. Choć może to zadziałać, jest to krok w stronę ciemnej strony. -1 za to.
Alfe
1
Miałem kilka innych dziwnych problemów podczas wypróbowywania tej odpowiedzi, zadałem pytanie, aby to sprawdzić
Rqomey
Jest to interesujące zastosowanie tee, ale uważam, że jest niewiarygodne i często drukuje tylko linię wyjściową, ale nie linię nagłówka.
dotancohen
4

Być może dwie pskomendy byłyby najłatwiejsze.

$ 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
źródło
2
Nie podoba mi się to rozwiązanie, przede wszystkim dlatego, że sytuacja może się zmienić między pierwszym a drugim ps auxpołączeniem ... A jeśli chcesz tylko tę statyczną pierwszą linię, dlaczego nie wykonać echa ręcznie?
Shadur
1
W tej sytuacji nie należy przejmować się zmianami między dwoma połączeniami . Pierwszy zapewni tylko nagłówek, który zawsze będzie pasował do wyniku drugiego.
Alfe,
2
Nie rozumiem, dlaczego zostało to odrzucone, z pewnością jest to realna opcja. Upvoting.
dotancohen
4

Możesz użyć pidstat z:

pidstat -C someApp
or
pidstat -p <PID>

Przykład:

# 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

Więcej informacji: http://linux.die.net/man/1/pidstat

harfa
źródło
Dzięki, miło to wiedzieć. Nie znajdzie jednak między innymi skryptów uruchamianych z Pythona.
dotancohen
4

Najpierw umieść w pliku .bashrc lub skopiuj / wklej w powłoce, aby przetestować.

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

Zastosowanie: 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

Upewnij się, że źródło .bashrc (lub .bash_profile, jeśli go tam umieścisz):

source ~/.bashrc

Funkcja zakończy się nawet automatycznie w wierszu poleceń powłoki. Jak powiedziałeś w innej odpowiedzi, możesz potokować pierwszą linię do pliku, aby zapisać jedno wywołanie do ps.

taco
źródło
1
Fajnie, używam tego rodzaju funkcji od lat. Dzwonię do mojej wersjipsl , która dzwoni tylko raz psi grepraz (i nie potrzebuje head).
Adam Katz
3

sortuj, ale zachowaj nagłówek na górze

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

I użyj tego w ten sposób

$ 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
źródło
Dzięki, niektóre z tych odpowiedzi omawiają ogólny przypadek tego pytania. Doskonały!
dotancohen,
3

Dzięki głównie Janisowi Papanagnou w comp.unix.shell korzystam z następującej funkcji:

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

Ma to wiele zalet:

  • Działa z bash, zsh i prawdopodobnie ksh
  • Jest to drop-in zamiennik dla grep, więc możesz nadal używać dowolnych flag: -ido dopasowywania bez rozróżniania wielkości liter, -Edla rozszerzonych wyrażeń regularnych itp.
  • Zawsze zwraca ten sam kod wyjścia co grep, na wypadek, gdybyś chciał programowo ustalić, czy którekolwiek wiersze faktycznie pasują
  • Nie drukuje nic, jeśli dane wejściowe były puste

Przykład użycia:

$ 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
źródło
2

Innym sposobem jest gnu ed:

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

lub jeśli powłoka obsługuje podstawianie procesów:

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

to jest:

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

Bardziej przenośny, bez gnu '!' podstawienia lub powłoki - używając tylko edwbudowanego, raby rwprowadzić dane wyjściowe ps auxdo bufora, a następnie usunąć niepasujące wiersze w 2,$zakresie i wydrukować wynik:

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

A ponieważ sedpolecenia w zaakceptowanej odpowiedzi generują również wiersz pasujący do siebie, z sedobsługującą -f-i powłoką obsługującą podstawianie procesów, uruchomiłbym:

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

który w zasadzie robi to samo co poprzednie edpolecenia.

don_crissti
źródło
1

Sposób Perla:

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

O wiele łatwiejszy do odczytania niż sedszybszy, bez ryzyka wybrania niepożądanych linii.

emazep
źródło
Perl?!?
dotancohen,
0

Jeśli dotyczy to tylko procesów grepowania z pełnymi nagłówkami, rozwinąłbym sugestię @ 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 -fpuzyska ten sam wynik, ale bez podpowłoki. Jeśli wymagane jest inne formatowanie:

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

źródło
-2

Jeśli znasz dokładne numery wierszy, perl to łatwe! Jeśli chcesz pobrać wiersz 1 i 5 z pliku, powiedz / etc / passwd:

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

Jeśli chcesz uzyskać także inne wiersze, po prostu dodaj ich liczby do tablicy.

Dagelf
źródło
1
Dziękuję Ci. Jeśli chodzi o PO, znam część tekstu w wierszu, ale nie numer wiersza.
dotancohen
To pojawia się jako odpowiedź w Google, gdy szukasz tego przypadku użycia ściśle związanego z PO, więc warto o tym tutaj wspomnieć.
Dagelf
1
Jeśli tak jest, to wysoce sugeruję, aby rozpocząć nowe pytanie i odpowiedzieć na nie za pomocą tej odpowiedzi. Odpowiadanie na własne pytania dotyczące SE jest w porządku, szczególnie w sytuacji, o której wspominasz. Śmiało i link do nowego pytania w komentarzu do PO.
dotancohen
Są takie pytania, ale obecnie nie pojawiają się w Google.
Dagelf,
Dagelf, sedno sprawy brzmi - twoja odpowiedź nie odpowiada na pytanie tutaj. @dotancohen ma rację - jeśli pojawia się jako odpowiedź w Google, gdy szuka tego przypadku użycia ściśle związanego z PO, to zadaj osobne pytanie - szczegółowo omawiając ten ściśle powiązany przypadek użycia - i odpowiedz na nie.
don_crissti