Como posso usar o teste if do bash e encontrar comandos juntos?

25

Eu tenho um diretório com logs de falha e gostaria de usar uma instrução condicional em um script bash com base em um comando find.

Os arquivos de log são armazenados neste formato:

/var/log/crashes/app-2012-08-28.log
/var/log/crashes/otherapp-2012-08-28.log

Desejo que a instrução if retorne true somente se houver um log de falha para um aplicativo específico que foi modificado nos últimos 5 minutos. O findcomando que eu usaria é:

find /var/log/crashes -name app-\*\.log -mmin -5

Não sei como incorporar isso a uma ifdeclaração corretamente. Eu acho que isso pode funcionar:

if [ test `find /var/log/crashes -name app-\*\.log -mmin -5` ] then
 service myapp restart
fi

Existem algumas áreas em que não estou claro:

  • Eu olhei para os sinalizadores if, mas não tenho certeza de qual deles, se houver, que devo usar.
  • Preciso da testdiretiva ou devo apenas processar diretamente os resultados do comando find ou usar find... | wc -lpara obter uma contagem de linhas?
  • Não é 100% necessário para responder a essa pergunta, mas testé para testar contra códigos de retorno que comandos retornam? E eles são meio invisíveis - fora de stdout/ stderr? Eu li a manpágina, mas ainda não sou muito claro sobre quando usar teste como depurá-la.
cwd
fonte
A resposta real para o caso geral é usar find ... -exec. Veja também os comandos de exemplo em Por que é um loop sobre a prática recomendada de saída do find?
Curinga
@Wildcard - infelizmente isso não resolve o caso geral: não funciona se houver mais de uma correspondência e a ação precisar ser executada apenas uma vez, e não funcionará se você precisar executar uma ação quando houver sem combinações. O primeiro pode ser resolvido usando ... -exec command ';' -quit, mas não creio que exista outra solução para além do que seja analisar o resultado. Além disso, em ambos os casos, o principal problema com a análise do resultado de find(ou seja, incapacidade de distinguir delimitadores de caracteres nos nomes de arquivos) não se aplica, pois você não precisa encontrar delimitadores nesses casos.
Jules

Respostas:

27

[e testsão sinônimos (exceto [requer ]), então você não deseja usar [ test:

[ -x /bin/cat ] && echo 'cat is executable'
test -x /bin/cat && echo 'cat is executable'

testretorna um status de saída zero se a condição for verdadeira, caso contrário, diferente de zero. Na verdade, isso pode ser substituído por qualquer programa para verificar seu status de saída, onde 0 indica sucesso e diferente de zero indica falha:

# echoes "command succeeded" because echo rarely fails
if /bin/echo hi; then echo 'command succeeded'; else echo 'command failed'; fi

# echoes "command failed" because rmdir requires an argument
if /bin/rmdir; then echo 'command succeeded'; else echo 'command failed'; fi

No entanto, todos os exemplos acima são testados apenas contra o status de saída do programa e ignoram a saída do programa.

Para find, você precisará testar se alguma saída foi gerada. -ntestes para uma string não vazia:

if [[ -n $(find /var/log/crashes -name "app-*.log" -mmin -5) ]]
then
    service myapp restart
fi

Uma lista completa dos argumentos de teste está disponível chamando help testna bashlinha de comando.

Se você estiver usando bash(e não sh), poderá usar o [[ condition ]]que se comporta de maneira mais previsível quando houver espaços ou outros casos especiais em sua condição. Caso contrário, geralmente é o mesmo que usar [ condition ]. Eu usei [[ condition ]]neste exemplo, como sempre que possível.

Também mudei `command`para $(command), que geralmente também se comporta de maneira semelhante, mas é mais agradável com comandos aninhados.

mrb
fonte
echopode falhar: tente echo 'oops' > /dev/full.
derobert
Essa resposta supera a raiz do problema, mas evita mencionar exatamente o que é isso.
precisa saber é
9

findsairá com êxito se não houver erros; portanto, você não pode contar com o status de saída para saber se encontrou algum arquivo. Mas, como você disse, você pode contar quantos arquivos foram encontrados e testar esse número.

Seria algo como isto:

if [ $(find /var/log/crashes -name 'app-*.log' -mmin -5 | wc -l) -gt 0 ]; then
    ...
fi

test(aka [) não verifica os códigos de erro dos comandos, possui uma sintaxe especial para realizar testes e sai com um código de erro 0 se o teste foi bem-sucedido ou 1 caso contrário. É ifaquele que verifica o código de erro do comando que você passa para ele e executa seu corpo com base nele.

Consulte man test(ou help test, se você usar bash) e help if(idem).

Nesse caso, wc -lproduzirá um número. Usamos testa opção -gtpara testar se esse número é maior que 0. Se for, test(ou [) retornará com o código de saída 0. ifinterpretará esse código de saída como bem-sucedido e executará o código dentro de seu corpo.

angus
fonte
1

Este seria

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)" ]; then

ou

if test -n "$(find /var/log/crashes -name app-\*\.log -mmin -5)"; then

Os comandos teste [ … ]são exatamente sinônimos. A única diferença é o nome e o fato de [exigir um fechamento ]como último argumento. Como sempre, use aspas duplas em torno da substituição do comando, caso contrário, a saída do findcomando será dividida em palavras e aqui você receberá um erro de sintaxe se houver mais de um arquivo correspondente (e quando não houver argumentos, [ -n ]for verdadeiro , enquanto você deseja o [ -n "" ]que é falso).

No ksh, bash e zsh, mas não no ash, você também pode usar o [[ … ]]que possui regras de análise diferentes: [é um comando comum, enquanto que [[ … ]]é uma construção de análise diferente. Você não precisa de aspas duplas dentro [[ … ]](embora elas não doam). Você ainda precisa do ;após o comando.

if [[ -n $(find /var/log/crashes -name app-\*\.log -mmin -5) ]]; then

Isso pode ser potencialmente ineficiente: se houver muitos arquivos /var/log/crashes, o find irá explorar todos eles. Você deve fazer a busca parar assim que encontrar uma correspondência ou logo depois. Com o GNU find (Linux não incorporado, Cygwin), use o -quitprimário.

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print -quit)" ]; then

Com outros sistemas, entre findno tubo headpara pelo menos sair logo após a primeira partida (a localização morrerá de um tubo quebrado).

if [ -n "$(find /var/log/crashes -name app-\*\.log -mmin -5 -print | head -n 1)" ]; then

(Você pode usar head -c 1se o seu headcomando suportar.)


Como alternativa, use zsh.

crash_files=(/var/log/crashes/**/app-*.log(mm-5[1]))
if (($#crash_files)); then
Gilles 'SO- parar de ser mau'
fonte
1

Isso deve funcionar

if find /var/log/crashes -name 'app-\*\.log' -mmin -5 | read
then
  service myapp restart
fi
Steven Penny
fonte
0
find /var/log/crashes -name app-\*\.log -mmin -5 -exec service myapp restart ';' -quit

é uma solução apropriada aqui.

-exec service myapp restart ';'faz findcom que invoque o comando que você deseja executar diretamente, em vez de precisar que o shell interprete qualquer coisa.

-quitfaz findcom que saia após o processamento do comando, impedindo que o comando seja executado novamente se houver vários arquivos que correspondam aos critérios.

Jules
fonte