Bash script multiple arguments: all args, functions, another script, and `typeset -f` over SSH

Bash multiple arguments with positional parameters, bash pass all arguments via "$@", bash pass multiple arguments to a function, forwarding to another script, and bash pass function over SSH with typeset -f or declare -f.

Published

Updated

Read time 4 min read

Reviewed byDeepak Prasad

Bash script multiple arguments: all args, functions, another script, and `typeset -f` over SSH

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.

bash
set -- one two 'three four'
printf 'argc=%s\n' "$#"
printf '1=%s 2=%s 3=%s\n' "$1" "$2" "$3"

Output:

text
argc=3
1=one 2=two 3=three four

Past $9 you must use braces: ${10}, not $10 (without braces, Bash reads $1 followed by a literal 0).

bash
set -- a b c d e f g h i j
echo "without braces: $10"
echo "with braces:    ${10}"

Output:

text
without braces: a0
with braces:    j

Bash pass all arguments: "$@" vs "$*"

Both expand to your parameters, but they behave differently inside double quotes, especially when IFS is not a single space.

bash
IFS=,
set -- a "b c" d
printf 'unquoted *: '; printf '<%s> ' $*; echo
printf 'quoted *:   '; printf '<%s> ' "$*"; echo
printf 'quoted @:   '; printf '<%s> ' "$@"; echo

Output:

text
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:

bash
inner() {
  printf '<%s>\n' "$@"
}

outer() {
  inner "$@"
}

outer hello "world wide"

Output:

text
<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.

bash
tmp=$(mktemp)
printf '%s\n' '#!/bin/bash' 'printf "child: <%s>\n" "$@"' >"$tmp"
chmod +x "$tmp"
"$tmp" aa 'bb cc'
rm -f "$tmp"

Output:

text
child: <aa>
child: <bb cc>

In a real project you would point at a fixed path:

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

bash
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
done

Output:

text
number=5
timeout=2

Bash 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):

bash
myfunc() {
  printf 'arg: <%s>\n' "$@"
}

fn=$(typeset -f myfunc)
bash -s -- a 'b c' <<EOF
$fn
myfunc "\$@"
EOF

Output:

text
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:

Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive …