Passing parameters to a Bash function Passing parameters to a Bash function bash bash

Passing parameters to a Bash function


There are two typical ways of declaring a function. I prefer the second approach.

function function_name {   command...} 

or

function_name () {   command...} 

To call a function with arguments:

function_name "$arg1" "$arg2"

The function refers to passed arguments by their position (not by name), that is $1, $2, and so forth. $0 is the name of the script itself.

Example:

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

Also, you need to call your function after it is declared.

#!/usr/bin/env shfoo 1  # this will fail because foo has not been declared yet.foo() {    echo "Parameter #1 is $1"}foo 2 # this will work.

Output:

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

Reference: Advanced Bash-Scripting Guide.


Knowledge of high level programming languages (C/C++, Java, PHP, Python, Perl, etc.) would suggest to the layman that Bourne Again Shell (Bash) functions should work like they do in those other languages.

Instead, Bash functions work like shell commands and expect arguments to be passed to them in the same way one might pass an option to a shell command (e.g. ls -l). In effect, function arguments in Bash are treated as positional parameters ($1, $2..$9, ${10}, ${11}, and so on). This is no surprise considering how getopts works. Do not use parentheses to call a function in Bash.


(Note: I happen to be working on OpenSolaris at the moment.)

# 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            $2backupWebRoot ~/public/www/ webSite.tar.zip

Want to use names for variables? Just do something this.

local filename=$1 # The keyword declare can be used, but local is semantically more specific.

Be careful, though. If an argument to a function has a space in it, you may want to do this instead! Otherwise, $1 might not be what you think it is.

local filename="$1" # Just to be on the safe side. Although, if $1 was an integer, then what? Is that even possible? Humm.

Want to pass an array to a function by value?

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

Inside the function, handle the arguments like this.

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

Need to pass a value and an array, but still use "$@" inside the function?

function linearSearch (){    local 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[@]}"

In Bash 4.3 and above, you can pass an array to a function by reference by defining the parameter of a function with the -n option.

function callingSomeFunction (){    local -n someArray=$1 # also ${1:?} to make the parameter mandatory.    for value in "${someArray[@]}" # Nice!    do        :    done}callingSomeFunction someArray # No $ in front of the argument. You pass by name, not expansion / value.


If you prefer named parameters, it's possible (with a few tricks) to actually pass named parameters to functions (also makes it possible to pass arrays and references).

The method I developed allows you to define named parameters passed to a function like this:

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

You can also annotate arguments as @required or @readonly, create ...rest arguments, create arrays from sequential arguments (using e.g. string[4]) and optionally list the arguments in multiple lines:

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

In other words, not only you can call your parameters by their names (which makes up for a more readable core), you can actually pass arrays (and references to variables - this feature works only in Bash 4.3 though)! Plus, the mapped variables are all in the local scope, just as $1 (and others).

The code that makes this work is pretty light and works both in Bash 3 and Bash 4 (these are the only versions I've tested it with). If you're interested in more tricks like this that make developing with bash much nicer and easier, you can take a look at my Bash Infinity Framework, the code below is available as one of its functionalities.

shopt -s expand_aliasesfunction 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 parameter:      shift    fi  done  echo "${evalString} local -i __paramIndex=${paramIndex};"}alias args='local __previousTrap=$(trap -p DEBUG); trap "eval \"\$(assignTrap \$BASH_COMMAND)\";" DEBUG;'