75

I have a script in a file named instance:

echo "hello world"
echo ${1}

And when I run this script using:

./instance solfish

I get this output:

hello world
solfish

But when I run:

echo $# 

It says "0". Why? I don't understand what $# means.

Please explain it.

16
  • 10
    Why do you run $#? What do you want to achieve? Where did you get this command. It is not relevant at all.
    – Pilot6
    Commented Jul 25, 2017 at 14:28
  • 12
    @Pilot6 in this case he asks for the meaning of a variable, this is not case specific so why would the op need to give that information?
    – ADDB
    Commented Jul 25, 2017 at 14:43
  • 25
    @solfish Pilot is trying to understand what you were expecting. You said you ran echo $# and it returned 0 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 that echo $# would do. If you ran ./instance solfish and ./instance contained echo $#, that would print 1 and not 0.
    – terdon
    Commented Jul 25, 2017 at 15:32
  • 1
  • 1
    Java also does not use argc.
    – JakeRobb
    Commented Jul 26, 2017 at 18:48

4 Answers 4

123

$# is a special variable in bash, that expands to the number of arguments (positional parameters) i.e. $1, $2 ... passed to the script in question or the shell in case of argument directly passed to the shell e.g. in bash -c '...' .....

This is similar to argc in C.


Perhaps this will make it clear:

$ bash -c 'echo $#'
0

$ bash -c 'echo $#' _ x
1

$ bash -c 'echo $#' _ x y
2

$ bash -c 'echo $#' _ x y z
3

Note that, bash -c takes argument after the command following it starting from 0 ($0; technically, it's just bash's way of letting you set $0, not an argument really), so _ is used here just as a placeholder; actual arguments are x ($1), y ($2), and z ($3).


Similarly, in your script (assuming script.sh) if you have:

#!/usr/bin/env bash
echo "$#"

Then when you do:

./script.sh foo bar

the script will output 2; likewise,

./script.sh foo

will output 1.

6
  • 4
    I think this answer is misleading. $# is the maximum index of the array of arguments. If you give one argument, that'll be available at $0, and $# will have the value 0. If you give two arguments, they'll be in $0 and $1, and $# will have the value 1.
    – JakeRobb
    Commented Jul 26, 2017 at 18:41
  • 4
    Furthermore, when you use bash -c, behavior is different than if you run an executable shell script, because in the latter case the argument with index 0 is the shell command used to invoke it. As such, I think the way to fix this answer is to change it to execute scripts as files instead of using bash -c, since that's how the asker was doing it.
    – JakeRobb
    Commented Jul 26, 2017 at 18:46
  • 4
    @JakeRobb No. For a script, $0 would be the script itself; arguments would be $1, $2, $3.... bash -c behavior is different as it is meant for non-interactive use and the arguments following the command would start from $0, i have mentioned this clearly i believe.
    – heemayl
    Commented Jul 27, 2017 at 3:08
  • 6
    @JakeRobb You've misheard me. If you give one argument, that'll be available at $0, and $# will have the value 0 -- this is plain wrong.
    – heemayl
    Commented Jul 27, 2017 at 12:14
  • 2
    If you put a complete program that looks at it args into bash -c, you should pass bash some meaningful name as filler for the 0th arg. bash -c 'echo "$@"' foo bar only prints bar, because "$@" doesn't include $0, same as always. bash -c doesn't "make $0 one of the args", it merely allows you to set "$0" the same way you when running a program with the execve system call or any shell way of overriding the 0th arg. $0 should never be considered "one of the args". Commented Jul 27, 2017 at 12:35
21

echo $# outputs the number of positional parameters of your script.

You have none, so it outputs 0.

echo $# is useful inside the script, not as a separate command.

If you run a script with some parameters like

./instance par1 par2

the echo $# placed into the script will output 2.

1
  • 6
    For the example of the OP: If you add the line echo $# to your script and than run again ./instance solfish you should get an additional output line with 1 since you provided 1 parameter
    – derHugo
    Commented Jul 25, 2017 at 14:31
17

$# is typically used in bash scripts to ensure a parameter is passed. Generally, you check for a parameter at the beginning of your script.

For example, here's a snippet of a script I was working on today:

if [[ $# -ne 1 ]]; then
    echo 'One argument required for the file name, e.g. "Backup-2017-07-25"'
    echo '.tar will automatically be added as a file extension'
    exit 1
fi

To summarize $# reports the number of parameters passed to a script. In your case, you passed no parameters and the reported result is 0.


Other # uses in Bash

The # is often used in bash to count the number of occurrences or the length of a variable.

To find the length of a string:

myvar="some string"; echo ${#myvar}

returns: 11

To find the number of array elements:

myArr=(A B C); echo ${#myArr[@]}

returns: 3

To find the length of the first array element:

myArr=(A B C); echo ${#myArr[0]}

returns: 1 (The length of A, 0 is the first element as arrays use zero-based indices/subscripts).

6
  • $# -ne 1? Why not $# -le 0 or equivalent? Commented Jul 26, 2017 at 5:37
  • @PatrickTrentin Because I don't want them to pass two or more parameters. As the Captain said in "Red October": "Just One". Commented Jul 26, 2017 at 12:53
  • Then: "exactly one argument required..." in the error message Commented Jul 26, 2017 at 13:07
  • @PatrickTrentin The error message spells out how the script is used with an example of the one argument usage. Furthermore the backup script is called by cron.daily which is only configured once by someone tech savvy: askubuntu.com/questions/917562/… Commented Jul 26, 2017 at 13:26
  • How that is relevant I would not know. Anyway, cheers. Commented Jul 26, 2017 at 13:43
14

$# 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
1
  • 2
    Kudos for the educative answer that went beyond what was asked but responded to the learning intentions of the original asker and others like me!
    – Ricardo
    Commented May 10, 2018 at 18:17

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .