Many are used to Graphical User Interfaces (GUI) and these GUIs are in general very convenient. However, when manipulating text files and text in general, the Command Line Interface or CLI can be very powerful and effective as well. In this module we take a look at the command line interface.

A command line interface is sometimes called a shell. The term shell refers to the outer layer of the operating system that is exposed to the user and we’ve already discussed two types of shells, namely GUIs and CLIs. However, the name shell is not often used for GUIs but more for CLIs. Another often used name for the command line interface is “terminal”. A terminal in this context is a program in the GUI that shows the command line interface. In practice, the terms command line, shell and terminal are synonymous.

The command line uses a special program to interpret commands, the command line interpreter. There are many command line interpreters available such as the Bourne Shell (sh), the Bourne Again Shell (bash), csh, COMMAND.COM but we are going to use Bash, the Bourne Again Shell, one of the most widely used shells that is available on almost every computer system, even on Windows nowadays.

Installation

Mac OS

On Mac OS, Bash is already available; simply open the program “Terminal”.

Windows

For Windows, we open the “Control Panel” and choose “Programs” and then “Programs and Features”. Here we click on “Turn Windows features on or off” on the left side and turn on “Windows Subsystem for Linux”. This requires a reboot. After the reboot we install “Ubuntu 20.04 LTS” from the Microsoft Store and launch it.

During the initial launch, Ubuntu will install itself. It will ask for a default UNIX username that can be different from your Windows username. UNIX usernames are typically all in lower case without any signs, so just letters, examples are:

  • your first name: jamie
  • your last name: doe
  • the first name initial + your last name: jdoe
  • your nickname, for example jaromil

The system will then ask for your password and confirmation.

Most likely, Ubuntu will want you to upgrade. To do so, run the following command that gives you superuser powers (superuser do) and requires you to type your password:

sudo apt update

This command retrieves the new list of all available software packages that can be installed and upgraded. Then run the following command to actually upgrade:

sudo apt upgrade

Press Enter to confirm. After this has finished, we are in a shell with Bash.

Linux

Linux installations have almost always Bash installed and often as default shell. Depending on your system open a terminal.

Basic Commands

It is unclear whether we are actually are running Bash now. To make sure that we are running Bash we can simply execute the command bash. This starts the shell Bash from the shell you were using. Note that this might have been Bash all along or perhaps a different shell. After running bash you are certain that you are using Bash as the command line interpreter.

[jamie@computer ~]$ bash
[jamie@computer ~]$ 

The computer “prompts” the user to enter a command. The prompt shows between brackets the current user jamie, the hostname of the computer computer, and the current directory ~ which refers to the user’s home directory. The $ means that the user is prompted to give input as a normal user without elevated privileges. The root user is the account with elevated privileges, the administrator account. As user root, the prompt sign is typically #.

To verify that we are using Bash, we can run the program env. This command will show you text with a set of variable names and their values. One of those variables (called environment variables) will be SHELL with probably the value /bin/bash or perhaps /usr/bin/bash. If this is not the case, then you are not using Bash as the command line interpreter.

[jamie@computer ~]$ env
SHELL=/bin/bash
PWD=/home/jamie
LOGNAME=jamie
MOTD_SHOWN=pam
HOME=/home/jamie
LANG=en_US.UTF-8
TERM=xterm-256color
USER=jamie
XDG_SESSION_ID=14
LC_TIME=en_GB.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
MAIL=/var/spool/mail/jamie
_=/usr/bin/env
...
[jamie@computer ~]$ 

A very useful command is man with which you can show manuals of commands. For example you can do man bash to read documentation on Bash or even man man to read documenation on man itself.

BASH(1)                     General Commands Manual                    BASH(1)

NAME
       bash - GNU Bourne-Again SHell

SYNOPSIS
       bash [options] [command_string | file]

COPYRIGHT
       Bash is Copyright (C) 1989-2020 by the Free Software Foundation, Inc.

