Eu quero fazer isso:
- execute um comando
- capturar a saída
- selecione uma linha
- selecione uma coluna dessa linha
Apenas como exemplo, digamos que eu queira obter o nome do comando de um $PID
(observe que este é apenas um exemplo, não estou sugerindo que esta seja a maneira mais fácil de obter um nome de comando a partir de um id de processo - meu verdadeiro problema é com outro comando cujo formato de saída não posso controlar).
Se eu correr ps
, recebo:
PID TTY TIME CMD
11383 pts/1 00:00:00 bash
11771 pts/1 00:00:00 ps
Agora eu faço ps | egrep 11383
e consigo
11383 pts/1 00:00:00 bash
Passo seguinte: ps | egrep 11383 | cut -d" " -f 4
. O resultado é:
<absolutely nothing/>
O problema é que cut
corta a saída em espaços simples, e conforme ps
adiciona alguns espaços entre a 2ª e a 3ª colunas para manter alguma semelhança com uma tabela, cut
pega uma string vazia. Claro, eu poderia usar cut
para selecionar o 7º e não o 4º campo, mas como posso saber, especialmente quando a saída é variável e desconhecida de antemão.
Respostas:
Uma maneira fácil é adicionar uma passagem de
tr
para espremer todos os separadores de campo repetidos:$ ps | egrep 11383 | tr -s ' ' | cut -d ' ' -f 4
fonte
tr
é mais leve do queawk
Acho que a maneira mais simples é usar o awk . Exemplo:
$ echo "11383 pts/1 00:00:00 bash" | awk '{ print $4; }' bash
fonte
ps | awk "\$1==$PID{print\$4}"
ou (melhor)ps | awk -v"PID=$PID" '$1=PID{print$4}'
. Claro, no Linux você poderia simplesmente fazerxargs -0n1 </proc/$PID/cmdline | head -n1
oureadlink /proc/$PID/exe
, mas de qualquer maneira ...;
em é{ print $4; }
obrigatório? Removê-lo parece não ter nenhum efeito para mim no Linux, apenas curioso quanto ao seu propósitoObserve que a
tr -s ' '
opção não removerá nenhum espaço inicial. Se sua coluna estiver alinhada à direita (como comps
pid) ...$ ps h -o pid,user -C ssh,sshd | tr -s " " 1543 root 19645 root 19731 root
Em seguida, o corte resultará em uma linha em branco para alguns desses campos se for a primeira coluna:
$ <previous command> | cut -d ' ' -f1 19645 19731
A menos que você o preceda com um espaço, obviamente
$ <command> | sed -e "s/.*/ &/" | tr -s " "
Agora, para este caso particular de números pid (não nomes), existe uma função chamada
pgrep
:Funções Shell
No entanto, em geral, ainda é possível usar funções do shell de maneira concisa, porque há uma coisa interessante sobre o
read
comando:$ <command> | while read a b; do echo $a; done
O primeiro parâmetro a ser lido
a
,, seleciona a primeira coluna e, se houver mais, todo o resto será colocadob
. Como resultado, você nunca precisa de mais variáveis do que o número de sua coluna +1 .Então,
while read a b c d; do echo $c; done
irá então imprimir a 3ª coluna. Conforme indicado em meu comentário ...
Uma leitura canalizada será executada em um ambiente que não passa variáveis para o script de chamada.
out=$(ps whatever | { read a b c d; echo $c; }) arr=($(ps whatever | { read a b c d; echo $c $b; })) echo ${arr[1]} # will output 'b'`
A Solução Array
Então, terminamos com a resposta de @frayser, que é usar a variável shell IFS, que tem como padrão um espaço, para dividir a string em um array. Porém, ele só funciona no Bash. Dash e Ash não apóiam isso. Tive muita dificuldade em dividir uma string em componentes em uma coisa do Busybox. É fácil obter um único componente (por exemplo, usando awk) e, em seguida, repeti-lo para cada parâmetro de que você precisa. Mas então você acaba chamando awk repetidamente na mesma linha, ou repetidamente usando um bloco de leitura com eco na mesma linha. O que não é eficiente nem bonito. Então você acaba dividindo usando
${name%% *}
e assim por diante. Faz você ansiar por algumas habilidades em Python porque, na verdade, o script de shell não é mais tão divertido se metade ou mais dos recursos com os quais você está acostumado se foram. Mas você pode assumir que mesmo o python não seria instalado em tal sistema, e não foi ;-).fonte
echo "$a"
eecho "$c"
embora.var=$(....... | { read a b c d; echo $c; })
. Isso só funciona para uma única (string), embora no Bash você possa dividi-la em uma matriz usandoar=($var)
tentar
ps |& while read -p first second third fourth etc ; do if [[ $first == '11383' ]] then echo got: $fourth fi done
fonte
Usando variáveis de matriz
set $(ps | egrep "^11383 "); echo $4
ou
A=( $(ps | egrep "^11383 ") ) ; echo ${A[3]}
fonte
Semelhante à solução awk de brianegge, aqui está o equivalente em Perl:
ps | egrep 11383 | perl -lane 'print $F[3]'
-a
ativa o modo de divisão automática, que preenche a@F
matriz com os dados da coluna.Use
-F,
se seus dados forem delimitados por vírgulas, em vez de delimitados por espaço.O campo 3 é impresso porque o Perl começa a contar a partir de 0 ao invés de 1
fonte
Obter a linha correta (exemplo para a linha nº 6) é feito com cabeça e cauda e a palavra correta (palavra nº 4) pode ser capturada com awk:
command|head -n 6|tail -n 1|awk '{print $4}'
fonte
awk NR=6 {print $4}
seria um pouco mais eficienteawk NR==6 {print $4}
* doh *Seu comando
ps | egrep 11383 | cut -d" " -f 4
perde um
tr -s
para espremer espaços, como a descontração explica em sua resposta .No entanto, talvez você queira usar
awk
, já que ele lida com todas essas ações em um único comando:ps | awk '/11383/ {print $4}'
Isso imprime a 4ª coluna nas linhas que contêm
11383
. Se você quer que isso combine11383
que ele se ele aparecer no início da linha, você pode dizerps | awk '/^11383/ {print $4}'
.fonte
Em vez de fazer todos esses greps e outras coisas, eu aconselho você a usar os recursos do ps para alterar o formato de saída.
Você obtém a linha de comando de um processo com o pid especificado e nada mais.
Isso é compatível com POSIX e, portanto, pode ser considerado portátil.
fonte
O Bash
set
analisará todas as saídas em parâmetros de posição.Por exemplo, com o
set $(free -h)
comando,echo $7
mostrará "Mem:"fonte
set $(sar -r 1 1)
;echo "${23}"
awk
é a melhor maneira de fazer isso.bash
e nãoawk
.