是否可以参考 中的索引$@
?我在任何地方都找不到任何可以使用的参考灰猫的维基,以及高级脚本编写指南和其他的在修改之前将其分配给不同的变量。
$ 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
$foo
REPLACEMENT
+(\/)
shopt -s extglob
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"
}
桀骜
遗憾的是,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
}