When writing a bash script, you won’t always know how many arguments a user will provide. You might be writing a tool that processes one file or a hundred files. Forcing a fixed number of parameters makes your script inflexible. Fortunately, Bash provides a simple and powerful way to handle this: the special variable $@.
This guide will walk you through the essential variables for handling script arguments and show you how to use $@ to process them effectively.
The Key Players: Understanding Bash Argument Variables
Before we start looping, let’s quickly review the special variables Bash gives us for accessing command-line parameters.
$#: This variable contains the count of the arguments passed to the script. It’s a number.$*: This variable expands to a single string containing all arguments, separated by the first character of the Internal Field Separator (IFS) variable (which is a space by default).$@: This variable expands to a list of separate words, with each argument treated as an individual, quoted string. This is crucial for handling arguments that contain spaces.
Why is "$@" usually better than "$*"?
When quoted, "$*" treats all arguments as a single string ("arg1 arg2 arg3"), while "$@" treats them as separate, individual strings ("arg1" "arg2" "arg3"). This distinction is vital when looping, as it ensures that arguments with spaces are not broken apart.
Let’s see it in action. Create a script named test-args.sh:
#!/bin/bash
# test-args.sh
echo "--- Looping with \"\$*\" ---"
for arg in "$*"; do
echo "Argument: '$arg'"
done
echo
echo "--- Looping with \"\$@\" ---"
for arg in "$@"; do
echo "Argument: '$arg'"
done
Now, make it executable and run it with an argument that contains spaces:
chmod +x test-args.sh
./test-args.sh one "two and three" four
The output clearly shows the difference:
--- Looping with "$*" ---
Argument: 'one two and three four'
--- Looping with "$@" ---
Argument: 'one'
Argument: 'two and three'
Argument: 'four'
As you can see, "$@" correctly preserved "two and three" as a single argument, which is exactly what we need.
Step-by-Step: Processing All Arguments in a Script
Let’s build a practical script that accepts an unknown number of file paths and reports some information about each one.
Step 1: Create the Script File
First, create a new file named process-files.sh and add the standard shebang line.
#!/bin/bash
# A script to process an unknown number of file paths.
Step 2: Check for at Least One Argument
It’s good practice to check if the user provided any arguments at all. We can use the $# variable for this. If no arguments are given, we print a usage message to standard error (>&2) and exit with an error code.
Add this block to your script:
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <file1> <file2> ..."
echo "Error: No file paths were provided." >&2
exit 1
fi
[ "$#" -eq 0 ]: This condition checks if the number of arguments ($#) is equal (-eq) to zero.echo ... >&2: This redirects our error message to the standard error stream, which is the correct place for diagnostics.exit 1: This terminates the script with a non-zero exit code, signaling that an error occurred.
Step 3: Loop Through Arguments with for and "$@"
This is the core of our script. We use a for loop to iterate over every item provided in "$@". Inside the loop, the variable file will hold the value of each argument, one at a time.
Add the loop to your script:
echo "--- Processing $# total files ---"
echo ""
for file in "$@"; do
echo "-> Analyzing '$file':"
if [ -f "$file" ]; then
# The argument is a regular file
echo " Type: File"
echo " Words: $(wc -w < "$file")"
elif [ -d "$file" ]; then
# The argument is a directory
echo " Type: Directory"
else
# The argument is not found
echo " Error: File or directory not found."
fi
echo "" # Add a newline for cleaner output
done
echo "--- All arguments processed. ---"
for file in "$@": This is the magic line. It tells Bash to loop through each argument passed to the script. The quotes around"$@"are essential for correctly handling paths with spaces.[ -f "$file" ]: This tests if the argument is a regular file.[ -d "$file" ]: This tests if the argument is a directory.$(wc -w < "$file"): This is a command substitution. It runs thewc -w(word count) command on the file and inserts the output.
Step 4: Make the Script Executable and Run It
Your final process-files.sh script should look like this:
#!/bin/bash
# A script to process an unknown number of file paths.
if [ "$#" -eq 0 ]; then
echo "Usage: $0 <file1> <file2> ..."
echo "Error: No file paths were provided." >&2
exit 1
fi
echo "--- Processing $# total files ---"
echo ""
for file in "$@"; do
echo "-> Analyzing '$file':"
if [ -f "$file" ]; then
# The argument is a regular file
echo " Type: File"
echo " Words: $(wc -w < "$file")"
elif [ -d "$file" ]; then
# The argument is a directory
echo " Type: Directory"
else
# The argument is not found
echo " Error: File or directory not found."
fi
echo "" # Add a newline for cleaner output
done
echo "--- All arguments processed. ---"
Save the file, make it executable, and test it with a mix of valid files, directories, and non-existent paths.
# First, create a dummy file with spaces in its name
echo "hello world" > "my test file.txt"
# Now run the script
chmod +x process-files.sh
./process-files.sh /etc/hosts "/etc" "my test file.txt" "nonexistent.file"
You should see output similar to this:
--- Processing 4 total files ---
-> Analyzing '/etc/hosts':
Type: File
Words: 45
-> Analyzing '/etc':
Type: Directory
-> Analyzing 'my test file.txt':
Type: File
Words: 2
-> Analyzing 'nonexistent.file':
Error: File or directory not found.
--- All arguments processed. ---
The script correctly identified each argument, handled the path with spaces, and processed everything in a single, elegant loop.
Conclusion
Handling a variable number of arguments is a fundamental skill in Bash scripting. By mastering $@, you can create powerful and flexible command-line tools.
Key Takeaways:
$#gives you the number of arguments."$@"is the best way to access all arguments as a list of individual, quoted items.- Always quote
"$@"in your loops (for arg in "$@") to correctly handle arguments containing spaces or other special characters.
You can now apply this pattern to any script that needs to act on a list of inputs, such as processing log files, backing up directories, or running commands on multiple servers.