2012-08-26 108 views
35

我必須在bash中編寫一個函數。該函數將需要大約7個參數。我知道我可以這樣調用一個函數:有沒有辦法避免在bash中的位置參數?

要調用函數與參數:

function_name $arg1 $arg2 

而且我可以參考這樣我的參數裏面的功能:

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

我的問題是,有沒有更好的方式參考函數內的參數?我可以避免$ 1,$ 2,$ 3,....這些東西,只需使用$ arg1,$ arg2,...?

是否有適當的方法,或者我需要重新分配這些參數給函數內的其他變量?例如:

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

任何例子將不勝感激。

+1

使用'$ 1'和'$ ARG1'有什麼區別? –

+0

@JonLin:我可以使用更直觀的名稱,而不是$ 1,$ 2。數字令人困惑。 – Bhushan

+2

你真的需要七個強制參數嗎?一種常見的方法是儘可能多地將選項變爲選項,並在有意義的地方提供默認值。如果您不必記住大量參數的特定位置順序,即使大多數用戶會在大多數時間覆蓋默認值,它也使您的腳本更容易使用。 – tripleee

回答

45

做的是,在功能分配參數局部變量的常用方法,即:

copy() { 
    local from=${1} 
    local to=${2} 

    # ... 
} 

另一種解決方案可能是getopt -style選項解析。

copy() { 
    local arg from to 
    while getopts 'f:t:' arg 
    do 
     case ${arg} in 
      f) from=${OPTARG};; 
      t) to=${OPTARG};; 
      *) return 1 # illegal option 
     esac 
    done 
} 

copy -f /tmp/a -t /tmp/b 

可悲的是,bash將無法處理長選項,這些選項更具可讀性,即:

copy --from /tmp/a --to /tmp/b 

對於這一點,您可能需要使用外部getopt程序(我認爲只在GNU系統上有很長的選項支持),或者手工實現長期選項解析器,即:

copy() { 
    local from to 

    while [[ ${1} ]]; do 
     case "${1}" in 
      --from) 
       from=${2} 
       shift 
       ;; 
      --to) 
       to=${2} 
       shift 
       ;; 
      *) 
       echo "Unknown parameter: ${1}" >&2 
       return 1 
     esac 

     if ! shift; then 
      echo 'Missing parameter argument.' >&2 
      return 1 
     fi 
    done 
} 

copy --from /tmp/a --to /tmp/b 

另見:using getopts in bash shell script to get long and short command line options


您也可以偷懶,只是通過「變量」作爲函數的自變量,即:

copy() { 
    local "${@}" 

    # ... 
} 

copy from=/tmp/a to=/tmp/b 

,你就會有在函數局部變量${from}${to}

請注意,同樣的問題適用於 - 如果一個特定的變量沒有通過,它將從父環境繼承。您可能需要添加一個「安全線」,如:

copy() { 
    local from to # reset first 
    local "${@}" 

    # ... 
} 

確保${from}${to}將給予取消不過去了。


如果有什麼非常糟糕是你的興趣,你也可以調用函數時指定的參數爲全局變量,即:

from=/tmp/a to=/tmp/b copy 

然後,你可以只使用${from}${to}copy()函數內。只需注意,你應該總是通過所有參數。否則,隨機變量可能泄漏到函數中。

from= to=/tmp/b copy # safe 
to=/tmp/b copy   # unsafe: ${from} may be declared elsewhere 

如果你有bash的4.1(我認爲),你也可以使用關聯數組嘗試。它會允許你傳遞命名參數,但它是醜陋的。喜歡的東西:

args=([from]=/tmp/a [to]=/tmp/b) 
copy args 

然後在copy(),你需要grab the array

+0

'local'方法:從=/tmp/a複製到=/tmp/b;看起來非常類似於如何調用Make target'copy'並將它傳遞給兩個新的環境變量(from和to)。我非常喜歡語法相似之處。 –

+0

@claytontstanley:是的,這是意圖;)。 –

+0

