70

私はbashで関数を書かなければなりません。この関数は約7つの引数を取ります。私は次のような関数を呼び出すことができることを知っています:

パラメータを使用して関数を呼び出すには:

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"
}

どんな例でも大歓迎です。

4

8 に答える 8

101

これを行う一般的な方法は、関数内のローカル変数に引数を割り当てることです。

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

    # ...
}

別の解決策は、getoptスタイルのオプション解析です。

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

参照:bashシェルスクリプトでgetoptsを使用して、長いコマンドラインオプションと短いコマンドラインオプションを取得する


怠惰になることもでき、関数への引数として「変数」を渡すだけです。

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()を取得する必要があります。

于 2012-08-26T07:41:58.617 に答える
15

あなたはいつでも環境を通して物事を渡すことができます:

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

arg1=banana arg2=apple foo
于 2012-08-26T11:05:32.643 に答える
9

あなたがしなければならないのは、関数呼び出しに入る途中で変数に名前を付けることだけです。

function test() {
    echo $a
}

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

ここに画像の説明を入力してください

これは関数の機能だけではありません。その関数を独自のスクリプトと呼び出し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 .

ここに画像の説明を入力してください

testが独自のスクリプトである場合は、localキーワードを使用する必要がないことに注意してください。

于 2017-05-15T02:47:57.263 に答える
4

シェル関数は、関数自体の内部でローカル変数として使用される変数名を除いて、呼び出し元のスコープで使用可能なすべての変数に完全にアクセスできます。さらに、関数内で設定された非ローカル変数は、関数が呼び出された後、外部で使用できます。次の例を考えてみましょう。

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"

   ...
}

これにより、シェル名前空間が関数ローカル変数で汚染されるのを防ぐことができます。

于 2012-08-26T07:51:10.233 に答える
2

私はあなたのための解決策があると思います。いくつかのトリックを使用して、名前付きパラメーターを配列とともに関数に実際に渡すことができます。

私が開発したメソッドを使用すると、次のような関数に渡されるパラメーターにアクセスできます。

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(およびその他)と同様に、すべてローカルスコープにあります。

この動作を実現するコードは非常に軽量で、bash3とbash4の両方で動作します(これらは私がテストした唯一のバージョンです)。bashを使用した開発をより便利で簡単にするこのようなトリックに興味がある場合は、私のBashInfinityFrameworkをご覧ください。以下のコードはその目的のために開発されました。

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\" \"\$@\"; [[ \$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'
于 2015-05-04T13:46:15.437 に答える
1

私は個人的に次のような構文を見たいと思っていました

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

しかし、それは問題ではなく、グローバル変数への参照がかなりあるので(スコープと名前の競合の警告がないわけではありません)、私のアプローチを共有します。

Michalの答えcopyからの関数を使用する:

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

これは悪いことです。なぜならfrom、とtoは非常に広い単語であるため、任意の数の関数がこれを使用できるからです。すぐに名前の競合や「リーク」が発生する可能性があります。

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_sort、とgi_promptです。

  • gi_no_sort完了候補がソートされているかどうかを決定するtrue/false値です。デフォルトはtrue
  • gi_promptは文字列です...まあ、それは自明です。デフォルトは「」です。

関数が取る実際の引数は、入力プロンプトに対する前述の「完了の提案」のソースであり、前述のリストは$@関数から取得されるため、「名前付き引数」はオプションであり[1]、区別する明確な方法はありません。完了を意味する文字列とブール/プロンプトメッセージ、または実際にはbashでスペースで区切られたものの間[2] ; 上記の解決策は私に多くのトラブルを救うことになりました。

ノート:

  1. したがって、ハードコーディングされた、、shiftなど$1$2問題外です。

  2. たとえば、、、"0 Enter a command: {1..9} $(ls)"の値0"Enter a command:"および1 2 3 4 5 6 7 8 9 <directory contents>?のセットです。または、、、、"0"およびそのセットの一部でもありますか"Enter"?Bashは、好むと好まざるとにかかわらず、後者を想定します。"a""command:"

于 2015-09-04T04:31:24.027 に答える
0

引数は個々のアイテムのタプルとして関数に送信されるため、名前自体はなく、位置のみが指定されます。これにより、以下のようないくつかの興味深い可能性が可能になりますが、それはあなたが$1で立ち往生していることを意味します。2ドルなど、それらをより適切な名前にマップするかどうかについては、関数の大きさ、およびコードの読み取りがどれだけ明確になるかが問題になります。複雑な場合は、意味のある名前($ BatchID、$ FirstName、$ SourceFilePath)をマッピングすることをお勧めします。ただし、単純なものの場合は、おそらく必要ありません。$ arg1のような名前を使用している場合、私は確かに気にしません。

これで、パラメーターをエコーバックするだけの場合は、パラメーターを反復処理できます。

for $arg in "$@"
do
  echo "$arg"
done

ただの楽しい事実。リストを処理しているのでない限り、おそらくもっと便利なものに興味があるでしょう

于 2012-08-26T07:28:54.900 に答える
0

これは古いトピックですが、それでも以下の機能を共有したいと思います(bash 4が必要です)。名前付き引数を解析し、スクリプト環境で変数を設定します。必要なすべてのパラメータに適切なデフォルト値があることを確認してください。最後のexportステートメントもevalである可能性があります。シフトと組み合わせて、すでにいくつかの位置パラメーターを取り、構文を変更したくない既存のスクリプトを拡張するのに最適ですが、それでもある程度の柔軟性が追加されます。

parseOptions()
{
  args=("$@")
  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
}
于 2014-08-19T11:47:27.283 に答える