How to Use $@ in Bash Scripting

Written by: Linuxopsys   |   Last updated: July 2, 2023

1. Introduction

There are certain special variables that you will come across only in Bash. These variables might appear challenging at first. But, understanding how Bash uses these combinations of characters will help you move from an average Linux user to an experienced one.

In this guide, we learn about $@ special variable and how it is useful in Bash. We will see how it differs from $* variable.

2. What is Bash $@?

$@ is an extremely simple yet powerful special parameter in Bash. Bash uses it to ease the passing and handling of positional parameters whether be it scripts or functions. Bash expands into the full list of positional arguments $1 $2 $3 … $n. It preserves the argument count through the expansion. 

3. Difference between $@ and $*

Without the use of double quotes, both $@ and $* have the same effect. All positional parameters from $1 to the last one used are expanded without any special handling. 

When the $* parameter is double quoted, it expands to as: 

"$1s$2s$3s……..$N" 

's' is the first character of IFS. If IFS is unset, the parameters get separated by spaces. When IFS becomes null, the parameters join without the intervening separators.

But when the $@ special parameter is wrapped with double quotes, it is equivalent to:

"$1" "$2" "$3"  ….. "$N"

This ensures all the positional parameters are the same as they were set initially and passed to the script or function. If we want to re-use these positional parameters to call another program, we should use double quoted "$@".

Now, let us practically interpret this with an example:

set -- "arg  1" "arg  2" "arg  3"

echo 1. 'With $*'
for word in $*; do echo "$word"; done

echo '2. With $@'
for word in $@; do echo "$word"; done

echo '3. With "$*"'
for word in "$*"; do echo "$word"; done

echo '4. With "$@"'
for word in "$@"; do echo "$word"; done
The output demonstrates the differences between $*, $@, "$*", and "$@" when iterating over command-line arguments in a shell script

In this example we have considered four cases:

In the first two cases, the output will be as:

arg
1
arg
2
arg
3

This is because every argument, separated by a space, is considered a separate word.

The third case outputs the following:

arg  1 arg  2 arg  3

This is because "$*" considers all the positional parameters as a single word.

In the final case, we get:

arg  1
arg  2
arg  3

This is because "$@" considers all the positional parameters as separate strings.

Overview Table  

Assuming "s” is the first character of the Input Field Separator, we can generalize the results of $* and $@ with and without the quotes as follows:

S_noSyntaxResult
1$*$1 $2 $3 … ${N}
2$@$1 $2 $3 … ${N}
3"$*""$1s$2s$3s…s${N}"
4"$@""$1" "$2" "$3" … "${N}"

4. Using “$@” Variable in Bash

From passing all the arguments to other commands, scripts, and functions to substring expansion, we can use the $@ notation. In this section, we will take a look at how to use this special symbol in Bash.

4.1 Accessing all arguments

Bash uses $@ to expand the parameters passed to the script. It begins with the first parameter. If we enclose in double quotes "$@", each parameter is expanded to a separate word.  "$@" is equivalent to "$1" "$2" ... "$n". 

Example:

#!/bin/bash 
echo Parameters are: "$@" 

Let us save this as mySampleScript1.sh and run it as 

./mySampleScript1.sh one two three
passed 3 parameters to the script

$@ is a special shell variable which expands to a list of all arguments. As we have passed 3 parameters to this script, we get to see those 3 parameters on the screen.

4.2 Utilizing $@ within Loops

We can use the $@ notation within the loops to refer to every command-line argument passed to a script one by one. 

Example:

#!/bin/bash
for arg in "$@"
do
	echo $arg
done

Run this script:

./mySampleScript2.sh one two three
script output using $@ in a for loop

To iterate over the command-line arguments, we are using $@ in a for loop. Then we print one argument at a time on the screen. One important thing to note is the symbol $@. It needs to be surrounded by double quotes. This way Bash handles every argument as a separate word.

4.3 Passing arguments to another command

The $@ symbol also helps to pass command-line arguments to other commands in Bash. It expands to all the command-line arguments passed to the current script when dealing with $@ as an argument to another command.

Example:

#!/bin/bash

key=$1
shift
grep $key $@

Let us execute this script as follows:

./mySampleScript3.sh sample *.txt
script will search for the keyword "sample" in all txt files

When we run mySampleScript3.sh, the first command-line argument $1 gets assigned to the key variable. The shift command then omits the first argument. $key and $@ are the arguments to the grep command. 

$@ expands to all the remaining command-line arguments. They are effectively treated as filenames (*.txt)  to be searched for the provided keyword. Hence, our script will search for the keyword "sample" in all txt files.

4.4 Substring expansion on parameters

The basic form of parameter expansion is ${parameter}. The fundamental form of substring expansion is:

${parameter:offset:length}

Let us understand this with examples:

Example 1:

set -- 1 2 3 4 5 6 7 8 9 
echo ${@:5:0}
echo ${@:5:2}
usage of ${@:5:2} extracts the fifth and sixth command-line arguments and prints them. However, the line ${@:5:0} will not produce any output.