我對可選參數使用全局變量方法,但是我在函數後面命名了所述變量,並在檢查後在函數內清除了它。例如,如果我有一個'get_input()'的可選提示符,那麼我會使用'gi_prompt =「等等等等'',並在函數中'unset gi_prompt'。通過對與該函數相關的所有內容進行命名空間劃分,這可以防止泄漏和命名衝突,同時允許一些靈活性。 –

0

參數作爲單個項目的元組發送到函數,因此它們沒有名稱,只是位置。這允許下面的一些有趣的可能性,但這確實意味着你被困在1美元。 2美元等等,是否要將它們映射到更好的名稱,問題歸結爲函數的大小,以及讀取代碼的清晰程度。如果其複雜,那麼映射有意義的名稱($ BatchID,$ FirstName,$ SourceFilePath)是個不錯的主意。對於簡單的東西,但可能沒有必要。如果您使用像$ arg1這樣的名稱,我肯定不會打擾。

現在,如果你只是想回顯參數,你可以在它們之間迭代:

for $arg in "[email protected]" 
do 
    echo "$arg" 
done 

只是一個有趣的事實;除非你正在處理一個清單,你可能感興趣的財產以後更多的有用

3

對於在函數本身內用作局部變量的那些變量名,Shell函數可以完全訪問其調用範圍內可用的任何變量,除外)。另外,函數內部的任何非局部變量在函數調用後都可以在外部使用。考慮下面的例子:

A=aaa 
B=bbb 

echo "A=$A B=$B C=$C" 

example() { 
    echo "example(): A=$A B=$B C=$C" 

    A=AAA 
    local B=BBB 
    C=CCC 

    echo "example(): A=$A B=$B C=$C" 
} 

example 

echo "A=$A B=$B C=$C" 

該片段具有下列輸出:

A=aaa B=bbb C= 
example(): A=aaa B=bbb C= 
example(): A=AAA B=BBB C=CCC 
A=AAA B=bbb C=CCC 

這種方法的明顯缺點是功能不是自包含的任何更多的和,設置變量的函數以外可能會有意想不到的副作用。如果你想將數據傳遞給一個函數而不先把它分配給一個變量,這也會讓事情變得更加困難,因爲這個函數不再使用位置參數。

來處理這種情況最常見的方法是使用一個函數內局部變量的參數和任何臨時變量:

example() { 
    local A="$1" B="$2" C="$3" TMP="/tmp" 

    ... 
} 

這樣就避免了污染具有函數局部變量的外殼命名空間。

7

您可以隨時通過環境傳遞的東西:

#!/bin/sh 
foo() { 
    echo arg1 = $arg1 
    echo arg2 = $arg2 
} 

arg1=banana arg2=apple foo 
0

這是一個老話題,但我仍想與大家分享以下功能(需要的bash 4)。它分析命名參數並在腳本環境中設置變量。只要確保你對所有你需要的參數都有合理的默認值。最後的出口聲明也可能只是一個評估。結合使用shift可以擴展現有腳本,這些腳本已經佔據了一些位置參數,並且您不想更改語法,但仍然增加了一些靈活性。

parseOptions() 
{ 
    args=("[email protected]") 
    for opt in "${args[@]}"; do 
    if [[ ! "${opt}" =~ .*=.* ]]; then 
     echo "badly formatted option \"${opt}\" should be: option=value, stopping..." 
     return 1 
    fi 
    local var="${opt%%=*}" 
    local value="${opt#*=}" 
    export ${var}="${value}" 
    done 
    return 0 
} 
1

我想我有一個解決方案給你。 通過一些技巧,您可以將命名參數與數組一起傳遞給函數。

我公司開發的方法可以讓你訪問傳遞給這樣的函數參數:

testPassingParams() { 

    @var hello 
    l=4 @array anArrayWithFourElements 
    l=2 @array anotherArrayWithTwo 
    @var anotherSingle 
    @reference table # references only work in bash >=4.3 
    @params anArrayOfVariedSize 

    test "$hello" = "$1" && echo correct 
    # 
    test "${anArrayWithFourElements[0]}" = "$2" && echo correct 
    test "${anArrayWithFourElements[1]}" = "$3" && echo correct 
    test "${anArrayWithFourElements[2]}" = "$4" && echo correct 
    # etc... 
    # 
    test "${anotherArrayWithTwo[0]}" = "$6" && echo correct 
    test "${anotherArrayWithTwo[1]}" = "$7" && echo correct 
    # 
    test "$anotherSingle" = "$8" && echo correct 
    # 
    test "${table[test]}" = "works" 
    table[inside]="adding a new value" 
    # 
    # I'm using * just in this example: 
    test "${anArrayOfVariedSize[*]}" = "${*:10}" && echo correct 
} 

fourElements=(a1 a2 "a3 with spaces" a4) 
twoElements=(b1 b2) 
declare -A assocArray 
assocArray[test]="works" 

testPassingParams "first" "${fourElements[@]}" "${twoElements[@]}" "single with spaces" assocArray "and more... " "even more..." 

test "${assocArray[inside]}" = "adding a new value" 

換句話說,不僅可以直呼其名的參數(這對於一個更可讀的核心組成),你實際上可以傳遞數組(以及對變量的引用 - 儘管這個特性只能用於bash 4.3)!另外,映射變量都在本地範圍內,就像$ 1(和其他)一樣。

使這項工作非常輕巧的代碼,並在bash 3和bash 4(這些都是我測試過的唯一版本)中都有效。如果您對這樣的更多技巧感興趣,這些技巧可以使bash更好更簡單地進行開發,您可以查看我的Bash Infinity Framework,下面的代碼是爲此目的而開發的。

Function.AssignParamLocally() { 
    local commandWithArgs=($1) 
    local command="${commandWithArgs[0]}" 

    shift 

    if [[ "$command" == "trap" || "$command" == "l="* || "$command" == "_type="* ]] 
    then 
     paramNo+=-1 
     return 0 
    fi 

    if [[ "$command" != "local" ]] 
    then 
     assignNormalCodeStarted=true 
    fi 

    local varDeclaration="${commandWithArgs[1]}" 
    if [[ $varDeclaration == '-n' ]] 
    then 
     varDeclaration="${commandWithArgs[2]}" 
    fi 
    local varName="${varDeclaration%%=*}" 

    # var value is only important if making an object later on from it 
    local varValue="${varDeclaration#*=}" 

    if [[ ! -z $assignVarType ]] 
    then 
     local previousParamNo=$(expr $paramNo - 1) 

     if [[ "$assignVarType" == "array" ]] 
     then 
      # passing array: 
      execute="$assignVarName=(\"\${@:$previousParamNo:$assignArrLength}\")" 
      eval "$execute" 
      paramNo+=$(expr $assignArrLength - 1) 

      unset assignArrLength 
     elif [[ "$assignVarType" == "params" ]] 
     then 
      execute="$assignVarName=(\"\${@:$previousParamNo}\")" 
      eval "$execute" 
     elif [[ "$assignVarType" == "reference" ]] 
     then 
      execute="$assignVarName=\"\$$previousParamNo\"" 
      eval "$execute" 
     elif [[ ! -z "${!previousParamNo}" ]] 
     then 
      execute="$assignVarName=\"\$$previousParamNo\"" 
      eval "$execute" 
     fi 
    fi 

    assignVarType="$__capture_type" 
    assignVarName="$varName" 
    assignArrLength="$__capture_arrLength" 
} 

Function.CaptureParams() { 
    __capture_type="$_type" 
    __capture_arrLength="$l" 
} 

alias @trapAssign='Function.CaptureParams; trap "declare -i \"paramNo+=1\"; Function.AssignParamLocally \"\$BASH_COMMAND\" \"\[email protected]\"; [[ \$assignNormalCodeStarted = true ]] && trap - DEBUG && unset assignVarType && unset assignVarName && unset assignNormalCodeStarted && unset paramNo" DEBUG; ' 
alias @param='@trapAssign local' 
alias @reference='_type=reference @trapAssign local -n' 
alias @var='_type=var @param' 
alias @params='_type=params @param' 
alias @array='_type=array @param' 
+0

