
ATUALIZAR:
Eu mudei a grep $1
parte para grep '$1'
(enquanto tentava dizer grep "$1"
) no roteiro e desta vez consegui o
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
mensagem (em vez da Terminated: 15
mensagem). Eu não entendo o que está acontecendo.
PERGUNTA:
Eu escrevi um script de shell simples chamado mykill
.
mykill
:
#!/bin/sh
kill `ps -A | grep $1 | grep -v 'grep' | grep -Eom 1 '^[0-9]+'`
No entanto, há um comportamento estranho. Quando escrevo a linha:
kill `ps -A | grep process_name | grep -v 'grep' | grep -Eom 1 '^[0-9]+'`
manualmente no bash, se nada aparecer como saída ps -A | grep process_name
, recebo o seguinte:
kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]
Se algo acontecer, o comando será executado corretamente e terminará silenciosamente.
Agora, se eu executar o script executando o mykill
arquivo, se algo surgir como saída de ps -A | grep process_name
, o script será executado corretamente e terminará silenciosamente, que é o mesmo comportamento de executar o comando manualmente.
Mas se nada aparecer como resultado de ps -A | grep process_name
, não recebo a mensagem sobre o uso do comando kill. Em vez disso, recebo:
Terminated: 15
Também verifiquei os códigos de retorno. Depois de tentar encerrar um processo inexistente escrevendo manualmente o comando no shell, echo $?
dá 1
. Porém, depois de tentar encerrar um processo inexistente chamando o script, echo $?
dá 143
.
O que está acontecendo aqui? Por que estou observando comportamentos diferentes ao executar o mesmo comando escrevendo-o manualmente no shell, em vez de executá-lo em um script de shell?
NOTA: Ambos sh
e meu shell de trabalho são bash
.
BÔNUS: Meu shell script poderia ser escrito de uma forma mais eficiente e/ou elegante, usando apenasUtilitários POSIX? Se sim, como?
Responder1
Uma nota de portabilidade. O formato de saída para ps -A
énão especificado pelo POSIXpara sistemas não compatíveis com Unix (como o FreeBSD) (você notará que as seções do formato de saída e a descrição da -f
opção estão todas marcadasXSIna especificação), então você não pode pós-processá-lo de maneira confiável e portátil.
Por exemplo, com ps
from procps
no Linux, ele produzirá PID TTY TIME CMD
colunas (onde CMD
está o nome do processo, não os argumentos do comando), enquanto no FreeBSD ele produzirá PID TT STAT TIME COMMAND
( COMMAND
sendo os argumentos).
Dado o seu uso de grep -v grep
, suponho que você esteja esperando o último ou pelo menos que ps -A
produza os argumentos do comando que o processo executou, em vez de apenas o nome do processo (geralmente derivado do nome do arquivo do último comando de execução ou do primeiro ( 0º ) argumento).
Se você grep
pretende grep
apenas usar argumentos de comando, você deve usar:
ps -A -o pid= -o args=
cuja saída é especificada por POSIX.
Agora, o seu problema é que mykill
está se matando por causa dos mykill foo
fósforos foo
.
Outro problema é que mykill grep
não mataria nada.
Aqui você poderia fazer:
#! /bin/sh -
PATTERN=${1?} export PATTERN
trap '' TERM # ignore SIGTERM for the shell and its children
ps -A -o pid= -o args= | awk '$0 ~ ENVIRON["PATTERN"] {
system("kill " $1); exit}'
(observe que o POSIX não especifica o caminho do sh
utilitário POSIX nem o mecanismo she-bang, portanto /bin/sh
pode não ser um shell POSIX. Na prática, porém, she-bang é suportado na maioria dos sistemas POSIX e /bin/sh
é um POSIX sh
ou Bourne sh
e o código acima deve funcionar em ambos).
Embora isso não seja ideal, pois sempre retorna um status de saída verdadeiro (0), mesmo quando nenhum processo é encontrado. Uma abordagem melhor seria:
#! /bin/sh -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
ps -A -o pid= -o args= | grep -e "$pattern" | {
read pid args && kill "$pid"
}
Em ambos os casos, eliminamos apenas o primeiro processo de correspondência conforme sua grep -m 1
abordagem sugere que você deseja fazer.
Agora, trap '' SIGTERM
garantimos que nossos processos não sejam eliminados, o que seria bom se matássemostodosos processos de correspondência, mas como aqui estamos apenas eliminando o primeiro correspondente, o problema é que esse primeiro pode muito bem ser aquele em execução mykill pattern
ou grep pattern
.
Em vez de adicionar alguns grep -ve grep -e mykill
(o que não seria infalível, pois poderia excluir mais processos do que o pretendido), você poderia tentar comparar os IDs dos processos correspondentes.
#! /bin/sh -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
# just in case
psoutput=$(exec ps -A -o pid= -o ppid= -o args=)
printf '%s\n' "$psoutput" | grep -e "$pattern" | {
while read -r pid ppid args; do
if [ "$pid" -ne "$$" ] && [ "$ppid" -ne "$$" ]; then
kill "$pid"
exit # with the exit status of kill above
fi
done
exit 1 # not found
}
(observe que $(...)
e read -r
são POSIX, mas não Bourne).
Ou usando ksh93
, bash
, zsh
ou yash
(nenhum dos quais são comandos POSIX), que é um shell com correspondência de expressão regular integrada:
#! /bin/bash -
pattern=${1?}
trap '' TERM # ignore SIGTERM for the shell and its children
# just in case
psoutput=$(exec ps -A -o pid= -o ppid= -o args=)
printf '%s\n' "$psoutput" | {
while read -r pid ppid args; do
if ((pid != $$ && ppid != $$)) && [[ $args =~ $pattern ]]; then
kill "$pid"
exit # with the exit status of kill above
fi
done
exit 1 # not found
}