将最后一个参数传递给Shell脚本


272

$1 是第一个论点。
$@是所有人。

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


我使用的是bash,但解决方案越轻便越好。
Thomas


10
使用也可以使用$ {!#}
Prateek Joshi 2015年

11
对于只庆典中,凯文小的回答提出的简单${!#}。使用进行测试bash -c 'echo ${!#}' arg1 arg2 arg3。对于bashkshzsh,建议使用Dennis Williamson的答案${@: -1}。而且${*: -1}也可以使用。使用进行测试zsh -c 'echo ${*: -1}' arg1 arg2 arg3。但这不适用于dashcshtcsh
olibre

2
${!#}不同于${@: -1},它也适用于参数扩展。您可以使用进行测试bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out
斯坦顿

Answers:


177

这有点hack:

for last; do true; done
echo $last

这个也是非常可移植的(再次,应该与bash,ksh和sh一起使用)并且它不会改变参数,这可能很好。

它使用这样的事实:for如果您不告诉它要进行循环的内容,则隐式地循环遍历参数;并且,事实是for循环变量没有范围:它们保留设置为的最后一个值。


10
@MichałŠrajer,我想您的意思是冒号而不是逗号;)
PawełNadolski 2013年

2
@MichałŠrajer true是POSIX的一部分
Rufflewind

4
对于旧的Solaris和旧的bourne shell(不是POSIX),我必须写“
forest

8
@mcoolive @LaurenceGolsalves除了更便于携带外,for last in "$@"; do :; done还使意图更加清晰。
阿德里安·昆特

1
@mcoolive:它甚至可以在1979年的unix v7 bourne外壳中使用。如果它同时在v7 sh和POSIX sh上运行,您将无法再移植:)
Dominik R

291

这仅适用于Bash:

echo "${@: -1}"

43
对于那些(想像我)想知道为什么需要空格的人,man bash可以这样说:>请注意,负偏移量必须与冒号分隔至少一个空格,以免与:-扩展混淆。

1
我一直在使用它,它仅在Windows中破坏MSYS2 bash。奇怪
史蒂文·卢

2
注意:此答案适用于所有 Bash阵列,与之不同的${@:$#}是仅适用于$@。如果要使用复制$@到新数组arr=("$@")${arr[@]:$#}则未定义。这是因为扩展中$@未包含第0个元素"$@"
拉玛先生

@ Mr.Llama:另一个要避免的地方$#是遍历数组,因为在Bash中,数组是稀疏的,虽然$#会显示数组中的元素数量,但不一定指向最后一个元素(或element + 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如果您需要对索引值进行操作。
暂停,直到另行通知。

80
$ set quick brown fox jumps

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

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

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

该空间是必需的,这样它就不会被解释为默认值

请注意,这仅适用于bash。


9
最佳答案,因为它还包括倒数第二个arg。谢谢!
e40 2013年

1
史蒂文(Steven),我不知道您在“刑罚箱”中做了什么,但我喜欢您在这里所做的工作。
Bruno Bronosky

4
是。简直是最好的。除了命令和最后论点之外的所有内容 ${@: 1:$#-1}
Dyno Fu

1
@DynoFu谢谢您,您回答了我的下一个问题。因此,变化可能看起来像是:echo ${@: -1} ${@: 1:$#-1},最后一个变为第一个,其余的向下滑动
Mike

73

对于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

1
$BASH_ARGV不能在bash函数内部工作(除非我做错了)。
Big McLargeHuge 2014年

1
Bash_ARGV具有调用bash(或调用函数)时的参数,而不是当前位置参数列表。
艾萨克(Isaac)

还请注意,BASH_ARGV将产生的是给定的最后一个arg的值,而不是简单的“最后一个值”。例如!:如果仅提供一个参数,则调用shift ${@:$#}不会产生任何结果(因为您移出了唯一的一个参数!),但是,BASH_ARGV仍然会(以前)为您提供最后一个参数。
史蒂文·卢


29

以下将为您工作。@是参数数组。:表示于。$#是参数数组的长度。所以结果是最后一个元素:

${@:$#} 

例:

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

请注意,这仅适用于bash。


尽管该示例是针对函数的,但脚本也以相同的方式工作。我喜欢这个答案,因为它简洁明了。
乔纳·布劳恩

1
而且它不是hacky。它使用语言的显式功能,而不是副作用或特殊qwerks。这应该是公认的答案。
musicin3d

25

试图将最后一个参数与所有先前的参数分开时发现了这一点。虽然某些答案确实有最后一个论点,但如果您还需要其他所有参数,它们对您的帮助不大。这样效果更好:

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

请注意,这仅适用于bash。


3
史蒂芬竹篙答案是更好一点:使用${@: -1}最后,并${@: -2:1}倒数第二(等等...)。示例:bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6prints 6。为了保持这种电流AgileZebra的方法,使用${@:$#-1:1}获得倒数第二。示例:bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6prints 5。(并${@:$#-2:1}获得第三名,依此类推...)
olibre 2015年

4
AgileZebra的答案提供了一种获取除最后论点之外的所有答案的方法,因此我不会说Steven的答案会取代它。但是,似乎没有理由使用$((...))减去1,您可以简单地使用${@:1:$# - 1}
dkasak

谢谢dkasak。更新以反映您的简化。
AgileZebra

22

这适用于所有POSIX兼容的shell:

eval last=\${$#}

资料来源:http : //www.faqs.org/faqs/unix-faq/faq/part2/section-12.html


2
请参阅附加在较旧的相同答案上的此注释
暂停,直到另行通知。

1
我在这里看到的最简单的便携式解决方案。@DennisWilliamson,这没有安全性问题,根据经验,引用似乎是正确的,除非有一种设置$#任意字符串的方法(我认为不是)。eval last=\"\${$#}\"也可以,显然是正确的。不明白为什么不需要引号。
Palec 2014年

1
对于bashzshdashksh eval last=\"\${$#}\"都可以。但是对于cshtcsh使用eval set last=\"\${$#}\"。请参见以下示例:tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3
olibre

12

这是我的解决方案:

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

码:

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

1
好的答案-简短,便携,安全。谢谢!
扬·拉林斯基

2
这是个好主意,但我有两个建议:首先应在"$1"和之间添加引号"$#"(请参见unix.stackexchange.com/a/171347的出色答案)。其次,echo可悲的是不可移植(尤其是-n),因此printf '%s\n' "$1"应该使用它。
joki

谢谢@joki我曾在许多不同的unix系统上工作,我也不信任echo -n,但是我不知道任何echo "$1"会失败的posix系统。无论如何,printf确实更可预测-已更新。
米哈尔Šrajer

@MichałŠrajer考虑“ $ 1”是“ -n”或“-”的情况,因此例如,ntharg 1 -nntharg 1 --在各种系统上可能产生不同的结果。您现在拥有的代码是安全的!
joki

10

从最旧到最新的解决方案:

最便携的解决方案,甚至更旧sh(可使用空格和glob字符)(无循环,速度更快):

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)。

对于所有外壳程序和较旧版本sh(适用于空格和glob字符):

$ 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



3
shift `expr $# - 1`
echo "$1"

这会将参数移动参数的数量减1,并返回剩余的第一个(也是唯一的)参数,它将是最后一个。

我仅在bash中进行了测试,但它也应该在sh和ksh中工作。


11
shift $(($# - 1))-无需外部实用程序。适用于Bash,ksh,zsh和破折号。
暂停,直到另行通知。

@丹尼斯:太好了!我不知道$((...))语法。
劳伦斯·贡萨尔维斯

您想要使用printf '%s\n' "$1"以避免来自的意外行为echo(例如-n)。
joki

2

如果要以一种非破坏性的方式进行操作,则一种方法是将所有参数传递给一个函数并返回最后一个参数:

#!/bin/bash

last() {
        if [[ $# -ne 0 ]] ; then
            shift $(expr $# - 1)
            echo "$1"
        #else
            #do something when no arguments
        fi
}

lastvar=$(last "$@")
echo $lastvar
echo "$@"

pax> ./qq.sh 1 2 3 a b
b
1 2 3 a b

如果你真的不在乎保留其他参数,那么您就不需要在函数中使用它,但是我很难考虑一种情况,除非您已经处理过其他参数,否则您永远都不想保留它们,在这种情况下,我将使用依次处理它们的process / shift / process / shift / ...方法。

我在这里假设您要保留它们,因为您没有遵循顺序方法。此方法还处理没有参数的情况,返回“”。您可以通过插入注释掉的else子句轻松地调整该行为。


2

对于tcsh:

set X = `echo $* | awk -F " " '{print $NF}'`
somecommand "$X"

我非常确定这是除任务之外的便携式解决方案。


我知道您永远都在此发布过,但是这个解决方案很棒-有人发布了tcsh感到高兴!
2015年

2

我发现@AgileZebra的答案(加上@starfry的评论)最有用,但是它设置heads为标量。数组可能更有用:

heads=( "${@:1:$(($# - 1))}" )
tail=${@:${#@}}

请注意,这仅适用于bash。


您将如何将磁头转换为数组?
Stephane

我已经通过添加括号来做到这一点,例如echo "${heads[-1]}"打印中的最后一个元素heads。还是我错过了什么?
EndlosSchleife

1

使用eval以下解决方案:

last=$(eval "echo \$$#")

echo $last

7
对于间接引用来说,eval是过大的,更不用说是不好的做法了,还有一个巨大的安全隐患(该值不在echo或$()内引用。Bash对于任何var都有一个用于间接引用的内置语法:!因此,last="${!#}"将使用相同的方法(更直接,更间接,更
内在

另一个答案中查看更干净的方法来执行相同操作。
Palec 2014年

的RHS上不需要@MestreLion引号=
汤姆·黑尔

1
@TomHale:对于的特定情况为true ${!#},但一般而言并非如此:如果内容包含文字空白,例如,仍需要用引号引起来last='two words'$()无论内容如何,仅空白是安全的。
MestreLion

1

阅读以上答案后,我编写了一个Q&D shell脚本(应在sh和bash上运行),以便在PGM.cpp上运行g ++以生成可执行映像PGM。假定命令行上的最后一个参数是文件名(.cpp是可选的),所有其他参数是选项。

#!/bin/sh
if [ $# -lt 1 ]
then
    echo "Usage: `basename $0` [opt] pgm runs g++ to compile pgm[.cpp] into pgm"
    exit 2
fi
OPT=
PGM=
# PGM is the last argument, all others are considered options
for F; do OPT="$OPT $PGM"; PGM=$F; done
DIR=`dirname $PGM`
PGM=`basename $PGM .cpp`
# put -o first so it can be overridden by -o specified in OPT
set -x
g++ -o $DIR/$PGM $OPT $DIR/$PGM.cpp

1

以下将设置LAST为最后一个参数,而不更改当前环境:

LAST=$({
   shift $(($#-1))
   echo $1
})
echo $LAST

如果不再需要其他参数并且可以将其转移,则可以简化为:

shift $(($#-1))
echo $1

出于可移植性原因,请执行以下操作:

shift $(($#-1));

可以替换为:

shift `expr $# - 1`

$()用反引号代替:

LAST=`{
   shift \`expr $# - 1\`
   echo $1
}`
echo $LAST

1
echo $argv[$#argv]

现在,我只需要添加一些文本,因为我的答案太短而无法发布。我需要添加更多文本进行编辑。


假定可以在哪个外壳中工作?不重击。不是鱼(有$argv但没有$#argv- $argv[(count $argv)]在鱼中起作用)。
Beni Cherniavsky-Paskin 2014年

1

这是我的复制功能的一部分:

eval echo $(echo '$'"$#")

要在脚本中使用,请执行以下操作:

a=$(eval echo $(echo '$'"$#"))

说明(最嵌套在前):

  1. $(echo '$'"$#")返回$[nr]where [nr]是参数的数量。例如,字符串$123(未扩展)。
  2. echo $123 求值时,返回123rd参数的值。
  3. eval只是扩展$123为参数的值,例如last_arg。这被解释为字符串并返回。

从2015年年中开始与Bash合作。


评估方法已经在这里进行了很多次介绍,但是这一方法对其工作原理进行了解释。可以进一步改进,但仍然值得保留。
Palec

0
#! /bin/sh

next=$1
while [ -n "${next}" ] ; do
  last=$next
  shift
  next=$1
done

echo $last

如果参数为空字符串,则此操作将失败,但将在99.9%的情况下起作用。
Thomas

0

尝试以下脚本查找最后一个参数

 # cat arguments.sh
 #!/bin/bash
 if [ $# -eq 0 ]
 then
 echo "No Arguments supplied"
 else
 echo $* > .ags
 sed -e 's/ /\n/g' .ags | tac | head -n1 > .ga
 echo "Last Argument is: `cat .ga`"
 fi

输出:

 # ./arguments.sh
 No Arguments supplied

 # ./arguments.sh testing for the last argument value
 Last Argument is: value

谢谢。


我怀疑这将因./arguments.sh“最后值”而失败
托马斯(Thomas)

感谢您检查托马斯,我已经尝试像您提到的#那样执行as脚本。#./arguments.sh“带double的最后一个值检查” Last Argument是:double
Ranjithkumar T

问题在于最后一个参数是“最后一个值”,而不是值。该错误是由包含空格的参数引起的。
Thomas

0

有一种更简洁的方法可以执行此操作。可以将bash脚本的参数放入数组中,这使得处理元素变得更加简单。下面的脚本将始终打印传递给脚本的最后一个参数。

  argArray=( "$@" )                        # Add all script arguments to argArray
  arrayLength=${#argArray[@]}              # Get the length of the array
  lastArg=$((arrayLength - 1))             # Arrays are zero based, so last arg is -1
  echo ${argArray[$lastArg]}

样品输出

$ ./lastarg.sh 1 2 buckle my shoe
shoe

0

使用参数扩展(删除匹配的开头):

args="$@"
last=${args##* }

倒数第一,也很容易:

prelast=${args% *}


仅当最后一个参数不包含空格时,此方法才有效。
Palec

如果最后一个参数是用双引号引起来的句子,并且该句子应该是一个参数,则您的prelast将失败。
Stephane

0

要返回最近使用的命令的最后一个参数,请使用特殊参数:

$_

在这种情况下,如果在脚本中使用它,然后再调用另一个命令,它将起作用。


这不是问题所要问的
retnikt

-1

只需使用!$

$ mkdir folder
$ cd !$ # will run: cd folder

这不是问题所要问的
retnikt
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.