Bash special parameters

$*

Expands to the positional parameters starting from one (the parameter with index “zero” is the script name). When the expansion occurs within double quotes, it expands to a single word with the value of each parameter separated by the first character of the IFS special variable.

Here is are some examples

#!/bin/bash
# Saved as bash_params_1.sh

echo "Printing \"\$*\":"
for a in "$*"; do
    echo $a;
done

We execute the script like this:

> bash_params_1.sh 1 2 "3 4"

it will output the following:

Printing "$*":
1 2 3 4

This means that in the case of “$*” all the positional parameters were concatenated in a quoted string (in this case, IFS had the default value of “ “). Because there is a quoted string, the for loop processed a single element.

Let’s change the value of IFS to “+”. We print both the value of “$*” and the output of the loop.

#!/bin/bash
# Saved as bash_params_1.sh

IFS=“+"
echo "Printing \"\$*\”:" "$*"
for a in "$*"; do
    echo $a;
done

After running the script with the same arguments we get the following:

Printing "$*": 1+2+3 4
1 2 3 4

What happened? The value of “$*” is indeed “1+2+3 4” as the 3 positional parameters were concatenated in a single quoted word using the new IFS. But why the loop output is still “1 2 3 4” and not the (somehow expected) “1+2+3 4”? The answer is due to “echo $a;”. In this case, $a is unquoted – in this case $a (which is indeed “1+2+3 4”) will be split (according to IFS) and the result of the split will be passed as multiple parameters to the echo command which outputs its arguments separated by spaces.

How to fix it? We quote “$a” to no longer allow the splitting when the echo command is applied.

#!/bin/bash
# Saved as bash_params_1.sh

IFS=“+"
echo "Printing \"\$*\”:" "$*"
for a in "$*"; do
    echo “$a";
done

The output now is the following:

Printing "$*": 1+2+3 4
1+2+3 4

Excellent. Now that we’ve understood “$*”, let’s see how $* is managed. We have the second script:

#!/bin/bash
# Saved as bash_params_2.sh

echo "Printing \$*:"
for a in $*; do
    echo $a;
done

which is executed with the same arguments as the first script:

> bash_params_2.sh 1 2 "3 4"

The output is the following:

Printing $*:
1
2
3
4

The $* is still a string where the 3 positional parameters have been merged (using the IFS) but this time the result is an unquoted string. When this string is processed by the for loop, it will simply generate all the words using the IFS separator. In this case, because IFS is the default “ “, even the 3rd positional parameter will be split in 2. We can see this by changing the IFS value:

#!/bin/bash
# Saved as bash_params_2.sh

IFS=“+"
echo "Printing \$*:"
for a in $*; do
    echo $a;
done

After executing the script again, we will get the following:

Printing $*:
1
2
3 4

As you can see, the 3rd positional parameter was left untouched by the for loop as the IFS used by the loop to break the input string is now “+”.

$@

Expands to the positional parameters, starting from one (again, the parameter with index “zero” is the script name). When the expansion occurs within double quotes, each parameter expands to a separate word.

What does this mean? The main difference between $* and $@ is that while $* is a word where all positional parameters have been concatenated using IFS, the $@ is an array, where each positional parameter is an element in that array!!!

Let’s see that in action: here is the 3rd script that is shows how both $@ and “$@“ work:

#!/bin/bash
# Saved as bash_params_3.sh

echo -e "\nPrinting \"\$@\":"
for a in "$@"; do
    echo $a;
done

echo -e "\nPrinting \$@:"
for a in $@; do
    echo $a;
done

We run it with the same parameters:

> bash_params_3.sh 1 2 "3 4"

The generated output is:

Printing "$@":
1
2
3 4

Printing $@:
1
2
3
4

In the “$@“ case, each positional parameter from the $@ array will be treated as a quoted string – that’s why we see the “3 4” treated as a single step by the for loop.

In the $@ case, is positional parameter from the $@ array will be treated as an unquoted string – this, the 3rd parameter (“3 4”) will be one again split by the for loop due to the fact that IFS is the default “ “.

$#

Expands to the number of positional parameters in decimal. For example, we have the following script:

#!/bin/bash
# Saved as bash_params_4.sh

echo -e "\nPrinting \$#:" $#

We run it with the same parameters:

> bash_params_4.sh 1 2 "3 4"

The generated output is:

Printing $#: 3

which is as expected as we have provided 3 parameters to the script.

$?

