Getting the last argument passed to a shell script

144,843

Solution 1

This is a bit of a hack:

for last; do true; done
echo $last

This one is also pretty portable (again, should work with bash, ksh and sh) and it doesn't shift the arguments, which could be nice.

It uses the fact that for implicitly loops over the arguments if you don't tell it what to loop over, and the fact that for loop variables aren't scoped: they keep the last value they were set to.

Solution 2

This is Bash-only:

echo "${@: -1}"

Solution 3

$ set quick brown fox jumps

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

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

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

The space is necessary so that it doesn't get interpreted as a default value.

Note that this is bash-only.

Solution 4

The simplest answer for bash 3.0 or greater is

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

That's it.

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

Output is:

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

Solution 5

The following will work for you.

  • @ is for array of arguments.
  • : means at
  • $# is the length of the array of arguments.

So the result is the last element:

${@:$#} 

Example:

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

Note that this is bash-only.

Share:
144,843

Related videos on Youtube

Thomas
Author by

Thomas

Owner and developer of TwistedWave, an audio editor written in C++, objective-c and Scheme thanks to Guile.

Updated on March 25, 2022

Comments

  • Thomas
    Thomas over 2 years

    $1 is the first argument.
    $@ is all of them.

    How can I find the last argument passed to a shell script?

    • Thomas
      Thomas over 14 years
      I was using bash, but the more portable solution the better.
    • Inshallah
      Inshallah over 14 years
    • Prateek Joshi
      Prateek Joshi over 8 years
      use can also use ${ !# }
    • oHo
      oHo almost 7 years
      For only bash, the Kevin Little's answer proposes the simple ${!#}. Test it using bash -c 'echo ${!#}' arg1 arg2 arg3. For bash, ksh and zsh, the Dennis Williamson's answer proposes ${@: -1}. Moreover ${*: -1} can also be used. Test it using zsh -c 'echo ${*: -1}' arg1 arg2 arg3. But that does not work for dash, csh and tcsh.
    • Arch Stanton
      Arch Stanton almost 6 years
      ${!#}, unlike ${@: -1}, also works with parameter expansion. You can test it with bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out.
    • Tom Hale
      Tom Hale almost 4 years
      Note: ${!#} doesn't work in zsh. For a solution which works in both {ba,z}sh, see here .
  • Thomas
    Thomas over 14 years
    This will fail if an argument is the empty string, but will work in 99.9% of the cases.
  • SourceSeeker
    SourceSeeker over 13 years
    shift $(($# - 1)) - no need for an external utility. Works in Bash, ksh, zsh and dash.
  • Laurence Gonsalves
    Laurence Gonsalves over 13 years
    @Dennis: Nice! I didn't know about the $((...)) syntax.
  • MestreLion
    MestreLion almost 13 years
    eval for indirect reference is overkill, not to mention bad practice, and a huge security concern (the value is not quote in echo or outside $(). Bash has a builtin syntax for indirect references, for any var: ! So last="${!#}" would use the same approach (indirect reference on $#) in a much safer, compact, builtin, sane way. And properly quoted.
  • Zombo
    Zombo over 12 years
    Also for next to last argument, do echo ${BASH_ARGV[1]}
  • SourceSeeker
    SourceSeeker almost 12 years
    See this comment attached to an older identical answer.
  • Paweł Nadolski
    Paweł Nadolski over 11 years
    @MichałŠrajer, I think you meant colon and not comma ;)
  • e40
    e40 almost 11 years
    Best answer, since it also includes the next to last arg. Thanks!
  • Thomas
    Thomas over 10 years
    I suspect this would fail with ./arguments.sh "last value"
  • Ranjithkumar T
    Ranjithkumar T over 10 years
    Thank you for checking Thomas, I have tried to perform the as script like you mentinoed # ./arguments.sh "last value" Last Argument is: value is working fine now. # ./arguments.sh "last value check with double" Last Argument is: double
  • Thomas
    Thomas over 10 years
    The problem is that the last argument was 'last value', and not value. The error is caused by the argument containing a space.
  • Palec
    Palec over 9 years
    See a cleaner way to perform the same in another answer.
  • Palec
    Palec over 9 years
    The simplest portable solution I see here. This one has no security problem, @DennisWilliamson, the quoting empirically seems to be done right, unless there is a way to set $# to an arbitrary string (I don’t think so). eval last=\"\${$#}\" also works and is obviously correct. Don’t see why the quotes are not needed.
  • Beni Cherniavsky-Paskin
    Beni Cherniavsky-Paskin over 9 years
    Which shell is this suppossed to work in? Not bash. Not fish (has $argv but not $#argv$argv[(count $argv)] works in fish).
  • Big McLargeHuge
    Big McLargeHuge over 9 years
    $BASH_ARGV doesn't work inside a bash function (unless I'm doing something wrong).
  • user3295674
    user3295674 over 9 years
    I know you posted this forever ago, but this solution is great - glad someone posted a tcsh one!
  • foo
    foo about 9 years
    For those (like me) wondering why is the space needed, man bash has this to say about it: > Note that a negative offset must be separated from the colon by at least one space to avoid being confused with the :- expansion.
  • Palec
    Palec almost 9 years
    The eval approach has been presented here many times already, but this one has an explanation of how it works. Could be further improved, but still worth keeping.
  • Rufflewind
    Rufflewind over 8 years
    @MichałŠrajer true is part of POSIX.
  • oHo
    oHo over 8 years
    The Steven Penny's answer is a bit nicer: use ${@: -1} for last and ${@: -2:1} for second last (and so on...). Example: bash -c 'echo ${@: -1}' prog 0 1 2 3 4 5 6 prints 6. To stay with this current AgileZebra's approach, use ${@:$#-1:1} to get the second last. Example: bash -c 'echo ${@:$#-1:1}' prog 0 1 2 3 4 5 6 prints 5. (and ${@:$#-2:1} to get the third last and so on...)
  • mcoolive
    mcoolive over 8 years
    With an old Solaris, with the old bourne shell (not POSIX), I have to write "for last in "$@"; do true; done"
  • Ján Lalinský
    Ján Lalinský over 7 years
    Great answer - short, portable, safe. Thanks!
  • dkasak
    dkasak over 7 years
    AgileZebra's answer supplies a way of getting all but the last arguments so I wouldn't say Steven's answer supersedes it. However, there seems to be no reason to use $((...)) to subtract the 1, you can simply use ${@:1:$# - 1}.
  • oHo
    oHo almost 7 years
    For bash, zsh, dash and ksh eval last=\"\${$#}\" is fine. But for csh and tcsh use eval set last=\"\${$#}\". See this example: tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3.
  • Bruno Bronosky
    Bruno Bronosky almost 7 years
    Steven, I don't know what you did to land in the Penalty Box, but I am loving your work on here.
  • playjava
    playjava almost 7 years
    While the example is for a function, scripts also work the same way. I like this answer because it is clear and concise.
  • joki
    joki over 6 years
    This is a great idea, but I have a couple of suggestions: Firstly quoting should be added both around "$1" and "$#" (see this great answer unix.stackexchange.com/a/171347). Secondly, echo is sadly non-portable (particularly for -n), so printf '%s\n' "$1" should be used instead.
  • joki
    joki over 6 years
    You'd want to use printf '%s\n' "$1" in order to avoid unexpected behaviour from echo (e.g. for -n).
  • musicin3d
    musicin3d over 6 years
    And it's not hacky. It uses explicit features of the language, not side effects or special qwerks. This should be the accepted answer.
  • Michał Šrajer
    Michał Šrajer over 6 years
    thanks @joki I worked with many different unix systems and I wouldn't trust echo -n either, however I am not aware on any posix system where echo "$1" would fail. Anyhow, printf is indeed more predictable - updated.
  • joki
    joki over 6 years
    @MichałŠrajer consider the case where "$1" is "-n" or "--", so for example ntharg 1 -n or ntharg 1 -- may yield different results on various systems. The code you have now is safe!
  • Xerz
    Xerz over 6 years
    Possible (poor) duplicate of stackoverflow.com/a/37601842/1446229
  • Palec
    Palec over 6 years
    This only works if the last argument does not contain a space.
  • Steven Lu
    Steven Lu over 6 years
    I've been using this and it breaks in MSYS2 bash in windows only. Bizarre.
  • Adrian Günter
    Adrian Günter about 6 years
    @mcoolive @LaurenceGolsalves beside being more portable, for last in "$@"; do :; done also makes the intent much clearer.
  • Serge Stroobandt
    Serge Stroobandt almost 6 years
    ARGV stands for "argument vector."
  • done
    done almost 6 years
    The BASH_ARGV has the arguments when bash was called (or to a function) not the present list of positional arguments.
  • Steven Lu
    Steven Lu almost 6 years
    Note also what BASH_ARGV will yield you is the value that the last arg that was given was, instead of simply "the last value". For example!: if you provide one single argument, then you call shift, ${@:$#} will produce nothing (because you shifted out the one and only argument!), however, BASH_ARGV will still give you that (formerly) last argument.
  • e2rabi
    e2rabi over 5 years
    Thanks this help me !
  • Dyno Fu
    Dyno Fu over 5 years
    yes. simply the best. all but command and last argument ${@: 1:$#-1}
  • Mr. Llama
    Mr. Llama over 5 years
    Note: This answer works for all Bash arrays, unlike ${@:$#} which only works on $@. If you were to copy $@ to a new array with arr=("$@"), ${arr[@]:$#} would be undefined. This is because $@ has a 0th element that isn't included in "$@" expansions.
  • SourceSeeker
    SourceSeeker over 5 years
    @Mr.Llama: Another place to avoid $# is when iterating over arrays since, in Bash, arrays are sparse and while $# will show the number of elements in the array it's not necessarily pointing to the last element (or element+1). In other words, one shouldn't do for ((i = 0; i++; i < $#)); do something "${array[$i]}"; done and instead do for element in "${array[@]}"; do something "$element"; done or iterate over the indices: for index in "${!array[@]}"; do something "$index" "${array[$index]}"; done if you need to do something with the values of the indices.
  • Mike
    Mike about 5 years
    @DynoFu thank you for that, you answered my next question. So a shift might look like: echo ${@: -1} ${@: 1:$#-1}, where last becomes first and the rest slide down
  • Tom Hale
    Tom Hale about 5 years
    @MestreLion quotes are not needed on the RHS of=.
  • MestreLion
    MestreLion about 5 years
    @TomHale: true for the particular case of ${!#}, but not in general: quotes are still needed if content contains literal whitespace, such as last='two words'. Only $() is whitespace-safe regardless of content.
  • Dominik R
    Dominik R over 4 years
    @mcoolive: it even works in the unix v7 bourne shell from 1979. you can't get more portable if it runs on both v7 sh and POSIX sh :)
  • Stephane
    Stephane over 4 years
    Your prelast fails if the last argument is a sentence enclosed in double quotes, with said sentence supposed to be one argument.
  • Stephane
    Stephane over 4 years
    How would you convert the heads into an array ?
  • EndlosSchleife
    EndlosSchleife over 4 years
    I already did by adding the parentheses, for example echo "${heads[-1]}" prints the last element in heads. Or am I missing something?
  • Atul
    Atul over 4 years
    echo $(@&$;&^$&%@^!@%^#**#&@*#@*#@(&*#_**^@^&(^@&*&@)
  • retnikt
    retnikt about 4 years
    This is not what the question is asking
  • retnikt
    retnikt about 4 years
    This is not what the question is asking
  • oguz ismail
    oguz ismail about 4 years
    @mcoolive On Solaris sh for last do true; done (no semicolon after last) works. That actually works with all sh implementations I have on my computer.
  • AgileZebra
    AgileZebra about 4 years
    Thanks dkasak. Updated to reflect your simplification.
  • Léa Gris
    Léa Gris over 3 years
    Alternatively: last() { shift $(($# - 1));printf %s "$1";}
  • Michał Šrajer
    Michał Šrajer over 3 years
    @LéaGris I believe you wound need to use expr instead of $(()) to make it work on all unix systems.
  • Léa Gris
    Léa Gris over 3 years
    @MichałŠrajer i Think if you are the guy tasked to write scripts for that 40 years old legacy SCO Unix, you are likely to know your way with the man pages and printed manual because browsing stackoverflow with Mozaic is going to be quite challenging on its own.
  • GregD
    GregD about 3 years
    Works for Bourne shell also, at least for me
  • roamer
    roamer about 3 years
    Attention, ${!#} get nothing when execute script with sh XXX.sh 1 2 3
  • TrueY
    TrueY about 3 years
    Mind the space between ':' and '-'! :) I missed that for the 1st time...
  • Eric Aya
    Eric Aya almost 3 years
    This has already been mentioned in some other answers, such as this one.
  • Isin Altinkaya
    Isin Altinkaya almost 3 years
    @Atul can you explain your interesting command?
  • Jani Uusitalo
    Jani Uusitalo over 2 years
    Note that when called without arguments, this yields the value of $0, which may not be what's intended.
  • F. Hauri  - Give Up GitHub
    F. Hauri - Give Up GitHub over 2 years
    use of $(( )) is useless! "${@: 1 : $# - 1 }" will work same!