The shell uses the following environment variables.

: (a colon)

: is a shell builtin command inherited from the Bourne Shell. It does nothing beyond expanding arguments and performing redirection and has return status zero. : is a no-op operator similar to true and false.

The syntax of the : command takes the following form:

: [arguments]

The example below illustrated how : is used in parameter expansion by setting a default value combined with :.

[hemimorphite@ubuntu ~]$ cat ./input.sh
#!/usr/bin/env bash
read -p "Enter your name: " name
: ${name:=Hemimorphite}  # if the user entered an empty string
echo "$name"
[hemimorphite@ubuntu ~]$ ./input.sh
Enter your name:
Hemimorphite

If : is omitted, the default value is passed and treated as a command.

[hemimorphite@ubuntu ~]$ cat ./input.sh
#!/usr/bin/env bash
read -p "Enter your name: " name
${name:=Hemimorphite}
echo "$name"
[hemimorphite@ubuntu ~]$ ./input.sh
Enter your name:
./input.sh: line 3: Hemimorphite: command not found
Hemimorphite

The usual way to do infinite loop in bash is using the true command in a while loop.

#!/usr/bin/env bash
while true
    do 
    # ...
done

Alternatively, we can use : instead of true to create an infinite loop.

#!/usr/bin/env bash
while :
        do 
        # ...
done
. (a period)

The dot command (.) is a command used to execute commands from a file in the current shell. In Bash, the source command is synonym to the dot command (.)

The syntax of the . command takes the following form:

. filename [arguments]

When you run an executable script as ./hello.sh, the commands are run in a new subshell, while when run as . hello.sh the current shell context will be used. This mean that the dot command will apply changes to your current shell.

Let's look at the simple example below.

#!/usr/bin/env bash
export A="hello world"
echo $A

When run as an executable using ./hello.sh, the A variable is not exported in your current shell and would just return an empty result.

[hemimorphite@ubuntu ~]$ ./hello.sh 
hello world
[hemimorphite@ubuntu ~]$ echo $A

When run the same script with the dot command using . hello.sh, your current shell context will be changed.

[hemimorphite@ubuntu ~]$ . hello.sh 
hello world
[hemimorphite@ubuntu ~]$ echo $A
hello world
break

Exit from a for, while, until, or select loop.

The syntax of the break command takes the following form:

break [n]

[n] is an optional argument and must be greater than or equal to 1. When [n] is provided, the n-th enclosing loop is exited. break 1 is equivalent to break.

Here is an example of using the break statement inside nested for loops.

When the argument [n] is not given, break terminates the innermost enclosing loop. The outer loops are not terminated:

[hemimorphite@ubuntu ~]$ cat loop.sh
#!/usr/bin/env bash

for i in {1..3}; do
    for j in {1..3}; do
        if [[ $j -eq 2 ]]; then
            break
        fi
        echo "j: $j"
    done
    echo "i: $i"
done
[hemimorphite@ubuntu ~]$ ./loop.sh
j: 1
i: 1
j: 1
i: 2
j: 1
i: 3

If you want to exit from the outer loop, use break 2. Argument 2 tells break to terminate the second enclosing loop:

[hemimorphite@ubuntu ~]$ cat loop.sh
#!/usr/bin/env bash

for i in {1..3}; do
    for j in {1..3}; do
        if [[ $j -eq 2 ]]; then
            break 2
        fi
        echo "j: $j"
    done
    echo "i: $i"
done
[hemimorphite@ubuntu ~]$ ./loop.sh
j: 1
continue

Resume the next iteration of a for, while, until, or select loop.

The syntax of the continue command takes the following form:

continue [n]

The [n] argument is optional and can be greater than or equal to 1. When [n] is given, the n-th enclosing loop is resumed. continue 1 is equivalent to continue.

In the example below, once the current iterated item is equal to 9, the continue statement will cause execution to return to the beginning of the loop and to continue with the next iteration.

[hemimorphite@ubuntu ~]$ cat loop.sh
#!/usr/bin/env bash

for i in {1..10}
do
    if [[ $i == '9' ]]
    then
        continue
    fi
    echo "Number $i!"
done
[hemimorphite@ubuntu ~]$ ./loop.sh
Number: 1
Number: 2
Number: 3
Number: 4
Number: 5
Number: 6
Number: 7
Number: 8
Number: 10

If you want to continue from the outer loop, use continue 2. Argument 2 tells continue to continue the second enclosing loop:

[hemimorphite@ubuntu ~]$ cat loop.sh
#!/usr/bin/env bash

for i in {1..5}
do
    for j in {1..5}
    do
        if [[ $j -eq 2 ]]
        then
            continue 2
        fi
        echo "j: $j"
    done
    echo "i: $i"
done
[hemimorphite@ubuntu ~]$ ./loop.sh
j: 1
j: 2
j: 3
i: 1
j: 1
j: 2
j: 3
i: 3
cd

The cd command changes the working directory of the current shell execution environment to directory. If you specify directory as an absolute path name, beginning with /, this is the target directory. cd assumes the target directory to be the name just as you specified it. If you specify directory as a relative path name, cd assumes it to be relative to the current working directory.

Two special symbols are also supported:

  • . Represents the current directory
  • .. Represents the parent of the current directory

The syntax of the cd command takes the following form:

cd [-L|[-P [-e]] [directory]

If [directory] is not supplied, the value of the HOME shell variable is used. If the shell variable CDPATH exists, it is used as a search path. If directory begins with a slash, CDPATH is not used.

The -P option means to not follow symbolic links: symbolic links are resolved while cd is traversing directory and before processing an instance of .. in directory. For example,

[hemimorphite@ubuntu ~]$ mkdir -p gallery/album/photos
[hemimorphite@ubuntu ~]$ ln -s gallery/album/photos myalbum
[hemimorphite@ubuntu ~]$ cd -P myalbum
[hemimorphite@ubuntu ~/gallery/album/photos]$ pwd
/home/hemimorphite/gallery/album/photos

By default, the -L option is supplied, symbolic links in directory are resolved after cd processes an instance of .. in directory.

[hemimorphite@ubuntu ~]$ mkdir -p gallery/album/photos
[hemimorphite@ubuntu ~]$ ln -s gallery/album/photos myalbum
[hemimorphite@ubuntu ~]$ cd myalbum
[hemimorphite@ubuntu ~/myalbum]$ pwd
/home/hemimorphite/myalbum

If the -e option is supplied with -P and the current working directory cannot be successfully determined after a successful directory change, cd will return an unsuccessful status.

[hemimorphite@ubuntu ~]$ mkdir -p gallery/album/photos
[hemimorphite@ubuntu ~]$ cd gallery/album/photos
[hemimorphite@ubuntu ~/gallery/album/photos]$ rmdir ../photos ../../album
[hemimorphite@ubuntu ~/gallery/album/photos]$ cd ..
cd: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory
[hemimorphite@ubuntu ~/gallery/album/photos/..]$ echo $?
0
[hemimorphite@ubuntu ~]$ mkdir -p gallery/album/photos
[hemimorphite@ubuntu ~]$ cd gallery/album/photos
[hemimorphite@ubuntu ~/gallery/album/photos]$ rmdir ../photos ../../album
[hemimorphite@ubuntu ~/gallery/album/photos]$ cd -Pe ..
cd: error retrieving current directory: getcwd: cannot access parent directories: No such file or directory
[hemimorphite@ubuntu ~/gallery/album/photos/..]$ echo $?
1

In the shell, the command cd - is a special case that changes the current working directory to the previous working directory by exchanging the values of the variables PWD and OLDPWD.

In the following steps, the variable curpath represents an intermediate value used to simplify the description of the algorithm used by cd. There is no requirement that curpath be made visible to the application.

  1. If no directory operand is given and the HOME environment variable is empty or undefined, the default behavior is implementation-defined and no further steps shall be taken.
    For example:
    [hemimorphite@ubuntu ~]$ HOME=
    [hemimorphite@ubuntu ~]$ cd
  2. If no directory operand is given and the HOME environment variable is set to a non-empty value, the cd command shall behave as if the directory named in the HOME environment variable was specified as the directory operand.
    For example:
    [hemimorphite@ubuntu ~]$ HOME=/home/hemimorphite
    [hemimorphite@ubuntu ~]$ cd
  3. If the directory operand begins with a slash / character, set curpath to the operand and proceed to step 7
  4. If the directory operand is . or .., proceed to step 6
  5. Starting with the first pathname in the colon-separated : pathnames of CDPATH if the pathname is non-null, test if the concatenation of that pathname, add a / character if that pathname did not end with a / character, and the directory operand names a directory. If the pathname is null, test if the concatenation of dot, a / character, and the operand names a directory. In either case, if the resulting string names an existing directory, set curpath to that string and proceed to step 7. Otherwise, repeat this step with the next pathname in CDPATH until all pathnames have been tested.
  6. Set curpath to the directory operand.
  7. If the -P option is in effect, proceed to step 10. If curpath does not begin with a / character, set curpath to the string formed by the concatenation of the value of PWD, a / character if the value of PWD did not end with a / character, and curpath.
  8. The curpath value shall then be converted to canonical form as follows, considering each component from beginning to end, in sequence:
    1. . components and any / characters that separate them from the next component shall be deleted.
    2. For each dot-dot component, if there is a preceding component and it is neither root nor dot-dot, then:
      1. If the preceding component does not refer (in the context of pathname resolution with symbolic links followed) to a directory, then the cd command shall display an appropriate error message and no further steps shall be taken.
      2. The preceding component, all / characters separating the preceding component from dot-dot, dot-dot, and all / characters separating dot-dot from the following component (if any) shall be deleted.
    3. An implementation may further simplify curpath by removing any trailing / characters that are not also leading / characters, replacing multiple non-leading consecutive / characters with a single /, and replacing three or more leading / characters with a single /. If, as a result of this canonicalization, the curpath variable is null, no further steps shall be taken.
  9. If curpath is longer than {PATH_MAX} bytes (including the terminating null) and the directory operand was not longer than {PATH_MAX} bytes (including the terminating null), then curpath shall be converted from an absolute pathname to an equivalent relative pathname if possible. This conversion shall always be considered possible if the value of PWD, with a trailing / added if it does not already have one, is an initial substring of curpath. Whether or not it is considered possible under other circumstances is unspecified. Implementations may also apply this conversion if curpath is not longer than {PATH_MAX} bytes or the directory operand was longer than {PATH_MAX} bytes.
  10. The cd command shall then perform actions equivalent to the chdir() function called with curpath as the path argument. If these actions fail for any reason, the cd command shall display an appropriate error message and the remainder of this step shall not be executed. If the -P option is not in effect, the PWD environment variable shall be set to the value that curpath had on entry to step 9 (i.e., before conversion to a relative pathname). If the -P option is in effect, the PWD environment variable shall be set to the string that would be output by pwd -P. If there is insufficient permission on the new directory, or on any parent of that directory, to determine the current working directory, the value of the PWD environment variable is unspecified.

If, during the execution of the above steps, the PWD environment variable is set, the OLDPWD environment variable shall also be set to the value of the old working directory.

When specifying a directory to change to, you can use either absolute or relative path names. The absolute or full path starts from the system root /, and the relative path starts from your current directory.

By default, when you log into your Linux system, your current working directory is set to your home directory. Assuming that the Downloads directory exists in your home directory, you can navigate to it by using the relative path to the directory:

[hemimorphite@ubuntu ~]$ cd Downloads

You can also navigate to the same directory by using its absolute path:

[hemimorphite@ubuntu ~]$ cd /home/hemimorphite/Downloads

On Unix-like operating systems, the current working directory is represented by a single dot (.). Two dots (..), one after the other, represent the parent directory or the directory immediately above the current one.

If you type cd ., you will change into the current directory or, in other words, the command will do nothing.

Suppose you are currently in the /usr/local/share directory. To switch to the /usr/local directory (one level up from the current directory), you would type:

[hemimorphite@ubuntu /usr/local/share]$ cd ..
[hemimorphite@ubuntu /usr/local]$

To move two levels up to the /usr directory, you type:

[hemimorphite@ubuntu /usr/local/share]$ cd ../../
[hemimorphite@ubuntu /usr]$

Let's say you are in the /usr/local/share directory, and you want to switch to the /usr/local/src. You can do that by typing:

[hemimorphite@ubuntu /usr/local/share]$ cd ../src
[hemimorphite@ubuntu /usr/local/src]$

To change back to the previous working directory, pass the dash (-) character as an argument to the cd command:

[hemimorphite@ubuntu /usr/local/share]$ cd ../src
[hemimorphite@ubuntu /usr/local/src]$ cd -
[hemimorphite@ubuntu /usr/local/share]$

To navigate to your home directory, simply type cd. Another way to return directly to your home directory is to use the tilde (~) character, as shown below:

[hemimorphite@ubuntu /usr/local/share]$ cd ~
[hemimorphite@ubuntu ~]$

For example, if you want to navigate to the Downloads directory, which is inside your home directory, you would type:

[hemimorphite@ubuntu /usr/local/share]$ cd ~/Downloads
[hemimorphite@ubuntu ~/Downloads]$

You can also navigate to another user's home directory using the following syntax:

[hemimorphite@ubuntu /usr/local/share]$ cd ~satella
[hemimorphite@ubuntu /home/satella]$

If the directory you want to change to has spaces in its name, you should either surround the path with quotes or use the backslash (\) character to escape the space:

[hemimorphite@ubuntu ~]$ mkdir "hello world"
[hemimorphite@ubuntu ~]$ cd "hello world"

Or

[hemimorphite@ubuntu ~]$ mkdir "hello world"
[hemimorphite@ubuntu ~]$ cd hello\ world
eval

eval command is used on a Unix or Linux system to execute the arguments as a shell command. The eval command is helpful when you want to execute a Unix or Linux command that has been saved in a variable.

The syntax of the eval command takes the following form:

eval [arguments]

The command or script that must be evaluated and run in this case is represented by [arguments]. It may contain commands, variables, and even sophisticated expressions.

Storing a command in a variable is useful, especially when you want to store it with an option or flag appended to the command. In the following example, we will store the expr command in a variable named command:

[hemimorphite@ubuntu ~]$ var1=10
[hemimorphite@ubuntu ~]$ var2=20
[hemimorphite@ubuntu ~]$ command='expr $var1 + $var2'
[hemimorphite@ubuntu ~]$ eval $command
30

In the following example, the eval command substitutes the date command placed within a string stored in the command variable. eval evaluates the string and executes the result:

[hemimorphite@ubuntu ~]$ command="echo \$(date)"
[hemimorphite@ubuntu ~]$ eval $command
Fri Jun 14 18:47:57 +07 2024
exec

The exec command executes a shell command without creating a new process. Instead, it replaces the currently open shell operation.

The syntax of the exec command takes the following form:

exec [-cl] [-a name] [command [arguments]]

If the -l option is supplied, the shell places a dash at the beginning of the zeroth argument passed to command. So if we ran the following command:

exec -l tail -f /etc/passwd

Open a second terminal. Run the ps auwwx command and it would produce the following output in the process list.

[hemimorphite@ubuntu ~]$ ps auwwx | grep tail
hemimor+    6977  0.0  0.0   8404  1008 pts/0    Ss+  22:03   0:00 -tail -f /etc/passwd
hemimor+    7163  0.0  0.0   9212  2372 pts/1    S+   22:23   0:00 grep --color=auto /etc/passwd

The -c option causes the supplied command to run with a empty environment. Environmental variables like PATH, are cleared before the command it run.

[hemimorphite@ubuntu ~]$ bash
[hemimorphite@ubuntu ~]$ ps auwwx | grep tail
SHELL=/bin/bash
SESSION_MANAGER=local/ubuntu:@/tmp/.ICE-unix/1259,unix/ubuntu:/tmp/.ICE-unix/1259
QT_ACCESSIBILITY=1
COLORTERM=truecolor
XDG_CONFIG_DIRS=/etc/xdg/xdg-cinnamon:/etc/xdg
XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0
GNOME_DESKTOP_SESSION_ID=this-is-deprecated
LANGUAGE=en_US
LC_ADDRESS=id_ID.UTF-8
LC_NAME=id_ID.UTF-8
[hemimorphite@ubuntu ~]$ exec -c printenv
[hemimorphite@ubuntu ~]$

The last option, -a [name], will pass name as the first argument to command. The command will still run as expected, but the name of the process will change.

exec -a HEMIMORPHITE tail -f /etc/passwd

Open a second terminal. Run the ps auwwx command and it would produce the following output in the process list.

[hemimorphite@ubuntu ~]$ ps auwwx | grep HEMIMORPHITE
hemimor+    6977  0.0  0.0   8404  1008 pts/0    Ss+  22:03   0:00 HEMIMORPHITE -f /etc/passwd
hemimor+    7163  0.0  0.0   9212  2372 pts/1    S+   22:23   0:00 grep --color=auto HEMIMORPHITE

As you can see, exec command passed HEMIMORPHITE as first argument to command, therefore it shows in the process list with that name.

The examples below demonstrate the behavior of the exec command in the terminal.

Open the terminal and list the running processes

[hemimorphite@ubuntu ~]$ ps
 PID TTY          TIME CMD
8185 pts/0    00:00:00 bash
8192 pts/0    00:00:00 ps

The output shows the currently running Bash shell and the ps command. The Bash shell has a unique PID.

To confirm, check the current process ID with:

[hemimorphite@ubuntu ~]$ echo $$
8185

The PID is the same as the output from the ps command, indicating this is the currently running Bash process.

Now, run exec followed by the sleep command:

[hemimorphite@ubuntu ~]$ exec sleep 100

The sleep command waits for 100 seconds.

Open another terminal tab, list all currently running processes and use grep command to find sleep process:

[hemimorphite@ubuntu ~]$ ps -ae | grep sleep
8185 pts/0    00:00:00 sleep

The PID for the process is the same as the Bash shell PID, indicating the exec command replaced the Bash shell process.

The Bash session (terminal tab) closes when the one hundred seconds are complete and the process ends.

Now, we will see how exec command works in Bash scripts.

Create a script file with the following content:

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

while true
do
        echo "1. Update "
        echo "2. Upgrade "
        echo "3. Exit"
    read Input
    case "$Input" in
        1) exec sudo apt update ;;
        2) exec sudo apt upgrade  ;;
        3) break
    esac
