Retornando um booleano de uma função Bash

211

Eu quero escrever uma função bash que verifique se um arquivo tem certas propriedades e retorna verdadeiro ou falso. Então eu posso usá-lo em meus scripts no "se". Mas o que devo retornar?

function myfun(){ ... return 0; else return 1; fi;}

então eu uso assim:

if myfun filename.txt; then ...

claro que isso não funciona. Como pode ser isto alcançado?

luca
fonte
3
soltar as functionpalavras-chave, myfun() {...}sufixos
glenn jackman
2
O que importa ifé o status de saída zero de myfun: se myfunsai com 0, then ...é executado; se é algo mais else ... é executado.
Eelvex 25/03
7
@ nhed: a functionpalavra-chave é um basismo, e causará erros de sintaxe em alguns outros shells. Basicamente, é desnecessário ou proibido, então por que usá-lo? Não é sequer útil como um destino grep, pois pode não estar lá (grep for ()).
Gordon Davisson
3
@GordonDavisson: what? existem outras conchas? ;-)
encerrou
Por favor, não use 0 e 1. Veja stackoverflow.com/a/43840545/117471
Bruno Bronosky

Respostas:

333

Use 0 para verdadeiro e 1 para falso.

Amostra:

#!/bin/bash

isdirectory() {
  if [ -d "$1" ]
  then
    # 0 = true
    return 0 
  else
    # 1 = false
    return 1
  fi
}


if isdirectory $1; then echo "is directory"; else echo "nopes"; fi

Editar

No comentário de @ amichair, isso também é possível

isdirectory() {
  if [ -d "$1" ]
  then
    true
  else
    false
  fi
}


isdirectory() {
  [ -d "$1" ]
}
Erik
fonte
4
Não, você não precisa fazer isso - veja a amostra.
Erik
46
Para melhor legibilidade, você pode usar o comando 'true' (que não faz nada e é concluído com êxito, ou seja, retorna 0) e o comando 'false' (que não faz nada e é concluído sem êxito, ou seja, retorna um valor diferente de zero). Além disso, uma função que termina sem uma declaração de retorno explícita retorna o código de saída do último comando executado; portanto, no exemplo acima, o corpo da função pode ser reduzido para apenas [ -d "$1" ].
Amichair #
24
Bengt: faz sentido quando você pensa nisso como "código de erro": código de erro 0 = tudo deu certo = 0 erro; código de erro 1 = a principal coisa que esta chamada deveria fazer falhou; mais: falha! procure na página de manual.
ovelha voadora
7
Faz sentido quando você considera que na programação as coisas geralmente só podem ter sucesso de uma maneira, mas podem falhar de maneiras infinitas. Bem, talvez não seja infinito, mas muitas chances estão contra nós. Sucesso / erro (s) não é booleano. Eu acho que isso "Use 0 para verdadeiro e 1 para falso". deve ler "Use 0 para obter sucesso e diferente de zero para falha".
Davos
6
Por favor, não use 0 e 1. Veja stackoverflow.com/a/43840545/117471
Bruno Bronosky
167

Por que você deveria se importar com o que eu digo, apesar de haver uma resposta mais de 250 votada

Não é isso 0 = truee 1 = false. É: zero significa nenhuma falha (sucesso) e diferente de zero significa falha (do tipo N) .

Embora a resposta selecionada seja tecnicamente "verdadeira" , não coloque return 1** no seu código como falso . Terá vários efeitos colaterais infelizes.

  1. Desenvolvedores experientes o identificarão como amador (pelo motivo abaixo).
  2. Desenvolvedores experientes não fazem isso (por todos os motivos abaixo).
  3. É propenso a erros.
    • Até desenvolvedores experientes podem confundir 0 e 1 como falso e verdadeiro, respectivamente (pelo motivo acima).
  4. Requer (ou encorajará) comentários estranhos e ridículos.
  5. Na verdade, é menos útil que status de retorno implícito.

Aprenda alguma festa

O manual do bash diz (ênfase minha)

retornar [n]

Faça com que uma função de shell pare de executar e retorne o valor n ao seu chamador. Se n não for fornecido , o valor retornado é o status de saída do último comando executado na função.

Portanto, nunca precisamos usar 0 e 1 para indicar Verdadeiro e Falso. O fato de fazer isso é essencialmente um conhecimento trivial, útil apenas para depurar código, entrevistar perguntas e surpreender os novatos.

O manual do bash também diz

caso contrário, o status de retorno da função é o status de saída do último comando executado

O manual do bash também diz

( $? ) Expande para o status de saída do pipeline de primeiro plano executado mais recentemente .

Uau, espera. Pipeline? Vamos voltar ao manual do bash mais uma vez.

Um pipeline é uma sequência de um ou mais comandos separados por um dos operadores de controle '|' ou '| &'.

Sim. Eles disseram que 1 comando é um pipeline. Portanto, todas as três citações estão dizendo a mesma coisa.

  • $? diz o que aconteceu por último.
  • Borbulha.

Minha resposta

Portanto, enquanto o @Kambus demonstrou que, com uma função tão simples, isso não returné necessário. Eu acho que era irrealisticamente simples comparado às necessidades da maioria das pessoas que lerão isso.

Por que return?

Se uma função retornará o status de saída do último comando, por que usar return? Porque faz com que uma função pare de executar.

Pare a execução sob várias condições

01  function i_should(){
02      uname="$(uname -a)"
03
04      [[ "$uname" =~ Darwin ]] && return
05
06      if [[ "$uname" =~ Ubuntu ]]; then
07          release="$(lsb_release -a)"
08          [[ "$release" =~ LTS ]]
09          return
10      fi
11
12      false
13  }
14
15  function do_it(){
16      echo "Hello, old friend."
17  }
18
19  if i_should; then
20    do_it
21  fi

O que temos aqui é ...

Line 04é um retorno explícito [-ish] true, porque o RHS de &&apenas é executado se o LHS for true

Linha 09retorna verdadeiro ou falso, correspondendo ao status da linha08

Linha 13retorna falsa por causa da linha12

(Sim, isso pode ser resolvido, mas o exemplo inteiro é artificial.)

Outro padrão comum

# Instead of doing this...
some_command
if [[ $? -eq 1 ]]; then
    echo "some_command failed"
fi

# Do this...
some_command
status=$?
if ! $(exit $status); then
    echo "some_command failed"
fi

Observe como definir uma statusvariável desmistifica o significado de $?. (É claro que você sabe o que $?significa, mas alguém com menos conhecimento do que você terá que pesquisar no Google algum dia. A menos que seu código esteja negociando em alta frequência, mostre algum amor , defina a variável.) Mas a verdadeira desvantagem é que "se não existe status "ou vice-versa" se o status de saída "puder ser lido em voz alta e explicar seu significado. No entanto, esse último pode ser um pouco ambicioso, porque ver a palavra exitpode fazer você pensar que está saindo do script, quando na realidade está saindo do $(...)subshell.


** Se você absolutamente insistir em usar return 1falso, sugiro que você use pelo menos return 255. Isso fará com que seu futuro eu ou qualquer outro desenvolvedor que precise manter seu código questione "por que isso é 255?" Então eles estarão prestando atenção e terão uma chance melhor de evitar um erro.

