zsh

ZSH Scripting Lesson: Parsing and Processing the Command-Line Arguments

In Linux systems, there is a variety of shell interpreters such as Bash, ZSH, Fish, and many more. However, they all have the ability to run the scripts and utilities in common.

When developing the shell scripts, we often need to provide the ability to read and parse the command-line arguments. These arguments allow us to control the behavior and functionality of the script during execution.

In this tutorial, we will learn how to read, parse, and process the command-line arguments using the ZSH scripting language.

Note: It is good to remember that ZSH is based on Bash and hence does inherit some features, even when parsing the command-line arguments.

Positional Parameters in ZSH

In ZSH and other shells, the positional parameters provide access to the command-line arguments of a shell function, script, or the shell itself.

The positional parameters are denoted by $1, $2, …, $N where “n” is a number that represents the nth positional parameter.

The “$0” parameter refers to the script’s name in which the execution is currently running.

In ZSH, the “*”, “@”, and “argv” parameters are arrays that contain all the positional parameters. Hence, $argv[n] is equivalent to simply running “$n”.

It is good to keep in mind that the positional parameters can be changed after the script execution has started using the “set” command and assigning it to the “argv” array. This follows the format of “n=value”.

ZSH Parsing: Simple Argument

Look at the following example script that demonstrates using ZSH to parse a basic command-line argument.

Start by creating the script file:

$ touch simple.sh

Add the script code as follows:

#!/usr/bin/env zsh
for arg in "$@"
do
echo "arg: $arg"
done

Save the file and make it executable.

$ chmod +x simple.sh

We can then run the script and provide the arguments as shown in the following:

./simple.sh MySQL PostgreSQL Redis

Once we run the previous command, we should get an output as shown in the following:

arg: MySQL
arg: PostgreSQL
arg: Redis

As you can see, the previous code iterates over the argument array and prints out the value of each passed argument.

Shift Positional Arguments

When parsing the arguments, you might need to shuffle the position of the positional arguments to a different position. This is where we can use the “shift” command to achieve this task.

The “shift” command allows us to shift the positional arguments, allowing “$2” to become “$1” and “$3” to become “$2”, and so on.

One of the most common use cases is when you must iterate over all the arguments while omitting the first. Instead of manually parsing and skipping the first one, you can use this shift command to solve all those issues.

Take a look at the following example:

#!/usr/bin/env zsh
shift
echo "After shift, arg 1: $1"

Once we run the previous code, we should get the output as follows:

./shift MySQL PostgreSQL Redis

Output:

$ ./shift.sh MySQL PostgreSQL Redis
After shift, arg 1: PostgreSQL

Working with ZSH Flags and Options

The most common use case of the command-line options is to specify a given flag or option. This allows us to provide more functionality that can be activated when needed during script execution.

For example, when running a script, you can invoke the “-h” or “—help” flag which can allow us to show the help and functionality of the script.

It is good to keep in mind that although the flags and options are used interchangeably, they differ in the form that the options accept as a value of the “while” flags.

An example of a flag is as follows:

./script.sh --help
./script -h

The following is an example of an option:

./script -f filename
./script --file=filename

ZSH Getopts

Getopts is a shell builtin command in ZSH, Bash, and other shells. It allows us to parse the command-line options during the execution of a script.

It works by processing the provided options and assigning the next option’s name to the shell variable provided as the second argument to getopts.

The following shows the basic syntax of the “getopts” command in ZSH shell:

$ getopts opstring name [arg …]

Let us breakdown the previous options as follows:

  1. Optstring – This contains the option letter to be recognized. If a letter is followed by a colon, the option is expected to have an argument which should be separted from it by a whitespace character.
  2. Name – This refers to the name of the variable that is used by the “getopts” command to report the next option name.
  3. Arg – This defines the arguments to be parse. If omitted, the command will parse the positional arguments as described in the previous section.

It is good to keep in mind that if the command encounters an invalid option, the command will place the “?” character into the name variable.

Similarly, when an option requires an argument but none is given, the command will place the “?” character in the name variable.

Lastly, if optstring begins with a colon, or the command places the “*” character in the name variable, and prints an error message.

Unlike other external GNU utilities, the “getopt” and “getopts” commands do not support long option names such as “—help”. Hence, only use a single letter option.

In the last resort, the “getopts” command returns a zero status when an option is found and it returns a non-zero status when the end of options occurs. The command also returns a zero status but with a variable set to “?” if it encounters an invalid option.

NOTE: For you to fetch the return values, ensure that the error reporting is not surpressed.

Let us take a look at an example of the “getopts” command in use:

#!/usr/bin/env zsh