done

Change the script permission to executable and run the script in the current environment to see the results:

[hemimorphite@ubuntu ~]$ chmod +x demo.sh
[hemimorphite@ubuntu ~]$ . demo.sh
1. Update 
2. Upgrade 
3. Exit

Executing the script with the source command applies the script behavior to the current Bash shell. Use exec to run Bash scripts within other programs for a clean exit.

The exec command finds use in manipulating file descriptors for error logging in Bash scripts. The default Linux file descriptors are:

  1. stdin (0) - Standard in
  2. stdout (1) - Standard out
  3. stderr (2) - Standard error

Create a Bash script with the following content:

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

# Create test.log file
touch test.log

# Save test.log to log_file variable
log_file="test.log"

# Redirect stdin to $log_file
exec 1>>$log_file

# Redirect stderr to the same place as stdin
exec 2>&1

echo "This line is added to the log file"
echo "And any other lines after"
eho "This line has an error and is logged as stderr"

Change the script permission to executable and run the script in the current environment to see the results:

[hemimorphite@ubuntu ~]$ chmod +x logging.sh
[hemimorphite@ubuntu ~]$ ./logging.sh

The script does not output any code. Instead, all the output logs to the test.log file.

Use the cat command to see the test.log file contents:

[hemimorphite@ubuntu ~]$ cat test.log
This line is added to the log file
And any other lines after
./logging.sh: line 17: eho: command not found
exit

exit command is used to exit the shell where it is currently running.

The syntax of the exit command takes the following form:

exit [n]

It takes one parameter as [n] and exits the shell with a return of status n. If [n] is not provided, then it simply returns the status of last command that is executed.

export

The export command is used to export environmental variables that are accessible by all processes running in the current shell session and its child processes.

The syntax of the export command takes the following form:

export [-fn] [-p] [name[=value]]

If the -f option is supplied, the names musrt refer to shell functions.

[hemimorphite@ubuntu ~]$ example_function() {
    echo "This is an example function."
}
[hemimorphite@ubuntu ~]$ export -f example_function 
[hemimorphite@ubuntu ~]$ bash -c example_function
This is an example function.

If the -p option is given or there is no option supplied, a list of names of all exported variables is displayed.

[hemimorphite@ubuntu ~]$ export -p
declare -x DBUS_SESSION_BUS_ADDRESS="unix:path=/run/user/1000/bus"
declare -x DISPLAY=":0"
declare -x HOME="/home/hemimorphite"
declare -x HOSTTYPE="x86_64"
declare -x LANG="C.UTF-8"
declare -x LESSCLOSE="/usr/bin/lesspipe %s %s"
declare -x LESSOPEN="| /usr/bin/lesspipe %s"
declare -x LOGNAME="hemimorphite"
declare -x LS_COLORS="rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.webp=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:"
declare -x MOTD_SHOWN="update-motd"
declare -x NAME="DESKTOP-J3NDV0Q"
declare -x OLDPWD
declare -x PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin"
declare -x PULSE_SERVER="unix:/mnt/wslg/PulseServer"
declare -x PWD="/home/hemimorphite"
declare -x SHELL="/bin/bash"
declare -x SHLVL="1"
declare -x TERM="xterm-256color"
declare -x USER="hemimorphite"
declare -x WAYLAND_DISPLAY="wayland-0"
declare -x WSL2_GUI_APPS_ENABLED="1"
declare -x WSLENV=""
declare -x WSL_DISTRO_NAME="Ubuntu"
declare -x WSL_INTEROP="/run/WSL/247687_interop"
declare -x XDG_DATA_DIRS="/usr/local/share:/usr/share:/var/lib/snapd/desktop"
declare -x XDG_RUNTIME_DIR="/run/user/1000/"

The output lists all the variables used in the current shell session, and it is usually the same as running export without options.

The -n option removes the specified variables and functions from the list of exported variables.

In the following example, we remove the HOME variable:

[hemimorphite@ubuntu ~]$ export -n HOME
[hemimorphite@ubuntu ~]$ export | grep HOME
[hemimorphite@ubuntu ~]$

If a variable name is followed by =value, the value of the variable is set to value.

[hemimorphite@ubuntu ~]$ export VARNAME="value"
[hemimorphite@ubuntu ~]$ printenv VARNAME
value

You also can assign a value to a variable first before exporting it using the export command. For example:

[hemimorphite@ubuntu ~]$ x=100
[hemimorphite@ubuntu ~]$ export x
[hemimorphite@ubuntu ~]$ printenv x
100
getopts

The getopts is used by shell scripts to parse positional parameters.

The syntax of the getopts command takes the following form:

getopts optstring name [arg …]

optstring contains the option characters to be recognized; if a character is followed by a colon, the option is expected to have an argument, which should be separated from it by whitespace.The colon (:) and question mark (?) may not be used as option characters. Each time it is invoked, getopts places the next option in the shell variable name, initializing name if it does not exist, and the index of the next argument to be processed into the variable OPTIND. OPTIND is initialized to 1 each time the shell or a shell script is invoked. When an option requires an argument, getopts places that argument into the variable OPTARG. The shell does not reset OPTIND automatically; it must be manually reset between multiple calls to getopts within the same shell invocation if a new set of parameters is to be used.

Here's a simple example that demonstrates the basic usage of getopts:

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

while getopts "a:b" option; do
    echo "Processing $option : OPTIND is $OPTIND"
    case $option in
    a)
        echo "Option a is set with argument: $OPTARG"
        ;;
    b)
        echo "Option b is set"
        ;;
    \?)
        echo "Invalid option: -$OPTARG"
        ;;
    esac
