Pasar parámetros a una función Bash

981

Estoy tratando de buscar cómo pasar parámetros en una función Bash, pero lo que aparece siempre es cómo pasar parámetros desde la línea de comandos.

Me gustaría pasar parámetros dentro de mi script. Lo intenté:

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

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

Pero la sintaxis no es correcta, ¿cómo pasar un parámetro a mi función?

stivlo
fuente
66
"... pero lo que aparece siempre es cómo pasar parámetros desde la línea de comandos" - ¡Sí! Esto se debe a que los scripts de Bash son básicamente secuencias de líneas de comando: invoque una función en un script de Bash exactamente como si fuera un comando en la línea de comando. :-) Su llamada sería myBackupFunction ".." "..." "xx"; sin paréntesis, sin comas.
Wil
44
La contraparte de esta pregunta: valor de retorno de una función bash
MSalters

Respuestas:

1619

Hay dos formas típicas de declarar una función. Prefiero el segundo enfoque.

function function_name {
   command...
} 

o

function_name () {
   command...
} 

Para llamar a una función con argumentos:

function_name "$arg1" "$arg2"

La función se refiere a los argumentos pasados ​​por su posición (no por su nombre), es decir, $ 1, $ 2, etc. $ 0 es el nombre del script en sí.

Ejemplo:

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

Además, debe llamar a su función después de que se declare.

#!/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.

Salida:

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

Referencia: Guía avanzada de secuencias de comandos Bash .

dogbane
fuente
44
Has olvidado los espacios, inténtalo function name() {}. Tal vez con un 'entrar' antes{}
lalo
21
Buena respuesta. Mis 2 centavos: en construcciones de shell que residen en un archivo que se obtiene (punteado) cuando es necesario, prefiero usar la functionpalabra clave y el (). Mi objetivo (en un archivo, no la línea de comandos) es aumentar la claridad, no reduce el número de caracteres escritos, es decir, function myBackupFunction() compound-statement.
Terry Gardner
22
@CMCDragonkai, la functionversión de la palabra clave es una extensión; la otra forma funciona en todos los shells compatibles con POSIX.
Charles Duffy
8
@TerryGardner, considere que sus intentos de aumentar la claridad están reduciendo la compatibilidad.
Charles Duffy
66
@RonBurk, tal vez, pero incluso si consideramos solo la claridad, la functionpalabra clave tenía garantías en los antiguos shells de la familia ksh que introdujeron que bash moderno no honra (en tales shells, las functionvariables se convirtieron en locales de forma predeterminada; en bash , no es asi). Como tal, su uso disminuye la claridad para cualquiera que conozca y pueda esperar el comportamiento ksh. Ver wiki.bash-hackers.org/scripting/obsolete
Charles Duffy
70

El conocimiento de lenguajes de programación de alto nivel (C / C ++ / Java / PHP / Python / Perl ...) sugeriría al lego que las funciones bash deberían funcionar como lo hacen en esos otros lenguajes. En cambio , las funciones bash funcionan como comandos de shell y esperan que se les pasen argumentos de la misma manera que uno podría pasar una opción a un comando de shell (por ejemplo ls -l). En efecto, los argumentos de función en bash se tratan como parámetros posicionales ( $1, $2..$9, ${10}, ${11}y así sucesivamente). Esto no es sorprendente teniendo en cuenta cómo getoptsfunciona. No use paréntesis para llamar a una función en bash.


( Nota : actualmente estoy trabajando en Open Solaris).

# 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

Quiere usar nombres para variables. Solo haz esto.

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

¿Quieres pasar una matriz a una función?

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

Dentro de la función, maneje los argumentos como este.

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

¿Necesita pasar un valor y una matriz, pero aún usa "$ @" dentro de la función?

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[@]}"
Anthony Rutledge
fuente
64

Si prefiere los parámetros con nombre, es posible (con algunos trucos) pasar los parámetros con nombre a las funciones (también hace posible pasar matrices y referencias).

El método que desarrollé le permite definir parámetros con nombre pasados ​​a una función como esta:

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

También puede anotar argumentos como @required o @readonly, crear ... descansar argumentos, crear matrices a partir de argumentos secuenciales (usando, por ejemplo string[4]) y opcionalmente enumerar los argumentos en varias líneas:

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[*]}"
}

En otras palabras, no solo puede llamar a sus parámetros por sus nombres (lo que compensa un núcleo más legible), sino que también puede pasar matrices (y referencias a variables; ¡esta función solo funciona en bash 4.3)! Además, las variables asignadas están todas en el ámbito local, igual que $ 1 (y otras).

El código que hace que esto funcione es bastante ligero y funciona tanto en bash 3 como en bash 4 (estas son las únicas versiones con las que lo he probado). Si está interesado en más trucos como este que hacen que el desarrollo con bash sea mucho más fácil y fácil, puede echar un vistazo a mi Bash Infinity Framework , el siguiente código está disponible como una de sus 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;'
niieani
fuente
¿Cuáles son los @var, @reference, @paramslas variables? ¿Qué debo buscar en Internet para obtener más información al respecto?
GypsyCosmonaut
3
¡Gran respuesta! Acabo de investigar Bash Infinity y parece que será realmente útil. ¡Gracias!
Jonathan Hult
Gracias @ JonathanHult! De hecho, actualicé mi respuesta anterior recientemente, y ahora es un código nuevo y reescrito del código que está actualmente en Bash Infinity 2.0. La razón por la que lo reescribí es por un error en la implementación anterior (está en los problemas de GitHub). Todavía no he tenido tiempo de integrar la nueva versión en Bash Infinity. Me alegra saber que ha sido útil.
niieani
Hola, @niieani, cuando intento crear una función bash en la forma que usas en tu respuesta, me dice que necesito instalar ucommon utils desde apt. ¿Es así como funciona tu script bash? ¿Estoy haciendo esto correctamente? Si entiendo que usted o alguien más básicamente construyó el programa de utilidad ucommon para permitir una extensión de Bash, ¿correcto?
David A. French
@ DavidA.Francés no, esto no debería suceder. No hay relación entre ucommony mi código. Es posible que tenga alguna herramienta instalada que cause el problema que mencionó, sin saber qué podría ser.
niieani
27

Se pierden los parens y las comas:

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

y la función debería verse así:

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

fuente
8

Espero que este ejemplo te pueda ayudar. Toma dos números del usuario, los alimenta a la función llamada add(en la última línea del código) y addlos sumará e imprimirá.

#!/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
Milad P.
fuente
66
Pasar por nombre de esa manera solo funciona para enteros pasados ​​al operador numérico (()), y solo funciona porque el operador numérico resuelve recursivamente las cadenas a valores. Si desea probar lo que quiero decir, intente ingresar '5' para x y luego 'x' para y y verá que agrega (x + y) = (5 + x) = (5 + 5) = 10. Para todos los demás casos de uso, su ejemplo fallará. En su lugar, debe usar 'agregar "$ x" "$ y"' para el código genérico.
Wil
6

Un ejemplo simple que se borrará tanto durante la ejecución del script como dentro del script mientras se llama a una función.

#!/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
Adiii
fuente
5

Pensé en hablar con otra forma de pasar parámetros con nombre a bash ... pasando por referencia. Esto es compatible a partir de 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];

Una sintaxis alternativa para bash 4.3 es usar un nameref

Aunque el nameref es mucho más conveniente ya que elimina las referencias sin problemas, algunas distribuciones compatibles más antiguas aún envían una versión anterior, por lo que aún no la recomendaré.

Wil
fuente
"Tubería". ¡Veo lo que hiciste alli!
Jacktose