Bashパラメータ配列$ @のインデックス作成と変更


11

でインデックスを参照することは可能$@ですか?私は、GrayCatのwikiのどこにも次のように使用する参照を見つけることができません。代わりに、「高度なスクリプトガイド」など、これを変更する前に別の変数に割り当てます。

$ echo ${@[0]}
-bash: ${@[0]}: bad substitution

目標はDRYです。最初の引数は1つのことに使用され、残りは別の引数に使用されます。正規化するコード、$@配列、またはこのための個別の関数の重複を避けたいです(ただし、これはおそらくこれが最も簡単な方法です。

明確化:コードをデバッグしやすくするために、可変長 の値を変更することが目的でした。現在のバージョンは私の好みでは少しハックですが、次のような奇妙なパスでも機能します$@

$'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n'

更新:これは不可能のようです。コードはコードとデータの両方の複製を使用しますが、少なくとも機能します。

path_common()
{
    # Get the deepest common path.
    local common_path="$(echo -n "${1:-}x" | tr -s '/')"
    common_path="${common_path%x}"
    shift # $1 is obviously part of $1
    local path

    while [ -n "${1+defined}" ]
    do
        path="$(echo -n "${1}x" | tr -s '/')"
        path="${path%x}"
        if [[ "${path%/}/" = "${common_path%/}/"* ]]
        then
            shift
        else
            new_common_path="${common_path%/*}"
            [ "$new_common_path" = "$common_path" ] && return 1 # Dead end
            common_path="$new_common_path"
        fi
    done
    printf %s "$common_path"
}

バウンティは、コードの妥当なサイズを維持し、すべての単体テストを成功させながら、コードの重複を取り除き、重複するスラッシュまたは保持するデータの重複$1と他のパラメーター、またはその両方を折りたたむことができるすべての人を対象としています。

test "$(path_common /a/b/c/d /a/b/e/f; echo x)" = /a/bx
test "$(path_common /long/names/foo /long/names/bar; echo x)" = /long/namesx
test "$(path_common / /a/b/c; echo x)" = /x
test "$(path_common a/b/c/d a/b/e/f ; echo x)" = a/bx
test "$(path_common ./a/b/c/d ./a/b/e/f; echo x)" = ./a/bx
test "$(path_common $'\n/\n/\n' $'\n/\n'; echo x)" = $'\n/\n'x
test "$(path_common --/-- --; echo x)" = '--x'
test "$(path_common '' ''; echo x)" = x
test "$(path_common /foo/bar ''; echo x)" = x
test "$(path_common /foo /fo; echo x)" = x
test "$(path_common $'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n' $'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n'; echo x)" = $'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n'x
test "$(path_common /foo/bar //foo//bar//baz; echo x)" = /foo/barx
test "$(path_common foo foo; echo x)" = foox
test "$(path_common /fo /foo; echo x)" = x

回答:


16

POSIX

すべてのパラメーターのスラッシュを正規化するために、回転引数トリックを使用します。シフトオフ$1して変換し、パラメーターリストの最後に結果を配置します。パラメータの数だけこれを行うと、すべてのパラメータが変換され、順序どおりに戻されます。

コードの2番目の部分では、混乱を少なくするためにロジックを変更しました。外側のループはパラメーターに対して反復し、内側のループはパスコンポーネントに対して反復します。for x; do … done位置パラメータを反復処理します。これは便利なイディオムです。文字列をパターンと照合するPOSIX準拠の方法、つまりcase構成を使用します。

ダッシュ0.5.5.1、pdksh 5.2.14、bash 3.2.39、bash 4.1.5、ksh 93s +、zsh 4.3.10でテスト済み。

補足:bash 4.1.5にはバグがあるようです(3.2にはありません)。ケースパターンがの場合"${common_path%/}"/*、テストの1つが失敗します。

posix_path_common () {
  for tmp; do
    tmp=$(printf %s. "$1" | tr -s "/")
    set -- "$@" "${tmp%.}"
    shift
  done
  common_path=$1; shift
  for tmp; do
    while case ${tmp%/}/ in "${common_path%/}/"*) false;; esac; do
      new_common_path=${common_path%/*}
      if [ "$new_common_path" = "$common_path" ]; then return 1; fi
      common_path=$new_common_path
    done
  done
  printf %s "$common_path"
}

bash、ksh

bash(またはksh)を使用している場合は、配列を使用できます—位置パラメーターに制限されているように見える理由がわかりません。これは、配列を使用するバージョンです。私はそれがPOSIXバージョンよりも明確ではないことを認めなければなりませんが、それは最初のn ^ 2シャッフルを回避します。

スラッシュ正規化部分については、私はは、ksh93構築物使用${foo//PATTERN/REPLACEMENT}のすべての出現を交換する構造をPATTERNして$fooことでREPLACEMENT。パターンは+(\/)、1つ以上のスラッシュに一致することです。bashの下shopt -s extglobで有効でなければなりません(同等に、bashをで開始しますbash -O extglob)。構成set ${!a[@]}体は、位置パラメータを配列の添え字のリストに設定しますa。これは、配列の要素を反復処理する便利な方法を提供します。

2番目の部分では、POSIXバージョンと同じループロジックを使用しています。今回は、[[ … ]]ここで対象とするすべてのシェルがサポートしているので使用できます。

bash 3.2.39、bash 4.1.5、ksh 93s +でテスト済み。

array_path_common () {
  typeset a i tmp common_path new_common_path
  a=("$@")
  set ${!a[@]}
  for i; do
    a[$i]=${a[$i]//+(\/)//}
  done
  common_path=${a[$1]}; shift
  for tmp; do
    tmp=${a[$tmp]}
    while [[ "${tmp%/}/" != "${common_path%/}/"* ]]; do
      new_common_path="${common_path%/*}"
      if [[ $new_common_path = $common_path ]]; then return 1; fi
      common_path="$new_common_path"
    done
  done
  printf %s "$common_path"
}

zsh

悲しいことに、zshには${!array[@]}ksh93バージョンをそのまま実行する機能がありません。さいわい、zshには、最初の部分を簡単にする2つの機能があります。位置パラメータは@配列のようにインデックスを付けることができるため、中間配列を使用する必要はありません。また、zshには配列反復構造があります"${(@)array//PATTERN/REPLACEMENT}"各配列要素のパターン置換を順番に実行し、結果の配列に評価されます(混乱を招きますが、結果が複数の単語であっても二重引用符が必要です。これはの一般化です"$@")。2番目の部分は基本的に変更されていません。

zsh_path_common () {
  setopt local_options extended_glob
  local tmp common_path new_common_path
  set -- "${(@)@//\/##//}"
  common_path=$1; shift
  for tmp; do
    while [[ "${tmp%/}/" != "${common_path%/}/"* ]]; do
      new_common_path="${common_path%/*}"
      if [[ $new_common_path = $common_path ]]; then return 1; fi
      common_path="$new_common_path"
    done
  done
  printf %s "$common_path"
}

テストケース

私のソリューションは最小限にテストされ、コメントされています。テストケースの構文を変更して、シェルのないシェルで解析$'…'し、より便利な方法でエラーを報告するようにしました。

do_test () {
  if test "$@"; then echo 0; else echo $? "$@"; failed=$(($failed+1)); fi
}

run_tests () {
  function_to_test=$1; shift
  failed=0
  do_test "$($function_to_test /a/b/c/d /a/b/e/f; echo x)" = /a/bx
  do_test "$($function_to_test /long/names/foo /long/names/bar; echo x)" = /long/namesx
  do_test "$($function_to_test / /a/b/c; echo x)" = /x
  do_test "$($function_to_test a/b/c/d a/b/e/f ; echo x)" = a/bx
  do_test "$($function_to_test ./a/b/c/d ./a/b/e/f; echo x)" = ./a/bx
  do_test "$($function_to_test '
/
/
' '
/
'; echo x)" = '
/
'x
  do_test "$($function_to_test --/-- --; echo x)" = '--x'
  do_test "$($function_to_test '' ''; echo x)" = x
  do_test "$($function_to_test /foo/bar ''; echo x)" = x
  do_test "$($function_to_test /foo /fo; echo x)" = x
  do_test "$($function_to_test '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
' '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'; echo x)" = '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'x
  do_test "$($function_to_test /foo/bar //foo//bar//baz; echo x)" = /foo/barx
  do_test "$($function_to_test foo foo; echo x)" = foox
  do_test "$($function_to_test /fo /foo; echo x)" = x
  if [ $failed -ne 0 ]; then echo $failed failures; return 1; fi
}

1
+50、すごい。とにかく、私が求めていた以上に。あなたは、素晴らしいです。
l0b0

POSIXの説明では、スラッシュを正規化する最初のループで、なぜ「。」を追加するのか。sprintfで次の行でそれを取り除きますか?コードはそれなしでも機能するようですが、あなたが私が知らないエッジケースを処理していると思います。
Alan De Smet

1
@AlanDeSmet文字列が改行で終わる場合は、大文字と小文字が区別されます。コマンド置換により、末尾の改行が削除されます。
Gilles 'SO-悪をやめる'

6

$ 1、$ 2 .. $ 9、$ {10}、$ {11} ..などを使用しませんか?それはあなたがやろうとしていることよりもさらに乾燥しています:)

$ と$ @ の関係の詳細:

$ @は、「すべての引数を含む配列のすべての要素」の省略形と見なすことができます

したがって、$ @は$ {args [@]}の一種の省略表現です(ここでの引数は、すべての引数を含む「仮想」配列です。実際の変数ではありません。ご注意ください)。

$ 1は$ {args [1]}、$ 2は$ {args [2]}などです。

[9]を押したら、中かっこを使用します。$ {10}は$ {args [10]}、$ {11}は$ {args [11]}などです。


コマンドライン引数を間接的に使用する

argnum=3  # You want to get the 3rd arg
do-something ${!argnum}  # Do something with the 3rd arg

例:

argc=$#
for (( argn=1; argn<=argc; argn++)); do
    if [[ ${!argn} == "foo" ]]; then
        echo "Argument $argn of $argc is 'foo'"
    fi
done

$ * number *を使用する必要があることの明らかな欠点は、のようにインデックス変数を使用できないことです${args[$i]}
2011

@intuitedすると、間接参照が使用されます。回答を編集します。
pepoluan 2011

5

最初の引数は1つのことに使用され、残りは別の引数に使用されます。

あなたが欲しいのは shift

$ set one two three four five
$ echo $@
one two three four five
$ echo $1
one
$ foo=$1
$ echo $foo
one
$ shift
$ echo $@
two three four five
$ shift 2
$ echo $@
four five
$ echo $1
four

1

なぜ$ 1 $ 2などを使用しないのかはよくわかりませんが、これはあなたのニーズに合うかもしれません。

$ script "ed    it" "cat/dog"  33.2  \D  

  echo "-------- Either use 'indirect reference'"
  for ((i=1;i<=${#@};i++)) ;do
    #  eval echo \"\$$i\" ..works, but as *pepoluan* 
    #    has pointed out: echo "${!i}" ..is better.
    echo "${!i}"
  done
  echo "-------- OR use an array"
  array=("$@")
  for ((i=0;i<${#array[@]};i++)) ;do
    echo "${array[$i]}" 
  done
  echo "-------- OR use 'set'"
  set  "$@"
  echo "$1"
  echo "$2"
  echo "$3"
  echo "$4"

出力

  -------- Either use 'indirect reference'
  ed    it
  cat/dog
  33.2
  D
  -------- OR use an array
  ed    it
  cat/dog
  33.2
  D
  -------- OR use 'set'
  ed    it
  cat/dog
  33.2
  D

set $ 1、$ 2などを作成するためにそれに続くすべての動作。これはもちろん元の値をオーバーライドするので、注意してください。


ああ...それで、 'eval'は間接参照を意味します... $ {!var}構成は、私の回答で書いたように、より安全です
pepoluan

@pepoluan ...そのことを警告してくれてありがとう。書く方がはるかに簡単です...(私は今、私が参照したWebページに戻ったところです。さらに読んだのであれば、そこでも言及されているのを見たでしょう:( ....
Peter.O

へへ。しかし、間接参照が左側で発生する場合、evalは必要な悪ですtho ':)
pepoluan

@peopluan ...大丈夫、それを指摘してくれてありがとう...そして余談ですevalが、なぜそうであると考えられているのか理解できませんevil... evalが「悪い」場合、$ {!var}も等しく「悪い」ですか?...私にとって、それは言語の一部であり、有用な部分です。しかし、私は間違いなく$ {!var}を好みます...
Peter.O

1

注意:ファイル名にスペースを使用できます。

function SplitFilePath {
    IFS=$'/' eval "${1}"=\( \${2} \)
}
function JoinFilePath {
    IFS=$'/' eval echo -n \"\${*}\"
    [ $# -eq 1 -a "${1}" = "" ] && echo -n "/"
}
function path_common {
    set -- "${@//\/\///}"       ## Replace all '//' with '/'
    local -a Path1
    local -i Cnt=0
    SplitFilePath Path1 "${1}"
    IFS=$'/' eval set -- \${2} 
    for CName in "${Path1[@]}" ; do
        [ "${CName}" != "${1}" ] && break;
        shift && (( Cnt++ ))
    done
    JoinFilePath "${Path1[@]:0:${Cnt}}"
}

スペースを含むファイル名のテストケースを追加し、先頭に/がない2つのテストを修正しました

    do_test () {

  if test "${@}"; then echo 0; else echo $? "$@"; failed=$(($failed+1)); fi
}

run_tests () {
  function_to_test=$1; shift
  failed=0
  do_test "$($function_to_test /a/b/c/d /a/b/e/f; echo x)" = /a/bx
  do_test "$($function_to_test /long/names/foo /long/names/bar; echo x)" = /long/namesx
  do_test "$($function_to_test / /a/b/c; echo x)" = /x      
  do_test "$($function_to_test a/b/c/d a/b/e/f ; echo x)" = a/bx
  do_test "$($function_to_test ./a/b/c/d ./a/b/e/f; echo x)" = ./a/bx
  do_test "$($function_to_test '
/
/
' '
/
'; echo x)" = '
/
'x
  do_test "$($function_to_test --/-- --; echo x)" = '--x'
  do_test "$($function_to_test '' ''; echo x)" = x
  do_test "$($function_to_test /foo/bar ''; echo x)" = x
  do_test "$($function_to_test /foo /fo; echo x)" = /x      ## Changed from x
  do_test "$($function_to_test '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
' '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'; echo x)" = '--$`\! *@ \a\b\e\E\f\r\t\v\\\"'\'' 
'x
  do_test "$($function_to_test /foo/bar //foo//bar//baz; echo x)" = /foo/barx
  do_test "$($function_to_test foo foo; echo x)" = foox
  do_test "$($function_to_test /fo /foo; echo x)" = /x          ## Changed from x
  do_test "$($function_to_test "/fo d/fo" "/fo d/foo"; echo x)" = "/fo dx"

  if [ $failed -ne 0 ]; then echo $failed failures; return 1; fi
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.