Como grep uma linha específica _e_ a primeira linha de um arquivo?

75

Assumindo um simples grep como:

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

Isso fornece muita informação, mas como a primeira linha do comando ps está faltando, não há contexto para a informação. Eu preferiria que a primeira linha de ps também fosse mostrada:

$ 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

Claro, eu poderia adicionar um regex ao grep especificamente para ps:

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

No entanto, eu preferiria uma solução mais geral, pois há outros casos em que eu gostaria de ter a primeira linha também.

Parece que esse seria um bom caso de uso para um descritor de arquivo "stdmeta" .

    
por dotancohen 12.09.2012 / 12:45

15 respostas

67

Boa maneira

Normalmente você não pode fazer isso com o grep, mas você pode usar outras ferramentas. O AWK já foi mencionado, mas você também pode usar sed , assim:

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

Como funciona:

  1. O utilitário Sed funciona em cada linha individualmente, executando comandos especificados em cada um deles. Você pode ter vários comandos, especificando várias opções -e . Podemos prefixar cada comando com um parâmetro de intervalo que especifica se esse comando deve ser aplicado a uma linha específica ou não.

  2. "1p" é um primeiro comando. Ele usa o comando p , que normalmente imprime todas as linhas. Mas prefixamos com um valor numérico que especifica o intervalo ao qual ele deve ser aplicado. Aqui, usamos 1 , o que significa primeira linha. Se você quiser imprimir mais linhas, poderá usar x,yp , em que x é a primeira linha a imprimir, y é a última linha a ser impressa. Por exemplo, para imprimir as primeiras 3 linhas, você usaria 1,3p

  3. O próximo comando é d , o que normalmente exclui todas as linhas do buffer. Antes desse comando, colocamos yourpattern entre dois caracteres / . Essa é a outra maneira (primeiro era especificar quais linhas, como fizemos com o comando p ) das linhas de endereçamento nas quais o comando deveria estar rodando. Isso significa que o comando só funcionará para as linhas que correspondem a yourpattern . Exceto, usamos o caractere ! antes do comando d , que inverte sua lógica. Portanto, agora removerá todas as linhas que não correspondem ao padrão especificado.

  4. No final, o sed irá imprimir todas as linhas restantes no buffer. Mas removemos as linhas que não correspondem ao buffer, para que apenas as linhas correspondentes sejam impressas.

Para resumir: nós imprimimos a 1ª linha, então deletamos todas as linhas que não correspondem ao nosso padrão da entrada. Resto das linhas são impressas (portanto, apenas as linhas que fazem correspondem ao padrão).

Problema de primeira linha

Como mencionado nos comentários, há um problema com essa abordagem. Se o padrão especificado também corresponder à primeira linha, ele será impresso duas vezes (uma vez pelo comando p e uma vez por causa de uma correspondência). Podemos evitar isso de duas maneiras:

  1. Adicionando o comando 1d após 1p . Como já mencionei, o comando d exclui linhas do buffer e especificamos seu intervalo em número 1, o que significa que ele excluirá apenas a primeira linha. Então o comando seria sed -e '1p' -e '1d' -e '/youpattern/!d'

  2. Usando o comando 1b , em vez de 1p . É um truque. O comando b nos permite pular para outro comando especificado por um rótulo (dessa forma, alguns comandos podem ser omitidos). Mas se este rótulo não for especificado (como no nosso exemplo), ele simplesmente salta para o final dos comandos, ignorando o resto dos comandos para nossa linha. Portanto, no nosso caso, o último comando d não removerá essa linha do buffer.

Exemplo completo:

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

Usando ponto e vírgula

Algumas implementações de sed podem economizar alguma digitação usando ponto-e-vírgula para separar comandos, em vez de usar várias opções de -e . Então, se você não se importa em ser portátil, o comando seria ps aux | sed '1b;/syslog/!d' . Ele funciona pelo menos em GNU sed e busybox implementações.

caminho louco

Aqui está, no entanto, uma maneira meio louca de fazer isso com o grep. Definitivamente não é ideal, estou postando isso apenas para fins de aprendizado, mas você pode usá-lo, por exemplo, se você não tiver outra ferramenta em seu sistema:

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

Como funciona

  1. Primeiro, usamos a opção -n para adicionar números de linha antes de cada linha. Queremos numerar todas as linhas que estamos correspondendo .* - qualquer coisa, até mesmo linha vazia. Como sugerido nos comentários, também podemos combinar '^', o resultado é o mesmo.

  2. Em seguida, estamos usando expressões regulares estendidas para que possamos usar o caractere especial \| , que funciona como OU. Então, combinamos se a linha começa com 1: (primeira linha) ou contém nosso padrão (neste caso, seu syslog ).

Problema de números de linha

Agora, o problema é que estamos obtendo esses números de linha feios em nossa saída. Se isso for um problema, podemos removê-los com cut , assim:

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

