获取传递给Shell脚本的最后一个参数

332

$1是第一个参数。
$@代表所有参数。

如何找到传递给Shell脚本的最后一个参数?


我之前在使用Bash,但更加易携带的方案会更好。 - Thomas
7
http://www.faqs.org/faqs/unix-faq/faq/part2/section-12.html - Inshallah
10
可以使用 ${ !# }。 - Prateek Joshi
13
仅适用于 [tag:bash],Kevin Little 的答案 提出了简单的 ${!#}。 可以使用 bash -c 'echo ${!#}' arg1 arg2 arg3 进行测试。 对于 [tag:bash],[tag:ksh] 和 [tag:zsh],Dennis Williamson 的答案 提出了 ${@: -1}。此外 ${*: -1} 也可以使用。 可以使用 zsh -c 'echo ${*: -1}' arg1 arg2 arg3 进行测试。 但是这对于 [tag:dash],[tag:csh] 和 [tag:tcsh] 不起作用。 - oHo
3
${!#}${@: -1}不同,它也可以与参数扩展一起使用。您可以使用bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out进行测试。 - Arch Stanton
注意:${!#}zsh中不起作用。对于在{ba,z}sh中都有效的解决方案,请参见此处 - Tom Hale
29个回答

338

这仅适用于Bash:

echo "${@: -1}"

69
对于那些像我一样想知道为什么需要空格的人,man bash 对此有如下解释:请注意,负偏移量必须与冒号至少隔开一个空格,以避免与“:-”扩展混淆。 - foo
1
我一直在使用它,但只有在Windows的MSYS2 bash中会出现错误。太奇怪了。 - Steven Lu
3
注意:这个答案适用于所有 Bash 数组,不像 ${@:$#} 只适用于 $@。 如果你复制 $@ 到一个新数组里,例如 arr=("$@")${arr[@]:$#} 将会是未定义的。这是因为 $@ 有一个零索引元素没有包含在 "$@" 的展开中。 - Mr. Llama
1
@Mr.Llama:另一个要避免使用$#的地方是在迭代数组时。因为在Bash中,数组是稀疏的,而$#会显示数组中的元素数量,但它不一定指向最后一个元素(或元素+1)。换句话说,不能做 for ((i = 0; i++; i < $#)); do something "${array[$i]}"; done,而应该这样做:for element in "${array[@]}"; do something "$element"; done,或者迭代索引:for index in "${!array[@]}"; do something "$index" "${array[$index]}"; done,如果您需要对索引值进行操作。 - Dennis Williamson
注意 ':' 和 '-' 之间的空格! :) 我第一次错过了这个... - TrueY
显示剩余3条评论

204

这有点像是一种技巧:

for last; do true; done
echo $last

这个方法也非常便携(同样适用于bash、ksh和sh),并且不会移动参数,这可能会很不错。

它利用了for循环默认循环参数的特性,如果你不告诉它要循环什么,它就会循环参数,并且for循环变量没有作用域限制:它们保留了最后设置的值。


10
@MichałŠrajer,我想你的意思是“冒号”而不是“逗号” ;) - Paweł Nadolski
3
“true” 是 POSIX 的一部分。详见 http://pubs.opengroup.org/onlinepubs/009695399/utilities/true.html。 - Rufflewind
5
在旧版本的Solaris操作系统中,使用旧版Bourne Shell(非POSIX标准),我需要编写“for last in”$@“; do true; done”来实现某个功能。 - mcoolive
11
除了更加便携之外,“for last in "$@"; do :; done”也让意图更加清晰。 - Adrian Günter
3
甚至在1979年的Unix V7 Bourne Shell中也能运行。如果它可以在V7 Sh和POSIX Sh上运行,那么就没有更加可移植的了 :) - Dominik R
显示剩余3条评论

100
$ set quick brown fox jumps

$ echo ${*: -1:1} # last argument
jumps

$ echo ${*: -1} # or simply
jumps

$ echo ${*: -2:1} # next to last
fox

添加空格是为了避免其被解释为默认值

请注意这仅适用于bash。


11
最佳答案,因为它还包括倒数第二个参数。谢谢! - e40
2
Steven,我不知道你是怎么进入“罚款盒子”的,但我非常喜欢你在这里的工作。 - Bruno Bronosky
4
是的。简单来说,这是最好的方法。除了命令和最后一个参数 ${@: 1:$#-1} - Dyno Fu
1
@DynoFu 谢谢你的回答,解决了我的下一个问题。因此,移位可能看起来像这样:echo ${@: -1} ${@: 1:$#-1},其中最后一个变成了第一个,其余依次向下滑动。 - Mike

79

对于 Bash 3.0 或更高版本,最简单的答案是:

_last=${!#}       # *indirect reference* to the $# variable
# or
_last=$BASH_ARGV  # official built-in (but takes more typing :)

就是这样。

$ cat lastarg
#!/bin/bash
# echo the last arg given:
_last=${!#}
echo $_last
_last=$BASH_ARGV
echo $_last
for x; do
   echo $x
done

输出结果为:

$ lastarg 1 2 3 4 "5 6 7"
5 6 7
5 6 7
1
2
3
4
5 6 7

2
$BASH_ARGV在bash函数内部不起作用(除非我做错了什么)。 - Big McLargeHuge
1
BASH_ARGV包含的是bash被调用时(或函数被调用时)的参数,而不是当前位置参数列表。 - user8017719
请注意,BASH_ARGV将为您提供的是最后一个参数的值,而不仅仅是“最后一个值”。例如:如果您提供了一个单一的参数,然后调用shift,${@:$#}将不会产生任何结果(因为您已经移除了唯一的参数!),但是,BASH_ARGV仍将为您提供该(以前的)最后一个参数。 - Steven Lu
注意,当使用 sh XXX.sh 1 2 3 执行脚本时,${!#} 不会得到任何东西。 - roamer
@roamer - 这只适用于 bash (/bin/bash)。你在 POSIX sh (/bin/sh) 中运行它,在许多系统上这不是 bash。 - Adam Katz

40
以下内容适用于您。 @代表参数数组。 :表示在。 $#是参数数组的长度。 因此结果是最后一个元素:
${@:$#} 

例子:

function afunction{
    echo ${@:$#} 
}
afunction -d -o local 50
#Outputs 50

请注意,这只适用于Bash。


虽然这个例子是针对函数的,但脚本也是同样的方式工作。我喜欢这个答案,因为它清晰而简洁。 - Jonah Braun
1
而且这并不是hacky的。它使用语言的显式特性,而不是副作用或特殊的qwerks。这应该是被接受的答案。 - musicin3d

35

使用索引和长度组合:

echo ${@:${#@}} 

请注意,这仅适用于Bash。


3
回显 $(@&$;&^$&%@^!@%^#**#&@#@#@(&*#_*^@^&(^@&&@) - Atul
2
@Atul,你能解释一下你有趣的命令吗? - Isin Altinkaya

30

在试图将最后一个参数与之前的所有参数分离时,发现了这个问题。虽然有些答案确实可以得到最后一个参数,但如果您需要所有其他参数,它们并没有太大帮助。以下方法效果更好:

heads=${@:1:$#-1}
tail=${@:$#}

请注意,这仅适用于Bash。


3
Steven Penny的答案 更好一些:使用${@: -1}获取最后一个参数,${@: -2:1}获取倒数第二个参数(以此类推)。例如:bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6会输出6。如果要沿用当前AgileZebra的方法,则使用${@:$#-1:1}来获取倒数第二个参数,例如bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6会输出5。(${@:$#-2:1}用于获取倒数第三个参数,以此类推...) - oHo
4
AgileZebra的答案提供了一种获取除了最后一个参数之外所有参数的方法,因此我不会说Steven的答案取代了它。然而,似乎没有理由使用$((...))来减去1,你可以直接使用${@:1:$#-1} - dkasak
感谢 dkasak。已更新以反映您的简化。 - AgileZebra

24

2
请参考附在一个旧的相同答案上的这个评论 - Dennis Williamson
1
这是我看到的最简单的便携式解决方案。这个没有安全问题,@DennisWilliamson,引用经验似乎做得很好,除非有一种方法将$#设置为任意字符串(我不认为会有)。eval last=\"\${$#}\"也可以工作,并且显然是正确的。不明白为什么不需要引号。 - Palec
1
对于 [tag:bash]、[tag:zsh]、[tag:dash] 和 [tag:ksh],eval last=\"\${$#}\" 是可以的。但是对于 [tag:csh] 和 [tag:tcsh],请使用 eval set last=\"\${$#}\"。参考以下示例:tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3 - oHo

19

这是我的解决方案:

  • 非常便携(所有的POSIX sh、bash、ksh、zsh都可以使用)
  • 不会移动原始参数(移动副本)。
  • 不使用 邪恶的 eval
  • 不会遍历整个列表
  • 不使用外部工具

代码:

ntharg() {
    shift $1
    printf '%s\n' "$1"
}
LAST_ARG=`ntharg $# "$@"`

1
很棒的答案 - 简洁、便携、安全。谢谢! - Ján Lalinský
2
这是一个很好的想法,但我有几个建议:首先应该在"$1""$#"周围添加引号(请参见此优秀答案https://unix.stackexchange.com/a/171347)。其次,`echo`不幸地不可移植(特别是对于`-n`),因此应改用`printf '%s\n' "$1"`。 - joki
感谢@joki,我曾经使用过许多不同的Unix系统,我也不相信echo -n,但是我不知道任何一个POSIX系统会在echo "$1"失败。无论如何,printf确实更可预测-已更新。 - Michał Šrajer
2
另一种写法:last() { shift $(($# - 1));printf %s "$1";} - Léa Gris
1
@MichałŠrajer 如果你被指派编写40年历史的遗留SCO Unix脚本,那么你很可能熟悉man页面和印刷手册,因为使用Mozaic浏览stackoverflow本身就会非常具有挑战性。 - Léa Gris
显示剩余4条评论

11

从最古老到较新的解决方案:

最便携的解决方案,甚至包括旧版本的 sh (适用于空格和通配符字符)(无循环,更快):

eval printf "'%s\n'" "\"\${$#}\""

自bash 2.01版本以来

$ set -- The quick brown fox jumps over the lazy dog

$ printf '%s\n'     "${!#}     ${@:(-1)} ${@: -1} ${@:~0} ${!#}"
dog     dog dog dog dog

对于ksh、zsh和bash:

$ printf '%s\n' "${@: -1}    ${@:~0}"     # the space beetwen `:`
                                          # and `-1` is a must.
dog   dog

而对于“倒数第二个”:

$ printf '%s\n' "${@:~1:1}"
lazy

使用printf来解决任何以破折号(如-n)开头的参数问题。

对于所有类型的shell和旧版的sh(适用于包含空格和通配符的情况),可采用以下方法:

$ set -- The quick brown fox jumps over the lazy dog "the * last argument"

$ eval printf "'%s\n'" "\"\${$#}\""
The last * argument

或者,如果您想设置一个last变量:

$ eval last=\${$#}; printf '%s\n' "$last"
The last * argument

而对于“倒数第二个”:

$ eval printf "'%s\n'" "\"\${$(($#-1))}\""
dog

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接