scripting parameters
(Written by Paul Cobbaut, https://github.com/paulcobbaut/, with contributions by: Alex M. Schapelle, https://github.com/zero-pytagoras/, Bert Van Vreckem https://github.com/bertvv/)
script parameters
On the CLI, you often pass on options and arguments to a command to alter its behaviour. Bash shell scripts also can have options and arguments, called positional parameters. These parameters are stored in variables with names ${1}
, ${2}
, ${3}
, and so on.
#! /bin/bash --
echo "The first argument is ${1}"
echo "The second argument is ${2}"
echo "The third argument is ${3}"
If you save this script in a file called pars.sh
, and make it executable, you can run it with parameters:
[student@linux scripts]$ ./pars.sh one two three
The first argument is one
The second argument is two
The third argument is three
Pay attention! In many code examples you encounter on the Internet, you'll see the positional parameters referenced as $1
, $2
, $3
, etc, without the braces. This is also valid in Bash. However, if you want to reference the tenth positional parameter and you write $10
, Bash will interpret this as the value of the first positional parameter followed by a zero. To avoid this, you should always use braces around the number, like ${10}
. For example:
#! /bin/bash --
# Confusing use of positional parameters, this will not expand as expected
echo "The first argument is $1"
echo "The tenth argument is $10"
If you run this script (let's call it tenparams.sh
):
[student@linux scripts]$ ./tenparams.sh one two three four five six seven eight nine ten
The first argument is one
The tenth argument is one0
So, if you write a $
followed by a number, only the first digit will be interpreted as the positional parameter! We recommend to always using braces to avoid this confusion.
#! /bin/bash --
# Confusing use of positional parameters, this will not expand as expected
echo "The first argument is ${1}"
echo "The tenth argument is ${10}"
When you run a script, other special pre-defined variables are available. The man page of bash(1)
has a full list, but we list a few here.
Variable | Description |
---|---|
${0} |
The name of the script |
$# |
The number of parameters |
$* |
All the parameters (as one long string) |
$@ |
All the parameters (as a list) |
$? |
The return code (0-255) of the last command |
$$ |
The process ID of the script |
An example script that uses these variables:
#! /bin/bash --
cat << _EOF_
The script name is: ${0}
Number of arguments: $#
The first argument is ${1}
The second argument is ${2}
The third argument is ${3}
PID of the script: $$
Last return code: $?
All the arguments (list): $@
All the arguments (string): $*
_EOF_
The output would look like this (if you save the script in a file called special-vars.sh
):
[student@linux scripts]$ ./special-vars.sh one two three
The script name is: ./special-vars.sh
Number of arguments: 3
The first argument is one
The second argument is two
The third argument is three
PID of the script: 5612
Last return code: 0
All the arguments (list): one two three
All the arguments (string): one two three
If you pass on less parameters than the script expects, the missing parameters will be interpreted as empty strings. If you start the script with set -o nounset
(or set -u
for short), the script will exit with an error if you try to reference a positional parameter that was not passed on. When parsing parameters, always check the number of arguments first to avoid this.
shift through parameters
The shift
command will drop the first positional parameter, and move all the others one position to the left, i.e. ${1}
will dissapear, ${2}
will become ${1}
, ${3}
will become ${2}
, and so on.
Using a while
loop in combination with shift
statement, you can parse all parameters one by one. This is a sample script (called shift.sh
) that does this:
#! /bin/bash --
if [ "$#" -eq "0" ]
then
echo "You have to give at least one parameter."
exit 1
fi
while (( $# ))
do
echo "arg: ${1}"
shift
done
The while
loop can also be written as while [ "$#" -gt "0" ]
.
Below is some sample output of the script above.
[student@linux scripts]$ ./shift.sh one
arg: one
[student@linux scripts]$ ./shift.sh one two three 1201 "33 42"
arg: one
arg: two
arg: three
arg: 1201
arg: 33 42
[student@linux scripts]$ ./shift.sh
You have to give at least one parameter.
for loop through parameters
You can also use a for
loop to iterate over the positional parameters. This is a sample script (called for.sh
) that does this:
#! /bin/bash --
if [ "$#" -eq "0" ]
then
echo "You have to give at least one parameter."
exit 1
fi
for arg in "${@}"
do
echo "arg: ${arg}"
done
This script behaves in the same way as the shift.sh
script. However, after the loop, all positional parameters are still available.
runtime input
You can ask the user for input with the read
command in a script.
Use option -p
to display a prompt, and -r
to prevent backslashes from being interpreted as escape characters.
You can also use read
to read lines from a file and then iterate on them.
Example output:
[student@linux scripts]$ cat inputfile.txt
one
two
three
[student@linux scripts]$ ./readlines.sh
line: one
line: two
line: three
sourcing a config file
The source
(as seen in the shell chapters), or the shortcut .
can be used to source a configuration file. With source
, the specified file is executed in the current shell, i.e. without creating a subshell. Consequently, variables set in the configuration file are available in the script that sources the file.
Below a sample configuration file for an application.
[student@linux scripts]$ cat myApp.conf
# The config file of myApp
# Enter the path here
myAppPath=/var/myApp
# Enter the number of quines here
quines=5
And here an application (my-app.sh
) that uses this file.
#! /bin/bash --
#
# Welcome to the myApp application
#
# Source the configuration file
. ./myApp.conf
echo "There are ${quines} quines"
The running application can use the values inside the sourced configuration file.
get script options with getopts
The getopts
function allows you to parse options given to a command. The following script allows for any combination of the options a, f and z.
#! /bin/bash --
while getopts ":afz" option;
do
case "${option}" in
a)
echo 'received -a'
;;
f)
echo 'received -f'
;;
z)
echo 'received -z'
;;
*)
echo "invalid option -${OPTARG}"
;;
esac
done
This is sample output from the script above. First we use correct options, then we enter twice an invalid option.
student@linux$ ./options.ksh
student@linux$ ./options.ksh -af
received -a
received -f
student@linux$ ./options.ksh -zfg
received -z
received -f
invalid option -g
student@linux$ ./options.ksh -a -b -z
received -a
invalid option -b
received -z
You can also check for options that need an argument, as this example script(argoptions.sh
) shows.
#! /bin/bash --
while getopts ":af:z" option
do
case $option in
a)
echo 'received -a'
;;
f)
echo "received -f with ${OPTARG}"
;;
z)
echo 'received -z'
;;
:)
echo "option -${OPTARG} needs an argument"
;;
*)
echo "invalid option -${OPTARG}"
;;
esac
done
This is sample output from the script above.
student@linux$ ./argoptions.sh -a -f hello -z
received -a
received -f with hello
received -z
student@linux$ ./argoptions.sh -zaf 42
received -z
received -a
received -f with 42
student@linux$ ./argoptions.sh -zf
received -z
option -f needs an argument
Additionally, there's a utility command called getopt
(part of the GNU project) that can be used to parse complex cases with mixed use of long options (like --verbose
), combinations of multiple short options (like -abc
for -a -b -c
), and options with arguments (like -f file
). The getopt
command will rewrite the command line options in a "canonical" form that is easier to parse with a while
loop. However, getopt
is not portable: BSD and macOS don't have GNU getopt
, but they have their own version of getopt
that is less powerful. Using getopt
is out of the scope of this chapter.
get shell options with shopt
You can toggle the values of variables controlling optional shell behaviour with the shopt
built-in shell command. The example below first verifies whether the cdspell option is set; it is not. The next shopt command sets the value, and the third shopt command verifies that the option really is set. You can now use minor spelling mistakes in the cd
command. The man page of bash(1)
has a complete list of options.
student@linux:~$ shopt -q cdspell ; echo $?
1
student@linux:~$ shopt -s cdspell
student@linux:~$ shopt -q cdspell ; echo $?
0
student@linux:~$ cd /Etc
/etc
practice: parameters and options
-
Write a script that receives four parameters, and outputs them in reverse order.
-
Write a script that receives two parameters (two filenames) and outputs whether those files exist.
-
Write a script that takes a filename as parameter, or asks for a filename if none was given. Verify the existence of the file, then verify that you own the file, and whether it is writable. If not, then make it writable.
-
Make a configuration file for the previous script. Put a logging switch in the config file, logging means writing detailed output of everything the script does to a log file in
/tmp
.
solution: parameters and options
-
Write a script that receives four parameters, and outputs them in reverse order.
-
Write a script that receives two parameters (two filenames) and outputs whether those files exist.
-
Write a script (e.g. named
chkw.sh
) that takes a filename as parameter, or asks for a filename if none was given. Verify the existence of the file, then verify that you own the file, and whether it is writable. If not, then make it writable.#! /bin/bash # Check if a filename was given as a parameter if [ "$#" -eq '0' ]; then read -r -p "Enter a filename: " filename else filename="${1}" fi # Check if the file exists if [ ! -f "${filename}" ]; then echo "File does not exist, or is not a file" exit 1 fi # Check if the file is owned by the user if [ ! -O "${filename}" ]; then echo "You do not own this file" exit 1 fi # Check if the file is writable if [ ! -w "${filename}" ]; then echo "File is not writable" chmod u+w "${filename}" echo "File is now writable" else echo "File is writable" fi
Interaction with the script:
[student@linux scripts]$ touch test{1..3}.txt [student@linux scripts]$ ls -l total 4 -rwxr--r--. 1 student student 970 25 okt 12:39 chkw.sh -rw-------. 1 student student 0 25 okt 12:41 test1.txt -rw-------. 1 student student 0 25 okt 12:41 test2.txt -rw-------. 1 student student 0 25 okt 12:41 test3.txt [student@linux scripts]$ chmod -w test1.txt [student@linux scripts]$ sudo chown root:root test2.txt [student@linux scripts]$ ls -l total 4 -rwxr--r--. 1 student student 970 25 okt 12:39 chkw.sh -r--------. 1 student student 0 25 okt 12:41 test1.txt -rw-------. 1 root root 0 25 okt 12:41 test2.txt -rw-------. 1 student student 0 25 okt 12:41 test3.txt [student@linux scripts]$ ./chkw.sh Enter a filename: test1.txt File is not writable File is now writable [student@linux scripts]$ ./chkw.sh test2.txt You do not own this file [student@linux scripts]$ ./chkw.sh test3.txt File is writable [student@linux scripts]$ ls -l total 4 -rwxr--r--. 1 student student 970 25 okt 12:39 chkw.sh -rw-------. 1 student student 0 25 okt 12:41 test1.txt -rw-------. 1 root root 0 25 okt 12:41 test2.txt -rw-------. 1 student student 0 25 okt 12:41 test3.txt
-
Make a configuration file for the previous script. Put a logging switch in the config file, logging means writing detailed output of everything the script does to a log file in /tmp.
#! /bin/bash . .chkwrc if [ "${logging}" = 'on' ]; then log="tee -a ${logfile}" date -Is >> "${logfile}" echo "Filename: ${1}" >> "${logfile}" else log='cat' fi # Check if a filename was given as a parameter if [ "$#" -eq '0' ]; then read -r -p "Enter a filename: " filename else filename="${1}" fi # Check if the file exists if [ ! -f "${filename}" ]; then echo "File does not exist, or is not a file" | ${log} exit 1 fi # Check if the file is owned by the user if [ ! -O "${filename}" ]; then echo "You do not own this file" | ${log} exit 1 fi # Check if the file is writable if [ ! -w "${filename}" ]; then echo "File is not writable" | ${log} chmod u+w "${filename}" echo "File is now writable" | ${log} else echo "File is writable" | ${log} fi
Configuration file (
.chkwrc
):Interaction with the script:
[student@linux scripts] $ ls -l total 4 -rwxr--r--. 1 student student 1133 26 okt 13:26 chkw.sh -r--------. 1 student student 0 25 okt 12:41 test1.txt -rw-------. 1 root root 0 25 okt 12:41 test2.txt -r--r-----. 1 student student 0 25 okt 12:41 test3.txt [student@linux scripts] $ ./chkw.sh test1.txt File is not writable File is now writable [student@linux scripts] $ ./chkw.sh test2.txt You do not own this file [student@linux scripts] $ ./chkw.sh test3.txt File is not writable File is now writable [student@linux scripts] $ cat /tmp/chkw.log 2024-10-26T13:27:26+02:00 File is not writable File is now writable 2024-10-26T13:27:50+02:00 Filename: test2.txt You do not own this file 2024-10-26T13:27:54+02:00 Filename: test3.txt File is not writable File is now writable