謝謝。我會試試看。 – Bhushan

+0

@Bhushan我剛剛更新了一個更多的代碼 - 傳遞引用,它是在bash 4.3中引入的。除此之外,它還可以將關聯數組傳遞給函數。 – niieani

1

我個人希望看到某種語法像

func(a b){ 
    echo $a 
    echo $b 
} 

但因爲這不是一個東西,和我看到的全局變量不少引用(沒有作用域的警告和命名衝突),我會分享我的方法。

使用copy功能從Michal's answer

copy(){ 
    cp $from $to 
} 
from=/tmp/a 
to=/tmp/b 
copy 

這是不好的,因爲fromto是任何數量的功能可以使用這個如此廣泛的詞。你可能會很快結束命名衝突或手中的「泄漏」。

letter(){ 
    echo "From: $from" 
    echo "To: $to" 
    echo 
    echo "$1" 
} 

to=Emily 
letter "Hello Emily, you're fired for missing two days of work." 

# Result: 
# From: /tmp/a 
# To: Emily 

# Hello Emily, you're fired for missing two days of work. 

所以我的方法是「命名空間」他們。我在函數之後命名變量,並在函數完成後將其刪除。當然,我只將它用於具有默認值的可選值。否則,我只是使用位置參數。

copy(){ 
    if [[ $copy_from ]] && [[ $copy_to ]]; then 
     cp $copy_from $copy_to 
     unset copy_from copy_to 
    fi 
} 
copy_from=/tmp/a 
copy_to=/tmp/b 
copy # Copies /tmp/a to /tmp/b 
copy # Does nothing, as it ought to 
letter "Emily, you're 'not' re-hired for the 'not' bribe ;)" 
# From: (no /tmp/a here!) 
# To: 

# Emily, you're 'not' re-hired for the 'not' bribe ;) 

我會做一個可怕的老闆......


在實踐中,我的函數名是不是「複製」或「信」更詳盡。

我記憶中最近的例子是get_input(),它有gi_no_sortgi_prompt

  • gi_no_sort是一個true/false值,它確定完成建議是否已排序。默認爲真
  • gi_prompt是一個字符串,這是...好吧,這是不言自明的。默認爲「」。

的實際參數的函數採用是用於輸入提示上述「完成建議」的源極,並作爲所述列表從[email protected]在功能服用,「命名ARGS」是可選的[1],並且沒有明顯的方法來區分作爲完成的字符串和布爾/提示消息,或者真的在bash中用空格分開的任何東西,對於這個問題[2];上述解決方案最終爲我節省了一大筆的麻煩。

筆記:

  1. 所以硬編碼shift$1$2等方面都出了問題。

  2. E.g.是"0 Enter a command: {1..9} $(ls)"的值爲0,"Enter a command:",和一組1 2 3 4 5 6 7 8 9 <directory contents>?或者是"0","Enter","a""command:"那個集合的一部分呢?無論你喜歡與否,Bash都會認爲後者。

0

您所要做的就是在函數調用的路上輸入名稱變量。

function test() { 
    echo $a 
} 

a='hello world' test 
#prove variable didnt leak 
echo $a . 

enter image description here

這不是功能只是一個功能,你可以有一個函數在它自己的腳本,調用a='hello world' test.sh,它會工作得同樣


作爲一個額外的一點樂趣,你可以將這個方法與位置參數結合起來(比如說你正在創建一個腳本,而有些用戶可能不知道變量名)。
哎呀,爲什麼不讓它有這些參數的默認值呢?好吧,很容易!

function test2() { 
    [[ -n "$1" ]] && local a="$1"; [[ -z "$a" ]] && local a='hi' 
    [[ -n "$2" ]] && local b="$2"; [[ -z "$b" ]] && local b='bye' 
    echo $a $b 
} 

#see the defaults 
test2 

#use positional as usual 
test2 '' there 
#use named parameter 
a=well test2 
#mix it up 
b=one test2 nice 

#prove variables didnt leak 
echo $a $b . 

enter image description here

注意,如果test是它自己的腳本,你並不需要使用local關鍵字。

相關問題