Inside a bash function, what do $1, $# and $0 refer to, and why must the tenth argument be written as ${10}?
answer
- arguments arrive positionally, never named
- the script's list is saved and restored
- one special parameter refuses to change
- single digit only, then braces
basics
~20 sA bash function receives its own positional parameters: $1, $2 … are the function's arguments and $# is their count, shadowing the script's for the duration of the call. $0 is the exception — it still names the script. Write ${10}, because $10 parses as $1 followed by 0.
solid answer
~50 sBash functions have no named parameters. When you call one, bash swaps in a fresh set of positional parameters, so inside the body `$1` is the function's first argument, `$#` is how many arguments the function got, and `"$@"` is the whole list. The script's own arguments are saved and restored when the function returns, so a function cannot see them unless you pass or save them. `$0` is deliberately not part of that set — it is fixed when the shell starts and keeps naming the script, which is why usage messages can print `$0` from anywhere. From the tenth argument on you need braces: `$10` is parsed as `${1}` followed by a literal `0`, so you write `${10}`. In practice you rarely index that far — iterate with `for a in "$@"` or consume with `shift`.
code
bash · 12 lines#!/usr/bin/env bash
show() {
echo "count=$#"
echo "first=$1"
echo "tenth=${10}"
echo "dollar0=$0"
shift 2
echo "after shift: count=$# first=$1"
}
show a b c d e f g h i j
echo "script still sees $# argument(s)"go deeper
Be ready to say plainly that bash functions take arguments positionally as $1, $2 and so on, that $# counts them, and that you loop over them with "$@".
Explain that bash saves and restores the caller's positional parameters around a call, that $0 is excluded from that swap, and that $10 parses as $1 followed by a literal 0.
Show the consequences in real scripts: a helper cannot see the script's command line unless you pass it, usage text prints $0 so it stays correct at any depth, and functions must validate $# because bash checks no arity.
Own the interface question — a bash function taking many positional arguments is a design smell; argue for few positional arguments, explicit environment or an options object built by the caller, and a documented calling contract in a shared library.
## Bash functions have no parameter list A bash function is declared with an empty pair of parentheses — they are punctuation, not a signature: ```bash greet() { echo "hello, $1" } greet world ``` You never write parameter names between the parentheses. Arguments arrive exactly the way a script's arguments arrive: **positionally**, through the special parameters `$1`, `$2`, …, plus `$#` (how many), `$@` and `$*` (all of them). There are two spellings of a definition. `name() { ...; }` is the POSIX form and works in every shell. `function name { ...; }` is a ksh/bash extension; bash treats it the same way, but it is not portable, so style guides and ShellCheck prefer the parenthesis form. Either way the body is a compound command, and the closing brace needs a `;` or a newline in front of it — `{ echo hi }` is a syntax error, `{ echo hi; }` is not. A definition is executed, not merely parsed: the function exists only after the shell has run the line that defines it. That is why library files are sourced near the top and why many scripts end with a single `main "$@"` call after all definitions. ## Calling swaps the positional parameters When a function is called, bash saves the current positional parameters and installs the function's arguments in their place. Inside the body: - `$1` … `$9` are the function's own arguments; - `$#` is the number the function received — it never counts the function name or the script name; - `"$@"` is the full list, one word per argument; - `shift` consumes the function's arguments only, leaving the caller's untouched. When the function returns, the previous set comes back. This is why a helper cannot peek at the script's command line by accident — a genuine safety property, but also a surprise for people expecting Python-style closures over `sys.argv`. If a helper truly needs the script's original arguments, capture them at the top level and pass them explicitly: ```bash script_args=("$@") # at top level main() { printf 'script got %d args\n' "${#script_args[@]}"; } main "$@" ``` ## $0 is the exception `$0` is set once when the shell starts — to the path the script was invoked with — and function calls do not touch it. Inside a function, `$0` is still `./deploy.sh`, not the function's name. The function's own name lives in `${FUNCNAME[0]}`. Confusing the two is a classic interview slip, and it matters in practice because usage messages usually print `$0` and should keep printing the script name no matter how deep the call is. ## Why ${10} needs braces The shell parses `$` followed by a *single* digit for positional parameters. So `$10` is `${1}` immediately followed by the character `0`: call a function with ten arguments and `echo "$10"` prints the first argument with a `0` glued onto it — quietly wrong, never an error. From ten on, brace it: `${10}`, `${11}`, `${12}`. In real scripts this almost never comes up, because a function that takes ten positional arguments is already a design smell. The idiomatic handling is to iterate or consume: ```bash log_all() { for line in "$@"; do printf '%s\n' "$line"; done; } drop_first() { shift; printf '[%s]' "$@"; echo; } ``` A bare `for line; do ... done` with no `in` list iterates `"$@"` — the same thing, written shorter. ## Arity is not checked Bash never validates how many arguments a function got. Extra ones simply sit in `"$@"` unused; missing ones expand to the empty string. That is why defensive scripts test `$#` explicitly, or use `${1:?message}` to abort when a required argument was not supplied. The interviewer's follow-up is usually exactly that: "so what happens if I call it with nothing?"
- Does a shift inside a function affect the script's positional parameters?No. The function has its own copy of the positional parameters, so `shift` consumes only the function's arguments. When the function returns, the caller's list is restored exactly as it was. That is what makes `shift`-driven helpers safe to call repeatedly from the same script.
- What is the difference between `name() { ...; }` and `function name { ...; }` in bash?In bash they behave the same. The parenthesis form is POSIX and runs in dash, ash and other shells; the `function` keyword is a ksh/bash extension and is not portable, so ShellCheck and most style guides prefer the parenthesis form. Mixing them — `function name() {}` — is the least portable spelling of all.
- Inside a function, how do you get the name of the function you are in?Use `${FUNCNAME[0]}`. `$0` is not it — `$0` stays fixed at the script's invocation path for the whole run, which is exactly what you want in a usage message but not what you want when logging which helper failed.
Calling a bash function is like handing someone a numbered stack of index cards: they read card 1, card 2, and count how many they got — but they never see the stack you were holding, which you get back when they hand yours over.
saying these in an interview costs you the question
- Says $0 becomes the function name inside a function
- Thinks $1 inside a function is the script's first argument
- Believes $10 expands to the tenth argument
- Says $# includes the script or function name in its count
- Claims shift inside a function consumes the caller's arguments