Passando parâmetros para uma função Bash


981

Estou tentando pesquisar como passar parâmetros em uma função Bash, mas o que surge é sempre como passar parâmetros na linha de comando.

Eu gostaria de passar parâmetros dentro do meu script. Eu tentei:

myBackupFunction("..", "...", "xx")

function myBackupFunction($directory, $options, $rootPassword) {
     ...
}

Mas a sintaxe não está correta, como passar um parâmetro para minha função?


6
"... mas o que surge é sempre como passar parâmetros a partir da linha de comando" - Sim! Isso ocorre porque os scripts Bash são basicamente sequências de linhas de comando - invocam uma função em um script Bash exatamente como se fosse um comando na linha de comando! :-) Sua chamada seria myBackupFunction ".." "..." "xx"; sem parênteses, sem vírgulas.
Wil as

4
A contrapartida desta pergunta: retornar valor de uma função bash
MSalters

Respostas:


1619

Existem duas maneiras típicas de declarar uma função. Eu prefiro a segunda abordagem.

function function_name {
   command...
} 

ou

function_name () {
   command...
} 

Para chamar uma função com argumentos:

function_name "$arg1" "$arg2"

A função refere-se a argumentos passados ​​por sua posição (não pelo nome), ou seja, $ 1, $ 2 e assim por diante. $ 0 é o nome do próprio script.

Exemplo:

function_name () {
   echo "Parameter #1 is $1"
}

Além disso, você precisa chamar sua função depois que ela for declarada.

#!/usr/bin/env sh

foo 1  # this will fail because foo has not been declared yet.

foo() {
    echo "Parameter #1 is $1"
}

foo 2 # this will work.

Resultado:

./myScript.sh: line 2: foo: command not found
Parameter #1 is 2

Referência: Guia Avançado de Bash-Scripting .


4
Você esqueceu os espaços, tente function name() {}. Talvez com um 'enter' antes{}
lalo 11/11

21
Boa resposta. Meus 2 centavos: em construções de shell que residem em um arquivo que é originado (pontilhado) quando necessário, eu prefiro usar a functionpalavra - chave e o (). Meu objetivo (em um arquivo, não na linha de comando) é aumentar a clareza, não reduzir o número de caracteres digitados, viz function myBackupFunction() compound-statement.
Terry Gardner

22
@CMCDragonkai, a functionversão da palavra - chave é uma extensão; o outro formulário funciona em todos os shells compatíveis com POSIX.
Charles Duffy

8
@TerryGardner, considere que suas tentativas de aumentar a clareza estão reduzindo a compatibilidade.
Charles Duffy

6
@RonBurk, talvez - mas mesmo se considerarmos apenas a clareza, a functionpalavra - chave tinha garantias nos antigos shells da família ksh que o introduziram que o bash moderno não honra (nesses shells, as functionvariáveis ​​foram localizadas por padrão; no bash , isso não). Como tal, seu uso diminui a clareza para quem conhece e pode esperar o comportamento do ksh. Veja wiki.bash-hackers.org/scripting/obsolete
Charles Duffy

70

O conhecimento de linguagens de programação de alto nível (C / C ++ / Java / PHP / Python / Perl ...) sugeriria ao leigo que as funções bash deveriam funcionar da mesma maneira que nessas outras linguagens. Em vez disso , as funções bash funcionam como comandos do shell e esperam que os argumentos sejam passados ​​para eles da mesma maneira que se pode passar uma opção para um comando do shell (por exemplo ls -l). Com efeito, argumentos de função no bash são tratados como parâmetros posicionais ( $1, $2..$9, ${10}, ${11}etc.). Isso não é surpresa, considerando como getoptsfunciona. Não use parênteses para chamar uma função no bash.


( Observação : eu estou trabalhando no Open Solaris no momento.)

# bash style declaration for all you PHP/JavaScript junkies. :-)
# $1 is the directory to archive
# $2 is the name of the tar and zipped file when all is done.
function backupWebRoot ()
{
    tar -cvf - $1 | zip -n .jpg:.gif:.png $2 - 2>> $errorlog &&
        echo -e "\nTarball created!\n"
}


# sh style declaration for the purist in you. ;-)
# $1 is the directory to archive
# $2 is the name of the tar and zipped file when all is done.
backupWebRoot ()
{
    tar -cvf - $1 | zip -n .jpg:.gif:.png $2 - 2>> $errorlog &&
        echo -e "\nTarball created!\n"
}


# In the actual shell script
# $0               $1            $2

backupWebRoot ~/public/www/ webSite.tar.zip

Deseja usar nomes para variáveis. Apenas faça isso.

declare filename=$1 # declare gives you more options and limits variable scope

Deseja passar uma matriz para uma função?

callingSomeFunction "${someArray[@]}" # Expands to all array elements.

Dentro da função, lide com argumentos como este.

function callingSomeFunction ()
{
    for value in "$@" # You want to use "$@" here, not "$*" !!!!!
    do
        :
    done
}

Precisa passar um valor e uma matriz, mas ainda usar "$ @" dentro da função?

function linearSearch ()
{
    declare myVar="$1"

    shift 1 # removes $1 from the parameter list

    for value in "$@" # Represents the remaining parameters.
    do
        if [[ $value == $myVar ]]
        then
            echo -e "Found it!\t... after a while."
            return 0
        fi
    done

    return 1
}

linearSearch $someStringValue "${someArray[@]}"

64

Se você preferir parâmetros nomeados, é possível (com alguns truques) realmente passar parâmetros nomeados para funções (também possibilita passar matrizes e referências).

O método que desenvolvi permite definir parâmetros nomeados passados ​​para uma função como esta:

function example { args : string firstName , string lastName , integer age } {
  echo "My name is ${firstName} ${lastName} and I am ${age} years old."
}

Você também pode anotar argumentos como @required ou @readonly, criar ... descansar argumentos, criar matrizes a partir de argumentos sequenciais (usando, por exemplo string[4]) e, opcionalmente, listar os argumentos em várias linhas:

function example {
  args
    : @required string firstName
    : string lastName
    : integer age
    : string[] ...favoriteHobbies

  echo "My name is ${firstName} ${lastName} and I am ${age} years old."
  echo "My favorite hobbies include: ${favoriteHobbies[*]}"
}

Em outras palavras, não apenas você pode chamar seus parâmetros pelos nomes (o que compõe um núcleo mais legível), como também pode transmitir matrizes (e referências a variáveis ​​- esse recurso funciona apenas no bash 4.3)! Além disso, as variáveis ​​mapeadas estão todas no escopo local, assim como $ 1 (e outras).

O código que faz esse trabalho é bastante leve e funciona tanto no bash 3 quanto no bash 4 (essas são as únicas versões com as quais eu testei). Se você estiver interessado em mais truques como esse que tornam o desenvolvimento com o bash muito mais agradável e fácil, você pode dar uma olhada no meu Bash Infinity Framework , o código abaixo está disponível como uma de suas funcionalidades.

shopt -s expand_aliases

function assignTrap {
  local evalString
  local -i paramIndex=${__paramIndex-0}
  local initialCommand="${1-}"

  if [[ "$initialCommand" != ":" ]]
  then
    echo "trap - DEBUG; eval \"${__previousTrap}\"; unset __previousTrap; unset __paramIndex;"
    return
  fi

  while [[ "${1-}" == "," || "${1-}" == "${initialCommand}" ]] || [[ "${#@}" -gt 0 && "$paramIndex" -eq 0 ]]
  do
    shift # first colon ":" or next parameter's comma ","
    paramIndex+=1
    local -a decorators=()
    while [[ "${1-}" == "@"* ]]
    do
      decorators+=( "$1" )
      shift
    done

    local declaration=
    local wrapLeft='"'
    local wrapRight='"'
    local nextType="$1"
    local length=1

    case ${nextType} in
      string | boolean) declaration="local " ;;
      integer) declaration="local -i" ;;
      reference) declaration="local -n" ;;
      arrayDeclaration) declaration="local -a"; wrapLeft= ; wrapRight= ;;
      assocDeclaration) declaration="local -A"; wrapLeft= ; wrapRight= ;;
      "string["*"]") declaration="local -a"; length="${nextType//[a-z\[\]]}" ;;
      "integer["*"]") declaration="local -ai"; length="${nextType//[a-z\[\]]}" ;;
    esac

    if [[ "${declaration}" != "" ]]
    then
      shift
      local nextName="$1"

      for decorator in "${decorators[@]}"
      do
        case ${decorator} in
          @readonly) declaration+="r" ;;
          @required) evalString+="[[ ! -z \$${paramIndex} ]] || echo \"Parameter '$nextName' ($nextType) is marked as required by '${FUNCNAME[1]}' function.\"; " >&2 ;;
          @global) declaration+="g" ;;
        esac
      done

      local paramRange="$paramIndex"

      if [[ -z "$length" ]]
      then
        # ...rest
        paramRange="{@:$paramIndex}"
        # trim leading ...
        nextName="${nextName//\./}"
        if [[ "${#@}" -gt 1 ]]
        then
          echo "Unexpected arguments after a rest array ($nextName) in '${FUNCNAME[1]}' function." >&2
        fi
      elif [[ "$length" -gt 1 ]]
      then
        paramRange="{@:$paramIndex:$length}"
        paramIndex+=$((length - 1))
      fi

      evalString+="${declaration} ${nextName}=${wrapLeft}\$${paramRange}${wrapRight}; "

      # continue to the next param:
      shift
    fi
  done
  echo "${evalString} local -i __paramIndex=${paramIndex};"
}

