将参数传递给Bash函数


980

我正在尝试搜索如何在Bash函数中传递参数,但是出现的总是总是如何从命令行传递参数。

我想在脚本中传递参数。我试过了:

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

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

但是语法不正确,如何将参数传递给我的函数?


6
“ ...但是总会出现如何从命令行传递参数”-是的!这是因为Bash脚本基本上是命令行的序列-完全像在命令行上的命令一样调用Bash脚本中的函数!:-)您的调用将是myBackupFunction“ ..”“ ...”“ xx”; 没有括号,没有逗号。
威尔

4
这个问题的对立面:bash函数的返回值
MSalters

Answers:


1618

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

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脚本指南


4
您忘记了空格,请尝试function name() {}。也许之前有一个“ enter”{}
lalo

21
好答案。我的2美分:在shell结构驻留在一个文件,是源(点)在需要的时候,我更喜欢使用的function关键字()。我的目标(在文件中,而不是在命令行中)是提高清晰度,而不是减少键入的字符数,即function myBackupFunction() compound-statement
特里·加德纳

22
@CMCDragonkai,function关键字版本是扩展名;另一种形式适用于所有POSIX兼容shell。
查尔斯·达菲

8
@TerryGardner,请考虑您尝试提高清晰度会降低兼容​​性。
查尔斯·达菲

6
@RonBurk,也许-但是,即使我们仅考虑清楚性,该function关键字在引入它的旧的ksh-family shell中也保证了现代bash不兑现(在此类shell中,function默认情况下将变量设置为local;在bash中, 它不是)。这样,它的使用会降低对ksh行为了解并可能期望的人的清晰度。参见wiki.bash-hackers.org/scripting/obsolete
Charles Duffy

68

熟悉高级编程语言(C / C ++ / Java / PHP / Python / Perl ...)会向外行暗示,bash函数应该像在其他语言中一样工作。相反,bash函数的工作方式类似于shell命令,并且期望将参数传递给它们的方式与可能将选项传递给shell命令的方式相同(例如ls -l)。实际上,bash中的函数参数被视为位置参数$1, $2..$9, ${10}, ${11},依此类推)。考虑到getopts工作原理,这不足为奇。不要使用括号在bash中调用函数。


注意:目前,我碰巧正在使用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

想要使用变量名称。就是这样

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

想要将数组传递给函数吗?

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

在函数内部,像这样处理参数。

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

需要传递值和数组,但仍在函数内部使用“ $ @”吗?

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

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

我开发的方法允许您定义传递给函数的命名参数,如下所示:

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

您还可以将参数注释为@required或@readonly,创建... rest参数,根据顺序参数创建数组(使用eg 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 param:
      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

3
好答案!我刚刚研究了Bash Infinity,它看起来确实很有帮助。谢谢!
乔纳森·霍尔特

谢谢@JonathanHult!实际上,我最近已经更新了上面的答案,现在它是一个较新的代码,已将代码片段重写为当前在Bash Infinity 2.0中的代码。我改写它的原因是因为旧的实现中存在一个错误(这在GitHub中的问题中)。还没有时间将新版本集成到Bash Infinity中。很高兴听到它的帮助。
niieani

嗨,@ niieani,当我尝试以您在回答中使用的形式创建bash函数时,它告诉我需要从apt安装ucommon utils。这是bash脚本的工作方式吗?我这样做正确吗?如果我了解您或其他人基本上构建了ucommon util程序以允许Bash扩展,是吗?
David A. French

@ DavidA.French不,这不应该发生。ucommon和我的代码之间没有关系。您可能安装了一些导致您提到的问题的工具,不知道会是什么。
niieani

27

错过括号和逗号:

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

该函数应如下所示:

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

8

我希望这个例子能对您有所帮助。它从用户那里获得两个数字,并将它们提供给所调用的函数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

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

6

一个简单的示例,它将在执行脚本期间或在调用函数时在脚本内部清除。

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

以为我会提到另一种将命名参数传递给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的另一种语法是使用nameref

尽管nameref可以无缝地取消引用,因此更加方便,但是某些受支持的较早发行版仍提供较旧的版本,因此我暂时不会推荐它。


“管道”。我看你在那儿干了什么!
Jacktose
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.