941

Let's say I have a function abc() that will handle the logic related to analyzing the arguments passed to my script.

How can I pass all arguments my Bash script has received to abc()? The number of arguments is variable, so I can't just hard-code the arguments passed like this:

abc $1 $2 $3 $4

Better yet, is there any way for my function to have access to the script arguments' variables?

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
devoured elysium
  • 101,373
  • 131
  • 340
  • 557
  • 3
    Possible duplicate of [Propagate all arguments in a bash shell script](https://stackoverflow.com/questions/4824590/propagate-all-arguments-in-a-bash-shell-script). (This question was actually posted before the one linked here. But the one in the link has more detailed answers and a more informative title and may be best as the reference question) – prosoitos Oct 25 '19 at 21:46

7 Answers7

1575

The $@ variable expands to all command-line parameters separated by spaces. Here is an example.

abc "$@"

When using $@, you should (almost) always put it in double-quotes to avoid misparsing of arguments containing spaces or wildcards (see below). This works for multiple arguments. It is also portable to all POSIX-compliant shells.

It is also worth noting that $0 (generally the script's name or path) is not in $@.

The Bash Reference Manual Special Parameters Section says that $@ expands to the positional parameters starting from one. When the expansion occurs within double quotes, each parameter expands to a separate word. That is "$@" is equivalent to "$1" "$2" "$3"....

Passing some arguments:

If you want to pass all but the first arguments, you can first use shift to "consume" the first argument and then pass "$@" to pass the remaining arguments to another command. In Bash (and zsh and ksh, but not in plain POSIX shells like dash), you can do this without messing with the argument list using a variant of array slicing: "${@:3}" will get you the arguments starting with "$3". "${@:3:4}" will get you up to four arguments starting at "$3" (i.e. "$3" "$4" "$5" "$6"), if that many arguments were passed.

Things you probably don't want to do:

"$*" gives all of the arguments stuck together into a single string (separated by spaces, or whatever the first character of $IFS is). This looses the distinction between spaces within arguments and the spaces between arguments, so is generally a bad idea. Although it might be ok for printing the arguments, e.g. echo "$*", provided you don't care about preserving the space within/between distinction.

Assigning the arguments to a regular variable (as in args="$@") mashes all the arguments together like "$*" does. If you want to store the arguments in a variable, use an array with args=("$@") (the parentheses make it an array), and then reference them as e.g. "${args[0]}" etc. Note that in Bash and ksh, array indexes start at 0, so $1 will be in args[0], etc. zsh, on the other hand, starts array indexes at 1, so $1 will be in args[1]. And more basic shells like dash don't have arrays at all.

Leaving off the double-quotes, with either $@ or $*, will try to split each argument up into separate words (based on whitespace or whatever's in $IFS), and also try to expand anything that looks like a filename wildcard into a list of matching filenames. This can have really weird effects, and should almost always be avoided. (Except in zsh, where this expansion doesn't take place by default.)

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Gordon Davisson
  • 118,432
  • 16
  • 123
  • 151
  • 3
    Read more on why it is important to have the double " around here: https://stackoverflow.com/a/4824637/4575793 – Cadoiz Jul 13 '19 at 15:54
  • 1
    `"${@:3}"` doesn't seem to work with `zsh`. – BiBi Sep 24 '19 at 23:12
  • @Bibi It works in zsh (v 5.3) for me, in both scripts and functions. How are you testing it? – Gordon Davisson Sep 25 '19 at 02:14
  • 2
    Extra comment: `$*` will be a single string with the first character of `IFS` being the concatenating element. – kvantour Oct 25 '19 at 15:49
  • 1
    @kvantour That's what happens if `$*` is in double-quotes. If it's unquoted, it's subject to word splitting and wildcard expansion, so it immediately gets re-split back to the elements *and* any elements that contain `$IFS` characters will also get split (and then any wildcards get expanded). Net result: without double-quotes, `$*` and `$@` wind up giving the same result. – Gordon Davisson Oct 25 '19 at 20:47
  • 1
    @GordonDavisson exactly. I found this information missing, but it does not belong in your great answer. A comment like it is now is better. – kvantour Oct 25 '19 at 20:53
  • @kvantour Agreed; the main reason I followed up was to clarify about double-quotes, since you didn't explicitly have them in your original comment, and I was worried that'd be misunderstood. Suggestion: how about you repost a version of your comment with explicit double-quotes, and then we can delete this mostly-irrelevant series of clarifications? – Gordon Davisson Oct 25 '19 at 22:23
  • Good answer; how do you get only option arguments? (beginning with dash for example) – DylanYoung Jul 10 '20 at 20:39
  • 1
    @DylanYoung You'd have to parse them to figure out which should be considered option arguments. For example, in `somecmd -a b c -d`, the `-a` is clearly an option, but is `b` an argument to `-a` (hence "part of" it), or a plain positional argument? You need to know whether `-a` takes an argument to decide that. Also, some commands only recognize `-` as indicating an option if it's before the first positional command, so `-d` might or might not be an option depending on which policy this command uses. – Gordon Davisson Jul 11 '20 at 04:00
  • Yes alas, that's what I feared and the same conclusion I came to myself. It'd be nice if there was a little library for this, though it seems tricky in posix shell (without arrays). For now we've settled for forcing the positional arguments to be passed and passing the rest through to the sub command (in the context of git aliases). – DylanYoung Jul 13 '20 at 02:52
168

I needed a variation on this, which I expect will be useful to others:

function diffs() {
        diff "${@:3}" <(sort "$1") <(sort "$2")
}

The "${@:3}" part means all the members of the array starting at 3. So this function implements a sorted diff by passing the first two arguments to diff through sort and then passing all other arguments to diff, so you can call it similarly to diff:

diffs file1 file2 [other diff args, e.g. -y]
hajamie
  • 2,848
  • 2
  • 22
  • 20
  • 17
    The `"${@:3}"` is also great when you have scripts that have arguments, but can also pass arguments to other scripts that they call. For example, a project of mine has a script for easily running the program, with an argument for the main class to use. Then `"${@:2}"` can be used to pass the remaining arguments to that entry point. – Kat Jul 14 '14 at 18:45
  • 12
    @Kat already mentioned this, but to clarify (in case you still have doubts): `command "$@"` is equivalent to `command $1 "${@:2}"`. – Mahn Apr 07 '16 at 22:38
  • 2
    OMG! I _kiss_ you! – Clay Bridges May 13 '20 at 18:33
70

Use the $@ variable, which expands to all command-line parameters separated by spaces.

abc "$@"
Daniel Böhmer
  • 14,463
  • 5
  • 36
  • 46
Mia Clarke
  • 8,134
  • 3
  • 49
  • 62
  • This will break up arguments in quotes into separate arguments though, which is very rarely what one expects when running a script. – alkanen Feb 14 '23 at 12:48
  • No, my mistake. It works when you quote the $@ as in your example, but not when using it without quotes as in the descriptive text. – alkanen Feb 14 '23 at 12:51
65

Here's a simple script:

#!/bin/bash

args=("$@")

echo Number of arguments: $#
echo 1st argument: ${args[0]}
echo 2nd argument: ${args[1]}

$# is the number of arguments received by the script. I find easier to access them using an array: the args=("$@") line puts all the arguments in the args array. To access them use ${args[index]}.

Giuseppe Cardone
  • 5,323
  • 2
  • 24
  • 30
49

It's worth mentioning that you can specify argument ranges with this syntax.

function example() {
    echo "line1 ${@:1:1}"; #First argument
    echo "line2 ${@:2:1}"; #Second argument
    echo "line3 ${@:3}"; #Third argument onwards
}

I hadn't seen it mentioned.

robstarbuck
  • 6,893
  • 2
  • 41
  • 40
31

abc "$@" is generally the correct answer. But I was trying to pass a parameter through to an su command, and no amount of quoting could stop the error su: unrecognized option '--myoption'. What actually worked for me was passing all the arguments as a single string :

abc "$*"

My exact case (I'm sure someone else needs this) was in my .bashrc

# run all aws commands as Jenkins user
aws ()
{
    sudo su jenkins -c "aws $*"
}
andrew lorien
  • 2,310
  • 1
  • 24
  • 30
  • 2
    Hey OP, looks like you may have a typo on the `abc` example – dannypaz May 10 '19 at 18:44
  • 4
    Exactly what I needed. My command was wrapped in quotes and this was the only thing that worked for me. – Toofy May 24 '19 at 16:26
  • 2
    Probably the way to use, but be aware of the risk when you use string arguments. Please look at the "$*" example here: https://stackoverflow.com/a/46205560/4575793 (This is more on '$@' vs '$*' plus there quoted variants) – Cadoiz Jul 13 '19 at 16:03
  • I’m trying to understand the difference: in both cases `bash` expands the variable (either `$@` or `$*`) before passing the resulting, expanded string to `su`. So why does one string contain all arguments (expanded from `$*`) and the other does not (expanded from `$@`)? – Jens Apr 07 '22 at 03:40
30
abc "$@"

$@ represents all the parameters given to your bash script.

Vivien Barousse
  • 20,555
  • 2
  • 63
  • 64