This provides the exit status of the last executed command from the most recent command pipeline. Here is an example:

#!/bin/bash
# Saved as bash_params_5.sh

ls bash_params_5.sh
echo -e "\nPrinting \$? after a successful ls command :" $?

ls x_bash_params_5.sh
echo -e "\nPrinting \$? after an unsuccessful ls command :" $?

We execute the bash_params_5.sh script with no parameters and the generated output is:

Printing $? after a successful ls command : 0
ls: x_bash_params_5.sh: No such file or directory

Printing $? after an unsuccessful ls command : 1

The first time we print $? we get the value 0 (zero) because the first ls command was successful. The second time, however, we get the value 1 as the ls command failed (we can see that from the error message too).

$-

This expands to the current option flags as specified upon invocation. The bash flags can be set:

  • implicitly by the shell itself
  • by using the set command

Here is the script to demonstrate this:

#!/bin/bash
# Saved as bash_params_6.sh

echo -e "\nPrinting \$-:" $-

After running the script with no parameters, we get the following output:

Printing $-: hB

this means that 2 option flags are active so far (these are being enabled implicitly by the shell):

  • h: Causes bash to remember (hash) where commands it has found using PATH are located (default).
  • B: brace expansion is enabled, which means that you can run a command repeatedly on a list of comma-separated parameters within braces. Example:
    > echo \"{These,words,are,quoted}\"
    

    will output

    "These" "words" "are" "quoted"
    

As said before, we can use the set command to alter the set of option flags. Here is an updated version of the previous script where we are enabling the “verbose” option:

#!/bin/bash
# Saved as bash_params_6.sh

set -v    # setting / enabling the “verbose” option
echo -e "\nPrinting \$-:" $-

The output will be now:

echo -e "\nPrinting \$-:" $-
Printing $-: hvB

There are 2 things to notice here:

  • first the command to be executed (“echo -e “\nPrinting \$-:” $-“) is first printed before the actual execution. That’s the effect of enabling the “verbose” option.
  • second, we can see that the list of enabled options is now “hvB” which means that “v” option has been added to the list.

$$

Expands to the process ID of the shell. Here is an example to show this:

#!/bin/bash
# Saved as bash_params_7.sh

echo -e "\nPrinting \$\$:" $$

b=0
for a in {1..1000000}; do
  b=$((a + b));
done
echo "b =" $b

Here we print the value of $$ and then we do a very length computation to keep the shell process running while we check the PID (Process ID) value).

We run the script with no parameters but we run it in background:

> bash_params_7.sh &

The scripts prints the $$ value of 35162 and then starts the lengthy computation. In the meantime, we run the ps command to see the processes that run. We get the following output:

  PID TTY           TIME CMD
35162 ttys000    0:01.38 bash bash_params_7.sh

As you can see, the ID of the process that runs our script is indeed 35162, he same ID from the $$ variable.

$!

Expands to the process ID of the most recently executed background (asynchronous) command. We can use the example above (the one showing $$) to demonstrate $!.

Remember that, after running “bash_params_7.sh &” we have demonstrated that the ID of the process that executed this script was saved in $$. The script was executed in background, thus after it completes, we can run the following command:

echo $!

We will see that the output is, again 35162 which is, indeed, the ID of the last process that was executed in background.

$0

Expands to the name of the shell or shell script. Quite trivial to demonstrate this. Here is the simplest possible script to demonstrate this:

#!/bin/bash
# Saved as bash_params_8.sh

echo -e "\nPrinting \$0:” $0

Executing this script we will get the following output:

Printing $0: bash_params_8.sh

$_

The underscore variable is set at shell startup and contains the absolute file name of the shell or script being executed as passed in the argument list. Subsequently, it expands to the last argument to the previous command, after expansion.

To demonstrate the first part of the statement above, we use a script similar with the one from the previous section:

#!/bin/bash
# Saved as bash_params_9.sh

echo -e "\nPrinting \$_:” $_

Executing this script we will get the following output:

Printing $_: bash_params_9.sh

Thus, if no commands were executed yet, the $_ parameter will contain the script name. What happens if we execute a command before printing $_?

#!/bin/bash
# Saved as bash_params_9.sh

ps -e -o pcpu,cpu,nice,state,cputime,args -m
echo -e "\nPrinting \$_:” $_

In this case, the script will output the following:

Printing $_: -m

meaning the last argument that was passed to the ps command.

This entry was posted in Unix and tagged . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s