Introduction to the shell#

The shell is for most Unix users the main interface to an Unix-based system like Linux or macOS. The shell is a command line interface to the system and comes in different flavors like bash, tcsh, ksh, zsh, etc. While multiple shells exist, the most popular shells used are bash for interactive use and sh for system scripting.

A lot can be configured in the shell, for example the prompt. The prompt is the string that is displayed before the command line and can be changed by modifying the PS1 variable. The prompt normally also shows the current working directory and the current user, but this is a convention. A second convention is that if the prompt ends with a #, then your in a shell with root privileges and when it ends with a $, then you are in a shell with normal user privileges.

The prompt shows the # when the shell is in a root user context#
# whoami
root
The prompt shows the $ when the shell is in a normal user context#
$ whoami
user01

This convention is used in all examples on this site together with the assumption that the user is logged in as user01 and is by default located in the /home/user01 directory.

Using su#

Every shell is running under the account that was used to login with. This account also refered to as an user is member of a primary group that is required, and can also have up to 16 or 32 secondary groups depending if Network File System (NFS) is used or not.

With the command id you can get the primary group and the secondary groups of the user that is currently logged in.

The command id shows the primary group and secondary groups for the user that is logged in#
$ id
uid=1000(user01) gid=1000(user01) groups=1000(user01)

The command can also be extended to get the groups of other users defined on the system as is show below for the user root.

The command id can also show the primary and secondary group for other users#
$ id root
uid=0(root) gid=0(root) groups=0(root)

A shell runs as the user that is logged in. This is the user that is used to run the command line and to see the effective user that is used to run the command whoami.

Show the effective user for the shell#
# whoami
root

The effective user can be changed by using the command su. This command changes the user that the shell runs as, but to accomplish this the password of the target user is required.

Change the effective user to the user root#
$ su root
password:

By default only the effective is changed by the su command that stands for switching user or substitute user, but the environment is not changed. To change the environment as well, use the - option, which is an alias for -l and --login, and the environment is also changed when the user changes.

Switching to user root#
$ su - root
Password:

When we test this by defining VAR1 and run the command echo $VAR1 to see the variable under the user root, then no result is given as the environment variable has been cleared when the user was switched to the user root.

All environment variables are cleared by default and created after the new login#
export VAR1="Hello World"
$ su - root -c 'echo $VAR1'
Password:

When we define the same variable and switching to root to run the same command the content of VAR1 is shown as the command su did not clear any environment variables as with the - option that simulates a new login.

All environment variables are passed to the new users and some are reinitialized#
export VAR1="Hello World"
$ su root -c 'echo $VAR1'
Password:
Hello World

Warning

Using the options -, -l, --login start the shell as a login shell with an environment similar to a real login:

  • clears all the environment variables except TERM and variables specified by --whitelist-environment

  • initializes the environment variables HOME, SHELL, USER, LOGNAME, and PATH

  • changes to the target user’s home directory

Passing environment variable from one user to another can introduce security risks as it can lead to data leakage or unexpected code execution based on set environment variables.

Using sudo#

With the command su one can switch to another user, but the password of the target must be known and also gives full access. Another command called sudo allows users to run commands as another user when this is defined with the option to do this passwordless or authenticating with their own password.

The command sudo works with both an allow and block list of commands that an user can execute as a certain user. With the command sudo -l an overview can be retrieved as is shown below.

Get an overview for the current user of all allowed and disallowed commands#
$ sudo -l
Matching Defaults entries for user01 on server01:
    !visiblepw, always_set_home, match_group_by_gid, always_query_group_plugin,
    env_reset, env_keep="COLORS DISPLAY HOSTNAME HISTSIZE KDEDIR LS_COLORS",
    env_keep+="MAIL QTDIR USERNAME LANG LC_ADDRESS LC_CTYPE",
    env_keep+="LC_COLLATE LC_IDENTIFICATION LC_MEASUREMENT LC_MESSAGES",
    env_keep+="LC_MONETARY LC_NAME LC_NUMERIC LC_PAPER LC_TELEPHONE",
    env_keep+="LC_TIME LC_ALL LANGUAGE LINGUAS _XKB_CHARSET XAUTHORITY",
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/var/lib/snapd/snap/bin

User user01 may run the following commands on server01:
    (ALL) ALL

One of the drawbacks of the command su is that the password of the target password must be known as has been showed in section Using su. The command su doesn’t require the target password when executed as the user root, and with sudo we can run this command without knowning the target password as is show in th example below.

Switch to the root account#
$ sudo su -
[sudo] password for user01:
#

This can also be used to run all kinds of commands like in the example below where the file /etc/sudoers can be read while only being readable by root.

Read file /etc/sudoers with command cat#
$ sudo cat /etc/sudoers
## Sudoers allows particular users to run various commands as
## the root user, without needing the root password.
##
...

By default the command sudo assumes that the target account will be root, but other accounts can be specified with the option -u as show in the following example where we view the content of a directory only accessable only by the user chrony or members of the group chrony.

Specify target user with option -u#
$ sudo -u chrony ls -la /var/log/chrony
total 0
drwxr-x---. 1 chrony chrony   0 Aug 31 15:54 .
drwxr-xr-x. 1 root   root   854 Jan 15 00:03 ..

Editing files with sudo isn’t advised for security reasons, but with the command sudoedit a copy of the file will be made to be safely modified and that will be used to later replace the original.

Edit the file /etc/hosts safely with command sudoedit#
$ sudoedit /etc/hosts

Warning

Allowing commands like vi and nano to be used via sudo opens the possibility read and/or change other files then originally defined, and to start new commands that aren’t defined by sudo as allowed command.

Monitoring the system#

Linux is a multi-user system and as a result different users can be logged on at the same time, but also the same user can be logged on multiple times. knowning the basics of the system can be useful to quickly see how busy the system is and maybe also how long the system is running.

Showing system basics#

Most Linux systems that are being used by multiple users it can be useful to quickly see how busy the system is and maybe also how long the system is running. With command uptime some basic information can be quickly retrieved as show in the example below. The example below shows that the system is running for 18 hours and 49 minutes, has one user logged on, and an average number of processes running in the last minute, and five and fiveteen minutes.

How long the system has been running#
$ uptime
 15:58:26 up 18:49,  1 user,  load average: 1.21, 1.66, 1.93

Linux is a multi-user system and as a result different users can be logged on at the same time, but also the same user can be logged on multiple times. With the command who an overview can be created to see which users are logged on, which terminal they’re using and when they started the session.

Show who is logged on#
$ who
user01   tty2         2023-01-14 21:09 (tty2)

The second command that can be used to see who is logged on to the system is w. This command also shows which command is being executed and the processor time consumpted.

Show who is logged on and what they are doing.#
$ w
USER     TTY        LOGIN@   IDLE   JCPU   PCPU WHAT
user01   tty2      Sat21   18:50m  0.04s  0.04s /usr/libexec/gnome-session-binary

Showing processes#

Linux is a multi-user and multi-process operating system allowing multi-users to run multi-processes at the same time, but who is running which processes? With the command ps a report of all current processes can be shown to see what is running on the system. The command ps comes in two flavors for its syntax and reporting as it both supports the standard syntax and BSD syntax. For now we will only focus on the standard syntax.

Show all processes#
$ ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 Mar07 ?        00:00:16 /usr/lib/systemd/systemd rhgb --switched-root --system --deserialize 36
root           2       0  0 Mar07 ?        00:00:00 [kthreadd]
root           3       2  0 Mar07 ?        00:00:00 [rcu_gp]
root           4       2  0 Mar07 ?        00:00:00 [rcu_par_gp]
root           5       2  0 Mar07 ?        00:00:00 [slub_flushwq]
root           6       2  0 Mar07 ?        00:00:00 [netns]
root           8       2  0 Mar07 ?        00:00:00 [kworker/0:0H-events_highpri]
...

Note

The command ps also supports the BSD syntax, but the output is different. The example below shows the same output as the example above, but in BSD syntax.

Show all processes BSD style#
$ ps aux

Within Linux processes can be organized in a tree structure where each process has a parent process and can have multiple child processes. With the command pstree a tree of processes can be shown to see how the processes are organized. The command pstree can be used to show the processes of a specific user or of a specific process. The example below shows the tree of processes starting with process identifier 1.

Display a tree of processes#
$ pstree 1
systemd─┬─ModemManager───3*[{ModemManager}]
        ├─NetworkManager───2*[{NetworkManager}]
        ├─abrt-dbus───2*[{abrt-dbus}]
        ├─3*[abrt-dump-journ]
        ├─abrtd───2*[{abrtd}]
        ├─accounts-daemon───3*[{accounts-daemon}]
        ├─alsactl
        ├─auditd───{auditd}
        ├─avahi-daemon───avahi-daemon
        ├─bluetoothd
        ├─chronyd
...

Most users will use the command ps to see which processes are running on the system and then use grep to filter out the correct lines. With the command pgrep the filtering can be done directly on the command line and the output can be used to further process the data.

Look up a processes based on name#
$ pgrep systemd
1
836
854
956
957
958
1034
1036
1754
27251
27282
27283

Note

The command pgrep can be used to find the process ID (PID) of a process based on its name, and the syntax can also be used for other commands like pkill and pidwait. The only exception is the option -l which is only supported by pgrep.

Process priority#

