Come eseguire il grep di una riga specifica _e_ la prima riga di un file?


76

Supponendo un grep semplice come:

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

Questo fornisce molte informazioni, ma poiché manca la prima riga del comando ps, non c'è contesto per le informazioni. Preferirei che venisse mostrata anche la prima riga di 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

Certo, potrei aggiungere una regex a grep appositamente per ps:

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

Tuttavia, preferirei una soluzione più generale in quanto vi sono altri casi in cui vorrei avere anche la prima riga.

Sembra che sarebbe un buon caso d'uso per un descrittore di file "stdmeta" .


9
La complessità richiesta da queste risposte mostra come la filosofia Unix di "fare una cosa e farla bene" a volte ci manchi quando misurata dal criterio di usabilità: conoscere tutti questi comandi abbastanza bene da applicarli a questo problema comune (filtrare le informazioni sul processo e vedere ancora le etichette delle colonne) mostra il lato negativo dell'approccio: a volte le cose non si adattano molto bene. Questo è il motivo per cui strumenti come acksono così utili, e perché il perlpassato è salito alle stelle sed, awkecc. In popolarità: è importante che le parti si riassumano in un insieme coerente.
iconoclasta,

3
ovviamente, per questo esempio particolare, potresti usare l' -Cargomento pse non dovresti inserirlo in grep. per esempio ps u -C someAppo ancheps u -C app1 -C app2 -C app3
cas