Bruno Bronosky
fonte
1
@ZeroPhase 1 & 0 para false & true seria ridículo. Se você possui um tipo de dados binário, não há razão para isso. O que você está lidando no bash é um código de status que reflete o sucesso (singular) e as falhas (plural). É " ifsucesso fazer isso, elsefazer aquilo". Sucesso em quê? Pode estar verificando se verdadeiro / falso, pode estar verificando uma sequência, número inteiro, arquivo, diretório, permissões de gravação, glob, regex, grep ou qualquer outro comando que esteja sujeito a falhas .
Bruno Bronosky
3
Evitar o uso padrão de 0/1 como valor de retorno apenas porque é obtuso e propenso a confusão é bobagem. Toda a linguagem shell é obtusa e propensa a confusão. -Se Bash usa a 0/1 = true false convenção / em seu próprio truee falsecomandos. Ou seja, a palavra-chave é trueliteralmente avaliada como um código de status 0. Além disso, as declarações if-then por natureza operam em booleanos, não em códigos de sucesso. Se ocorrer um erro durante uma operação booleana, ele não deve retornar verdadeiro ou falso, mas simplesmente interromper a execução. Caso contrário, você obtém falsos positivos (trocadilho).
Beejor
1
"if! $ (exit $ status); then" - Isso merece uma explicação melhor. Não é intuitivo. Eu tenho que pensar se o programa será encerrado antes de imprimir a mensagem ou não. O resto da sua resposta foi boa, mas isso estragou tudo.
Craig Hicks
1
@BrunoBronosky Eu li sua resposta e acho que entendo a distinção entre o pipeline de conteúdo (stdin-> stdout) e os códigos de erro nos valores de retorno. No entanto, não entendo por que o uso return 1deve ser evitado. Supondo que eu tenha uma validatefunção, acho razoável return 1que a validação falhe. Afinal, esse é um motivo para interromper a execução de um script se ele não for tratado adequadamente (por exemplo, ao usar set -e).
JepZ #
1
@Jepz é uma ótima pergunta. Você está correto que return 1é válido. Minha preocupação é com todos os comentários aqui dizendo "0 = verdadeiro 1 = falso é [inserir palavra negativa]". É provável que essas pessoas leiam seu código algum dia. Portanto, para eles, ver return 255(ou 42 ou mesmo 2) deve ajudá-los a pensar mais sobre isso e não confundi-lo como "verdadeiro". set -eainda vai pegá-lo.
Bruno Bruno # 1818
31
myfun(){
    [ -d "$1" ]
}
if myfun "path"; then
    echo yes
fi
# or
myfun "path" && echo yes
Kambus
fonte
1
E a negação?
einpoklum
Que tal, @einpoklum?
Re
@ MarkReed: eu quis dizer, adicione o caso "else" ao seu exemplo.
einpoklum
myfun "caminho" || echo no
Hrobky 12/03/2019
13

Cuidado ao verificar o diretório apenas com a opção -d!
se a variável $ 1 estiver vazia, o cheque ainda será bem sucedido. Para ter certeza, verifique também se a variável não está vazia.

#! /bin/bash

is_directory(){

    if [[ -d $1 ]] && [[ -n $1 ]] ; then
        return 0
    else
        return 1
    fi

}


#Test
if is_directory $1 ; then
    echo "Directory exist"
else
    echo "Directory does not exist!" 
fi
RenRen
fonte
1
Não tenho certeza de como isso responde à pergunta. Embora seja bom saber que um $ 1 vazio pode retornar um true quando vazio, ele não fornece nenhuma ideia de como retornar true ou false a partir de uma função bash. Eu sugeriria a criação de uma nova pergunta "O que acontece quando você faz um teste em uma variável de shell vazia?" E então postando isso como a resposta.
DRaehal
3
Observe que, se você adicionar aspas apropriadas a $1( "$1"), não precisará procurar uma variável vazia. O [[ -d "$1" ]]iria falhar porque este ""não é um diretório.
morgents 20/05
3

Encontrei um ponto (ainda não mencionado explicitamente?) Sobre o qual estava tropeçando. Ou seja, não como retornar o booleano, mas como avaliá-lo corretamente!

Eu estava tentando dizer if [ myfunc ]; then ..., mas isso é simplesmente errado. Você não deve usar os suportes! if myfunc; then ...é a maneira de fazer isso.

