Scripts that take more than one flag still use the same primitives: positional
parameters ($1, $2, …), a count ($#), shift, and two ways to pass every word
the user typed: "$@" and "$*". Only "$@" keeps each argument separate after quoting,
which matters as soon as paths or flags contain spaces. The sections below show how
to read arguments, forward them into functions, hand them to another script, and—on
trusted remotes—reuse a function body with typeset -f or declare -f.
Tested all the commands and code from this article on Ubuntu 25.04, kernel 6.14.0-37-generic, Bash 5.2.37.
For functions in more depth, see
bash function. For proper -o / --long parsing,
prefer bash getopts once the CLI gets busy.
Bash multiple arguments: positional parameters
Arguments arrive as $1, $2, …, and the script or function name is $0 (the path
you were invoked with). $# is how many arguments you have.
set -- one two 'three four'
printf 'argc=%s\n' "$#"
printf '1=%s 2=%s 3=%s\n' "$1" "$2" "$3"Output:
argc=3
1=one 2=two 3=three fourPast $9 you must use braces: ${10}, not $10 (without braces, Bash reads $1
followed by a literal 0).
set -- a b c d e f g h i j
echo "without braces: $10"
echo "with braces: ${10}"Output:
without braces: a0
with braces: jBash pass all arguments: "$@" vs "$*"
Both expand to your parameters, but they behave differently inside double quotes,
especially when IFS is not a single space.
IFS=,
set -- a "b c" d
printf 'unquoted *: '; printf '<%s> ' $*; echo
printf 'quoted *: '; printf '<%s> ' "$*"; echo
printf 'quoted @: '; printf '<%s> ' "$@"; echoOutput:
unquoted *: <a> <b c> <d>
quoted *: <a,b c,d>
quoted @: <a> <b c> <d>Rule of thumb: "$@" is almost always what people mean by “bash pass all arguments”—one quoted expansion per argument, spaces and all. Use "$*" only when you intentionally want a single joined string.
Bash pass multiple arguments to a function
Inside a function, $1, $2, $#, and "$@" refer to that function’s call, not
the outer script. To pass everything from the caller into a helper, forward
explicitly:
inner() {
printf '<%s>\n' "$@"
}
outer() {
inner "$@"
}
outer hello "world wide"Output:
<hello>
<world wide>If you wrote inner $* without quotes, words would split again and you would lose
the original boundaries.
Bash pass all arguments to another script
A wrapper often ends with exec so the child replaces the shell and keeps the same
PID semantics; if you need the wrapper to run after the child exits, drop exec.
tmp=$(mktemp)
printf '%s\n' '#!/bin/bash' 'printf "child: <%s>\n" "$@"' >"$tmp"
chmod +x "$tmp"
"$tmp" aa 'bb cc'
rm -f "$tmp"Output:
child: <aa>
child: <bb cc>In a real project you would point at a fixed path:
#!/bin/bash
exec /usr/local/bin/real-tool "$@"That one line is the usual answer to “bash pass all arguments to another script.”
Parsing many flags with while, case, and shift
Before you hand-roll loops, skim
bash getopts—it covers grouped flags and errors more
safely. When you do use while / case, consume arguments with shift so $1
always names the “current” token.
set -- -n 5 -t 2
while [[ $# -gt 0 ]]; do
case $1 in
-n)
echo "number=$2"
shift 2
;;
-t)
echo "timeout=$2"
shift 2
;;
*)
echo "unknown=$1"
shift
;;
esac
doneOutput:
number=5
timeout=2Bash pass function over SSH: typeset -f and declare -f
Remote Bash does not know your local functions. You can print a function’s source
with typeset -f name or declare -f name and evaluate it on the other side. Treat
this like shipping code: use it only for hosts you trust, mind quoting, and remember
ssh is not a generic RPC framework.
Local sanity check (same pattern a remote bash -s would run after reading a small
script from stdin):
myfunc() {
printf 'arg: <%s>\n' "$@"
}
fn=$(typeset -f myfunc)
bash -s -- a 'b c' <<EOF
$fn
myfunc "\$@"
EOFOutput:
arg: <a>
arg: <b c>On a real host you might fold the same idea into ssh user@host bash -s -- … with
careful handling of secrets and of what the function body contains.
Summary
Positional parameters, shift, and quoting are the whole story for argument handling.
Use "$@" when each word must stay separate—especially before functions or exec /path/to/tool "$@". After the ninth parameter, use ${10} and friends. To run a
function on a remote Bash over ssh, you can print its definition with typeset -f
or declare -f and evaluate it there first; that is powerful, sensitive to quoting,
and only appropriate for hosts you trust.
Further reading:

