329

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

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

Zombo
  • 1
  • 62
  • 391
  • 407
Thomas
  • 10,358
  • 4
  • 27
  • 35
  • I was using bash, but the more portable solution the better. – Thomas Dec 06 '09 at 00:21
  • 7
    http://www.faqs.org/faqs/unix-faq/faq/part2/section-12.html – Inshallah Dec 06 '09 at 02:11
  • 10
    use can also use ${ !# } – Prateek Joshi Oct 06 '15 at 11:43
  • 13
    For only [tag:bash], the [Kevin Little's answer](https://stackoverflow.com/a/2439775/938111) proposes the simple `${!#}`. Test it using `bash -c 'echo ${!#}' arg1 arg2 arg3`. For [tag:bash], [tag:ksh] and [tag:zsh], the [Dennis Williamson's answer](https://stackoverflow.com/a/1854031/938111) proposes `${@: -1}`. Moreover `${*: -1}` can also be used. Test it using `zsh -c 'echo ${*: -1}' arg1 arg2 arg3`. But that does not work for [tag:dash], [tag:csh] and [tag:tcsh]. – oHo Jul 22 '17 at 21:19
  • 3
    `${!#}`, unlike `${@: -1}`, also works with parameter expansion. You can test it with `bash -c 'echo ${!#%.*}' arg1.out arg2.out arg3.out`. – Arch Stanton Aug 05 '18 at 10:33
  • **Note:** `${!#}` doesn't work in `zsh`. For a solution which works in both {ba,z}sh, see [here](https://stackoverflow.com/a/53043446/5353461) . – Tom Hale Aug 30 '20 at 15:18

29 Answers29

336

This is Bash-only:

echo "${@: -1}"
Dennis Williamson
  • 346,391
  • 90
  • 374
  • 439
  • 68
    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. – foo May 28 '15 at 03:34
  • 1
    I've been using this and it breaks in MSYS2 bash in windows only. Bizarre. – Steven Lu Mar 23 '18 at 18:25
  • 3
    **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. – Mr. Llama Mar 18 '19 at 17:26
  • 1
    @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. – Dennis Williamson Mar 18 '19 at 17:38
  • Works for Bourne shell also, at least for me – GregD Apr 05 '21 at 19:50
  • Mind the space between ':' and '-'! :) I missed that for the 1st time... – TrueY May 21 '21 at 16:07
  • Note that when called without arguments, this yields the value of $0, which may not be what's intended. – Jani Uusitalo Dec 12 '21 at 10:28
  • You will get a shellcheck warning here, but it's a [shellcheck bug](https://github.com/koalaman/shellcheck/issues/1141), so you can disable the warning here. – Johannes Aug 23 '22 at 07:48
202

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.

Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299
100
$ 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.

Nimantha
  • 6,405
  • 6
  • 28
  • 69
Zombo
  • 1
  • 62
  • 391
  • 407
  • 11
    Best answer, since it also includes the next to last arg. Thanks! – e40 Jul 07 '13 at 00:54
  • 2
    Steven, I don't know what you did to land in the Penalty Box, but I am loving your work on here. – Bruno Bronosky Sep 22 '17 at 15:09
  • 4
    yes. simply the best. all but command and last argument ```${@: 1:$#-1}``` – Dyno Fu Jan 09 '19 at 07:52
  • 1
    @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 – Mike Apr 18 '19 at 19:46
79

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
Kevin Little
  • 12,436
  • 5
  • 39
  • 47
  • 2
    `$BASH_ARGV` doesn't work inside a bash function (unless I'm doing something wrong). – Big McLargeHuge Dec 28 '14 at 15:46
  • 1
    The BASH_ARGV has the arguments when bash was called (or to a function) not the present list of positional arguments. –  Sep 20 '18 at 21:55
  • 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. – Steven Lu Sep 24 '18 at 20:13
  • Attention, `${!#}` get nothing when execute script with `sh XXX.sh 1 2 3` – roamer May 17 '21 at 11:16
  • @roamer – This only works in bash (`/bin/bash`). You ran it through POSIX sh (`/bin/sh`), which on many systems is not bash. – Adam Katz Mar 07 '23 at 18:51
40

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.

Tahsin Turkoz
  • 4,356
  • 1
  • 27
  • 18
  • While the example is for a function, scripts also work the same way. I like this answer because it is clear and concise. – Jonah Braun Oct 03 '17 at 03:43
  • 1
    And it's not hacky. It uses explicit features of the language, not side effects or special qwerks. This should be the accepted answer. – musicin3d Jan 05 '18 at 16:09
34

Use indexing combined with length of:

echo ${@:${#@}} 

Note that this is bash-only.

oguz ismail
  • 1
  • 16
  • 47
  • 69
Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
30

Found this when looking to separate the last argument from all the previous one(s). Whilst some of the answers do get the last argument, they're not much help if you need all the other args as well. This works much better:

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

Note that this is bash-only.

AgileZebra
  • 593
  • 6
  • 14
  • 3
    The [Steven Penny's answer](http://stackoverflow.com/a/11217798/938111) 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...) – oHo Nov 26 '15 at 08:05
  • 4
    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}`. – dkasak Nov 04 '16 at 20:02
  • Thanks dkasak. Updated to reflect your simplification. – AgileZebra Jun 08 '20 at 11:06
24

This works in all POSIX-compatible shells:

eval last=\${$#}

Source: http://www.faqs.org/faqs/unix-faq/faq/part2/section-12.html

poiuz
  • 249
  • 2
  • 2
  • 2
    See [this comment](http://stackoverflow.com/questions/1853946/getting-the-last-argument-passed-to-a-shell-script/1854031#comment8318885_1853991) attached to an older identical answer. – Dennis Williamson Jul 13 '12 at 12:21
  • 1
    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. – Palec Oct 17 '14 at 11:47
  • 1
    For [tag:bash], [tag:zsh], [tag:dash] and [tag:ksh] `eval last=\"\${$#}\"` is fine. But for [tag:csh] and [tag:tcsh] use `eval set last=\"\${$#}\"`. See this example: `tcsh -c 'eval set last=\"\${$#}\"; echo "$last"' arg1 arg2 arg3`. – oHo Jul 22 '17 at 21:11
19

Here is mine solution:

  • pretty portable (all POSIX sh, bash, ksh, zsh) should work
  • does not shift original arguments (shifts a copy).
  • does not use evil eval
  • does not iterate through the whole list
  • does not use external tools

Code:

ntharg() {
    shift $1
    printf '%s\n' "$1"
}
LAST_ARG=`ntharg $# "$@"`
Michał Šrajer
  • 30,364
  • 7
  • 62
  • 85
  • 1
    Great answer - short, portable, safe. Thanks! – Ján Lalinský Oct 07 '16 at 09:45
  • 2
    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 https://unix.stackexchange.com/a/171347). Secondly, `echo` is sadly non-portable (particularly for `-n`), so `printf '%s\n' "$1"` should be used instead. – joki Jan 04 '18 at 09:37
  • 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. – Michał Šrajer Jan 09 '18 at 14:11
  • @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! – joki Jan 10 '18 at 18:20
  • 2
    Alternatively: `last() { shift $(($# - 1));printf %s "$1";}` – Léa Gris Nov 19 '20 at 15:03
  • @LéaGris I believe you wound need to use `expr` instead of $(()) to make it work on all unix systems. – Michał Šrajer Dec 02 '20 at 12:37
  • 1
    @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. – Léa Gris Dec 02 '20 at 13:20
  • I'm getting "0" returned if there are no arguments passed, that should be considered – polynomial_donut Feb 05 '23 at 19:06
  • @polynomial_donut – `last() { shift $(($# - 1)) 2>/dev/null && printf %s "$1"; }` will silently exit (with error code 1) if not given an argument – Adam Katz Mar 07 '23 at 18:18
11

From oldest to newer solutions:

The most portable solution, even older sh (works with spaces and glob characters) (no loop, faster):

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

Since version 2.01 of bash

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

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

For ksh, zsh and bash:

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

And for "next to last":

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

Using printf to workaround any issues with arguments that start with a dash (like -n).

For all shells and for older sh (works with spaces and glob characters) is:

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

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

Or, if you want to set a last var:

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

And for "next to last":

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

If you are using Bash >= 3.0

echo ${BASH_ARGV[0]}
dusan
  • 9,104
  • 3
  • 35
  • 55
9

For bash, this comment suggested the very elegant:

echo "${@:$#}"

To silence shellcheck, use:

echo ${*:$#}

As a bonus, both also work in zsh.

Tom Hale
  • 40,825
  • 36
  • 187
  • 242
5
shift `expr $# - 1`
echo "$1"

This shifts the arguments by the number of arguments minus 1, and returns the first (and only) remaining argument, which will be the last one.

I only tested in bash, but it should work in sh and ksh as well.

Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299
3

I found @AgileZebra's answer (plus @starfry's comment) the most useful, but it sets heads to a scalar. An array is probably more useful:

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

Note that this is bash-only.

Edit: Removed unnecessary $(( )) according to @f-hauri's comment.

EndlosSchleife
  • 515
  • 7
  • 19
2

If you want to do it in a non-destructive way, one way is to pass all the arguments to a function and return the last one:

#!/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

If you don't actually care about keeping the other arguments, you don't need it in a function but I have a hard time thinking of a situation where you would never want to keep the other arguments unless they've already been processed, in which case I'd use the process/shift/process/shift/... method of sequentially processing them.

I'm assuming here that you want to keep them because you haven't followed the sequential method. This method also handles the case where there's no arguments, returning "". You could easily adjust that behavior by inserting the commented-out else clause.

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
2

A solution using eval:

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

echo $last
Mikael S
  • 5,206
  • 2
  • 23
  • 21
  • 7
    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. – MestreLion Aug 07 '11 at 16:28
  • See a cleaner way to perform the same in [another answer](http://stackoverflow.com/a/8868871/2157640). – Palec Oct 17 '14 at 11:23
  • @MestreLion quotes are not needed on the RHS of`=`. – Tom Hale Jun 11 '19 at 07:15
  • 1
    @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. – MestreLion Jun 18 '19 at 21:23
2

For tcsh:

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

I'm quite sure this would be a portable solution, except for the assignment.

Palec
  • 12,743
  • 8
  • 69
  • 138
Perfect64
  • 159
  • 1
  • 6
1

After reading the answers above I wrote a Q&D shell script (should work on sh and bash) to run g++ on PGM.cpp to produce executable image PGM. It assumes that the last argument on the command line is the file name (.cpp is optional) and all other arguments are options.

#!/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
David E.
  • 89
  • 2
1

The following will set LAST to last argument without changing current environment:

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

If other arguments are no longer needed and can be shifted it can be simplified to:

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

For portability reasons following:

shift $(($#-1));

can be replaced with:

shift `expr $# - 1`

Replacing also $() with backquotes we get:

LAST=`{
   shift \`expr $# - 1\`
   echo $1
}`
echo $LAST
Paweł Nadolski
  • 8,296
  • 2
  • 42
  • 32
1
echo $argv[$#argv]

Now I just need to add some text because my answer was too short to post. I need to add more text to edit.

Alireza
  • 4,976
  • 1
  • 23
  • 36
1

This is part of my copy function:

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

To use in scripts, do this:

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

Explanation (most nested first):

  1. $(echo '$'"$#") returns $[nr] where [nr] is the number of parameters. E.g. the string $123 (unexpanded).
  2. echo $123 returns the value of 123rd parameter, when evaluated.
  3. eval just expands $123 to the value of the parameter, e.g. last_arg. This is interpreted as a string and returned.

Works with Bash as of mid 2015.

Palec
  • 12,743
  • 8
  • 69
  • 138
esavier
  • 423
  • 4
  • 13
  • 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. – Palec Aug 18 '15 at 23:17
  • Just `eval a="\$$#"` will work without any echoes. – AnrDaemon Dec 20 '22 at 11:26
1

To return the last argument of the most recently used command use the special parameter:

$_

In this instance it will work if it is used within the script before another command has been invoked.

Thain
  • 107
  • 5
0
#! /bin/sh

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

echo $last
Craig Trader
  • 15,507
  • 6
  • 37
  • 55
0

Try the below script to find last argument

 # 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

Output:

 # ./arguments.sh
 No Arguments supplied

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

Thanks.

Ranjithkumar T
  • 1,886
  • 16
  • 21
  • I suspect this would fail with ./arguments.sh "last value" – Thomas Nov 05 '13 at 03:25
  • 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 – Ranjithkumar T Nov 05 '13 at 17:56
  • The problem is that the last argument was 'last value', and not value. The error is caused by the argument containing a space. – Thomas Nov 08 '13 at 03:02
0

There is a much more concise way to do this. Arguments to a bash script can be brought into an array, which makes dealing with the elements much simpler. The script below will always print the last argument passed to a script.

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

Sample output

$ ./lastarg.sh 1 2 buckle my shoe
shoe
tekbot
  • 11
  • 2
0

Using parameter expansion (delete matched beginning):

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

It's also easy to get all before last:

prelast=${args% *}
Jurij
  • 11
0
$ echo "${*: -1}"

That will print the last argument

Truoc Pham
  • 21
  • 4
  • This has already been mentioned in some other answers, such as [this one](https://stackoverflow.com/a/11217798/2227743). – Eric Aya Aug 12 '21 at 10:14
0

With GNU bash version >= 3.0:

num=$#                 # get number of arguments
echo "${!num}"         # print last argument
Cyrus
  • 84,225
  • 14
  • 89
  • 153
-1

Just use !$.

$ mkdir folder
$ cd !$ # will run: cd folder
3pwd
  • 119
  • 2
  • 5