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

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

是否可以参考 中的索引$@?我在任何地方都找不到任何可以使用的参考灰猫的维基,以及高级脚本编写指南其他的在修改之前将其分配给不同的变量。

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

目标是干燥:第一个参数用于一件事,其余的用于其他事情,我想避免重复规范化的代码、数组$@,或为此创建一个单独的函数(尽管此时它可能是最简单的出路)。

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

$'--$`\! *@ \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

答案1

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

bash、ksh

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

对于斜杠规范化部分,我使用 ksh93 构造函数将所有出现的in${foo//PATTERN/REPLACEMENT}替换为。该模式用于匹配一个或多个斜线;在 bash 下,必须有效(相当于使用 启动 bash )。该构造将位置参数设置为数组的下标列表。这提供了一种迭代数组元素的便捷方法。PATTERN$fooREPLACEMENT+(\/)shopt -s extglobbash -O extglobset ${!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"
}

桀骜

遗憾的是,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"
}

测试用例

我的解决方案只经过了最低限度的测试和注释。我已更改测试用例的语法,以便在没有的 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
}

答案2

为什么不直接使用 $1、$2 .. $9、${10}、${11}.. 等等?更是干燥-比你想要做的更重要:)

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

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

因此,$@ 是 ${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

例子:

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

答案3

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

我想你想要的是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

答案4

注意我支持文件名中的空格。

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
}

相关内容