# show help
function show_help {
 echo "Usage: $0 [-h] [-f filename]"
 return 0
}
# Option strings
SHORT='hf:'
LONG='help,file:'

# - Temporarily disable error reporting for unbound variables
setopt local_options unset
# Default values
file=''
# Parse arguments
while getopts ${SHORT} opt; do
 case "$opt" in
   h) show_help; exit 0 ;;
   f) file=$OPTARG ;;
   *) echo "Unknown option: -$OPTARG" >&2; exit 1 ;;
esac
done
echo "File: $file"

In the given example, we start by defining a function that runs when we use the “-h” option in the script. This displays the “help” menu of the function.

We then define the option strings both in short and long format.

We also proceed to temporarily disable the error that reports for unbound variables which prevents any error if no arguments are given.

Finally, we use the “getopts” command to parse the command-line options and execute the necessary code for the matching blocks.

For example, to show the “help” menu from the previous script, we can run the following code:

./script.sh -h
Usage: show_help [-h] [-f filename]

As you can see, using the “-h” option shows the “help” menu that is defined in the show_help() function.

To pass the file argument, we can use the “-f” parameters as follows:

./script.sh -f /var/log/www/access.log
File: /var/log/www/access.log

As you can see, we use the “-f” option to provide the path to the target file. For our case, the script does nothing with the provided filepath. However, in a real-world case, you can use this path to perform a variety of operations depending on your desired script functionality.

Globbing and RegEx

Instead of manually parsing the arguments and taking them apart one by one, you can take advantage of globbing and regular expressions supported by ZSH parse arguments.

This is not an introduction to globbing or regular expressions. Check our tutorials on that to learn more.

Consider the following example script:

#!/usr/bin/env zsh

# Enable extended globbing
setopt extended_glob

for arg in "$@"; do
if [[ $arg == --*=* ]]; then
   key=${arg%%=*}                                                                                                                                                                      
   value=${arg#*=}                                                                                                                                                                    
   echo "Long option: $key, with value: $value"                                                                                                                                        
 elif [[ $arg == -* && $arg != --* ]]; then                                                                                                                                            
   echo "Short option: $arg"                                                                                                                                                          
 else                                                                                                                                                                                  
   echo "Argument: $arg"      
fi
done

In this example, we start by enabling the extended globbing in ZSH using the “set” command.

We then proceed to configure globbing with regular expressions which allows the script to differentiate between the log in options with values, short options, and just general arguments when running the script.

Working with Associative Arrays

When it comes to more a complex flag and option parsing, it is advisable to take advantage of associative arrays in order to store the option values.

You can also define the custom parsing logic to handle the combinations of options or arguments. For example, notice that when connecting to MySQL CLI, you can specify the username in a single line as follows:

$ mysql -uroot -p

Such functionality is handled by custom logic and features such as associative arrays.

Consider the following example code:

#!/usr/bin/env zsh

# Declare associative array
declare -A options

# Parsing logic
while (( $# > 0 )); do
case $1 in
   --option1) options[option1]=$2; shift 2;;                                                                                                                                            
   --option2) options[option2]=$2; shift 2;;                                                                                                                                            
   --*) echo "Invalid option: $1"; exit 1;;                                                                                                                                            
   *) options[argument]=$1; shift;;                    
esac
done

for key value in ${(kv)options}; do
echo "$key is set to $value"
done

In the given example, we start by declaring an associative array named “options”. An associative array is also known as a dictionary in languages like Python.

Next, we declare a “while” loop that iterates as long as there are positional parameters left to process.

Inside the loop, we create a case statement that performs a pattern match on the first positional parameter.

This continues until all the arguments are processed and the script iterates over the arrays with a “for” loop.

Frequently Asked Questions

1. How do I add the new command-line options to my ZSH script?

To add the new command-line options, extend the case statement within the “while” loop. For example, if you want to add a “—verbose” option, insert a line like “–verbose) options[verbose]=1; shift;;”.

2. How can I handle the options with optional values?

ZSH’s getopts doesn’t directly support the options with optional values.

3. What does the “shift” command do and why do I need to use it when parsing the options?

The “shift” command removes the first argument from the list of positional parameters, effectively shifting all other arguments down by one position.

Hence, “shift” allows us to move past the arguments that have already been processed to prevent processing the same argument multiple times.

Conclusion

Now the ball is in your court. We taught you everything that you need to know about parsing the arguments in a ZSH script. We even taught you about the advanced features such as globbing and pattern matching to process the arguments.

About the author

John Otieno

My name is John and am a fellow geek like you. I am passionate about all things computers from Hardware, Operating systems to Programming. My dream is to share my knowledge with the world and help out fellow geeks. Follow my content by subscribing to LinuxHint mailing list