The shell uses the following environment variables.

CDPATH

A colon-separated list of directories used as a search path for the cd builtin command.

If the target directory of the cd command is specified as a relative path name, the cd command first looks for the target directory in the current directory (.). If the target is not found, the path names listed in the CDPATH variable are searched consecutively until the target directory is found and the directory change is completed. If the target directory is not found, the current working directory is left unmodified. For example, the CDPATH variable is set to /home/hemimorphite, and four directories exist under /home/hemimorphite, layouts, posts, assets and vendors. If you are in the /home/hemimorphite/posts directory and type cd vendors, you change directories to /home/hemimorphite/vendors, even though you do not specify a full path.

HOME

The current user's home directory; the default for the cd builtin command. The value of this variable is also used by tilde expansion (~).

cd $HOME
# OR
cd ~
IFS

A list of characters that separate fields; used when the shell splits words as part of expansion.

The default value is a space, a tab, and a newline (IFS=$' \t\n').

You can print it with the following command:

cat -etv <<<"$IFS"

You should see something as follows on your Linux terminal:

 ^I$
$

The IFS variable is commonly used with read command, parameter expansions and command substitution.

In the read command, if multiple variable-name arguments are specified, IFS is used to split the line of input so that each variable gets a single field of the input. (The last variable gets all the remaining fields, if there are more fields than variables.) Any whitespace characters in IFS will be trimmed from the beginning and end of the input line, even when only one variable is given.

# Using default IFS=$' \t\n'
[hemimorphite@ubuntu ~]$ read -r a b c <<< '   A      B  C'
[hemimorphite@ubuntu ~]$ echo "$a $b $c"
A B C
# Using a custom IFS value
[hemimorphite@ubuntu ~]$ IFS=: read -r user pwhash uid gid gecos home shell \
    <<< 'root:*:0:0:System Administrator:/var/root:/bin/sh'
[hemimorphite@ubuntu ~]$ echo "$user $pwhash $uid $gid $gecos $home $shell"
root * 0 0 System Administrator /var/root /bin/sh

If IFS contains a mixture of whitespace and non-whitespace characters then any non-whitespace IFS character or IFS whitespace characters (any sequence of one or more whitespace IFS characters count as single whitespace) acts as a single field delimiter. For example:

[hemimorphite@ubuntu ~]$ IFS=' ,'
[hemimorphite@ubuntu ~]$ sentence="This is        a, simple,     example"
[hemimorphite@ubuntu ~]$ printf 'word -> "%s" \n' $sentence
word -> "This" 
word -> "is" 
word -> "a" 
word -> "simple" 
word -> "example"
[hemimorphite@ubuntu ~]$ IFS=$' \t\n' read -r a b c \
    <<< 'the    plain gold      ring'
[hemimorphite@ubuntu ~]$ echo "=$a= =$b= =$c="
=the= =plain= =gold      ring=

The above example shows that splitting and delimiter-consolidation are not performed on the remaining part of a line when assigning excess fields to the last variable.

[hemimorphite@ubuntu ~]$ IFS=: read -r a b c \
    <<< '1:2:::3::4'
[hemimorphite@ubuntu ~]$ echo "=$a= =$b= =$c="
=1= =2= =::3::4=

Note that out of the three consecutive colons which follow field 2, precisely one colon was removed in order to terminate field 2. The remaining two colons, as well as two more colons later on, were all left untouched, and assigned to variable c verbatim.

OPTARG

The value of the option processed by the getopts builtin.

Let's create a bash script called command.sh

[hemimorphite@ubuntu ~]$ cat command.sh
#!/bin/bash

# Parse command-line options
while getopts ":f:d:" flag; do
    case $flag in
    f) echo "The filename is: ${OPTARG}"
      ;;
    d) echo "The directory is: ${OPTARG}"
    esac
done

The Bash script utilizes the getopts command to parse command-line options. It specifies two options, -f and -d in the optstring. As there is a colon (:) after both options, if triggered each of them requires an argument.

Inside the loop, a case statement is used to check the currently processed option. If the option -f is encountered, the script prints out the filename specified with the option using the special variable OPTARG. Similarly, if the option -d is found, the script displays the directory provided with the option using OPTARG.

[hemimorphite@ubuntu ~]$ ./command.sh -f file1.txt -d /home hemimorphite
The filename is: file1.txt
The directory is: /home hemimorphite
OPTERR

OPTERR controls if Bash displays errors generated by the getopts builtin command. getopts does not print errors if OPTERR has a value of 0. Value 1 enables the errors.

The default value is 1.

OPTIND

The index of the next parameter/argument processed by the getopts builtin.

[hemimorphite@ubuntu ~]$ cat command.sh
#!/bin/bash

