传递参数给Bash函数

1465
我正在尝试搜索如何在Bash函数中传递参数,但总是出现如何从“命令行”传递参数的内容。我想在我的脚本中传递参数。我已经尝试了:

我尝试搜索如何在 Bash 函数中传递参数,但总是找到如何从“命令行”传递参数的内容。

我希望能够在脚本内部传递参数。我已经尝试过:

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

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

但是语法不正确。我该如何向我的函数传递参数?


13
“...但总是关于如何从命令行传递参数”,是的!这是因为Bash脚本基本上就是一系列的命令行 - 在Bash脚本中调用函数就像在命令行上输入一个命令一样! :-) 调用函数时,不需要括号和逗号,只需要写成myBackupFunction“..”“...”“xx”。 - Wil
5
这个问题的对应问题是:在Bash函数中返回值 - MSalters
参见:在Bash中将数组作为参数传递 - Gabriel Staples
7个回答

2248

声明函数有两种典型方法,我更喜欢第二种方法。

function function_name {
   command...
} 
或者
function_name () {
   command...
} 

调用带有参数的函数:

function_name "$arg1" "$arg2"
该函数按位置而非名称引用传递的参数,即$1$2等等。 $0是脚本自身的名称。

示例:

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

此外,您需要在函数被声明之后再调用它。

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

输出:

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

参考资料:高级Bash脚本指南


6
您忘记了空格,请尝试使用 function name() {}。也许在 {} 前面加一个换行符。 - Leandro
34
好的回答。我个人认为,在需要调用文件时,对于存在文件中的shell构建,我更喜欢使用 function 关键字和括号 ()。在文件中,我的目标是增加可读性,而不是减少输入的字符数,例如: function myBackupFunction() compound-statement - Terry Gardner
38
@CMCDragonkai,“function”关键字版本是一种扩展,另一种形式适用于所有符合POSIX标准的shell。 - Charles Duffy
21
@TerryGardner,请考虑你试图增加清晰度的做法可能会降低兼容性。 - Charles Duffy
12
@RonBurk,也许 -- 但即使我们只考虑清晰度,function 关键字在旧的 ksh shell 家族中的保证现代 bash 不遵守(在这样的 shell 中,function 默认将变量设置为局部变量;在 bash 中,它不会)。因此,对于任何了解并可能期望 ksh 行为的人来说,它的使用会降低清晰度。请参见http://wiki.bash-hackers.org/scripting/obsolete - Charles Duffy
显示剩余6条评论

172
高级编程语言的知识(如C/C++,Java,PHP,Python,Perl等)可能会让外行人误以为Bourne Again Shell(Bash)函数的工作方式与其他语言相同。
然而,Bash函数的工作方式更像是shell命令,它们期望以与传递选项给shell命令相同的方式传递参数(例如,ls -l)。实际上,Bash中的函数参数被视为位置参数($1,$2..$9,${10},${11}等等)。考虑到getopts的工作原理,这并不令人意外。在Bash中,不要使用括号来调用函数。
(注意:我目前正在工作的是OpenSolaris。)
# 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

想要为变量使用名称吗?只需按照这样做。
local filename=$1 # The keyword declare can be used, but local is semantically more specific.

小心点,如果函数的参数中有空格,你可能想这样做!否则,$1 可能不是你想象的那样。
local filename="$1" # Just to be on the safe side. Although, if $1 was an integer, then what? Is that even possible? Humm.

想要通过值将数组传递给一个函数吗?
callingSomeFunction "${someArray[@]}" # Expands to all array elements.

在函数内部,像这样处理参数。
function callingSomeFunction ()
{
    for value in "$@"; do # You want to use "$@" here, not "$*" !!!!!
        :
    done
}

需要传递一个值和一个数组,但在函数内部仍然使用"$@"吗?
function linearSearch ()
{
    local myVar="$1"

    shift 1 # Removes $1 from the parameter list

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

    return 1
}

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

在Bash 4.3及以上版本中,您可以通过使用-n选项来定义函数的参数,将数组作为引用传递给函数。
function callingSomeFunction ()
{
    local -n someArray=$1 # also ${1:?} to make the parameter mandatory.

    for value in "${someArray[@]}"; do # Nice!
        :
    done
}

callingSomeFunction myArray # No $ in front of the argument. You pass by name, not expansion / value.

据我所知,最后一个示例无法正常工作。我尝试在bash v5+上运行它,但只是在循环中返回整个数组,而不是每个项目。 - iomv
1
经过再次测试,我发现这是我的错误,因为我在行内声明数组而不是在之前声明。 - iomv
3
然而,务必小心“循环变量引用”问题。无论你在函数内将数组声明为什么名称,都不要在调用上下文/客户端代码中使用相同的名称作为数组参数名称。请注意,我更改了最后一个示例以帮助人们避免“循环名称引用”问题。虽然你犯了错误,但这是个好决定。 :-) Translated: 然而,一定要注意“循环变量引用”问题。在函数内无论你用哪个名称声明数组,都不要在调用函数的代码中使用相同的名称作为数组参数。请注意,我更改了最后一个示例,以帮助人们避免“循环名称引用”问题。虽然你自己犯了错误,但这是个好决定。 :-) - Anthony Rutledge

84

如果您更喜欢使用命名参数,实际上可以(通过一些技巧)向函数传递命名参数(也可以传递数组和引用)。

我开发的方法允许您像这样定义传递给函数的命名参数:

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

你还可以使用 @required 或 @readonly 注释参数,创建...rest 参数,从连续的参数中创建数组(例如使用string[4]),并可选地在多行中列出参数:

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

换句话说,你不仅可以通过参数名称调用它们(这使得核心更易读),还可以传递数组(和变量引用 - 该功能仅在Bash 4.3中有效)!此外,映射的变量都在本地范围内,就像$1(和其他变量)一样。

实现这一点的代码非常简单,并且适用于Bash 3和Bash 4(这是我测试过的唯一版本)。如果你对类似这样的技巧感兴趣,使得使用bash进行开发更加美好和容易,那么可以查看我的Bash Infinity Framework,下面的代码是其功能之一。

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 parameter:
      shift
    fi
  done
  echo "${evalString} local -i __paramIndex=${paramIndex};"
}

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

@var@reference@params变量是什么?我应该在互联网上查找哪些内容以了解更多信息? - GypsyCosmonaut
1
嗨@niieani,当我尝试按照你在答案中使用的形式创建一个bash函数时,它告诉我需要从apt安装ucommon utils。这是你的bash脚本的工作方式吗?我做得对吗?如果我理解正确,你或其他人基本上构建了ucommon util程序以允许扩展Bash,对吗? - Aslan French
@DavidA.French 不,这不应该发生。 ucommon 和我的代码之间没有联系。 可能是你安装了某些工具导致了你提到的问题,我不知道它可能是什么。 - niieani
1
考虑到这个问题,涉及的内容太多了。像 local filename=$1 这样的东西对大多数人来说已经足够好了。此外,在 bash 中,我们可以使用 declare -A 来创建关联数组。你已经可以将数组作为列表传递了!callingSomeFunction "${someArray[@]}" - Anthony Rutledge

55

去掉括号和逗号:

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

函数应该长这样:

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

21

一个简单的示例,无论是在执行脚本期间还是在调用函数时都能清楚地解释。

#!/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-separated value
# You can also pass parameters during executing the script
print_param_value "$1" "$2" # Parameter $1 and $2 during execution

# Suppose our script name is "param_example".
# Call it like this:
#
# ./param_example 5 5
#
# Now the parameters will be $1=5 and $2=5

就我个人而言,我也遵循这种形式,但我想知道是否有任何额外的好处? - tijko

14

它从用户那里获取两个数字,将它们提供给代码的最后一行调用的add函数,并且add将对它们求和并打印出来。

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

8
以这种方式通过名称进行传递,仅适用于传入数值运算符(())的整数,并且这仅适用于数值运算符将字符串递归解析为值。 如果您想测试我的意思,请尝试输入“5”作为x,然后输入“x”作为y,您将看到它添加了(x + y)=(5 + x)=(5 + 5)= 10。对于所有其他用例,您的示例将失败。 相反,您应该使用“add”$x“”$y“”进行通用代码。 - Wil

10

将命名参数传递给Bash的另一种方式是通过引用传递。从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];

在Bash 4.3中,使用引用名称作为备选语法。

虽然引用名称更加便捷,因为它可以无缝解除引用,但是某些旧的支持版本仍然会安装旧版本,所以我不建议立即使用它。


“pipe in”。我看到你做了什么! - Jacktose

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接