alias args='local __previousTrap=$(trap -p DEBUG); trap "eval \"\$(assignTrap \$BASH_COMMAND)\";" DEBUG;'

Quais são os @var, @reference, @paramsvariáveis? O que devo procurar na internet para saber mais sobre isso?
GypsyCosmonaut

3
Ótima resposta! Acabei de pesquisar o Bash Infinity e parece que será realmente útil. Obrigado!
Jonathan Hult 28/02

Obrigado @JonathanHult! Na verdade, atualizei minha resposta acima recentemente e agora é um trecho mais novo e reescrito do código para o atualmente no Bash Infinity 2.0. A razão pela qual reescrevi é por causa de um bug na implementação antiga (está nos problemas do GitHub). Ainda não tivemos tempo de integrar a nova versão ao Bash Infinity. Fico feliz em saber que tem sido útil.
Niieani 28/02

Oi @niieani quando tento criar uma função bash no formulário que você usa na sua resposta, ela me diz que preciso instalar utilitários comuns do apt. É assim que seu script bash funciona? Estou fazendo isso corretamente? Se eu entendo que você ou outra pessoa basicamente criou o programa utilitário comum para permitir uma extensão do Bash, correto?
David A. French

@ DavidA.French não, isso não deveria acontecer. Não há relação entre ucommone meu código. É possível que você tenha alguma ferramenta instalada que cause o problema mencionado, sem ideia do que poderia ser.
Niieani 2/08

27

Perca as parênteses e vírgulas:

 myBackupFunction ".." "..." "xx"

e a função deve ficar assim:

function myBackupFunction() {
   # here $1 is the first parameter, $2 the second etc.
}

8

Espero que este exemplo possa ajudá-lo. Ele pega dois números do usuário, os alimenta com a função chamada add(na última linha do código) e addos soma e os imprime.

#!/bin/bash

read -p "Enter the first  value: " x
read -p "Enter the second value: " y

add(){
    arg1=$1 #arg1 gets to be the first  assigned argument (note there are no spaces)
    arg2=$2 #arg2 gets to be the second assigned argument (note there are no spaces)

    echo $(($arg1 + $arg2))
}

add x y #feeding the arguments

6
Passar por nome dessa maneira funciona apenas para números inteiros passados ​​para o operador numérico (()) e funciona apenas porque o operador numérico resolve recursivamente as seqüências de caracteres em valores. Se você quiser testar o que quero dizer, tente digitar '5' para x e depois 'x' para y e verá que ele adiciona (x + y) = (5 + x) = (5 + 5) = 10. Para todos os outros casos de uso, seu exemplo falhará. Em vez disso, você deve usar 'add "$ x" "$ y"' para código genérico.
Wil

6

Um exemplo simples que limpará durante a execução do script ou dentro do script ao chamar uma função.

#!/bin/bash
echo "parameterized function example"
function print_param_value(){
    value1="${1}" # $1 represent first argument
    value2="${2}" # $2 represent second argument
    echo "param 1 is  ${value1}" #as string
    echo "param 2 is ${value2}"
    sum=$(($value1+$value2)) #process them as number
    echo "The sum of two value is ${sum}"
}
print_param_value "6" "4" #space sparted value
#you can also pass paramter durign executing script
print_param_value "$1" "$2" #parameter $1 and $2 during executing

#suppose our script name is param_example
# call like this 
# ./param_example 5 5
# now the param will be $1=5 and $2=5

5

Pensei em entrar com menção de outra maneira de passar parâmetros nomeados para bash ... passando por referência. Isso é suportado no bash 4.0

#!/bin/bash
function myBackupFunction(){ # directory options destination filename
local directory="$1" options="$2" destination="$3" filename="$4";
  echo "tar cz ${!options} ${!directory} | ssh root@backupserver \"cat > /mnt/${!destination}/${!filename}.tgz\"";
}

declare -A backup=([directory]=".." [options]="..." [destination]="backups" [filename]="backup" );

myBackupFunction backup[directory] backup[options] backup[destination] backup[filename];

Uma sintaxe alternativa para o bash 4.3 está usando um nameref

Embora o nameref seja muito mais conveniente, pois desreferencia perfeitamente, algumas distros suportadas mais antigas ainda enviam uma versão mais antiga, por isso ainda não a recomendarei.


"Tubo". Eu vi o que você fez lá!
Jacktose 12/03/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.