while getopts "ab:c" flag; do
    echo "$flag" "$OPTIND" "$OPTARG"
done
[hemimorphite@ubuntu ~]$ ./command.sh -ac -b value1
a 1
c 2
b 4 value1

In ./command.sh -ac -b value1, arg1 is -ac, arg2 is -b, arg3 is value1.

While processing option -a, the next unprocessed option is -c which is in arg1. So the index stored in OPTIND is 1. For the following option -c, the next unprocessed option is -b which is arg2. So the index stored in OPTIND is 2. Finally, for the option -b, while processing option -b, arg3 (value1) is processed as an argument of option -b. That's why OPTIND stores 4 or the index of arg4.

[hemimorphite@ubuntu ~]$ ./command.sh -a -c -b value1
a 2
c 3
b 5 value1

In ./command.sh -a -c -b value1, arg1 is -a, arg2 is -c, arg3 is -b, arg4 is value1.

While processing option -a, the next unprocessed option is -c which is in arg2. So the index stored in OPTIND is 2. For the following option -c, the next unprocessed option is -b which is arg3. So the index stored in OPTIND is 3. Finally, for the option -b, while processing option -b, arg4 (value1) is processed as an argument of option -b. That's why OPTIND stores 5 or the index of arg5.

PATH

A colon-separated list of directories in which the shell looks for commands. This is the variable that tells the bash shell where to find different executable files and scripts. The shell will check the directories listed in the PATH variable for the script you are trying to find.

Let's say you wrote a little shell script called hello and have it located in a directory called /home/hemimorphite/bin.

[hemimorphite@ubuntu ~/bin]$ cat hello
#!/bin/bash

echo Hello "$USER"
[hemimorphite@ubuntu ~/bin]$ chmod +x hello

Add /home/hemimorphite/bin to the $PATH variable with the following command:

[hemimorphite@ubuntu ~/bin]$ export PATH=$PATH:/home/hemimorphite/bin

You should now be able to execute the script anywhere on your system by just typing in its name, without having to include the full path as you type it.

[hemimorphite@ubuntu ~/bin]$ hello
Hello hemimorphite
MAIL

Name of file to check for incoming mail.

MAILCHECK

defines the interval in seconds when the shell should check for mail. (default 60 seconds).

MAILPATH

List of filenames, separated by colons (:), to check for incoming mail.

Run the following command to install mail

sudo apt install mailutils

Let us create a new user named satella, echidna, and minerva using the useradd command on Ubuntu:

[hemimorphite@ubuntu ~/]$ sudo useradd -s /bin/bash -d /home/satella/ -m -G sudo satella
[hemimorphite@ubuntu ~/]$ sudo passwd satella
[hemimorphite@ubuntu ~/]$ sudo useradd -s /bin/bash -d /home/echidna/ -m -G sudo echidna
[hemimorphite@ubuntu ~/]$ sudo passwd echidna
[hemimorphite@ubuntu ~/]$ sudo useradd -s /bin/bash -d /home/minerva/ -m -G sudo minerva
[hemimorphite@ubuntu ~/]$ sudo passwd minerva

Where,

  • -s /bin/bash, set /bin/bash as login shell of the new account
  • -d /home/satella/, set /home/satella/ as home directory of the new Ubuntu account
  • -m, create the user's home directory
  • -G sudo, make satella user can use sudo command

Switch to user satella.

[hemimorphite@ubuntu ~/]$ sudo su - satella
[satella@ubuntu ~/]$ 

Use the echo command to send an email without entering the interactive mode of the mail command. Write the email body and pipe the echo command output to the mail command.

[satella@ubuntu ~/]$ echo "This is the email body" | mail -s "this is email subject" echidna, minerva

After 60 seconds (the value of MAILCHECK), on echidna's or minerva's prompt press ENTER and the prompt will print the message:

You have mail in /var/mail/echidna

Shell will use the value of MAIL as the name of the file to check, unless MAILPATH is set; in which case, the shell will check each file in the MAILPATH list for new mail. You can use this mechanism to have the shell print a different message for each mail file: for each mail filename in MAILPATH, append a question mark followed by the message you want printed.

You could define MAILPATH to be:

MAILPATH="\
/usr/mail/satella/echidna?Mail from Echidna has arrived.:\
/usr/mail/satella/minerva?There is new mail from Paul."

The backslashes at the end of each line allow you to continue your command on the next line. Now, if you get mail from echidna, the shell will print:

Mail from Echidna has arrived.

You can also use the variable $_ in the message to print the name of the current mail file or execute a command $(date) to displays the current date and time. For example:

MAILPATH="\
/usr/mail/satella/echidna?Mail from Echidna has arrived in $_ at $(date).:\
/usr/mail/satella/minerva?There is new mail from Paul in $_ at $(date)."