DESCRIPTION
       Bash  is  an  sh-compatible  command language interpreter that executes
       commands read from the standard input or from a file.  Bash also incor‐
       porates useful features from the Korn and C shells (ksh and csh).

       Bash  is  intended  to  be a conformant implementation of the Shell and
       Utilities portion  of  the  IEEE  POSIX  specification  (IEEE  Standard
       1003.1).  Bash can be configured to be POSIX-conformant by default.

OPTIONS
       All of the single-character shell options documented in the description
       of the set builtin command, including -o, can be used as  options  when
 Manual page bash(1) line 1 (press h for help or q to quit)

Another very useful command is pwd, show the current working directory. The shell is always in a directory, the working directory. Note that / is the root directory and directory paths are separated by /, so /home/jamie is the directory jamie in the directory /home and home is in directory / (pronounced as root).

[jamie@computer ~]$ pwd
/home/jamie
[jamie@computer ~]$ 

To list the files and directories in the current working directory, you can execute ls (for simply listing the files and directories), ls -l (listing more metadata about each file) and ls -al that also lists hidden files. Hidden files are prefixed with a dot, so .bashrc would be a hidden resource file for Bash.

[jamie@computer ~]$ ls -al
total 28
drwx------ 2 jamie jamie 4096 Jan 30 18:06 .
drwxr-xr-x 5 root  root  4096 Jan 30 12:28 ..
-rw------- 1 jamie jamie 3101 Jan 30 17:53 .bash_history
-rw-r--r-- 1 jamie jamie   21 Jan  8  2022 .bash_logout
-rw-r--r-- 1 jamie jamie   57 Jan  8  2022 .bash_profile
-rw-r--r-- 1 jamie jamie  141 Jan 30 12:30 .bashrc_bak
-rw-r--r-- 1 jamie jamie 3729 Feb  2  2022 .screenrc
[jamie@computer ~]$ 

To make a new directory, we can execute mkdir new-dir that creates a directory new-dir in the current working directory. To go there, we can change the current working directory with the command cd new-dir (the command cd with argument new-dir. In a sense you now went deeper in the directory tree. To go back up in the directory tree you can do cd .. where .. essentially means one level higher.

[jamie@computer ~]$ mkdir new-dir
[jamie@computer ~]$ cd new-dir/
[jamie@computer new-dir]$ cd ..
[jamie@computer ~]$ 

Note that in many cases, Bash can autocomplete commands or arguments for you. So, for example, removing new-dir we can type rmdir n<Tab> and if there is only one directory that starts with n, Bash will immediately autocomplete this. If there are more than one options, you can type an additional Tab, so rmdir n<Tab><Tab> and Bash will show you the list of options. You can type an additional character to reduce the options until there is only one option that Bash will then autocomlete.

Note that this works for commands as well, so most likely, you would be able to this:

rmd<Tab> n<Tab>

to execute

rmdir new-dir

Text processing

Command line tools typically print text: all the output of the command line tools we’ve seen so far is text. Typically the input to command line tools is text as well.

Since the env command may show many different environment variables but we’re only interested in the SHELL variable, we prefer to select from the output of env only the line with SHELL in it. Since the output of env is simply text, we can apply some text processing tools to achieve just this. The tool grep takes as input a so called regular expression and text and filters the text based on this regular expression. The name grep stands for [g]et [r]egular [e]x[p]ression. With the following command we “pipe” the text output of env to serve as input for the command grep where we use the regular expression “SHELL” to match any line with this string in it:

env | grep SHELL

You can now inspect only the line we are interested in:

[jamie@computer ~]$ env | grep SHELL
SHELL=/bin/bash
[jamie@computer ~]$ 

In the previous module we talked about text files and binary files. To verify that the output of the env is indeed only text we can take a look at the very bits of that output. We first store the output of the env command in a file by “piping” the output of the env command directly to the file output-env.txt with >:

env > output-env.txt
[jamie@computer ~]$ env > output-env.txt
[jamie@computer ~]$ 

If we execute ls we can see the file output-env.txt is now listed. We can look at the contents of the file as if it were executed by env with the command cat (from concatenate, it can concatenate multiple files into one, don’t forget autocompletion cat o<Tab>):

cat output-env.txt
[jamie@computer ~]$ ls
output-env.txt
[jamie@computer ~]$ cat output-env.txt 
SHELL=/bin/bash
PWD=/home/jamie
LOGNAME=jamie
MOTD_SHOWN=pam
HOME=/home/jamie
LANG=en_US.UTF-8
TERM=xterm-256color
USER=jamie
XDG_SESSION_ID=14
LC_TIME=en_GB.UTF-8
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/lib/jvm/default/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
MAIL=/var/spool/mail/jamie
_=/usr/bin/env
...
[jamie@computer ~]$ 

The program xxd takes as input a file and shows the hexadecimal representation of the bytes together with the character if the byte happens to represent a character. If the byte cannot be interpreted as a character, it is shown as a dot.

The output of xxd applied on the file is (xxd o<Tab>):

xxd output-env.txt

The output consists of three columns. At the left you can see the offset of the bytes in the file in hexadecimal notation. The middle column shows you the value of the bytes in hexadecimal. The right column shows you the character if the byte can be interpreted as such. Below you can see a possible output:

00000000: 5348 454c 4c3d 2f62 696e 2f62 6173 680a  SHELL=/bin/bash.
00000010: 5057 443d 2f68 6f6d 652f 6a61 6d69 650a  PWD=/home/jamie.
00000020: 4c4f 474e 414d 453d 6a61 6d69 650a 4d4f  LOGNAME=jamie.MO
00000030: 5444 5f53 484f 574e 3d70 616d 0a48 4f4d  TD_SHOWN=pam.HOM
00000040: 453d 2f68 6f6d 652f 6a61 6d69 650a 4c41  E=/home/jamie.LA
00000050: 4e47 3d65 6e5f 5553 2e55 5446 2d38 0a54  NG=en_US.UTF-8.T
00000060: 4552 4d3d 7874 6572 6d2d 3235 3663 6f6c  ERM=xterm-256col
00000070: 6f72 0a55 5345 523d 6a61 6d69 650a 5844  or.USER=jamie.XD
00000080: 475f 5345 5353 494f 4e5f 4944 3d31 340a  G_SESSION_ID=14.
00000090: 4c43 5f54 494d 453d 656e 5f47 422e 5554  LC_TIME=en_GB.UT

The first byte has value 0x53 and apparently this is the ASCII value for the character ‘S’. If we look up 0x53 ($5 * 16 + 3) = 83) in the ASCII Table, we can verify that this is indeed the value for character ‘S’. In the last position on the first line, we see the non-printable byte 0x0a (10). We can see in the ASCII Table that this byte represents a linefeed, or a newline in the ASCII encoding. This byte tells the program showing text to insert a new line for the following text.

We can contrast this text file with a binary file. The shell that you are running, Bash, is an executable, a program. We can view the contents of this program by means of xxd as well. In the above output we can see that my shell is the file /bin/bash. Since an executable is likely to be a large file we want to show only the first part of the output of xxd. To accomplish this we pipe the output of xxd as input to the command head that shows only the first ten lines:

xxd /bin/bash | head

The output is:

[jamie@computer ~]$ xxd /bin/bash | head
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000  .ELF............
00000010: 0300 3e00 0100 0000 f018 0200 0000 0000  ..>.............
00000020: 4000 0000 0000 0000 1073 0e00 0000 0000  @........s......
00000030: 0000 0000 4000 3800 0d00 4000 1a00 1900  ....@.8...@.....
00000040: 0600 0000 0400 0000 4000 0000 0000 0000  ........@.......
00000050: 4000 0000 0000 0000 4000 0000 0000 0000  @.......@.......
00000060: d802 0000 0000 0000 d802 0000 0000 0000  ................
00000070: 0800 0000 0000 0000 0300 0000 0400 0000  ................
00000080: 1803 0000 0000 0000 1803 0000 0000 0000  ................
00000090: 1803 0000 0000 0000 1c00 0000 0000 0000  ................
[jamie@computer ~]$ 

We can see that the second to fourth byte are the characters “ELF” (denoting that this is an ELF binary format) and some bytes can be interpreted as characters but this is likely a coincedence.

We could execute cat /bin/bash | head but this will likely give us gibberish and we rely on head encountering 10 coincedental newlines to stop the input. With reset we can reset the terminal to normal in case it broke.

Besides this text processing capabilities, a large advantage of text files is the fact that it is possible to conveniently show differences between text files. Let’s create two files that are very similar. We will use the command echo that simply echoes the text:

echo Is anyone there?
[jamie@computer ~]$ echo Is anyone there?
Is anyone there?
[jamie@computer ~]$ 

We can redirect this to a file appending each line with >> to the file:

echo Is anyone there? >> file1
echo Come here! >> file1
echo Come here! >> file1
[jamie@computer ~]$ echo Is anyone there? >> file1
[jamie@computer ~]$ echo Come here! >> file1
[jamie@computer ~]$ echo Come here! >> file1
[jamie@computer ~]$ 

We then copy the file to file2:

cp file1 file2
[jamie@computer ~]$ cp file1 file2
[jamie@computer ~]$ 

And add the following to file1:

echo This way, we must come together. >> file1
[jamie@computer ~]$ echo This way, we must come together. >> file1
[jamie@computer ~]$ 

And the following to file2:

echo We must come together! >> file2
[jamie@computer ~]$ echo We must come together! >> file2
[jamie@computer ~]$ 

We have now two files that are for a large part similar but different in the last line. We can show this difference with the utility diff:

diff --color file1 file2
[jamie@computer ~]$ diff --color file1 file2
4c4
< This way, we must come together.
---
> We must come together!
[jamie@computer ~]$ 

You can see that the lines that are similar are ignored and the lines that are different are highlighted. The ability to visualize differences in lines of text is a very powerful feature of text files. It is often used in programming to highlight the difference in source code from an old version to a new version.

Visualizing differences in binary files is not possible in the same manner. For example, typically, CAD files are not text based and to highlight differences between two versions of the same CAD file would need support from the CAD program to show the differences, whereas for text files we can use the standard diff utility to accomplish this.

To conclude this section, the fact that text files are easy to analyze and process with standardized tools such as diff and grep makes the command line and text files popular in programming. Because these features, source code is often written in text files and programmers use special programs called text editors to process files.

Text editors

Text editors are programs with powerful text processing features for text files. They are often used by programmers and there are many different versions with different capabilities. Two of the most popular and old text editors are Emacs and Vim (derived from the older, also still used version vi). Nowadays, Integrated Development Environments (IDEs) are also popular such as Visual Studio Code and Eclipse and these programs often provide many services for programming around the core of a text editor. Often these text editors are loosely based on the two “old” editors Vim and Emacs. Since Vim and Emacs are very powerful editors, the distinction between IDEs and text editor is sometimes not so clear.

For this primer, we will introduce the simple command-line text editor nano, a text editor that is simple, lightweight and almost always installed by default on Linux machines.

For example, we can open file1 from above in nano with:

nano file1
  GNU nano 7.2                         file1                                    
Is anyone there?
Come here!
Come here!
This way, we must come together.
















                                [ Read 4 lines ]
^G Help      ^O Write Out ^W Where Is  ^K Cut       ^T Execute   ^C Location
^X Exit      ^R Read File ^\ Replace   ^U Paste     ^J Justify   ^/ Go To Line

We can simply see the contents of the file and we see at the bottom commands. For example, we can close nano with the command ^X, which means Ctrl-x. We will use this tool in the next section that introduces us to Git.