-d especifica o delimitador, -f especifica os campos (ou colunas) que queremos imprimir. Portanto, queremos cortar cada linha em cada caractere : e imprimir apenas a segunda e todas as colunas subseqüentes. Isso efetivamente remove a primeira coluna com seu delimitador e é exatamente disso que precisamos.

    
por 12.09.2012 / 13:24
57

Como você se sente ao usar awk em vez 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 : Número de registros == 1; ie. a primeira linha
  • || : ou:
  • /syslogd/ : padrão para pesquisar

Também pode valer a pena olhar para pgrep , embora isso seja mais para scripts do que para saída voltada para o usuário. Evita, no entanto, que o comando grep apareça na saída.

chopper:~> pgrep -l syslogd
19 syslogd
    
por 12.09.2012 / 13:08
30
ps aux | { read line;echo "$line";grep someApp;}

EDIT: após comentários

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

Eu pensei que head -1 iria ler todas as entradas, mas depois de testá-las, também funciona.

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

saída é

this is a test
this line should be ok
    
por 12.09.2012 / 14:46
14

Filtro interno de suporte Ps,

Suponha que você esteja procurando por um processo bash:

ps -C bash -f

Listará todo o processo denominado bash .

    
por 12.09.2012 / 13:14
6

Eu costumo enviar o cabeçalho para stderr :

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

Isso geralmente é suficiente para propósitos de leitura humana. por exemplo:

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

A parte entre colchetes pode entrar em seu próprio script para uso geral.

Há uma conveniência adicional em que a saída pode ser canalizada adicionalmente (para sort etc.) e o cabeçalho permanecerá no topo.

    
por 13.09.2012 / 07:21
5

Você também pode usar tee e head :

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

Observe, entretanto, que, desde que tee seja incapaz de ignorar os sinais SIGPIPE (veja, por exemplo, o discussão aqui ) essa abordagem precisa de uma solução alternativa para ser confiável. A solução é ignorar sinais SIGPIPE, isso pode ser feito por exemplo em bash como shells:

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

Observe também que a ordem de saída não é garantida .

    
por 12.09.2012 / 13:19
4

Talvez dois comandos ps sejam mais fáceis.

$ 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
    
por 12.09.2012 / 13:08
4

Você pode usar o pidstat com:

pidstat -C someApp
or
pidstat -p <PID>

Exemplo:

# 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

Mais informações: link

    
por 12.09.2012 / 13:42
4

Coloque o seguinte no seu arquivo .bashrc ou copie / cole primeiro no shell, para testar.

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

Uso: psls [padrão 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

Certifique-se de criar seu .bashrc (ou .bash_profile se você o colocar lá):

source ~/.bashrc

A função será completada automaticamente na linha de comando do shell. Como você afirmou em outra resposta, você pode canalizar a primeira linha para um arquivo para salvar uma chamada para ps.

    
por 13.09.2012 / 06:05
3

ordene, mas mantenha a linha de cabeçalho na topo

# 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 use-o assim

$ 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
    
por 13.09.2012 / 06:41
2

Outra maneira com gnu ed :

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

ou, se o shell suportar a substituição do processo:

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

isto é:

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

Mais portátil, sem gnu '!' ou substituição de shell - usando apenas ed r para r incorporado na saída de ps aux no buffer e exclua linhas não correspondentes em o intervalo 2,$ e imprime o resultado:

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

E como os comandos sed na saída de resposta aceita também correspondem à própria linha, com um sed que suporta -f- e um shell que suporta a substituição de processo, eu executaria:

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

que praticamente faz a mesma coisa que os comandos anteriores ed .

    
por 02.08.2015 / 20:53
2

Graças principalmente a Janis Papanagnou no comp.unix.shell, eu uso a seguinte função:

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

Isso tem várias vantagens:

  • Funciona com bash, zsh e provavelmente ksh
  • É um substituto fácil para o grep, portanto, você pode continuar usando os sinalizadores desejados: -i para correspondência sem distinção entre maiúsculas e minúsculas, -E para expressões regulares estendidas, etc.
  • Sempre produz o mesmo código de saída do grep, caso você deseje determinar programaticamente se alguma linha realmente corresponde
  • Imprime nada se a entrada estiver vazia

Exemplo de uso:

$ 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
    
por 27.04.2016 / 16:55
1

O caminho Perl:

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

Muito mais fácil de ler do que sed , mais rápido, sem risco de escolher linhas indesejadas.

    
por 23.09.2016 / 04:51
0

Se isso é apenas para processos grep com cabeçalhos completos, eu expandiria a sugestão do @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 -fp obterá o mesmo resultado, mas sem um subnível. Se outra formatação for necessária:

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

Se você sabe os números de linha exatos, é fácil com o perl! Se você deseja obter as linhas 1 e 5 de um arquivo, diga / etc / passwd:

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

Se você quiser outras linhas também, basta adicionar os números delas na matriz.

    
por 23.03.2016 / 14:35