1
@iconoclast: ovviamente la soluzione Unixy sarebbe uno strumento in grado di multiplexare più righe ognuna da filtrare attraverso diversi set di filtri. Kinda una versione generalizzata di ps aux | { head -1; grep foo; }cui parla @Nahuel Fouilleul di seguito (la sua è probabilmente l'unica soluzione che sarei in grado di ricordare sul posto se necessario)
Lie Ryan

@iconoclast: mancanza di esperienza e conoscenza degli strumenti, ciò che gli strumenti fanno davvero bene sembrerà sempre del tutto inutile. Conoscere bene un comando non è sul campo di usabilità, è sul bastone di leggere il manuale e la pratica. Questi strumenti esistono da decenni. Funzionano e si adattano molto bene (e in modo pulito).
Ярослав Рахматуллин,

@ ЯрославРахматуллин: Penso che potresti aver frainteso del tutto quello che ho detto. (Forse perché l'inglese non è la tua prima lingua?) "Usabilità" è correlata a UX ("esperienza utente") non a utilità (o "utilità"). Sottolineare che quando un'operazione semplice è così complessa fa male l'usabilità NON è la stessa cosa che dire che gli strumenti sono inutili. Ovviamente non sono inutili. Nessuno nella loro mente giusta direbbe che sono inutili.
iconoclasta,

Risposte:


67

Buon modo

Normalmente non puoi farlo con grep ma puoi usare altri strumenti. AWK è già stato menzionato ma puoi anche usare sed, in questo modo:

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

Come funziona:

  1. L'utilità Sed funziona su ciascuna riga singolarmente, eseguendo i comandi specificati su ciascuna di esse. Puoi avere più comandi, specificando diverse -eopzioni. Possiamo anteporre ogni comando con un parametro range che specifica se questo comando deve essere applicato o meno a una riga specifica.

  2. "1p" è un primo comando. Usa un pcomando che normalmente stampa tutte le linee. Ma lo anteponiamo con un valore numerico che specifica l'intervallo a cui dovrebbe essere applicato. Qui, usiamo 1che significa prima linea. Se si desidera stampare più linee, è possibile utilizzare x,ypdove xè la prima linea per la stampa, yè ultima riga da stampare. Ad esempio, per stampare le prime 3 righe, si utilizzerà1,3p

  3. Il comando successivo è dche normalmente elimina tutte le righe dal buffer. Prima di questo comando mettiamo yourpatterntra due /personaggi. Questo è l'altro modo (in primo luogo era di specificare quali linee come abbiamo fatto con il pcomando) di indirizzare le linee su cui il comando dovrebbe essere in esecuzione. Ciò significa che il comando funzionerà solo per le linee corrispondenti yourpattern. Tranne, usiamo il !carattere prima del dcomando che inverte la sua logica. Quindi ora rimuoverà tutte le linee che non corrispondono al modello specificato.

  4. Alla fine, sed stamperà tutte le righe rimaste nel buffer. Ma abbiamo rimosso le linee che non corrispondono dal buffer, quindi verranno stampate solo le linee corrispondenti.

Per riassumere: stampiamo la prima riga, quindi eliminiamo dall'input tutte le righe che non corrispondono al nostro modello. Resto delle linee vengono stampati (in modo che solo le linee che fanno corrispondere al modello).

Problema di prima linea

Come menzionato nei commenti, c'è un problema con questo approccio. Se il motivo specificato corrisponde anche alla prima riga, verrà stampato due volte (una volta per pcomando e una volta a causa di una corrispondenza). Possiamo evitarlo in due modi:

  1. Aggiunta del 1dcomando dopo 1p. Come ho già accennato, il dcomando elimina le righe dal buffer e specifichiamo che è intervallo per il numero 1, il che significa che eliminerà solo la 1a riga. Quindi il comando sarebbesed -e '1p' -e '1d' -e '/youpattern/!d'

  2. Usando il 1bcomando, invece di 1p. È un trucco. bIl comando ci consente di passare ad altri comandi specificati da un'etichetta (in questo modo alcuni comandi possono essere omessi). Ma se questa etichetta non viene specificata (come nel nostro esempio) passa alla fine dei comandi, ignorando il resto dei comandi per la nostra linea. Quindi, nel nostro caso, l'ultimo dcomando non rimuoverà questa riga dal buffer.

Esempio completo:

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

Usando il punto e virgola

Alcune sedimplementazioni possono farti risparmiare un po 'di battitura utilizzando il punto e virgola per separare i comandi anziché utilizzare più -eopzioni. Quindi se non ti interessa essere portatile il comando sarebbe ps aux | sed '1b;/syslog/!d'. Funziona almeno in GNU sede busyboximplementazioni.

Modo folle

Ecco, tuttavia, un modo piuttosto folle per farlo con grep. Non è sicuramente ottimale, sto pubblicando questo solo a scopo di apprendimento, ma potresti usarlo ad esempio, se non hai altri strumenti nel tuo sistema:

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

Come funziona

  1. Innanzitutto, utilizziamo l' -nopzione per aggiungere i numeri di riga prima di ogni riga. Vogliamo numerare tutte le linee che stiamo abbinando .*- qualsiasi cosa, anche una linea vuota. Come suggerito nei commenti, possiamo anche abbinare '^', il risultato è lo stesso.

  2. Quindi stiamo usando espressioni regolari estese in modo da poter usare \|caratteri speciali che funzionano come OR. Quindi abbiniamo se la linea inizia con 1:(prima riga) o contiene il nostro modello (in questo caso è syslog).

Problema con i numeri di riga

Ora il problema è che stiamo ottenendo questi brutti numeri di riga nel nostro output. Se questo è un problema, possiamo rimuoverli con cut, in questo modo:

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

-dL'opzione specifica il delimitatore, -fspecifica i campi (o colonne) che vogliamo stampare. Quindi vogliamo tagliare ogni riga su ogni :carattere e stampare solo la 2a e tutte le colonne successive. Questo rimuove efficacemente la prima colonna con il suo delimitatore ed è esattamente ciò di cui abbiamo bisogno.


4
Anche la numerazione delle righe può essere eseguita con cat -ne apparirebbe più chiara come se si fosse abusato di un grep per questo.
Alfe,

1
nlnon conta le righe vuote (ma le stampa senza numero di riga), cat -nformatta la numerazione con spazi precedenti, grep -n .rimuove le righe vuote e aggiunge due punti. Tutti hanno le loro ... er ... caratteristiche ;-)
Alfe,

2
Risposta ben scritta molto educativa. Ho provato a sostituire "Pretend" (quasi all'inizio) con "Prepend" per te, ma voleva ulteriori cambiamenti e non avevo voglia di cambiare le schifezze casuali nel tuo post, quindi potresti voler risolvere questo problema.
Bill K,

2
ps aux | sed '1p;/pattern/!d'stamperà la prima riga due volte se corrisponde al modello . La cosa migliore è utilizzare il bcomando: ps aux | sed -e 1b -e '/pattern/!d'. cat -nnon è POSIX. grep -n '^'numererebbe ogni riga (non è un problema per l'output ps che non ha righe vuote). nl -ba -d $'\n'numera ogni riga.
Stéphane Chazelas,

2
Nota che 1b;...non è portatile né POSIX, non ci può essere nessun altro comando dopo "b", quindi hai bisogno di una nuova riga o di un'altra espressione -e.
Stéphane Chazelas,

58

Come ti senti a usare awkinvece di 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: Numero di record == 1; vale a dire. la prima riga
  • ||: o:
  • /syslogd/: Pattern da cercare

Potrebbe anche valere la pena dare un'occhiata pgrep, anche se questo è più per gli script piuttosto che per l'output rivolto all'utente. grepTuttavia, evita che il comando stesso venga visualizzato nell'output.

chopper:~> pgrep -l syslogd
19 syslogd

Molto bene, grazie. Questo è anche ben programmabile per future espansioni.
dotancohen,

Devo imparare un po 'di imbarazzo. molto bella.
user606723,

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

EDIT: dopo i commenti

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

Pensavo di head -1leggere tutti gli input, ma dopo averli testati, funziona anche.

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

l'uscita è

this is a test
this line should be ok

2
Questa è l'idea spiegata direttamente in bash. Mi piacerebbe dare più di un pollice in su per questo. Potrei semplicemente usarlo { IFS='' read line; ... }nel caso in cui l'intestazione inizi con gli spazi.
Alfe,

Questo attacca esattamente il problema direttamente. Bello!
dotancohen,

3
Vorrei solo usare al head -1posto del combo read / echo.
Chepner,

1
Bene, funziona con head -n1il mio bash. Questo può probabilmente essere specifico dell'implementazione. La mia testa non sta leggendo l'intero input in questo caso, solo la prima riga, lasciando il resto nel buffer di input.
Krzysztof Adamski,

2
head -n1è più breve, ma sembra che anche le specifiche POSIX siano silenziose su quanto del suo input è consentito leggere, quindi forse read line; echo $lineè più portatile dopo tutto.
Chepner,

14

Ps supporto filtro interno,

Supponiamo che tu stia cercando un processo bash:

ps -C bash -f

Elencherà tutto il processo chiamato bash.


Grazie, è bello saperlo. Tuttavia, non troverà script avviati da Python, tra gli altri.
dotancohen,

6

Tendo a inviare l'intestazione a stderr :

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

Questo di solito è sufficiente per scopi di lettura umana. per esempio:

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

La parte tra parentesi potrebbe essere inserita nel proprio script per uso generale.

C'è un'ulteriore comodità in quanto l'output può essere ulteriormente convogliato (a sortecc.) E l'intestazione rimarrà in cima.


5

Puoi anche usare teee head:

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

Si noti tuttavia che fino a quando teenon è possibile ignorare i SIGPIPEsegnali (vedere ad esempio la discussione qui ) questo approccio richiede una soluzione alternativa per essere affidabile. La soluzione alternativa è ignorare i segnali SIGPIPE, ad esempio questo può essere fatto in questo modo come bash come shell:

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

Si noti inoltre che l' ordine di uscita non è garantito .


Non farei affidamento su questo per funzionare, la prima volta che l'ho eseguito (zsh) ha prodotto intestazioni di colonna sotto i risultati grep. La seconda volta andava bene.
Rqomey,

1
Non ho ancora visto questo, ma un modo per aumentare l'affidabilità è quello di inserire un piccolo ritardo in cantiere prima del grep: | { sleep .5; cat }.
Thor,

2
L'aggiunta di sleep per evitare problemi di concorrenza è sempre un trucco. Anche se questo potrebbe funzionare, è un passo verso il lato oscuro. -1 per questo.
Alfe,

1
Ho avuto alcuni altri strani problemi mentre provavo questa risposta, ho creato una domanda da controllare
Rqomey,

Questo è un uso interessante di tee, ma lo trovo inaffidabile e spesso stampa solo la riga di output, ma non la riga di intestazione.
dotancohen,

4

Forse due pscomandi sarebbero i più facili.

$ 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
Non mi piace questa soluzione, principalmente perché la situazione potrebbe cambiare tra la prima e la seconda ps auxchiamata ... E se vuoi solo quella prima linea statica, perché non riecheggiarla manualmente?
Shadur,

1
I cambiamenti tra le due chiamate non devono essere disturbati in questa situazione. Il primo fornirà solo il titolo che si adatterà sempre all'output del secondo.
Alfe,

2
Non vedo perché questo sia stato sottoposto a downgrade, sicuramente è un'opzione praticabile. Upvoting.
dotancohen,

4

Puoi usare pidstat con:

pidstat -C someApp
or
pidstat -p <PID>

Esempio:

# 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

Ulteriori informazioni: http://linux.die.net/man/1/pidstat


Grazie, è bello saperlo. Tuttavia, non troverà script avviati da Python, tra gli altri.
dotancohen,

4

Inserisci quanto segue nel tuo file .bashrc o copia / incolla prima nella shell, per il test.

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

Utilizzo: psls [modello 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

Assicurati di procurarti il ​​tuo .bashrc (o .bash_profile se invece lo metti lì):

source ~/.bashrc

La funzione si completerà anche automaticamente dalla riga di comando della shell. Come indicato in un'altra risposta, è possibile reindirizzare la prima riga a un file per salvare una chiamata su ps.


1
Bene, uso questo tipo di funzione da anni. Chiamo la mia versionepsl , che chiama solo pse grepuna volta ciascuna (e non è necessaria head).
Adam Katz,

3

ordina ma mantieni la riga di intestazione in alto

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

E usalo in questo modo

$ 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

Grazie, alcune di queste risposte discutono il caso generale di questa domanda. Perfetto!
dotancohen,

3

Grazie principalmente a Janis Papanagnou in comp.unix.shell, utilizzo la seguente funzione:

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

Questo ha una serie di vantaggi:

  • Funziona con bash, zsh e probabilmente ksh
  • È un sostituto drop-in per grep, quindi puoi continuare a usare qualsiasi flag tu voglia: -iper la corrispondenza senza distinzione tra maiuscole e minuscole, -Eper regex estese, ecc.
  • Produce sempre lo stesso codice di uscita di grep, nel caso in cui si desideri determinare a livello di codice se tutte le righe corrispondono effettivamente
  • Non stampa nulla se l'input era vuoto

Esempio di utilizzo:

$ 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

Un altro modo con gnu ed:

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

oppure, se la shell supporta la sostituzione di processo:

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

questo è:

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

Più portatile, senza gnu '!' o sostituzione della shell - utilizzando solo edincorporato rper rtrasferire l'output ps auxnel buffer e quindi eliminare le righe non corrispondenti 2,$nell'intervallo e stampare il risultato:

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

E poiché i sedcomandi nella risposta accettata producono anche la riga corrispondente, con un sedsupporto che supporta -f-e una shell che supporta la sostituzione del processo, eseguirei:

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

che praticamente fa la stessa cosa dei edcomandi precedenti .


1

La via del Perl:

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

Molto più facile da leggere rispetto a sed, più veloce, nessun rischio di scegliere linee indesiderate.



0

Se fosse solo per i processi grepping con intestazioni complete, espanderei il suggerimento di @ 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 -fpotterrà lo stesso risultato ma senza una subshell. Se è richiesta un'altra formattazione:

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

-2

Se conosci i numeri esatti delle righe, è facile con perl! Se vuoi ottenere la linea 1 e 5 da un file, dì / etc / passwd:

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

Se vuoi ottenere anche altre righe, aggiungi i loro numeri nell'array.


1
Grazie. Per quanto riguarda l'OP, conosco parte del testo nella riga, ma non il numero della riga.
dotancohen,

Questo appare come una risposta su Google quando si cerca questo caso d'uso strettamente correlato al PO, quindi vale la pena notare qui.
Dagelf,

1
In tal caso, ti consiglio vivamente di iniziare una nuova domanda e rispondere con questa risposta. È perfettamente bene rispondere alle tue domande su SE, specialmente nella situazione che menzioni. Vai avanti e collega alla tua nuova domanda in un commento sul PO.
dotancohen,

Ci sono domande del genere, ma al momento non vengono visualizzate su Google.
Dagelf,

Dagelf, la linea di fondo è - la tua risposta non risponde alla domanda qui. @dotancohen ha ragione - se questo appare come una risposta su Google quando si cerca questo caso d'uso strettamente correlato all'OP, porre una domanda separata - dettagliare quel caso d'uso strettamente correlato - e rispondere.
don_crissti,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.