done
[hemimorphite@ubuntu ~]$ chmod +x demo.sh
[hemimorphite@ubuntu ~]$ ./demo.sh -b -a value1
Processing b : OPTIND is 2
Option b is set
Processing a : OPTIND is 4
Option a is set with argument: value1
[hemimorphite@ubuntu ~]$ ./demo.sh -a value1 -b
Processing a : OPTIND is 3
Option a is set with argument: value1
Processing b : OPTIND is 4
Option b is set

We mentioned above that the other variable that getopts will set for you is the index of where you are up to in processing the options; this is the OPTIND variable. It's the index of the next script argument to be processed, so if your script takes arguments: demo.sh -b -a value1, then as it's processing -s, the OPTIND is 2, because the next thing it will process will be the 2nd argument (-a). When it's processing -a value1, OPTIND is 4, because value1 is the 3rd script argument and the next index is 4.

When getopts reaches the end of the options, it exits with a status value of 1. It also sets name to the character ? and sets OPTIND to the index of non-option argument. getopts recognizes the end of the options by any of the following situations:

  • Finding an option that require an argument but not supply with an argument
  • Finding an option that doesn't start with -
  • Encountering an error (for example, an unrecognized option letter)

If an invalid option is seen or a required argument of an option is not found, a question mark (?>) is placed in name and, prints an error message and unsets OPTARG.

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

while getopts "a:b" option; do
    echo "Processing $option : OPTIND is $OPTIND"
    case $option in
    a)
        echo "Option a is set with argument: $OPTARG"
        ;;
    b)
        echo "Option b is set"
        ;;
    \?)
        echo "Invalid option: -$OPTARG"
        ;;
    esac
done
[hemimorphite@ubuntu ~]$ chmod +x demo.sh
[hemimorphite@ubuntu ~]$ ./demo.sh -a
./demo.sh: option requires an argument -- a
Processing ? : OPTIND is 2
Invalid option: -
[hemimorphite@ubuntu ~]$ ./demo.sh -z
./demo.sh: illegal option -- z
Processing ? : OPTIND is 2
Invalid option: -

If the first character of optstring is a colon, silent error reporting is used.

If an invalid option is seen and silent error reporting is used, a question mark (?>) is placed in name and, the option character found is placed in OPTARG and no error message is printed.

If a required argument of an option is not found and silent error reporting is used, then a colon (:) is placed in name and OPTARG is set to the option character found.

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

while getopts ":a:b" option; do
    echo "Processing $option : OPTIND is $OPTIND"
    case $option in
    a)
        echo "Option a is set with argument: $OPTARG"
        ;;
    b)
        echo "Option b is set"
        ;;
    :)
        echo "Argument missing"
        ;;
    \?)
        echo "Invalid option: -$OPTARG"
        ;;
    esac
done
[hemimorphite@ubuntu ~]$ chmod +x demo.sh
[hemimorphite@ubuntu ~]$ ./demo.sh -a
Processing : : OPTIND is 2
Argument missing
[hemimorphite@ubuntu ~]$ ./demo.sh -z
Processing ? : OPTIND is 2
Invalid option: -z
hash

The hash command affects the way the current shell remembers a command's path name, either by adding a path name to a list or purging the contents of the list.

The syntax of the hash command takes the following form:

hash [-r] [-p filename] [-dt] [name]

When we run any commands or programs in the Linux shells, it records the location of the binary of these commands in a hash table.

The commands are found by searching through the directories listed in PATH.

To list the entries in the hash table, we can run the hash command without any arguments:

[hemimorphite@ubuntu ~]$ hash
hash: hash table empty

Since we haven't run any commands, there are no entries in the hash table.

[hemimorphite@ubuntu ~]$ ls >/dev/null
[hemimorphite@ubuntu ~]$ ls >/dev/null
[hemimorphite@ubuntu ~]$ which ls >/dev/null
[hemimorphite@ubuntu ~]$ hash
hits	command
    1	/usr/bin/which
    2	/usr/bin/ls

We can run the hash command to add commands into the hash table without running it.

[hemimorphite@ubuntu ~]$ hash whoami grep xargs
[hemimorphite@ubuntu ~]$ hash
hits	command
    0	/usr/bin/grep
    0	/usr/bin/whoami
    0	/usr/bin/xargs

The hash command resets the table when we supply the -r option:

[hemimorphite@ubuntu ~]$ hash whoami grep xargs
[hemimorphite@ubuntu ~]$ hash
hits	command
    0	/usr/bin/grep
    0	/usr/bin/whoami
    0	/usr/bin/xargs
[hemimorphite@ubuntu ~]$ hash -r
[hemimorphite@ubuntu ~]$ hash
hash: hash table empty

We can remove specific commands from the table using the -d option followed by the command name.

[hemimorphite@ubuntu ~]$ ls >/dev/null
[hemimorphite@ubuntu ~]$ ls >/dev/null
[hemimorphite@ubuntu ~]$ which ls >/dev/null
[hemimorphite@ubuntu ~]$ hash -d ls
[hemimorphite@ubuntu ~]$ hash
hits	command
    1	/usr/bin/which

The hash command provides us a way to set the path of commands manually using the –p option. We specify the -p option followed by the path to the binary and then the command name we want to associate it with.

[hemimorphite@ubuntu ~]$ hash -p /usr/bin/date another-date
[hemimorphite@ubuntu ~]$ another-date
Sat Jun 15 07:17:01 PM WIB 2024

The hash command provides us a way to display the lists of commands in the hash table using the –l option.

[hemimorphite@ubuntu ~]$ ls
[hemimorphite@ubuntu ~]$ mkdir example
[hemimorphite@ubuntu ~]$ rmdir example
[hemimorphite@ubuntu ~]$ touch file
[hemimorphite@ubuntu ~]$ rm file
[hemimorphite@ubuntu ~]$ hash -l
builtin hash -p /usr/bin/ls ls
builtin hash -p /usr/bin/mkdir mkdir
builtin hash -p /usr/bin/rmdir rmdir
builtin hash -p /usr/bin/touch touch
builtin hash -p /usr/bin/rm rm

The hash command doesn't report any shell built-in commands.

[hemimorphite@ubuntu ~]$ echo hello
hello
[hemimorphite@ubuntu ~]$ pwd
/home/hemimorphite
[hemimorphite@ubuntu ~]$ hash
hash: hash table empty
pwd

The pwd (print working directory) command is used to displays the full pathname of the current directory.

The syntax of the pwd command takes the following form:

pwd [-LP]

The -L option is used to display the logical current directory. This means it shows the path you used to get to the directory, even if it involves symbolic links. The default behavior of pwd command is the same as pwd -L.

[hemimorphite@ubuntu ~]$ ln -s /var/log link_to_log
[hemimorphite@ubuntu ~]$ cd link_to_log
[hemimorphite@ubuntu ~/link_to_log]$ pwd -L
/home/hemimorphite/link_to_log

When we use pwd -L, it returns /home/hemimorphite/link_to_log, which is the logical path we used.

The -P option is used to display the physical current directory. This means it shows the actual location of the directory, ignoring symbolic links.

[hemimorphite@ubuntu ~]$ ln -s /var/log link_to_log
[hemimorphite@ubuntu ~]$ cd link_to_log
[hemimorphite@ubuntu ~/link_to_log]$ pwd -P
/var/log

After navigating to link_to_log, when we use pwd -P, it returns /var/log, which is the actual location of the directory.

readonly

The readonly command is used to mark shell variables and functions as unchangeable. Once a variable or function is set as readonly, its value or function definition cannot be changed or unset.

The syntax of the readonly command takes the following form:

readonly [-aAf] [-p] [name[=value]] …

The readonly command without option is used to mark shell variables as readonly or unchangeable.

[hemimorphite@ubuntu ~]$ var1="Initial value"
[hemimorphite@ubuntu ~]$ readonly var1
[hemimorphite@ubuntu ~]$ var1="New value"
bash: var1: readonly variable

The -f option is used to mark shell functions as readonly or unchangeable.

[hemimorphite@ubuntu ~]$ func1() {
>   echo "This function is readonly"
> }
[hemimorphite@ubuntu ~]$ readonly -f func1
[hemimorphite@ubuntu ~]$ func1() {
>   echo "Change function output"
> }
bash: func1: readonly function