All processes in Linux have a priority which can be used to influence the scheduling of the process. The priority of a process can be set by the user or by the system. The priority of a process can be set between -20 and 19 where -20 is the highest priority and 19 is the lowest priority. The default priority of a process is 0. The priority of a process can be set with the command renice.

With the command ps the priority of a process can be shown if the option -l is used to use the long format output. The column PRI shows the priority of the process, but the column NI shows the priority of the process normalized to the range -20 to 19.

Show all processes in long format#
$ ps -efl
F S UID          PID    PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
4 S root           1       0  0  80   0 - 43062 -      Mar07 ?        00:00:16 /usr/lib/systemd/systemd rhgb --switched-root --system --deserialize 36
1 S root           2       0  0  80   0 -     0 -      Mar07 ?        00:00:00 [kthreadd]
1 I root           3       2  0  60 -20 -     0 -      Mar07 ?        00:00:00 [rcu_gp]
1 I root           4       2  0  60 -20 -     0 -      Mar07 ?        00:00:00 [rcu_par_gp]
1 I root           5       2  0  60 -20 -     0 -      Mar07 ?        00:00:00 [slub_flushwq]
1 I root           6       2  0  60 -20 -     0 -      Mar07 ?        00:00:00 [netns]
1 I root           8       2  0  60 -20 -     0 -      Mar07 ?        00:00:00 [kworker/0:0H-events_highpri]
...

By default the priority of a process is set to 0. The priority of a process can be changed with the command renice. The command renice can be used to change the priority of a process or to start a process with a different priority. The command renice can be used to change the priority of a process with the option -p and the process ID (PID) of the process. The command renice can be used to start a process with a different priority by specifying the command to run after the options.

Change the priority of a process#
$ renice -n 10 -p 1234

Using the command nice a process can be started with a different priority to already get less priority by the scheduler.

Run a process with a nicer priority (with a default nicess of 10)#
$ nice script.sh

By default a process has a niceness level of 0, but the command nice sets this to level 10 unless the option -n is used to specify a different niceness level.

Run a process with priority level 15#
$ nice -n 15 script.sh

While the process priority can be set to a value between -20 to 19, users are only allowed to select priorities from 0 to 19. The priorities -20 to -1 are reserved for the system. The command renice will fail if a user tries to set a priority level below 0 as shown in the example below.

Trying to set priority level to -1 for a process as a non-root user#
$ renice -n -1 7970
renice: failed to set priority for 7970 (process ID): Permission denied

The command renice can be used to change the priority of a set of processes. The command pgrep can be used to find the process IDs (PIDs) of a set of processes and the output of pgrep can be used as input for renice.

Combining commands to change the priority for a set of processes#
$ renice -n 15 $(pgrep <process name>)

Working with files withing the shell#

Linux is a files oriented operating system and understanding how to work with files is essential to work with Linux. This section will cover the basic commands to work with files and directories.

Basic commands for files and directories#

The command ls can be used to list the files and directories in a directory. The command ls can be used to list the files and directories in the current directory if no directory is specified. The command ls can be used to list the files and directories in a specific directory if the directory is specified as an argument to the command.

List the files and directories in the current directory#
$ ls
file1 file2 file3

The command touch can be used to create an empty file. The command touch can be used to create an empty file with the name specified as an argument to the command.

Create an empty file#
$ touch file1

The command cp can be used to copy a file. The command cp can be used to copy a file with the name specified as the first argument to the command to a file with the name specified as the second argument to the command.

Copy a file#
$ cp file1 file2

The command mv can be used to move a file. The command mv can be used to move a file with the name specified as the first argument to the command to a file with the name specified as the second argument to the command.

Move a file#
$ mv file1 file2

The command rm can be used to remove a file. The command rm can be used to remove a file with the name specified as an argument to the command.

Remove a file#
$ rm file1

The command mkdir can be used to create a directory. The command mkdir can be used to create a directory with the name specified as an argument to the command.

Create a directory#
$ mkdir dir1

The command rmdir can be used to remove a directory. The command rmdir can be used to remove a directory with the name specified as an argument to the command. The command rmdir can only be used to remove empty directories.

Remove a directory#
$ rmdir dir1

Archiving and compression#

The command tar can be used to create and extract archives. The command tar can be used to create archives with the option c and to extract archives with the option x. The option f can be used to specify the archive file to use. The option v can be used to show the files that are archived or extracted.

Achive three files in archive file archive.tar#
$ tar cf archive.tar file1 file2 file3

The command tar can also be used to create an archive for a directory by instead of specifying the files to archive, the directory to archive can be specified. The example below creates an archive for the directory /etc.

Archive all files and directories in directory /etc into archive /root/etcbackup.tar#
# tar cf /root/etcbackup.tar /etc

