$#
is the number of arguments, but remember it will be different in a function.
$#
is the number of positional parameters passed to the script, shell, or shell function. This is because, while a shell function is running, the positional parameters are temporarily replaced with the arguments to the function. This lets functions accept and use their own positional parameters.
This script always prints 3
, regardless of how many arguments were passed to the script itself, because "$#"
in the function f
expands to the number of arguments passed to the function:
#!/bin/sh
f() {
echo "$#"
}
f a b c
This is important because it means code like this does not work as you might expect, if you're not familiar with how positional parameters work in shell functions:
#!/bin/sh
check_args() { # doesn't work!
if [ "$#" -ne 2 ]; then
printf '%s: error: need 2 arguments, got %d\n' "$0" "$#" >&2
exit 1
fi
}
# Maybe check some other things...
check_args
# Do other stuff...
In check_args
, $#
expands to the number of arguments passed to the function itself, which in that script is always 0.
If you want such functionality in a shell function, you'd have to write something like this instead:
#!/bin/sh
check_args() { # works -- the caller must pass the number of arguments received
if [ "$1" -ne 2 ]; then
printf '%s: error: need 2 arguments, got %d\n' "$0" "$1" >&2
exit 1
fi
}
# Maybe check some other things...
check_args "$#"
This works because $#
is expanded outside the function and passed to the function as one of its positional parameters. Inside the function, $1
expands to the first positional parameter that was passed to the shell function, rather than to the script it's part of.
Thus, like $#
, the special parameters $1
, $2
, etc., as well as $@
and $*
, also pertain to the arguments passed to a function, when they are expanded in the function. However, $0
does not change to the name of the function, which is why I was still able to use it to produce a quality error message.
$ ./check-args-demo a b c
./check-args-demo: error: need 2 arguments, got 3
Similarly, if you define one function inside another, you're working with the positional parameters passed to the innermost function in which the expansion is performed:
#!/bin/sh
outer() {
inner() {
printf 'inner() got %d arguments\n' "$#"
}
printf 'outer() got %d arguments\n' "$#"
inner x y z
}
printf 'script got %d arguments\n' "$#"
outer p q
I called this script nested
and (after running chmod +x nested
) I ran it:
$ ./nested a
script got 1 arguments
outer() got 2 arguments
inner() got 3 arguments
Yes, I know. "1 arguments" is a pluralization bug.
The positional parameters can also be changed.
If you're writing a script, the positional parameters outside a function will be the command-line arguments passed to the script unless you have changed them.
One common way to change them is with the shift
builtin, which shifts each positional parameter to the left by one, dropping the first one and decreasing $#
by 1:
#!/bin/sh
while [ "$#" -ne 0 ]; do
printf '%d argument(s) remaining.\nGot "%s".\n\n' "$#" "$1"
shift
done
$ ./do-shift foo bar baz # I named the script do-shift.
3 argument(s) remaining.
Got "foo".
2 argument(s) remaining.
Got "bar".
1 argument(s) remaining.
Got "baz".
They can also be changed with the set
builtin:
#!/bin/sh
printf '%d args: %s\n' "$#" "$*"
set foo bar baz
printf '%d args: %s\n' "$#" "$*"
$ ./set-args a b c d e # I named the script set-args.
5 args: a b c d e
3 args: foo bar baz
$#
? What do you want to achieve? Where did you get this command. It is not relevant at all.echo $#
and it returned0
which is normal. You were surprised by this, but you don't explain what you were expecting or why you were surprised. So it would help us give you a better answer if you explained what you were expecting. What you thought thatecho $#
would do. If you ran./instance solfish
and./instance
containedecho $#
, that would print1
and not0
.