The -a option is used to mark indexed array variables as readonly or unchangeable.

[hemimorphite@ubuntu ~]$ declare -a countries
[hemimorphite@ubuntu ~]$ countries=("India" "France" "United Kingdom")
[hemimorphite@ubuntu ~]$ readonly -a countries
[hemimorphite@ubuntu ~]$ countries=("Japan" "Spanyol" "United State")
bash: countries: readonly variable

The -A option is used to mark associative array variables as readonly or unchangeable.

[hemimorphite@ubuntu ~]$ declare -A country_capitals
[hemimorphite@ubuntu ~]$ country_capitals=(["India"]="New Delhi" ["France"]="Paris" ["United Kingdom"]="London")
[hemimorphite@ubuntu ~]$ readonly -A country_capitals
[hemimorphite@ubuntu ~]$ country_capitals=(["Japan"]="Tokyo" ["Spanyol"]="Madrid" ["United State"]="Washington")
bash: country_capitals: readonly variable

The -p option is used to display the list of all readonly variables.

[hemimorphite@ubuntu ~]$ readonly -p
declare -r BASHOPTS="checkwinsize:cmdhist:complete_fullquote:expand_aliases:extglob:extquote:force_fignore:globasciiranges:histappend:interactive_comments:login_shell:progcomp:promptvars:sourcepath"
declare -ar BASH_VERSINFO=([0]="5" [1]="1" [2]="16" [3]="1" [4]="release" [5]="x86_64-pc-linux-gnu")
declare -ir EUID="1000"
declare -ir PPID="260303"
declare -r SHELLOPTS="braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor"
declare -ir UID="1000"

The -f option is also used to display the list of all readonly functions.

[hemimorphite@ubuntu ~]$ func1() {
>   echo "This function is readonly"
> }
[hemimorphite@ubuntu ~]$ readonly -f func1
[hemimorphite@ubuntu ~]$ readonly -f
func1 ()
{
    echo "This function is readonly"
}
declare -fr func1
return

The return command is used in the script to return the value called in the function. The return command is always used in the function, if used outside the function it has no effect. This command stops the execution of the function where it is used.

The syntax of the return command takes the following form:

return [n]

The return command takes a parameter [n], if n is mentioned then it returns [n] and if n is not mentioned then it returns the status of the last command executed within the function or script. n can only be a numeric value.

The special variable $? is used to hold the return value and the status of last executed command.

[hemimorphite@ubuntu ~]$ add() {
>   add=$(($1+$2))
>   return $add
> }
[hemimorphite@ubuntu ~]$ add 25 26
[hemimorphite@ubuntu ~]$ echo $?
51
shift

The shift command is used to shift the positional parameters (such as arguments passed to a bash script) to the left, putting each parameter in a lower position.

The syntax of the shift command takes the following form:

shift [n]

The shift command takes a parameter [n], if [n] is mentioned then the current positional parameters are shifted left [n] times. If [n] is not specified, the default value of n is 1. So the commands shift 1 and shift (with no argument) do the same thing

If a parameter is shifted to a position with a number less than 1, its value is discarded. So the command shift always discards the previous value of $1, and shift 2 always discards the previous values of $1 and $2.

The special positional parameter $0 is excluded from all shift operations, and never modified by the shift command.

Parameters with a number 10 or greater can be referenced by putting the number in brackets, for example ${10}, ${11}, or ${12345}.

Bash keeps track of the total number of positional parameters. This number is stored in the special shell variable $#.

The value of $# decreases by n every time you run shift.

You can pass arguments to a bash script by typing them after the script's name when running it. Each argument should be separated by a space.

Inside the script, you can access these arguments using special variables. $1 represents the first argument, $2 the second, and so on. Let's look at an example:

[hemimorphite@ubuntu ~]$ cat demo.sh
#!/bin/bash
echo 1: $1
echo 1: $2
[hemimorphite@ubuntu ~]$ chmod +x demo.sh
[hemimorphite@ubuntu ~]$ ./demo.sh one two
1: one
1: two

You will encounter situations that require more than just $1, $2, etc. Bash provides shift command to help you manage these scenarios.

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

USAGE="usage: $0 arg1 arg2 ... argN"

if (( $# == 0 )) 
then
    echo "$USAGE"
    exit 1 
fi
n=1
print "The arguments to the script are:" 
while (($#)) 
do
    echo $n: $1 
    n=$((n+1))
    shift 
done
[hemimorphite@ubuntu ~]$ chmod +x demo.sh
[hemimorphite@ubuntu ~]$ ./demo.sh one two three four five six
The arguments to the script are: 
1: one 
2: two 
3: three 
4: four
5: five
6: six
times

The shift command is used to print out the user and system times used by the shell and its children.

trap

The trap command is used to catch any supported signal and react upon it.

The syntax of the trap command takes the following form:

trap [-lp] [arg] [sigspec …]

If arg is absent (and there is a single sigspec) or equal to -, each specified signal's disposition is reset to the value it had when the shell was started. For example:

trap - SIGINT SIGABRT

If arg is the null string, then the signal specified by each sigspec is ignored by the shell; in other words, the signal specified by each sigspec is disabled.

trap "" SIGINT SIGABRT

The -l option causes the shell to print a list of all the signals and their numbers.

[hemimorphite@ubuntu ~]$ trap -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

The trap -l command doesn't display signals 32 and 33 in the output because they aren't supported on Linux.

The -p option is used to display the trap commands.

[hemimorphite@ubuntu ~]$ trap "echo SIGINT terminated the process" SIGINT
[hemimorphite@ubuntu ~]$ trap "echo SIGTERM terminated the process" SIGTERM
[hemimorphite@ubuntu ~]$ trap -p
trap -- 'echo SIGINT terminated the process' SIGINT
trap -- 'echo SIGTERM terminated the process' SIGTERM
[hemimorphite@ubuntu ~]$ trap -p SIGINT
trap -- 'echo SIGINT terminated the process' SIGINT
[hemimorphite@ubuntu ~]$ trap - SIGINT SIGTERM

If a sigspec is 0 or EXIT, arg is executed when the shell or subshell exits.

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

# this executes when the subshell exits
trap "echo Exiting subshell..." EXIT
[hemimorphite@ubuntu ~]$ chmod +x demo.sh
[hemimorphite@ubuntu ~]$ ./demo.sh
Exiting subshell...
[hemimorphite@ubuntu ~]$ cat demo.sh
#!/bin/bash

# this executes when the shell exits
trap "echo Exiting shell..." EXIT

exit
[hemimorphite@ubuntu ~]$ chmod +x demo.sh
[hemimorphite@ubuntu ~]$ . demo.sh
Exiting shell...
[hemimorphite@ubuntu ~]$ trap - EXIT

If a sigspec is RETURN, the command arg is executed each time the shell script finishes executing by the . or source builtins.

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

# this executes when the script finishes executing
trap "echo Returning..." RETURN
[hemimorphite@ubuntu ~]$ . demo.sh
Returning...
[hemimorphite@ubuntu ~]$ trap - RETURN

If a sigspec is DEBUG, the command arg is executed before every simple command, for command, case command, select command, every arithmetic for command, and before the first command executes in a shell function.

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

trap "echo Executing..." DEBUG

echo Hello World

whoami

which echo
[hemimorphite@ubuntu ~]$ ./demo.sh
Executing...
Hello World
Executing...
hemimorphite
Executing...
/usr/bin/echo

If a sigspec is ERR, the command arg is executed whenever a pipeline (which may consist of a single simple command), a list, or a compound command returns a non-zero exit status. The ERR trap is not executed if the failed command is part of the command list immediately following an until or while keyword, part of the test following the if or elif reserved words, part of a command executed in a && or || list except the command following the final && or ||, any command in a pipeline but the last, or if the command's return status is being inverted using !. These are the same conditions obeyed by the errexit (-e) option.

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

trap "echo An error occurred." ERR

# unknown command
getcommand
[hemimorphite@ubuntu ~]$ ./demo.sh
./demo.sh: line 6: getcommand: command not found
An error occurred.

We can use the trap command to intercept signals so that we can handle them.

We can set and invoke handlers for any and all available signals

We can even prevent the default action for all signals except SIGKILL and SIGSTOP:

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

trap "echo 'sending SIGHUP signal'" 1
trap "echo 'sending SIGINT signal'" 2
trap "echo 'sending SIGQUIT signal'" 3
trap "echo 'sending SIGILL signal'" 4
trap "echo 'sending SIGTRAP signal'" 5
trap "echo 'sending SIGABRT signal'" 6
trap "echo 'sending SIGBUS signal'" 7
trap "echo 'sending SIGFPE signal'" 8
#trap "echo 'sending SIGKILL signal'" 9
trap "echo 'sending SIGUSR1 signal'" 10
trap "echo 'sending SIGSEGV signal'" 11
trap "echo 'sending SIGUSR2 signal'" 12
trap "echo 'sending SIGPIPE signal'" 13
trap "echo 'sending SIGALRM signal'" 14
trap "echo 'sending SIGTERM signal'" 15
trap "echo 'sending SIGSTKFLT signal'" 16
trap "echo 'sending SIGCHLD signal'" 17
trap "echo 'sending SIGCONT signal'" 18
#trap "echo 'sending SIGSTOP signal'" 19
trap "echo 'sending SIGTSTP signal'" 20
trap "echo 'sending SIGTTIN signal'" 21
trap "echo 'sending SIGTTOU signal'" 22
trap "echo 'sending SIGURG signal'" 23
trap "echo 'sending SIGXCPU signal'" 24
trap "echo 'sending SIGXFSZ signal'" 25
trap "echo 'sending SIGVTALRM signal'" 26
trap "echo 'sending SIGPROF signal'" 27
trap "echo 'sending SIGWINCH signal'" 28
trap "echo 'sending SIGIO signal'" 29
trap "echo 'sending SIGPWR signal'" 30
trap "echo 'sending SIGTSTP signal'" 31
[hemimorphite@ubuntu ~]$ chmod +x demo.sh
[hemimorphite@ubuntu ~]$ . demo.sh
[hemimorphite@ubuntu ~]$ kill -SIGHUP $$
sending SIGHUP signal
[hemimorphite@ubuntu ~]$ kill -SIGINT $$
sending SIGINT signal
[hemimorphite@ubuntu ~]$ kill -SIGQUIT $$
sending SIGQUIT signal
[hemimorphite@ubuntu ~]$ kill -SIGILL $$
sending SIGILL signal
[hemimorphite@ubuntu ~]$ kill -SIGTRAP $$
sending SIGTRAP signal
[hemimorphite@ubuntu ~]$ kill -SIGABRT $$
sending SIGABRT signal
[hemimorphite@ubuntu ~]$ kill -SIGBUS $$
sending SIGBUS signal
[hemimorphite@ubuntu ~]$ kill -SIGFPE $$
sending SIGFPE signal
[hemimorphite@ubuntu ~]$ kill -SIGUSR1 $$
sending SIGUSR1 signal
[hemimorphite@ubuntu ~]$ kill -SIGSEGV $$
sending SIGSEGV signal
[hemimorphite@ubuntu ~]$ kill -SIGUSR2 $$
sending SIGUSR2 signal
[hemimorphite@ubuntu ~]$ kill -SIGPIPE $$
sending SIGPIPE signal
[hemimorphite@ubuntu ~]$ kill -SIGALRM $$
sending SIGALRM signal
[hemimorphite@ubuntu ~]$ kill -SIGTERM $$
sending SIGTERM signal
[hemimorphite@ubuntu ~]$ kill -SIGSTKFLT $$
sending SIGSTKFLT signal
[hemimorphite@ubuntu ~]$ kill -SIGCHLD $$
sending SIGCHLD signal
[hemimorphite@ubuntu ~]$ kill -SIGCONT $$
sending SIGCONT signal
[hemimorphite@ubuntu ~]$ kill -SIGTSTP $$
sending SIGTSTP signal
[hemimorphite@ubuntu ~]$ kill -SIGTTIN $$
sending SIGTTIN signal
[hemimorphite@ubuntu ~]$ kill -SIGTTOU $$
sending SIGTTOU signal
[hemimorphite@ubuntu ~]$ kill -SIGURG $$
sending SIGURG signal
[hemimorphite@ubuntu ~]$ kill -SIGXCPU $$
sending SIGXCPU signal
[hemimorphite@ubuntu ~]$ kill -SIGXFSZ $$
sending SIGXFSZ signal
[hemimorphite@ubuntu ~]$ kill -SIGVTALRM $$
sending SIGVTALRM signal
[hemimorphite@ubuntu ~]$ kill -SIGPROF $$
sending SIGPROF signal
[hemimorphite@ubuntu ~]$ kill -SIGWINCH $$
sending SIGWINCH signal
[hemimorphite@ubuntu ~]$ kill -SIGIO $$
sending SIGIO signal
[hemimorphite@ubuntu ~]$ kill -SIGPWR $$
sending SIGPWR signal
[hemimorphite@ubuntu ~]$ kill -SIGTSTP $$
sending SIGTSTP signal
[hemimorphite@ubuntu ~]$ trap - SIGHUP SIGINT SIGQUIT SIGILL SIGTRAP SIGABRT SIGBUS SIGFPE SIGUSR1 SIGSEGV SIGUSR2 SIGPIPE SIGALRM SIGTERM SIGSTKFLT SIGCHLD SIGCONT SIGTSTP SIGTTIN SIGTTOU SIGURG SIGXCPU SIGXFSZ SIGVTALRM SIGPROF SIGWINCH SIGIO SIGPWR SIGTSTP
umask

The umask command is used to set default permissions for files or directories the user creates.

The syntax of the umask command takes the following form:

umask [-p] [-S] [mode]

In Linux, each file is associated with an owner and a group and assigned with permission access rights for three different classes of users:

  • The file owner
  • The group members
  • Everyone else

There are three permissions types that apply to each class:

  • The read permission
  • The write permission
  • The execute permission

The following example shows the permissions for a directory:

drwxr-xr-x 12 hemimorphite hemimorphite 4.0K Jun  16 20:51 dirname
|[-][-][-]    [----------] [----------]
| |  |  |           |           |       
| |  |  |           |           +----> Group
| |  |  |           +----------------> Owner
| |  |  +----------------------------> Others Permissions
| |  +-------------------------------> Group Permissions
| +----------------------------------> Owner Permissions
+------------------------------------> File Type

The first character represents the file type which can be a regular file (-), a directory (d), a symbolic link (l), or any other special type of file.

Character r with an octal value of 4 stands for read, w with an octal value of 2 for write, x with an octal value of 1 for execute permission, and (-) with an octal value of 0 for no permissions.

If we represent the file permissions using a numeric notation, we will come up to the number 755:

  • Owner: rwx = 4+2+1 = 7
  • Group: r-x = 4+0+1 = 5
  • Other: r-x = 4+0+1 = 5

The first digit represents the special permissions, and if it is omitted, it means that no special permissions are set on the file. In the example above 755 is the same as 0755. The first digit can be a combination of 4 for setuid, 2 for setgid, and 1 for Sticky Bit.

On Linux systems, the default creation permissions are 666 for files, which gives read and write permission to user, group, and others, and to 777 for directories, which means read, write and execute permission to user, group, and others. By default, Linux does not allow a file to be created with execute permissions.

The default creation permissions can be modified using the umask command.

umask affects only the current shell environment. On most Linux distributions, the umask value is set in the pam_umask.so or /etc/profile file.

To view the current mask value:

[hemimorphite@ubuntu ~]$ umask
022

The umask value contains the permission bits that will NOT be set on the newly created files and directories.

Once we get the umask value, that is 022, we can calculated the permissions for:

  • Files: 666 - 022 = 644 (the default creation permissions for file – umask value = the permissions for file). The owner can read and modify the files. Group and others can only read the files.
  • Directories: 777 - 022 = 755 (the default creation permissions for drectory – umask value = the permissions for drectory). The owner can cd into the directory, and list, read, modify, create or delete the files in the directory. Group and others can cd into the directory and list and read the files.

If the -S option is supplied without a mode argument, the mask is printed in a symbolic format.

[hemimorphite@ubuntu ~]$ umask -S
u=rwx,g=rx,o=rx

If the -p option is supplied, and mode is omitted, the output is in a form that may be reused as input.

[hemimorphite@ubuntu ~]$ umask -p
umask 0022
unset

The unset is used to unset or undefine values and attributes of variables and functions.

The syntax of the unset command takes the following form:

unset [-fnv] [name]

If the -v option is given, each name refers to a shell variable and that variable is removed.

[hemimorphite@ubuntu ~]$ varname="hemimorphite blog"
[hemimorphite@ubuntu ~]$ echo $varname
hemimorphite blog
[hemimorphite@ubuntu ~]$ unset -v varname
[hemimorphite@ubuntu ~]$ echo $varname

[hemimorphite@ubuntu ~]$

If the -f option is given, the names refer to shell functions, and the function definition is removed.

[hemimorphite@ubuntu ~]$ getname() {
>   echo "hemimorphite blog"
> }
[hemimorphite@ubuntu ~]$ getname
hemimorphite blog
[hemimorphite@ubuntu ~]$ unset -f getname
[hemimorphite@ubuntu ~]$ getname

[hemimorphite@ubuntu ~]$

If the -n option is supplied, and name is a variable with the nameref attribute, name will be unset rather than the variable it references.

[hemimorphite@ubuntu ~]$ var=blog
[hemimorphite@ubuntu ~]$ declare -n varnameref=${var}name
[hemimorphite@ubuntu ~]$ varnameref="hemimorphite blog"
[hemimorphite@ubuntu ~]$ echo $blogname
hemimorphite blog
[hemimorphite@ubuntu ~]$ echo $varnameref
hemimorphite blog
[hemimorphite@ubuntu ~]$ unset -n varnameref
[hemimorphite@ubuntu ~]$ echo $blogname
hemimorphite blog
[hemimorphite@ubuntu ~]$ echo $varnameref

[hemimorphite@ubuntu ~]$

-n option has no effect if the -f option is supplied.

If no options are supplied, each name refers to a variable or refers to a function; is removed.

Readonly variables and functions can't be unset.

[hemimorphite@ubuntu ~]$ readonly blogname="hemimorphite"
[hemimorphite@ubuntu ~]$ unset blogname
-bash: unset: blogname: cannot unset: readonly variable
test

The test command compares one element against another and returns true or false.

The syntax of the test command takes the following form:

test expr

Or

[ expr ]

test exits with the status determined by expr. Placing the expr between square brackets ([ and ]) is the same as testing the expr with test. To see the exit status at the command prompt, echo the value $? A value of 0 means the expression evaluated as true, and a value of 1 means the expression evaluated as false.

Expressions of test command take the following forms:

Expression Description Example
expression expression is true
[hemimorphite@ubuntu ~]$ test -n hemimorphite
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -n hemimorphite ]
[hemimorphite@ubuntu ~]$ echo $?
0
! expression expression is false
[hemimorphite@ubuntu ~]$ test ! -z hemimorphite
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ ! -z hemimorphite ]
[hemimorphite@ubuntu ~]$ echo $?
0
expression1 -a expression2 both expression1 and expression2 are true
[hemimorphite@ubuntu ~]$ test -n hemimorphite -a hemimorphite != Hemimorphite
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -n hemimorphite -a hemimorphite != Hemimorphite ]
[hemimorphite@ubuntu ~]$ echo $?
0
expression1 -o expression2 either expression1 or expression2 is true
[hemimorphite@ubuntu ~]$ test -z hemimorphite -o hemimorphite != Hemimorphite
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -z hemimorphite -o hemimorphite != Hemimorphite ]
[hemimorphite@ubuntu ~]$ echo $?
0
-n string the length of string is nonzero
[hemimorphite@ubuntu ~]$ test -n hemimorphite
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -n hemimorphite ]
[hemimorphite@ubuntu ~]$ echo $?
0
string equivalent to -n string
[hemimorphite@ubuntu ~]$ test hemimorphite
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ hemimorphite ]
[hemimorphite@ubuntu ~]$ echo $?
0
-z string the length of string is zero
[hemimorphite@ubuntu ~]$ test -z ''
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -z '' ]
[hemimorphite@ubuntu ~]$ echo $?
0
string1 = string2 the strings are equal
[hemimorphite@ubuntu ~]$ test 'hemimorphite blog' = 'hemimorphite blog'
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ 'hemimorphite blog' = 'hemimorphite blog' ]
[hemimorphite@ubuntu ~]$ echo $?
0
string1 != string2 the strings are not equal
[hemimorphite@ubuntu ~]$ test 'hemimorphite blog' != 'Hemimorphite Blog'
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ 'hemimorphite blog' != 'Hemimorphite Blog' ]
[hemimorphite@ubuntu ~]$ echo $?
0
integer1 -eq integer2 integer1 equals integer2
[hemimorphite@ubuntu ~]$ test 100 -eq 100
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ 100 -eq 100 ]
[hemimorphite@ubuntu ~]$ echo $?
0
integer1 -ge integer2 integer1 is greater than or equal to integer2
[hemimorphite@ubuntu ~]$ test 100 -ge 60
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ 100 -ge 60 ]
[hemimorphite@ubuntu ~]$ echo $?
0
integer1 -gt integer2 integer1 is greater than integer2
[hemimorphite@ubuntu ~]$ test 100 -gt 60
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ 100 -gt 60 ]
[hemimorphite@ubuntu ~]$ echo $?
0
integer1 -le integer2 integer1 is less than or equal to integer2
[hemimorphite@ubuntu ~]$ test 80 -le 120
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ 80 -le 120 ]
[hemimorphite@ubuntu ~]$ echo $?
0
integer1 -lt integer2 integer1 is less than integer2
[hemimorphite@ubuntu ~]$ test 80 -lt 120
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ 80 -lt 120 ]
[hemimorphite@ubuntu ~]$ echo $?
0
integer1 -ne integer2 integer1 is not equal to integer2
[hemimorphite@ubuntu ~]$ test 80 -ne 120
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ 80 -ne 120 ]
[hemimorphite@ubuntu ~]$ echo $?
0
file1 -ef file2 file1 and file2 have the same device and inode (index node) numbers. The following metadata exists in an inode:
  • File type
  • Permissions
  • Owner ID
  • Group ID
  • Size of file
  • Time last accessed
  • Time last modified
  • Soft/Hard Links
  • Access Control List (ACLs)
# create a file
[hemimorphite@ubuntu ~]$ touch doc1.txt
# create a hard link to doc1.txt
[hemimorphite@ubuntu ~]$ ln doc1.txt doc2.txt
# create a soft link to doc1.txt
[hemimorphite@ubuntu ~]$ ln -s doc1.txt doc3.txt
[hemimorphite@ubuntu ~]$ test doc1.txt -ef doc2.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ test doc1.txt -ef doc3.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ test doc2.txt -ef doc3.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ doc1.txt -ef doc2.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ doc1.txt -ef doc3.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ doc2.txt -ef doc3.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
file1 -nt file2 file1 is newer (modification date) than file2
# create a file
[hemimorphite@ubuntu ~]$ touch olddoc.txt
# create a newer file
[hemimorphite@ubuntu ~]$ touch newdoc.txt
# create a soft link to doc1.txt
[hemimorphite@ubuntu ~]$ test newdoc.txt -nt olddoc.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ newdoc.txt -nt olddoc.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
file1 -ot file2 file1 is older (modification date) than file2
# create a file
[hemimorphite@ubuntu ~]$ touch olddoc.txt
# create a newer file
[hemimorphite@ubuntu ~]$ touch newdoc.txt
# create a soft link to doc1.txt
[hemimorphite@ubuntu ~]$ test olddoc.txt -ot newdoc.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ olddoc.txt -ot newdoc.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-b file file exists and is block special (block device). A block special file acts as a direct interface to a block device. A block device is any device which performs data I/O in units of blocks.
Examples of block special files:
  • dev/fdn: mounted partitions of floppy disks. The letter n refers to a physical device
  • dev/scdn or dev/srn: mounted partitions of SCSI CD-ROMs. The letter n refers to a physical device
  • dev/hdxn: mounted partitions of Integrated Drive Electronics (IDE) controllers. The letter x refers to a physical device, and the number n refers to a partition on that device. For instance, /dev/hda1 is the first partition on the first physical storage device.
  • dev/sdxn: mounted partitions of SCSI disks. The letter x refers to a physical device, and the number n refers to a partition on that device. For instance, /dev/sda1 is the first partition on the first physical storage device.
  • dev/mmcblkxpn: mounted partitions of SD/MMC cards. The letter x refers to a physical device, and the number n refers to a partition on that device. For instance, /dev/sda1 is the first partition on the first physical storage device.
  • /dev/loopn — loop devices. These are special devices which allow a file in the filesystem to be used as a block device. The file may contain an entire filesystem of its own, and be accessed as if it were a mounted partition on a physical storage device. For example, an ISO disk image file may be mounted as a loop device.
  • etc
