An essential part of learning to work with RHEL 9 and Linux distributions generally involves gaining proficiency in working in the shell environment. While graphical desktop environments such as GNOME, included with Linux, provide a user-friendly interface to the operating system, the shell environment provides far greater capabilities, flexibility, and automation than can ever be achieved using graphical desktop tools. The shell environment also provides a means for interacting with the operating system when a desktop environment is unavailable, a common occurrence when working with a server-based operating system such as RHEL 9 or a damaged system that will not fully boot.
Therefore, this chapter aims to provide an overview of the default shell environment on RHEL 9 (specifically the Bash shell).
What is a Shell?
The shell is an interactive command interpreter environment within which commands may be typed at a prompt or entered into a file as a script and executed. The origins of the shell can be traced back to the early days of the UNIX operating system. In fact, in the early days of Linux, before the introduction of graphical desktops, the shell was the only way for a user to interact with the operating system.
A variety of shell environments have been developed over the years. The first widely used shell was the Bourne shell, written by Stephen Bourne at Bell Labs.
Yet another early creation was the C shell which shared some syntax similarities with the C Programming Language and introduced usability enhancements such as command-line editing and history.
The Korn shell (developed by David Korn at Bell Labs) is based on features provided by both the Bourne and C shells.
The default shell on RHEL 9 is the Bash shell (shorthand for Bourne Again SHell). This shell, which began life as an open-source version of the Bourne shell, was developed for the GNU Project by Brian Fox and is based on features provided by both the Bourne shell and the C shell.
Gaining Access to the Shell
From within the GNOME desktop environment, the shell prompt may be accessed from a Terminal window by selecting the Activities option in the top bar, entering Terminal into the search bar, and clicking the Terminal icon.
When remotely logging into a RHEL 9 server, for example, using SSH, the user is presented with a shell prompt. The chapter entitled “Configuring SSH Key-based Authentication on RHEL” will cover details on accessing a remote server using SSH. When booting a server-based system in which a desktop environment has not been installed, the shell is entered immediately after the user completes the login procedure at the physical console terminal or remote login session.
Entering Commands at the Prompt
Commands are entered at the shell command prompt simply by typing the command and pressing the Enter key. While some commands perform tasks silently, most will display some form of output before returning to the prompt. For example, the ls command can be used to display the files and directories in the current working directory:
$ ls Desktop Documents Downloads Music Pictures Public Templates Videos
The available commands are either built into the shell itself or reside on the physical file system. The location on the file system of a command may be identified using the which command. For example, to find out where the ls executable resides on the file system:
$ which ls alias ls='ls --color=auto' /usr/bin/ls
Clearly, the ls command resides in the /usr/bin directory. Note also that an alias is configured, a topic that will be covered later in this chapter. Using the which command to locate the path to commands built into the shell will result in a message indicating the executable cannot be found. For example, attempting to find the location of the history command (which is built into the shell rather than existing as an executable on the file system) will result in output similar to the following:
$ which history /usr/bin/which: no history in (/home/demo/.local/bin:/home/demo/bin:/usr/share/Modules/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin)
Getting Information about a Command
Many of the commands available to the Linux shell can seem cryptic. To find out detailed information about what a command does and how to use it, use the man command specifying the name of the command as an argument. For example, to learn more about the pwd command:
$ man pwd
A detailed description of the pwd command will be displayed when the above command is executed. Many commands will also provide additional information when run with the –help command-line option:
$ wc --help
Bash Command-line Editing
Early shell environments did not provide any form of line editing capabilities. This meant that if you spotted an error at the beginning of a long command-line, you were typing, you had to delete all the following characters, correct the error and then re-enter the remainder of the command.
Fortunately, Bash provides a wide range of command-line editing options, as outlined in the following table:
Ctrl-b or Left Arrow
Move the cursor back one position
Ctrl-f or Right Arrow
Move the cursor forward one position
Delete the character currently beneath the cursor
Delete the character to the left of the cursor
Undo previous change (can be repeated to undo all previous changes)
Move the cursor to the start of the line
Move the cursor to the end of the line
Meta-f or Esc then f
Move cursor forward one word
Meta-b or Esc then b
Move the cursor back one word
Clear the screen of everything except the current command
Delete to the end of the line from the current cursor position
Meta-d or Esc then d
Delete to end of the current word
Meta-DEL or Esc then DEL
Delete beginning to the current word
Delete from the current cursor position to the previous white space
Working with the Shell History
In addition to command-line editing features, the Bash shell provides command-line history support. A list of previously executed commands may be viewed using the history command:
$ history 1 ps 2 ls 3 ls –l / 4 ls 5 man pwd 6 man apropos
In addition, Ctrl-p (or up arrow) and Ctrl-n (or down arrow) may be used to scroll back and forth through previously entered commands. Finally, when the desired command from the history is displayed, press the Enter key to execute it.
Another option is to enter the ‘!’ character, followed by the first few characters of the command to be repeated, followed by the Enter key.
Many shell commands take one or more filenames as arguments. For example, to display the content of a text file named list.txt, the cat command would be used as follows:
$ cat list.txt
Similarly, the content of multiple text files could be displayed by specifying all the file names as arguments:
$ cat list.txt list2.txt list3.txt list4.txt
Instead of typing in each name, pattern matching can be used to specify all files with names matching certain criteria. For example, the ‘*’ wildcard character can be used to simplify the above example:
$ cat *.txt
The above command will display the content of all files ending with a .txt extension. This could be further restricted to any file names beginning with list and ending in .txt:
$ cat list*.txt
Single character matches may be specified using the ‘?’ character:
$ cat list?.txt
Filename and Path Completion
Rather than typing in a complete file name or path or using pattern matching to reduce the amount of typing, the shell provides the filename completion feature. To use filename completion, enter the first few characters of the file or path name and then press the Esc key twice. The shell will then complete the filename for you with the first file or path name in the directory that matches the characters you entered. To obtain a list of possible matches, press Esc = after entering the first few characters.
Input and Output Redirection
As previously mentioned, many shell commands output information when executed. By default, this output goes to a device file named stdout which is essentially the terminal window or console in which the shell is running. Conversely, the shell takes input from a device file named stdin, which by default is the keyboard.
Output from a command can be redirected from stdout to a physical file on the file system using the ‘>’ character. For example, to redirect the output from an ls command to a file named files.txt, the following command would be required:
$ ls *.txt > files.txt
Upon completion, files.txt will contain the list of files in the current directory. Similarly, the contents of a file may be fed into a command in place of stdin. For example, to redirect the contents of a file as input to a command:
$ wc –l < files.txt
The above command will display the number of lines in the files.txt file.
It is important to note that the ‘>’ redirection operator creates a new file or truncates an existing file when used. To append to an existing file, use the ‘>>’ operator:
$ ls *.dat >> files.txt
In addition to standard output, the shell also provides standard error output using stderr. While output from a command is directed to stdout, any error messages generated by the command are directed to stderr. This means that if stdout is directed to a file, error messages will still appear in the terminal. This is generally the desired behavior, though stderr may also be redirected if desired using the ‘2>’ operator:
$ ls dkjfnvkjdnf 2> errormsg
On completion of the command, an error reporting that the file named dkjfnvkjdnf could not be found will be contained in the errormsg file.
Both stderr and stdout may be redirected to the same file using the &> operator:
$ ls /etc dkjfnvkjdnf &> alloutput
On completion of execution, the alloutput file will contain both a listing of the contents of the /etc directory and the error message associated with the attempt to list a non-existent file.
Working with Pipes in the Bash Shell
In addition to I/O redirection, the shell also allows output from one command to be piped directly as input to another command. A pipe operation is achieved by placing the ‘|’ character between two or more commands on a command line. For example, to count the number of processes running on a system, the output from the ps command can be piped through to the wc command:
$ ps –ef | wc –l
There is no limit to the number of pipe operations that can be performed on a command line. For example, to find the number of lines in a file that contain the name Smith:
$ cat namesfile | grep Smith | wc –l
As you gain proficiency with the shell environment, you will likely frequently issue commands with the same arguments. For example, you may often use the ls command with the l and t options:
$ ls –lt
To reduce the amount of typing involved in issuing a command, it is possible to create an alias that maps to the command and arguments. For example, to create an alias such that entering the letter l will cause the ls –lt command to be executed, the following statement would be used:
$ alias l="ls –lt"
Entering l at the command prompt will now execute the original statement.
Shell environment variables provide temporary storage of data and configuration settings. The shell itself sets up several environment variables that the user may change to modify the behavior of the shell. A listing of currently defined variables may be obtained using the env command:
$ env SSH_CONNECTION=192.168.0.19 61231 192.168.0.28 22 MODULES_RUN_QUARANTINE=LD_LIBRARY_PATH LANG=en_US.UTF-8 HISTCONTROL=ignoredups HOSTNAME=RHELdemo-pc.ebookfrenzy.com XDG_SESSION_ID=15 MODULES_CMD=/usr/share/Modules/libexec/modulecmd.tcl USER=demo ENV=/usr/share/Modules/init/profile.sh SELINUX_ROLE_REQUESTED= PWD=/home/demo HOME=/home/demo SSH_CLIENT=192.168.0.19 61231 22 SELINUX_LEVEL_REQUESTED= . .
Perhaps the most useful environment variable is PATH. This defines the directories in which the shell will search for commands entered at the command prompt and the order in which it will do so. The PATH environment variable for a user account on a newly installed RHEL 9 system will likely be configured as follows:
$ echo $PATH /home/demo/.local/bin:/home/demo/bin:/usr/share/Modules/bin:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin
Another useful variable is HOME, which specifies the current user’s home directory. If, for example, you wanted the shell to also look for commands in the scripts directory located in your home directory, you would modify the PATH variable as follows:
$ export PATH=$PATH:$HOME/scripts
The current value of an existing environment variable may be displayed using the echo command:
$ echo $PATH
You can create your own environment variables using the export command. For example:
$ export DATAPATH=/data/files
A useful trick to assign the output from a command to an environment variable involves using back quotes (`) around the command. For example, to assign the current date and time to an environment variable called NOW:
$ export NOW=`date` $ echo $NOW Wed Mar 29 12:39:20 PM EDT 2023
If there are environment variables or alias settings that need to be configured each time you enter the shell environment, they may be added to a file in your home directory named .bashrc. For example, the following .bashrc file is configured to set up the DATAPATH environment variable and an alias:
# .bashrc # Source global definitions if [ -f /etc/bashrc ]; then . /etc/bashrc fi # User specific environment PATH="$HOME/.local/bin:$HOME/bin:$PATH" export PATH # Uncomment the following line if you don't like systemctl's auto-paging feature: # export SYSTEMD_PAGER= # User specific aliases and functions export DATAPATH=/data/files alias l="ls -lt"
Writing Shell Scripts
So far, we have focused exclusively on the interactive nature of the Bash shell. By interactive, we mean manually entering commands individually and executing them at the prompt. In fact, this is only a small part of what the shell is capable of. Arguably one of the most powerful aspects of the shell involves the ability to create shell scripts. Shell scripts are text files containing statement sequences that can be executed within the shell environment to perform tasks. In addition to the ability to execute commands, the shell provides many programming constructs, such as for and do loops and if statements, that you might reasonably expect to find in a scripting language.
Unfortunately, a detailed overview of shell scripting is beyond the scope of this chapter. However, many books and web resources dedicated to shell scripting do the subject much more justice than we could ever hope to achieve here. In this section, therefore, we will only be providing a very small taste of shell scripting.
The first step in creating a shell script is to create a file (for this example, we will name it simple. sh) and add the following as the first line:
The #! is called the “shebang” and is a special sequence of characters indicating that the path to the interpreter needed to execute the script is the next item on the line (in this case, the sh executable located in /bin). This could equally be, for example, /bin/csh or /bin/ksh if either were the interpreter you wanted to use.
The next step is to write a simple script:
#!/bin/sh for i in * do echo $i done
All this script does is iterate through all the files in the current directory and display the name of each file. This script may be executed by passing the name of the script through as an argument to sh:
$ sh simple.sh
To make the file executable (thereby negating the need to pass it through to the sh command), the chmod command can be used:
$ chmod +x simple.sh
Once the execute bit has been set on the file’s permissions, it may be executed directly. For example:
We briefly toured the Bash shell environment in this chapter of RHEL 9 Essentials. In the world of graphical desktop environments, it is easy to forget that an operating system’s true power and flexibility can often only be utilized by dropping down below the user-friendly desktop interface and using a shell environment. Moreover, familiarity with the shell is necessary to administer and maintain server-based systems that do not have the desktop installed or when attempting to repair a system damaged to the point that the desktop or Cockpit interface will no longer launch.
The shell’s capabilities go far beyond the areas covered in this chapter. If you are new to the shell, we strongly encourage you to seek additional resources. Once familiar with the concepts, you will quickly find that it is quicker to perform many tasks using the shell in a terminal window than to wade through menus and dialogs on the desktop.