73

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.

muru
  • 197,895
  • 55
  • 485
  • 740
solfish
  • 1,531
  • 10
    Why do you run $#? What do you want to achieve? Where did you get this command. It is not relevant at all. – Pilot6 Jul 25 '17 at 14:28
  • @Pilot6 i cant get your point "why do you run" could you tell me what you mean pls? – solfish Jul 25 '17 at 14:39
  • You ran this command, why you did it? Give a reason. What were you trying to get with this command? – Pilot6 Jul 25 '17 at 14:40
  • 11
    @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 Jul 25 '17 at 14:43
  • @Pilot6 , as i said at very beginning on my que, i try to learn smothing on ubuntu and bash, so i m doing write some scripts on my own and asking forum to more experience people than me, why asking me that kind of question still not get – solfish Jul 25 '17 at 14:44
  • @ADDB I tried to get some context. – Pilot6 Jul 25 '17 at 14:53
  • 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 Jul 25 '17 at 15:32
  • @terdon thanks for your explanation, sorry for misunderstanding – solfish Jul 25 '17 at 16:37
  • @TrevorBoydSmith For Python, the equivalent is len(sys.argv[1:]) – wjandrea Jul 25 '17 at 19:36
  • @TrevorBoydSmith I know, I'm just clarifying that argc is not the equivalent in Python. – wjandrea Jul 25 '17 at 21:38
  • 1
    https://stackoverflow.com/questions/9630203/what-does-do-in-bash-aka-hash-dollar-sign-pound-dollar-sign. https://stackoverflow.com/questions/5163144/what-are-the-special-dollar-sign-shell-variables, https://superuser.com/questions/247127/what-is-and-in-linux/247131 – muru Jul 26 '17 at 01:12
  • 1
    Java also does not use argc. – JakeRobb Jul 26 '17 at 18:48
  • Furthermore, argc is generally the length of the argument array; in Bash, $# is the maximum index of the argument array, which is one less than its length. – JakeRobb Jul 26 '17 at 18:49
  • Unix has the concept of man pages; you can learn more about it with man man. man bash answers many questions about bash, including this. – Martin Schröder Jul 27 '17 at 12:39

4 Answers4

122

$# 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.

heemayl
  • 91,753
  • 3
    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 Jul 26 '17 at 18:41
  • 3
    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 Jul 26 '17 at 18:46
  • 3
    @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 Jul 27 '17 at 03: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 Jul 27 '17 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". – Peter Cordes Jul 27 '17 at 12:35
  • 1
    @heemayl in "bash -c 'echo $0' foo", what do you call "foo" if not an argument? (My whole point here is that this situation is going to be confusing to a non-expert and as such warrants more explanation than you gave it.) – JakeRobb Jul 28 '17 at 03:49
19

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.

Pilot6
  • 90,100
  • 91
  • 213
  • 324
  • 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 Jul 25 '17 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).

  • $# -ne 1? Why not $# -le 0 or equivalent? – Patrick Trentin Jul 26 '17 at 05:37
  • @PatrickTrentin Because I don't want them to pass two or more parameters. As the Captain said in "Red October": "Just One". – WinEunuuchs2Unix Jul 26 '17 at 12:53
  • Then: "exactly one argument required..." in the error message – Patrick Trentin Jul 26 '17 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: https://askubuntu.com/questions/917562/backup-linux-configuration-scripts-and-documents-to-gmail/922493#922493 – WinEunuuchs2Unix Jul 26 '17 at 13:26
  • How that is relevant I would not know. Anyway, cheers. – Patrick Trentin Jul 26 '17 at 13:43
  • @PatrickTrentin To make you happy I'm changing "Argument required..." to "One argument required..." here in this answer, on the another answer linked in comment and on my local storage device. I didn't think it was a big deal but because you have pointed it out others might notice as well. – WinEunuuchs2Unix Jul 26 '17 at 14:02
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
muru
  • 197,895
  • 55
  • 485
  • 740
Eliah Kagan
  • 117,780
  • 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 May 10 '18 at 18:17