[hemimorphite@ubuntu ~]$ test -b /dev/sda1
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ test -b /dev/loop1
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -b /dev/sda1 ]
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -b /dev/loop1 ]
[hemimorphite@ubuntu ~]$ echo $?
0
-c file file exists and is character special. A character special file is similar to a block device, but data is written one character (eight bits, or one byte) at a time.
Examples of character special files:
  • /dev/stdin (Standard input)
  • /dev/stdout (Standard output.)
  • /dev/stderr (Standard error.)
  • /dev/random (PRNG which may delay returning a value to acquire additional entropy)
  • /dev/urandom (PRNG which always returns a value immediately, regardless of required entropy)
  • /dev/null (The null device. Reading from this file always gets a null byte; writing to this file successfully does nothing)
[hemimorphite@ubuntu ~]$ test -c /dev/stdin
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ test -c /dev/urandom
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -c /dev/stdin ]
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -c /dev/urandom ]
[hemimorphite@ubuntu ~]$ echo $?
0
-d file file exists and is a directory
[hemimorphite@ubuntu ~]$ mkdir docs
[hemimorphite@ubuntu ~]$ test -d docs
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -d docs ]
[hemimorphite@ubuntu ~]$ echo $?
0
-e file file exists
[hemimorphite@ubuntu ~]$ touch specs.txt
[hemimorphite@ubuntu ~]$ test -e specs.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -e specs.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-f file file exists and is a regular file
[hemimorphite@ubuntu ~]$ touch specs.txt
[hemimorphite@ubuntu ~]$ test -f specs.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -f specs.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-g file file exists and is set-group-ID
[hemimorphite@ubuntu ~]$ touch specs.txt
[hemimorphite@ubuntu ~]$ chmod g+s specs.txt
[hemimorphite@ubuntu ~]$ test -g specs.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -g specs.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-G file file exists and is owned by the effective group ID
[hemimorphite@ubuntu ~]$ touch specs.txt
[hemimorphite@ubuntu ~]$ ls -la specs.txt
-rw-r--r-- 1 hemimorphite hemimorphite 0 Jun 18 01:00 specs.txt
# user hemimorphite is in hemimorphite group and specs.txt is owned by hemimorphite group
[hemimorphite@ubuntu ~]$ test -G specs.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -G specs.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-h file file exists and is a symbolic link (same as -L)
[hemimorphite@ubuntu ~]$ touch doc1.txt
[hemimorphite@ubuntu ~]$ ln -s doc1.txt doc2.txt
[hemimorphite@ubuntu ~]$ test -h doc2.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -h doc2.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-k file file exists and has its sticky bit set. When a directory or a file has the sticky bit set, its files can be deleted or renamed only by the file owner, directory owner and the root user.
[hemimorphite@ubuntu ~]$ touch doc1.txt
[hemimorphite@ubuntu ~]$ chmod +t doc1.txt
[hemimorphite@ubuntu ~]$ test -k doc2.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -k doc2.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-L file file exists and is a symbolic link (same as -h)
[hemimorphite@ubuntu ~]$ touch doc1.txt
[hemimorphite@ubuntu ~]$ ln -s doc1.txt doc2.txt
[hemimorphite@ubuntu ~]$ test -L doc2.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -L doc2.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-O file file exists and is owned by the effective user ID
[hemimorphite@ubuntu ~]$ touch specs.txt
[hemimorphite@ubuntu ~]$ ls -la specs.txt
-rw-r--r-- 1 hemimorphite hemimorphite 0 Jun 18 01:00 specs.txt
# specs.txt is owned by hemimorphite
[hemimorphite@ubuntu ~]$ test -O specs.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -O specs.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-p file file exists and is a named pipe
[hemimorphite@ubuntu ~]$ mkfifo pipeone
[hemimorphite@ubuntu ~]$ test -p pipeone
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -p pipeone ]
[hemimorphite@ubuntu ~]$ echo $?
0
-r file file exists and read permission is granted
[hemimorphite@ubuntu ~]$ touch specs.txt
[hemimorphite@ubuntu ~]$ ls -la specs.txt
-rw-r--r-- 1 hemimorphite hemimorphite 0 Jun 18 01:00 specs.txt
[hemimorphite@ubuntu ~]$ test -r specs.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -r specs.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-s file file exists and has a size greater than zero
[hemimorphite@ubuntu ~]$ touch specs.txt
[hemimorphite@ubuntu ~]$ echo Bash Language Specification > specs.txt
[hemimorphite@ubuntu ~]$ test -s specs.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -s specs.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-S file file exists and is a socket. Sockets are Linux file descriptors that serve as the communication end-points for processes running on that device.
# create a socket
[hemimorphite@ubuntu ~]$ nc -Ul server.sock &
[hemimorphite@ubuntu ~]$ test -S server.sock
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -S server.sock ]
[hemimorphite@ubuntu ~]$ echo $?
0
-t fd file descriptor fd is opened on a terminal. File Descriptors are positive integers that act as abstract handles for IO/resources and files. All file descriptors that a process contains are stored in the directory /proc/<PID>/fd/. <PID> stands for the process ID.
[hemimorphite@ubuntu ~]$ touch out.txt
# Opening file descriptors #4 for reading and writing
[hemimorphite@ubuntu ~]$ exec 4<>out.txt 
# $$ is the process ID of the current instance of Bash
[hemimorphite@ubuntu ~]$ test -t /proc/$$/fd/4
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -t /proc/$$/fd/4 ]
[hemimorphite@ubuntu ~]$ echo $?
0
-u file file exists and its set-user-ID bit is set
[hemimorphite@ubuntu ~]$ touch specs.txt
[hemimorphite@ubuntu ~]$ chmod u+s specs.txt
[hemimorphite@ubuntu ~]$ test -u specs.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -u specs.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-w file file exists and write permission is granted
[hemimorphite@ubuntu ~]$ touch specs.txt
[hemimorphite@ubuntu ~]$ chmod u+w specs.txt
[hemimorphite@ubuntu ~]$ ls -la specs.txt
-rw-r--r-- 1 hemimorphite hemimorphite 0 Jun 18 01:00 specs.txt
[hemimorphite@ubuntu ~]$ test -w specs.txt
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -w specs.txt ]
[hemimorphite@ubuntu ~]$ echo $?
0
-x file file exists and execute (or search) permission is granted
[hemimorphite@ubuntu ~]$ touch build.sh
[hemimorphite@ubuntu ~]$ chmod u+x build.sh
[hemimorphite@ubuntu ~]$ ls -la build.sh
-rwxr--r-- 1 hemimorphite hemimorphite 0 Jun 18 15:56 build.sh
[hemimorphite@ubuntu ~]$ test -x build.sh
[hemimorphite@ubuntu ~]$ echo $?
0
[hemimorphite@ubuntu ~]$ [ -x build.sh ]
[hemimorphite@ubuntu ~]$ echo $?
0