Archives can be tested with the option t to show the files that are in the archive. The option f can be used to specify the archive file to use.

Test if archive /root/etcbackup.tar can be read successfully.#
# tar tf /root/etcbackup.tar

Archives are by default not compressed, but with different options the archive can be compressed. The option z can be used to compress the archive with Gzip. The option j can be used to compress the archive with Bzip2. The option J can be used to compress the archive with xz.

Creating a compressed archive with tar and the option z for Gzip compression of the archive.

Use the option z for Gzip compression of the archive#
# tar czf /root/etcbackup.tar.gz /etc

Creating a compressed archive with tar and the option j for Bzip2 compression of the archive. The option to create a compressed archive can also be used to extract the compressed archive as shown in the example.

Extracting a compressed archive#
# mkdir /root/etcbackup
# cd /root/etcbackup
# tar xjf /root/etcbackup.tar.bz2

Transfering files with SCP#

Copying a file to a remote host with the command scp which stands for secure copy and will assume you want to login with the same username as currently logged in.

Copy file /etc/passwd to directory /tmp on server02#
$ scp /etc/passwd server02:/tmp

Copy a file to a remote host like in the previous example, but now with a specific user called root that will be used to login to server02.

Specify the remote username that must be used during login#
$ scp /etc/groups root@server02:/tmp

Copy a file from a remote host as root to the current directory:

Retrieve a file from a remove host#
$ scp root@server02:/tmp/groups .

With the -r flag the scp command will copy the contents of the directory:

Copy all files and directories under /etc locally to a remote host#
$ scp -r /etc user01@server02:~

Transfering files with SFTP#

The command sftp is a program for secure file transfer between hosts and works similar like the ftp command. In the example below a connection to the remote host localhost is established with the user user01. And directly the remote host can be browsed with the ls command.

Connect to a remote host with SFTP#
$ sftp user01@localhost
user01@localhost's password:
Connected to localhost.

sftp> ls
bin file.sh output

With the mkdir command a new directory can be created on the remote host and with the cd command the current directory can be changed.

Create and change directory in a remote host with SFTP#
sftp> mkdir test
sftp> cd test

With the put command a file can be uploaded to the remote host. The file will be uploaded to the current directory on the remote host.

Upload a file to a remote host with SFTP#
sftp> put /etc/hosts
Uploading /etc/hosts to /home/user/test/hosts
/etc/hosts 100% 510 0.5KB/s 00:00

Using rsync to copy files#

Rsync is a fast, reliable, efficient, secure, and portable file transfer. The example below will copy the contents of the directory /etc to the directory /srv/rsync/etc/:

Copy the directory of a directory on same hosts#
$ rsync -av /etc /srv/rsync

The example below will copy the contents of the directory /etc/ to the directory /srv/rsync/:

Copy the contents of a directory on same hosts#
$ rsync -av /etc/ /srv/rsync

Copy log files from server01 to /tmp on the current host:

Copy log files from a remote host#
$ rsync -av server01:/var/log /tmp

Remote login with SSH keys#

By default you will be asked for a password when you login to a remote host with the command ssh. This can be avoided by using SSH keys. The following example will generate a SSH key pair and copy the public key to the remote host.

Generate a SSH key pair#
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/user01/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/username/.ssh/id_rsa.
Your public key has been saved in /Users/username/.ssh/id_rsa.pub.
The key fingerprint is:
01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db [email protected]
The key's randomart image is:
+--[ RSA 2048]----+
|                 |
|                 |
|        . E +    |
|       . o = .   |
|      . S =   o  |
|       o.O . o   |
|       o .+ .    |
|      . o+..     |
|       .+=o      |
+-----------------+

Warning

The command ssh-keygen uses the default key name id_rsa. If you want to use a different name, you must specify it with the -f option.

The public key is stored in the file ~/.ssh/id_rsa.pub and the private key in the file ~/.ssh/id_rsa. The private key must be kept secret and should not be shared with anyone. The public key can be shared with anyone and must be added to the file ~/.ssh/authorized_keys on the remote host. In the example below the public key is copied to the remote host.

Copy the public key to a remote host#
$ scp ~/.ssh/id_rsa.pub user@remotehost:~/.ssh/myhost.pub

On the remote host the public key must be added to the file ~/.ssh/authorized_keys:

Login to the remote host and add the public key to the authorized keys#
$ cat myhost.pub >> .ssh/authorized_keys

With the command ssh-copy-id the public key can be copied to the remote host and added to the file ~/.ssh/authorized_keys automatically as is show in the example below:

Copy locally available keys to authorise logins on a remote machine#
$ ssh-copy-id [user@]remotehost

When connecting to the remote host we are automatically logged in without being asked for a password.