Usando $? em uma declaração if

12
function foo {
   (cd $FOOBAR;
   <some command>
   if [$? -ne 0]
   then
      echo "Nope!"
   else
      echo "OK!"
   fi
   )
}

Estou tentando escrever uma função como a acima e colocá-la no meu arquivo .bashrc. Depois de originar o arquivo e executar, recebo:

Tempo total: 51 segundos -
flash: [1: comando não encontrado
OK!

Alguém pode me ajudar a entender o que fiz de errado?

Amir Afghani
fonte
4
Testar se $?é igual a 0 com uma ifinstrução é inútil, ifespera um comando e, se esse comando retornar 0, ele executa o código no bloco. assim if true; then echo hello; fiecoará Olá desde que o comando trueretornou 0.
llua
1
@llua Não é inútil. $?mantém o status do último pipeline , que não é o comando test( [) na ifinstrução O exemplo está testando se some commandfoi bem-sucedido. Você pode fazer o mesmo com &&e ||, mas pode criar linhas longas e ilegíveis em comparação com if [ $? -eq 0 ]. O mesmo argumento vale paraif some command
bonsaiviking
@bonsaiviking Estou bem ciente do que se $?expande, estou apontando que não há sentido em testser usado; desde que if some commandfaz a mesma coisa com uma instrução versus ter duas instruções separadas. se o comando já for longo, adicionar mais três caracteres não tornaria o 'ilegível' muito mais 'ilegível'.
llua

Respostas:

33

Adicione um espaço após o [e outro antes ]:

function foo {
   (cd $FOOBAR;
   <some command>
   if [ $? -ne 0 ]
   then
      echo "Nope!"
   else
      echo "OK!"
   fi
   )
}

[é um builtin shell, é um comando como echo, read, expr... ele precisa de um espaço atrás dele, e requer uma correspondência ].

Escrita [ $? -ne 0 ]é realmente invocando [e dando-lhe 4 parâmetros: $?, -ne, 0, e ].

Nota: o fato de você estar recebendo um erro ao dizer [1: command not foundsignifica que $?estava tendo o valor de 1.

aularon
fonte
1
Acabei de verificar que sua resposta está correta na minha VM Linux.
samiam 4/02/2014
[é um link para testtambém
Ricky Beam
2
@ RickyBeam na maioria dos shells, [é um shell embutido e /usr/bin/[raramente é usado.
5604 Patrick
20

Ou você pode pular $?completamente. Se o seu comando for cmd, o seguinte deve funcionar:

function foo {
   (cd $FOOBAR;
   if cmd
   then
      echo "OK!"
   else
      echo "Nope!"
   fi
   )
}
unxnut
fonte
6

É uma boa prática atribuir o valor de retorno a uma variável antes de usá-lo

retval="$?"
if [ $retval -ne 0 ]

Permite reutilizar o valor de retorno. por exemplo, em uma declaração if ... elif ... else ...

Abdul
fonte
-1: você esqueceu o espaço depois [(Sem o espaço, ele se torna um erro de sintaxe bash) Desfará se você editar e corrigir o erro.
samiam 4/02/2014
Sim, você está certo. Eu consertei isso.
Abdul
@samiam que o último comentário foi direcionado a você
terdon
4
:) Você poderia expandir um pouco sua resposta. Por que é uma boa prática? Que tipo de erro pode ser evitado?
terdon
1
@terdon, é uma prática ruim usar $?diretamente, pois isso será interrompido se você, ao editar o script posteriormente, colocar uma linha entre o comando e a $?verificação.
Samiam
3

A única razão pela qual você deseja usar $?como argumentos para um [comando (se esse [comando é executado na parte da condição de uma ifinstrução ou não) é quando você deseja discriminar um status de retorno específico, como:

until
  cmd
  [ "$?" -gt 1 ]
do
  something
done

A sintaxe para todos aqueles if, while, until... declarações é

if cmd-list1
then cmd-list2
else cmd-list3
fi

Que é executado cmd-list2se cmd-list1for bem-sucedido ou cmd-list3não.

O [ "$?" -eq 0 ]comando é no-op. Ele define $?como 0 se $?for 0 e $?como diferente de zero se for diferente de zero.

Se você deseja executar algo em caso de cmdfalha, é:

if ! cmd
then ...
fi

Geralmente, você não precisa mexer e $?muito menos saber qual valor significa trueou false. Os únicos casos são como eu disse acima, se você precisar discriminar um valor específico ou se precisar salvá-lo para mais tarde (por exemplo, devolvê-lo como o valor de retorno de uma função) como:

f() {
  cmd; ret=$?
  some cleanup
  return "$ret"
}

Lembre-se também de que deixar uma variável sem aspas é o operador split + glob. Não faz sentido chamar esse operador aqui, portanto deve ser:

[ "$?" -ne 0 ]

não [ $? -ne 0 ], muito menos [$? -ne 0 ](que só chamaria o [comando se $IFScontivesse o primeiro caractere de $?).

Observe também que a maneira Bourne de definir uma função é ficar function-name()na frente de um comando. Esse é o caso em todas as Bourne como o escudo, exceto bashe yash(e versões recentes do posh), que só permitem um comando composto (comandos compostos sendo {...}ou (...)ou coisas como for...done, if...fi...

function foo { ... }é a kshsintaxe de definição da função. Não há motivo para você querer usá-lo aqui.

Seu código pode ser gravado de forma portável (POSIXly):

foo() (
  cd -P -- "$FOOBAR" || return # what if the cd failed!
  if
    <some command>
  then
    echo 'OK!'
  else
    echo 'Nope!'
  fi
)

Observe também que cdsem -Ptem um significado muito especial (lida com caminhos que contêm ..componentes de maneira diferente de qualquer outro comando), portanto, é melhor incluí-lo em scripts para evitar confusões.

(essa função retorna falsese cdfalhar, mas não se <some command>falhar).

Stéphane Chazelas
fonte
1

Acredito que o comando abaixo fará tudo o que você deseja em uma linha.

(( verify = $?!=0?'Nope!':'OK!' ))
Jeight
fonte