Comportamento diferente no shell script vs no shell?

3

ATUALIZAÇÃO:

Eu mudei a parte grep $1 para grep '$1' (enquanto eu estava tentando dizer grep "$1" ) no script e desta vez eu peguei o

kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

mensagem (em vez da mensagem Terminated: 15 ). 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 eu escrevo a linha:

kill 'ps -A | grep process_name | grep -v 'grep' | grep -Eom 1 '^[0-9]+''

manualmente no bash, se nada aparecer como a saída de ps -A | grep process_name , recebo o seguinte:

kill: usage: kill [-s sigspec | -n signum | -sigspec] pid | jobspec ... or kill -l [sigspec]

Se algo aparecer, o comando é executado corretamente e termina silenciosamente.

Agora, se eu executar o script executando o arquivo mykill , se algo aparecer como a saída de ps -A | grep process_name , o script será executado corretamente e terminará silenciosamente, o que é o mesmo comportamento que executar o comando manualmente. / p>

Mas se nada aparecer como a saída de ps -A | grep process_name , não recebo a mensagem sobre o uso do comando kill. Em vez disso, eu recebo:

Terminated: 15

Também verifiquei os códigos de retorno. Depois de tentar finalizar um processo inexistente gravando manualmente o comando no shell, echo $? fornece 1 . No entanto, depois de tentar finalizar um processo inexistente chamando o script, echo $? fornece 143 .

O que está acontecendo aqui? Por que estou observando comportamentos diferentes ao executar o mesmo comando escrevendo-o manualmente no shell, vs executando-o dentro de um script de shell?

NOTA: O sh e o meu shell de trabalho são bash .

BÔNUS: O meu shell script pode ser escrito de maneira mais eficiente e / ou elegante, usando apenas POSIX utilitários ? Se sim, como?

    
por Utku 03.10.2016 / 14:10

1 resposta

4

Uma nota de portabilidade. O formato de saída para ps -A é não especificado por POSIX para 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 opção -f estão todas marcadas com XSI na especificação), então você não pode realmente pós-processá-lo de forma confiável .

Por exemplo, com o ps de procps no Linux, ele produzirá, PID TTY TIME CMD colunas (onde CMD é o nome do processo, não o comando args) enquanto no FreeBSD ele gera PID TT STAT TIME COMMAND (com COMMAND sendo os argumentos).

Dado seu uso de grep -v grep , eu suponho que você esteja esperando o último ou pelo menos que ps -A produza os argumentos do comando que o processo executou em oposição a apenas o nome do processo (geralmente derivado do nome do arquivo do último comando execute ou o primeiro argumento (0 th )).

Se o seu grep se destina a grep apenas nos argumentos do comando, você deve usar:

ps -A -o pid= -o args=

cuja saída é especificada por POSIX.

Agora, seu problema é que mykill está se matando porque o mykill foo corresponde a 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}'

(note que POSIX não especifica o caminho do utilitário POSIX sh nem o mecanismo she-bang, portanto /bin/sh pode não ser um shell POSIX. Na prática, o she-bang é suportado na maioria dos POSIX sistemas e /bin/sh é um POSIX sh ou o Bourne sh e o código acima deve funcionar em ambos).

Embora isso não seja o ideal, pois sempre retorna um status de saída true (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, como a abordagem grep -m 1 sugere que você queira fazer.

Agora, com trap '' SIGTERM , garantimos que nossos processos não sejam eliminados, o que seria OK se matássemos all os processos correspondentes, mas, como aqui, estamos apenas matando o primeiro correspondente , o problema é que aquele primeiro pode muito bem ser aquele executando 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 comparar os IDs do processo 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 interna:

#! /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
}
    
por 03.10.2016 / 14:42