索引和修改 Bash 参数数组 $@

l0b*_*0b0 11 bash parameter

是否可以引用中的索引$@?我在GrayCat 的 wiki 中的任何地方都找不到任何可使用的参考,并且高级脚本指南其他人在修改它之前将其分配给不同的变量。

$ echo ${@[0]}
-bash: ${@[0]}: bad substitution
Run Code Online (Sandbox Code Playgroud)

目标是DRY:第一个参数用于一件事,其余用于其他用途,我想避免复制代码以进行规范化、$@数组或为此创建单独的函数(尽管在此点它可能是最简单的方法)。

说明:目的是修改可变长度 的值,$@使代码更易于调试。当前版本对我来说有点太hacky了,尽管它甚至适用于奇怪的路径,例如

$'--$`\! *@ \a\b\e\E\f\r\t\v\\\"\' \n'
Run Code Online (Sandbox Code Playgroud)

更新:看起来这是不可能的。代码现在同时使用代码和数据重复,但至少它可以工作:

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"
}
Run Code Online (Sandbox Code Playgroud)

赏金去的人谁可以摆脱的重复代码,以折叠重复斜线或重复的数据保存$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
Run Code Online (Sandbox Code Playgroud)

Gil*_*il' 17

POSIX

为了规范所有参数中的斜线,我将使用旋转参数技巧:$1移开,变换它并将结果放在参数列表的末尾。如果你这样做的次数和参数一样多,你就已经转换了所有的参数,而且你已经把它们按顺序恢复了。

对于代码的第二部分,我将您的逻辑更改为不那么混乱:外循环迭代参数,内循环迭代路径组件。for x; do … done迭代位置参数,这是一个方便的习惯用法。我使用符合 POSIX 的方式将字符串与模式匹配:case构造。

使用 dash 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 中没有):如果 case 模式是"${common_path%/}"/*,则其中一个测试失败。

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"
}
Run Code Online (Sandbox Code Playgroud)

bash,ksh

如果您使用 bash(或 ksh),则可以使用数组——我不明白为什么您似乎将自己限制在位置参数上。这是一个使用数组的版本。我不得不承认它并不比 POSIX 版本特别清晰,但它确实避免了最初的 n^2 改组。

对于斜线规范化部分,我使用 ksh93 构造${foo//PATTERN/REPLACEMENT}构造来替换所有出现的PATTERNin $fooby REPLACEMENT。模式是+(\/)匹配一个或多个斜线;在 bash 下,shopt -s extglob必须有效(相当于,以 开始 bash bash -O extglob)。该构造set ${!a[@]}将位置参数设置为数组的下标列表a。这提供了一种迭代数组元素的便捷方法。

对于第二部分,我使用与 POSIX 版本相同的循环逻辑。这一次,我可以使用,[[ … ]]因为这里针对的所有 shell 都支持它。

使用 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"
}
Run Code Online (Sandbox Code Playgroud)

zsh

遗憾的是,zsh 缺乏按${!array[@]}原样执行 ksh93 版本的功能。幸运的是,zsh 有两个特性使第一部分变得轻而易举。您可以像索引@数组一样索引位置参数,因此无需使用中间数组。并且 zsh 有一个数组迭代构造"${(@)array//PATTERN/REPLACEMENT}"依次对每个数组元素执行模式替换并计算结果数组(令人困惑的是,即使结果是多个单词,您也需要双引号;这是 的概括"$@")。第二部分基本不变。

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"
}
Run Code Online (Sandbox Code Playgroud)

测试用例

我的解决方案经过最低限度的测试和评论。我已经更改了您的测试用例的语法,以便在没有的 shell 下解析$'…'并以更方便的方式报告失败。

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
}
Run Code Online (Sandbox Code Playgroud)


pep*_*uan 6

为什么不直接使用 $1、$2 .. $9、${10}、${11}.. 等等?它比您尝试做的更干燥:)

更多关于 $ number和 $@之间的关系:

$@ 可以被认为是“包含所有参数的数组的所有元素”的简写

所以,$@ 是 ${args[@]} 的一种简写(这里的 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
Run Code Online (Sandbox Code Playgroud)

例子:

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


for*_*sck 5

第一个参数用于一件事,其余的用于另一件事,

我想你想要的是 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
Run Code Online (Sandbox Code Playgroud)