It expands till the length of characters of the value of parameter starting at the character specified by offset. The variables length and offset can be arithmetic expressions as well. 

 In the first case, we have passed the length as zero, hence we do not get any visible output. Similarly,we are picking 2 characters beginning with the 5th position.

Example 2:

set -- 1 2 3 4 5 6 7 8 9 
echo ${@:7}
the output of the script will be 7 8 9, which corresponds to the command-line arguments starting from the seventh position onward.

If the length variable is ignored, it expands the parameter at the character specified by the offset. It extends till the end. 

Example 3:

set -- 1 2 3 4 5 6 7 8 9 
echo ${@:5: -2}
showing error when the length is less than zero

We get the expected expansion error when the length is less than zero.

Example 4:

set -- 1 2 3 4 5 6 7 8 9 
echo ${@: -3:2}
output of the script will be 7 8, which corresponds to the third-to-last and second-to-last command-line arguments.

A negative offset is taken relative to one greater than the greatest positional parameter. This way an offset of -1 evaluates to the last positional parameter. It is interpreted from the end of the value of parameter. The expansion is the characters between the offset till the length. A negative offset has to be separated from the colon by at least one space. 

We have an offset of -3 which corresponds to the number 7. And we pick two characters from there, thus getting 7 and 8 on the screen.

Example 5:

set -- 1 2 3 4 5 6 7 8 9 
echo ${@:0}
echo ${@:0:2}
offset at zeroth position is bash

This example illustrates that the offset at zeroth position is bash. In the first case, we get the output as -bash followed by the 9 digits since we haven’t specified the length. In the next case, we have restricted it to pull out the first two positions by specifying the length as 2. This gave us the result as -bash 1.

4.5 Handling quoting and spaces

When the parameter is quoted, Bash hands over all arguments the way it received them, even if there are unnecessary spaces. Let us see how the outcome differs with and without the use of the quotes.

Example:

#!/bin/bash 
echo Parameters without quotes are: $@
echo Parameters with quotes are: "$@" 

Let us save this as mySampleScript4.sh and run it as:

./mySampleScript4.sh "one     two                 three"
The first line shows the command-line parameters without quotes, and the second line shows the command-line parameters with quotes

In the first case, since we haven’t wrapped the $@ with double quotes, we are losing the extra spaces between the positional arguments passed to the script. In the other case, quoting with double quotes ensures that those spacings are retained.

4.6 Functions and $@

$@ is employed to pass arguments between functions. 

Let us understand this with an example. We have two functions f1 and f2. Both accept positional parameters. If f1 calls f2, then we can pass all the arguments from f1 to f2 by simply using:

f2 "$@" 

This will expand to all the positional parameters passed to function1. Let us write a simple script to understand it even better.

#!/bin/bash

#function 2
f2()
{
	echo "Bye, $1 $2"
}

#function 1
f1()
{
	echo "Hello, $1 $2"
	#calling function 2 inside function 1
	f2 "$@"
}

#triggering f1
f1 "Roger" "Federer"
The execution flow of the script, showing the greeting message from f1 and the farewell message from f2, both using the provided arguments.

We have 2 functions where f2 is being called by f1. We run the script by calling f1 with 2 arguments. Hence it will echo the hello message. After this, it will call f2 with the same parameters that it consumed. This will result in printing of the bye message.

5. Example Script

We will take a real-world problem where the user provides a list of files and the script generates backup of those files by appending the timestamp.

Example:

#!/bin/bash

usage()
{
	echo "Usage: ./backup.sh <file1> [<file2> ... <fileN>]"
}

if [ "$#" -lt 1 ]
then
echo "No file specified." 
	usage
exit 1
fi

for file in "$@"
do
	if [ ! -f "$file" ]
	then
		echo "[ERROR] File $file doesn't exist"
		continue
	else
			currentDate=`date +%d-%m-%y`
			currentTime=`date +%H-%M`
			cp $file ${file}_${currentDate}_${currentTime}
			RC=${?}
			if [[ ${RC} -eq 0 ]]
			then
				echo "Copied $file to ${file}_${currentDate}_${currentTime}"
			fi
	fi
	
done
examples script - for loop iterates over each command-line argument ($@), which represents the files to be backed up

1. The usage function displays how to run the script.

2. We count the number of arguments available to the script. No arguments are supplied if it is less than 1. In this case, we call the usage method and terminate the script.

3. If the arguments are more than 1, then we read all the arguments using "$@" one by one using the for loop.

4. For every entry, we check if the specified input is a file or not. If it is not a file, we display an error message and jump to the next iteration using the continue statement.

5. If it is a file, we extract the current date and time from the Bash date command.

6. We then create a backup copy the file with the name: file_<dateDD-MM-YYYY>_<timeHH-MM>

7. We then verify the exit status of the cp command. If its numerical value is zero, it indicates that the command succeeded. 

8. We finally display the successful copy message.

9. We repeat this for the next entry in the for loop.

SHARE

Comments

Please add comments below to provide the author your ideas, appreciation and feedback.

Leave a Reply

Leave a Comment