Como no @Bruno e outros, reiteramos truee falsesão comandos , não valores! Isso é muito importante para entender os booleanos nos scripts de shell.

Neste post, expliquei e demonstrei usando variáveis booleanas : https://stackoverflow.com/a/55174008/3220983 . Eu sugiro fortemente verificar isso, porque é muito relacionado.

Aqui, fornecerei alguns exemplos de retorno e avaliação de booleanos a partir de funções:

Este:

test(){ false; }                                               
if test; then echo "it is"; fi                                 

Não produz saída de eco. (ou seja, false retorna falso)

test(){ true; }                                                
if test; then echo "it is"; fi                                 

Produz:

it is                                                        

(ou seja, true retorna verdadeiro)

E

test(){ x=1; }                                                
if test; then echo "it is"; fi                                 

Produz:

it is                                                                           

Porque 0 (ou seja, verdadeiro) foi retornado implicitamente .

Agora, isso é o que estava me ferrando ...

test(){ true; }                                                
if [ test ]; then echo "it is"; fi                             

Produz:

it is                                                                           

E

test(){ false; }                                                
if [ test ]; then echo "it is"; fi                             

TAMBÉM produz:

it is                                                                           

Usar os colchetes aqui produziu um falso positivo ! (Eu deduzo que o resultado do comando "externo" é 0.)

A principal vantagem do meu post é: não use colchetes para avaliar uma função booleana (ou variável) como você faria para uma verificação de igualdade típica, por exemplo if [ x -eq 1 ]; then...!

BuvinJ
fonte
2

Use os comandos trueou falseimediatamente antes do seu e return, em seguida, returnsem parâmetros. O returnusará automaticamente o valor do seu último comando.

O fornecimento de argumentos para returné inconsistente, digite específico e propenso a erros se você não estiver usando 1 ou 0. E, como comentários anteriores declararam, usar 1 ou 0 aqui não é o caminho certo para abordar essa função.

#!/bin/bash

function test_for_cat {
    if [ $1 = "cat" ];
    then
        true
        return
    else
        false
        return
    fi
}

for i in cat hat;
do
    echo "${i}:"
    if test_for_cat "${i}";
    then
        echo "- True"
    else
        echo "- False"
    fi
done

Resultado:

$ bash bash_return.sh

cat:
- True
hat:
- False
mrteatime
fonte
1

Pode funcionar se você reescrever isso function myfun(){ ... return 0; else return 1; fi;}como este function myfun(){ ... return; else false; fi;}. Ou seja, se falseé a última instrução na função, você obtém resultado falso para toda a função, mas returninterrompe a função com resultado verdadeiro de qualquer maneira. Acredito que seja verdade para o meu intérprete de festa, pelo menos.

Sobrinho
fonte
1

Encontrei a forma mais curta para testar a saída da função é simplesmente

do_something() {
    [[ -e $1 ]] # e.g. test file exists
}

do_something "myfile.txt" || { echo "File doesn't exist!"; exit 1; }
Roy Shilkrot
fonte
1

Por razões de legibilidade do código, acredito que o retorno verdadeiro / falso deve:

  • estar em uma linha
  • seja um comando
  • seja fácil de lembrar
  • mencione a palavra-chave returnseguida por outra palavra-chave ( trueou false)

Minha solução é return $(true)ou return $(false)como mostrado:

is_directory()
{
    if [ -d "${1}" ]; then
        return $(true)
    else
        return $(false)
    fi
}
Charlie
fonte
0

Seguindo @Bruno Bronosky e @mrteatime, ofereço a sugestão de que você escreva seu retorno booleano "para trás". É isso que eu quero dizer:

foo()
{
    if [ "$1" == "bar" ]; then
        true; return
    else
        false; return
    fi;
}

Isso elimina o feio requisito de duas linhas para cada declaração de retorno.

BuvinJ
fonte