Passer des paramètres à une fonction Bash

981

J'essaie de rechercher comment passer des paramètres dans une fonction Bash, mais ce qui revient est toujours comment passer des paramètres à partir de la ligne de commande.

Je voudrais passer des paramètres dans mon script. J'ai essayé:

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

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

Mais la syntaxe n'est pas correcte, comment passer un paramètre à ma fonction?

stivlo
la source
6
"... mais ce qui revient, c'est toujours comment passer le paramètre depuis la ligne de commande" - Oui! C'est parce que les scripts Bash sont essentiellement des séquences de lignes de commande - appelez une fonction dans un script Bash exactement comme s'il s'agissait d'une commande sur la ligne de commande! :-) Votre appel serait myBackupFunction ".." "..." "xx"; pas de parenthèses, pas de virgules.
Wil
4
La contrepartie de cette question: valeur de retour d'une fonction bash
MSalters

Réponses:

1619

Il existe deux façons typiques de déclarer une fonction. Je préfère la deuxième approche.

function function_name {
   command...
} 

ou

function_name () {
   command...
} 

Pour appeler une fonction avec des arguments:

function_name "$arg1" "$arg2"

La fonction fait référence aux arguments passés par leur position (pas par leur nom), c'est-à-dire $ 1, $ 2, etc. $ 0 est le nom du script lui-même.

Exemple:

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

De plus, vous devez appeler votre fonction après sa déclaration.

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

Production:

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

Référence: Advanced Bash-Scripting Guide .

dogbane
la source
4
Vous avez oublié les espaces, essayez function name() {}. Peut-être avec une entrée avant{}
lalo
21
Bonne réponse. Mes 2 cents: dans les constructions shell qui résident dans un fichier qui est sourcé (en pointillé) lorsque cela est nécessaire, je préfère utiliser le functionmot - clé et le (). Mon but (dans un fichier, pas la ligne de commande) est d'augmenter la clarté, pas réduire le nombre de caractères tapés, à savoir, function myBackupFunction() compound-statement.
Terry Gardner
22
@CMCDragonkai, la functionversion du mot clé est une extension; l'autre forme fonctionne dans tous les shells compatibles POSIX.
Charles Duffy
8
@TerryGardner, considérez que vos tentatives pour augmenter la clarté réduisent la compatibilité.
Charles Duffy
6
@RonBurk, peut-être - mais même si nous ne considérons que la clarté, le functionmot - clé avait des garanties dans les anciens shells de la famille ksh qui l'introduisaient que bash moderne n'honore pas (dans de tels shells, les functionvariables étaient locales par défaut; dans bash , ce ne est pas). En tant que tel, son utilisation diminue la clarté pour quiconque connaît et pourrait s'attendre au comportement de ksh. Voir wiki.bash-hackers.org/scripting/obsolete
Charles Duffy
70

La connaissance des langages de programmation de haut niveau (C / C ++ / Java / PHP / Python / Perl ...) suggérerait au profane que les fonctions bash devraient fonctionner comme elles le font dans ces autres langages. Au lieu de cela , les fonctions bash fonctionnent comme des commandes shell et s'attendent à ce que des arguments leur soient transmis de la même manière que l'on pourrait passer une option à une commande shell (par exemple ls -l). En effet, les arguments de fonction dans bash sont traités comme des paramètres positionnels ( $1, $2..$9, ${10}, ${11}, etc.). Ce n'est pas une surprise compte tenu du getoptsfonctionnement. N'utilisez pas de parenthèses pour appeler une fonction dans bash.


( Remarque : il se trouve que je travaille actuellement sur 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

Vous souhaitez utiliser des noms pour les variables. Faites ça.

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

Vous voulez passer un tableau à une fonction?

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

À l'intérieur de la fonction, gérez les arguments comme celui-ci.

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

Besoin de passer une valeur et un tableau, mais toujours utiliser "$ @" à l'intérieur de la fonction?

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
la source
64

Si vous préférez les paramètres nommés, il est possible (avec quelques astuces) de passer réellement des paramètres nommés aux fonctions (permet également de passer des tableaux et des références).

La méthode que j'ai développée vous permet de définir des paramètres nommés passés à une fonction comme celle-ci:

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

Vous pouvez également annoter des arguments comme @required ou @readonly, créer ... des arguments de repos, créer des tableaux à partir d'arguments séquentiels (en utilisant par exemple string[4]) et éventuellement lister les arguments sur plusieurs lignes:

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 d'autres termes, non seulement vous pouvez appeler vos paramètres par leurs noms (ce qui constitue un noyau plus lisible), vous pouvez en fait passer des tableaux (et des références à des variables - cette fonctionnalité ne fonctionne que dans bash 4.3 cependant)! De plus, les variables mappées sont toutes dans la portée locale, tout comme $ 1 (et autres).

Le code qui fait ce travail est assez léger et fonctionne à la fois en bash 3 et bash 4 (ce sont les seules versions avec lesquelles je l'ai testé). Si vous êtes intéressé par plus de trucs comme celui-ci qui rendent le développement avec bash beaucoup plus agréable et plus facile, vous pouvez jeter un œil à mon framework Bash Infinity , le code ci-dessous est disponible comme l'une de ses fonctionnalités.

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
la source
Quels sont les @var, @reference, les @paramsvariables? Que dois-je rechercher sur Internet pour en savoir plus à ce sujet?
GypsyCosmonaut
3
Très bonne réponse! Je viens de faire des recherches sur Bash Infinity et il semble que ce sera vraiment utile. Merci!
Jonathan Hult
Merci @JonathanHult! J'ai en fait mis à jour ma réponse ci-dessus récemment, et c'est maintenant une version plus récente et réécrite du code à celle actuellement dans Bash Infinity 2.0. La raison pour laquelle je l'ai réécrit est à cause d'un bug dans l'ancienne implémentation (c'est dans les problèmes sur GitHub). Je n'ai pas encore eu le temps d'intégrer la nouvelle version dans Bash Infinity. Heureux d'entendre que cela a été utile.
niieani
Salut @niieani lorsque j'essaie de créer une fonction bash sous la forme que vous utilisez dans votre réponse, cela me dit que j'ai besoin d'installer des utilitaires communs à partir d'apt. Est-ce ainsi que fonctionne votre script bash? Suis-je en train de faire ça correctement? Si je comprends bien que vous ou quelqu'un d'autre avez essentiellement construit le programme ucommon util pour permettre une extension de Bash, n'est-ce pas?
David A. French
@ DavidA.French non, cela ne devrait pas arriver. Il n'y a aucune relation entre ucommonet mon code. Il est possible que vous ayez installé un outil qui provoque le problème que vous avez mentionné, aucune idée de ce que cela pourrait être.
niieani
27

Manquez les parens et les virgules:

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

et la fonction devrait ressembler à ceci:

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

la source
8

J'espère que cet exemple peut vous aider. Il prend deux nombres à l'utilisateur, les transmet à la fonction appelée add(dans la toute dernière ligne du code), et addles résume et les 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
Milad P.
la source
6
Le passage par nom de cette manière ne fonctionne que pour les entiers passés à l'opérateur numérique (()), et cela ne fonctionne que parce que l'opérateur numérique résout récursivement les chaînes en valeurs. Si vous souhaitez tester ce que je veux dire, essayez de saisir «5» pour x puis «x» pour y et vous verrez qu'il ajoute (x + y) = (5 + x) = (5 + 5) = 10. Pour tous les autres cas d'utilisation, votre exemple échouera. Au lieu de cela, vous devez utiliser 'ajouter "$ x" "$ y"' pour le code générique.
Wil
6

Un exemple simple qui effacera à la fois lors de l'exécution du script ou à l'intérieur du script lors de l'appel d'une fonction.

#!/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
la source
5

Je pensais que j'apporterais une mention d'une autre façon de passer des paramètres nommés à bash ... en passant par référence. Ceci est pris en charge à 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];

Une syntaxe alternative pour bash 4.3 utilise un nameref

Bien que le nameref soit beaucoup plus pratique en ce qu'il déréférence de manière transparente, certaines distributions plus anciennes sont toujours livrées avec une version plus ancienne, donc je ne le recommanderai pas encore tout à fait.

Wil
la source
«Pipe in». Je vois ce que tu as fait là!
Jacktose