Block I Illinois Library Illinois Open Publishing Network

Introducing the Unix Command Line

Dinesh Rathi, Henry Grob, Vandana Singh, and Martin Wolske

Jump to a section:
Most of the time, our daily work on computers is done through a graphical user interface (GUI) in which we do some typing of text in combination with the clicking of drop-down menus and option buttons. Those drop-downs and option buttons hide the wide range of text that is actually executed through those mostly simpler clicks we make.But sometimes we need greater control than is provided through those menus and buttons, which have been shaped in part to simplify at the cost of reducing flexibility. And sometimes we need to just quickly hop into a computer to do something and then pop right back out. In these cases, and especially when done remotely from another computer, the terminal interface is a much faster alternative. For this reason, Unix and Unix-based Operating Systems like MacOS and Linux have always included at their core a Command Line Interface (CLI) such as a terminal window. While for most of its existence Microsoft pushed people to ignore their command prompt alternative and to instead strictly work within the graphical user interface, beginning with Windows 7, Microsoft has included their PowerShell command-line shell and scripting language.

Listing files and directories

In Linux and Unix Operating Systems, commands start with the command itself, for instance:

ls

If you type in the ls command, you’ll get a list of files, folders, and directories.

In the Unix command line terminal, the command ls returns a list of files and directories at the ~ location.

Many commands also have options, switches, or flags. For instance, the ls command has many options you can use, including the -a to list all files, including those that start with a period to keep them a little more hidden.

In the Unix command line terminal, the command ls -a returns a list of files and directories at the ~ location, including hidden files which begin with a period.

As another example, if we add not just a -a but also a -l to an ls, we can have all files listed in a long format using option grouping.

In the Unix command line terminal, the command ls -la returns a list of files and directories at the ~ location, including hidden files which begin with a period, in a detailed format which provides permissions, file sizes, and modification history.

While ls is the basic command, and ls -la is the command with options, we can also add in arguments at the end, for instance, to get a listing of files, folders, and directories within a specific directory. As an example:

ls -la /

This command would give us the list of files, folders, and directories within the very top of the tree structure.

In the Unix command line terminal, the command "ls -la /" returns a list of files and directories at the top of the Linux tree structure. This structure includes the following directories: /bin, /boot, /dev, /etc, /home, /lib, /man, /media, /mnt, /opt, /proc, /root, /run, /sbin, /srv, /sys, /usr, and /var.

To create an empty or blank file, use the touch command, followed by the name and extension of the new file:

touch mynewfile.txt

To create a new directory, use the mkdir command, followed by the name of the new directory you would like to create:

mkdir cstories

Copying Files

To move a file from one place to another, use the mv command. This has the effect of moving rather than copying the file, so you end up with only one file rather than two. It can also be used to rename a file by moving the file to the same directory but giving it a different name.

mv file1 file2

This command moves (or renames) file1 to file2.

Remove Files and Directories

To delete (remove) a file, use the rm command. As an example, we are going to create a file and then delete it. Type the following:

$ touch tempfile.txt
$ ls
$ rm tempfile.txt
$ ls 

The ls command is used first to check if it has created the file and second to check if it has deleted the file.

You can use the rmdir command to remove a directory (make sure it is empty first). Note that Unix will not let you remove a non-empty directory.

Create a directory called tempstuff using mkdir, then remove it using the rmdir command.

Searching the contents of a file

Using less, you can search though a text file for a keyword (pattern). For example, to search through a file called science.txt for the word science, type:

$ less science.txt

Then, while still running less (i.e., don’t press [q] to quit), type a forward slash [/] followed by the word to search:

/science

As you can see, less finds and highlights the keyword. Type [n] to search for the next occurrence of the word.

Another powerful search tool is grep, one of many standard Unix utilities. It searches files for specified words or patterns. First clear the screen, then type:

$ grep science science.txt

As you can see, grep has printed out each line containing the word science. Or has it? Try typing:

$ grep Science science.txt

The grep command is case sensitive; it distinguishes between Science and science.

To ignore upper/lower case distinctions, use the -i flag:

$ grep -i science science.txt

To search for a phrase or pattern, you must enclose it in single quotes (the apostrophe symbol). For example, to search for spinning top, type:

$ grep -i 'spinning top' science.txt

Some of the other options of grep are:

  • -v display those lines that do NOT match.
  • -n precede each matching line with the line number.
  • -c print only the total count of matched lines.

Try some of them and see the different results. Don’t forget, you can use more than one option at a time, for example, the command to find the number of lines without the words science or Science is:

$ grep -ivc science science.txt

Redirection

Most processes initiated by Unix commands write to the standard output (that is, they write to the terminal screen). Many take their input from the standard input (that is, they read it from the keyboard). There is also the standard error, where processes write their error messages, by default, to the terminal screen. We have already seen one use of the cat command to write the contents of a file to the screen. Now type cat without specifing a file to read:

$ cat

Then type a few words on the keyboard and press the [Return] key. Finally hold the [Ctrl] key down and press [d] (written as ^D for short) to end the input. What has happened?

If you run the cat command without specifing a file to read, it reads the standard input (the keyboard), and on receiving the’end of file’ (^D), copies it to the standard output (the screen). In Unix, we can redirect both the input and the output of commands.

Redirecting the Output

We use the > symbol to redirect the output of a command. For example, to create a file called list1, containing a list of fruit, type:

$ cat > list1

Then type in the names of some fruit. Press [Return] after each one.

pear
banana
apple
^D (Control D to stop)

What happens is the cat command reads the standard input (the keyboard) and the > redirects the output, which normally goes to the screen, into a file called list1.

To read the contents of the file, type:

$ cat list1

Using the above method, create another file called list2.

Add the following fruit to the file: orange, plum, mango, grapefruit.

The form >> appends additional output to a file. So to add more items to the file list1, type:

$ cat >> list1

Then type in the names of more fruit:

peach
grape
orange
^D (Control D to stop)

To read the contents of the file, type:

$ cat list1

You should now have two files. One contains six fruit, the other contains four fruit. We will now use the cat command to join (concatenate) list1 and list2 into a new file called biglist. Type:

$ cat list1 list2 > biglist

What this is doing is reading the contents of list1 and list2 in turn, then outputting the text to the file biglist.

To read the contents of the new file, type:

$ cat biglist

Redirecting the Input

We use the < symbol to redirect the input of a command. The command sort alphabetically or numerically sorts a list. Type:

$ sort

Then type in the names of some vegetables. Press [Return] after each one.

carrot
beetroot
artichoke
^D (control d to stop)

The output will be:
artichoke
beetroot
carrot

Using < you can redirect the input to come from a file rather than the keyboard. For example, to sort the list of fruit, type:

$ sort < biglist

And the sorted list will be output to the screen.

To output the sorted list to a file, type:

$ sort < biglist > slist

Use cat to read the contents of the new sorted file.

Pipes

To see who is on the system with you, type:

$ who

One method to get a sorted list of names is to type:

$ who > names.txt
$ sort < names.txt

This is a bit slow and you have to remember to remove the temporary intermediary file called names when you have finished. What you really want to do is connect the output of the who command directly to the input of the sort command. This is exactly what pipes do. The symbol for a pipe is the vertical bar |.

For example, type in:

$ who | sort

This will give the same result as above, but quicker and cleaner. To find out how many users are logged on, type:

$ who | wc -l

Wildcards

The character * is called a wildcard, and will match against none or more character(s) in a file (or directory) name. For example, in your current directory, type:

$ ls list*

This will list all files in the current directory starting with list. Try typing:

$ ls *list

This will list all files in the current directory ending with list.

The character ? will match exactly one character. So the command ls ?ouse will match files like ‘house’ and ‘mouse’. But it will not match ‘grouse’.

Try typing:

$ ls ?list

Filename conventions

A directory is merely a special type of file. So the rules and conventions for naming files apply also to directories. In naming files, characters with special meanings such as these should be avoided:

/ * & %

Also, avoid using spaces within names. The safest way to name a file is to use only alphanumeric characters, that is, letters and numbers, together with _ (underscore) and . (dot).

File names conventionally start with a lower-case letter and may end with a dot followed by the extension, a group of letters indicating the contents of the file. For example, all files consisting of C code may be named with the ending .c, for example, program1.c. Then, in order to list all files containing C code in your home directory, you need only type ls *.c in that directory.

Beware: some applications give the same name to all the output files they generate. For example, some compilers, unless given the appropriate option, produce compiled files named a.out. Should you forget to use that option, you are advised to rename the compiled file immediately, otherwise the next such file will overwrite it and the original file will be lost.

Getting Help

There are online manuals which give information about most commands. The manual pages tell you which options a particular command can take, and how each option modifies the behavior of the command. Type the man command to read the manual page for a particular command.

For example, to find out more about the wc (word count) command, type:

$ man wc

Alternatively, the following command gives a one-line description of the command, but omits any information about options.

$ whatis wc

When you are not sure of the exact name of a command, the apropos command will give you the commands with keyword in their manual page header.

$ apropos keyword

For example, try typing:

$ apropos copy

File System Security

In your current directory, type:

$ ls -l

The letter L stands for long listing. You will see that you now get lots of details about the contents of your directory, similar to the example below.

Also, the command ls -lg gives additional information as to which group owns the file (beng95 in this example):

-rwxrw-r-- 1 ee51ab beng95 2450 Sept29 11:52 file1

Each file (and directory) has associated access rights. In the left-hand column is a 10 symbol string consisting of the symbols d, r, w, x, -, and occasionally s or S.

The first character in the line of output specifies the type: A d indicates a directory. Otherwise, the character will be a dash (-). The 9 remaining symbols indicate the permissions, or access rights, and are taken as three groups of 3.

  • The first character, -, notes that this is a file.
  • Next, the first group of three characters gives the file permissions for the user that owns the file (or directory). In the above example, rwx are the permissions for the user ee51ab.
  • The middle group of three characters gives the permissions for the group of users to whom the file (or directory) belongs. In the above example, rw- are the permissions for the group eebeng95.
  • The rightmost group of three characters gives the permissions for all others. In the above example, r-- are the permissions for any user who does not own the file or is not in the group to whom the file belongs.

The symbols r, w, and – have slightly different meanings depending on whether they refer to a simple file or to a directory.

Access rights on files are:

  • r (or -), indicates read permission (or otherwise), that is, the presence or absence of permission to read and copy the file.
  • w (or -), indicates write permission (or otherwise), that is, the permission (or otherwise) to change a file.
  • x (or -), indicates execution permission (or otherwise), that is, the permission to execute a file, where appropriate.

Access rights on directories are:

  • r allows users to list files in the directory.
  • w means that users may delete files from the directory or move files into it.
  • x means the right to access files in the directory. This implies that you may read files in the directory provided you have read permission on the individual files. So, in order to read a file, you must have execute permission on the directory containing that file, and hence on any directory containing that directory as a subdirectory, and so on, up the tree.

Some examples:

A file that everyone can read, write, and execute (and delete):

-rwxrwxrwx

A file that only the owner can read and write. No one else can read or write and no one has execution rights (e.g., your mailbox file):

-rw-------

Changing access rights

To “change a file mode,” meaning to change the access rights of a file, use chmod.

Only the owner of a file can use chmod to change the permissions of a file. The options of chmod are as follows:

Symbol Meaning
u User
g Group
o Other
a All
r Read
w Write (and delete)
x Execute (and access directory)
+ Add permission
Take away permission

For example, to remove read, write, and execute permissions on the file biglist for the group and others, type:

$ chmod go-rwx biglist

This will leave the other permissions unaffected. To give read and write permissions on the file biglist to all, run:

$ chmod a+rw biglist

Processes and Jobs

A process is an executing program identified by a unique PID (process identifier). To see information about your processes, with their associated PID and status, type:

$ ps

A process may be in the foreground, in the background, or be suspended. In general, the shell does not return the Unix prompt until the current process has finished executing. Some processes take a long time to run and hold up the terminal. Backgrounding a long process has the effect that the Unix prompt is returned immediately, and other tasks can be carried out while the original process continues executing.

For example, the command sleep waits a given number of seconds before continuing. To wait 10 seconds, type:

$ sleep 10

After 10 seconds, it will return to the command prompt. Until the command prompt is returned, you can do nothing except wait. To background a process, type an ampersand (&) at the end of the command line. To run sleep in the background, type:

$ sleep 10 &

The & runs the job in the background and returns the prompt straight away, allowing you to run other programs while waiting for that one to finish. The first line in the above example is typed in by the user; the next line, indicating job number and PID, is returned by the machine. The user is notified of a job number (numbered from 1) enclosed in square brackets, together with a PID, and is notified when a background process is finished. Backgrounding is useful for jobs which will take a long time to complete.

To background a current foreground process, type at the prompt:

$ sleep 100

You can suspend the process running in the foreground by holding down the [control] key and typing [z] (written as ^Z ) Then to put it in the background, type:

$ bg

Note: do not background programs that require user interaction; e.g., pine.

Listing suspended and background processes

When a process is running, backgrounded or suspended, it will be entered onto a list along with a job number. To examine this list, type:

$ jobs

An example of a job list could be:

  • [1] Suspended sleep 100
  • [2] Running netscape
  • [3] Running nedit

To restart (foreground) a suspended process, type:

$ fg %jobnumber

For example, to restart sleep 100, type:

$ fg %1

Typing fg with no job number foregrounds the last suspended process.

Killing a process

It is sometimes necessary to kill (terminate or signal) a process, for example, when an executing program is in an infinite loop. To kill a job running in the foreground, type ^C (control c). For example, run:

$ sleep 100
^C

To kill a suspended or background process, type:

$ kill %jobnumber

For example, run the following, and suppose that by running jobs, you find that sleep is job number 4:

$ sleep 100 &
$ jobs
$ kill %4

To check whether this has worked, examine the job list again to see if the process has been removed.
Alternatively, processes can be killed by finding their process numbers (PIDs) and using kill PID_number. The command ps stands for process status.

$ sleep 100 &
$ ps
PID TT S TIME COMMAND
20077 pts/5 S 0:05 sleep 100
21563 pts/5 T 0:00 netscape
21873 pts/5 S 0:25 nedit

To kill off the process sleep 100, type the following command. Then type ps again to see if it has been removed from the list.

$ kill 20077

If a process refuses to be killed, uses the -9 option:

$ kill -9 20077

Note: It is not possible to kill off other users’ processes.

A Starting List of Common Terminal Commands

|

  • The pipe key | on the keyboard is used to take the output of one command and send it to another command. For instance, to view through the history of commands run, screen by screen, you would combine the history command and the less command as follows:
    history | less

apt-get

  • The Advanced Package Tool has been developed by the Debian GNU/Linux branch of Linux, and is incorporated into all child Linux operating systems, like Ubuntu and Raspberry Pi OS, that use the Debian core.
    • Synchronize the list of packages available by typing:
      sudo apt-get update
    • Upgrade all the software packages currently installed by typing:
      sudo apt-get upgrade
    • Install new applications, for instance the Apache web server, version two, by adding the following to apt-get:
      sudo apt-get install apache2

cat

  • The cat command, short for concatenate, lists the content of a text file. In the case of very long file contents, the less command works better.
    cat science.txt

cd

  • The cd, or change directory, command helps us to move around the file hierarchy tree structure of Linux.
/ the root of the tree, containing all other main directories
/bin the essential binary executable files of the system
/boot the startup files for Linux
/dev the entry point for peripherals
/etc the commands and files necessary for system administration
/etc/vnc as one example, the subdirectory for commands and files necessary to administer the VNC server
/home personal user directories
/home/pi as one example, the subdirectory for the base user “pi”
/lib shared libraries essential for the system during startup
/mnt mount points for temporary partitions like a USB hard drive
/opt packages for some supplementary applications like minecraft-pi, pigpio, sonic-pi, and Wolfram
/root directory of the root administrator
/sbin essential high-level binary executable files
/tmp temporary files
/usr secondary hierarchy
/usr/bin majority of binary executable files and user commands
/usr/include header files for the C and C++ programming language
/usr/lib shared libraries for the system
/usr/local data pertaining to the programs installed on the local machine by the root user
/usr/sbin the binary executable files that are not essential for the system but that are reserved for the system administrator
/usr/share reserved for non-dependent data of the architecture
/usr/src code source files
/var variable data

clear

  • The clear command is helpful when your terminal has become cluttered with lots of output from commands and you want to start with a clean slate. This will clear all text and leave you with the $ prompt at the top of the window.

df -h

  • Use this to find out the size of your available disk spaces, how much is currently being used, and how much is available. Many are tmp, or temporary spaces, while others are formally defined /dev devices.

dig

  • Dig, the Domain Information Groper, is a tool used to search through Domain Name System (DNS) servers for various DNS records. As such, it can be very useful for troubleshooting public IP name and IP address problems. (NOTE: IP addresses in the ranges 10.0.0.0 to 10.255.255.255, 172.16.0.0 to 172.31.255.255, and 192.168.0.0 to 192.168.255.255 are all private. That is, they do not need to be registered to be used on local networks, and therefore do not appear in the DNS system of servers.)
  • To find the public IP address for the IP name google.com:
dig google.com
  • To find the public IP address for the IP name ischool.illinois.edu:
dig ischool.illinois.edu
  • To find the IP name for the public IP address 216.58.192.174:
dig -x 216.58.192.174
  • To find the IP name for the public IP address 192.17.172.3:
dig -x 192.17.172.3

find

  • The find command is used to find files using their filename.

grep

  • The grep, or global regular expression print, command is a powerful tool to search for a given string in a file, or in a range of files within a directory, or even a directory and subdirectories. For fun, try running this command in the pi home directory:
grep -r "the" *

head

  • The head command writes the first ten lines of a file to the screen. First clear the screen then type:
head science.txt

history

  • The history command will list the history of commands you’ve written, at least back a certain amount of time or a certain number of commands run. A sweet little trick is to use the ! character followed by the number of a command to repeat that command, for instance, the 137th command you ran:
!137

hostname -I

  • Shows the IP address(es) of your Raspberry Pi

ifconfig

  • Used to check the status of wired Ethernet (eth0) and wireless Ethernet (wlan0) Internet connections.
    • inet = the assigned Internet Protocol version 4 (IPv4) address, if any
    • inet6 = the assigned Internet Protocol version 6 (IPV6) address, if any
    • ether = the MAC address, roughly equivalent to a serial number for that specific network adapter

iwconfig

  • Indicates which WiFi network the wireless adapter is using at the moment.

iwlist

  • Used to get more detailed wireless information than is provided from iwconfig about a wireless adapter interface. The most commonly used argument with iwlist is scan, giving a list of Access Points and Ad-Hoc cells in range at the moment.
iwlist wlan0 scan
  • To narrow down the information provided, it can be useful to combine this with a grep command to search just for the ESSID, or Extended Service Set Identification identifying a specific 802.11 wireless network:
iwlist wlan0 scan | grep ESSID

kill

  • Terminate the process based on its Process ID (PID) number. By default, the kill command sends a request to the PID specified, officially known as a SIGTERM, asking for it to shut itself down:
kill 12345
  • To forcefully quit a PID specified, officially known as a SIGKILL, you need to add in an additional kill statement.
kill -s KILL 12345
  • You can choose to kill several PIDs all at once, also.
kill 12345 12346 543 139
  • To kill all PIDs with the same name, for instance, all PIDs running apache2 sessions, you run the killall command instead.
killall apache2

less

  • The less command is considered to be a more powerful version of the older more command. That’s right, the creators thought less is more! Instead of using the cat command to list a file, you can use the less command. It opens the file for viewing. From there, you can use the space bar to jump ahead one screen, the b character to scroll back one screen, the down and up arrows to go down and up one line, and many other tags to do a range of other movements. Hit [Q] to quit out of the file. Less is also used instead of cat for reading long files.

ls

  • The ls, or list, command is the most common way to get a list of files. This is often combined with the options -la and then an argument to search a specific directory.

lsusb

  • Lists the USB hardware currently connected to your Raspberry Pi.

man

  • The man, or manual, command is used to show the basic operating manual for an available command, including why to use it, the options available in its use, defaults, some examples, and the authors of the command.

mkdir

  • The mkdir, or make directory, command allows you to create a new directory. As an example, let’s use the cd command to make sure we’re in our home directory (the easiest way to do that is to use the ~ argument), then use the mkdir command to make a Python subdirectory.
cd ~ 
mkdir Python

mv

  • the mv, or move, command allows users to move a file to another directory, with the first argument indicating which file is to be moved, and the second where it is to be moved. For instance, if you create a new Python command in the pi home directory and later want to move it to a directory called Python in the pi home directory:
mv /home/pi/somepythoncode.py /home/pi/Python

nano

  • A commonly used default text editor. For instance, to edit a file you found using ls that is called example.txt, type:
nano example.txt

ping

  • A core command for testing the connection between two devices on a network, whether local or over a wider network (e.g., campus network, Internet). This can be done using an Internet Protocol address, or an IP address number. For instance, both of the following check the connection between your device and the same other device:
ping illinois.edu
ping 192.17.172.3

ps

  • The ps (processes status) command is one of the earliest native Unix/Linux utilities for viewing information about running processes, the operations on computer data within programs being currently run.
  • Print all processes currently running by all users, listed by user, extended to include TTY commands and also internal commands:
ps aux
  • Print all processes currently running in wide, or double wide format:

ps auxw
ps auxww

raspi-config

  • The core configuration command for the Raspberry Pi OS as run from the terminal interface. This can also be run from the graphical user interface.

reboot

  • Use this command to do an immediate reboot of your computer.

rm

  • The rm, or remove, command only removes files. However, the -r option will recursively remove a directory, any subdirectories, and any files within those directories. To go even further, you can add in the -f option to forcefully remove items. Use this command with great care:
rm -rf somedirectory
  • And do not, under any circumstances, ever do this command, as you’ll put an end to the operating system and all files within:
rm -rf /

rmdir

  • The rmdir, or remove directory, allows you to remove a directory and files held within that directory.

scp

  • Short for “secure copy.” Uses the secure shell, or SSH, to copy files to and from a remote device. For instance, to copy the example.py Python file from your laptop to the user pi’s home directory on the Raspberry Pi, type in the following (example uses an IP address in the IllinoisNet_Guest local area network domain):
scp example.py pi@172.16.108.44:/home/pi
  • To copy the file anotherexample.py Python file from the user pi’s home directory on the Raspberry Pi to your laptop, type in:
scp pi@172.16.108.44:/home/pi/anotherexample.py

screen

  • The screen command detaches a terminal window, creating a separate window within which you can access and control the Raspberry Pi.

shutdown

  • Use this command to shut down your Raspberry Pi computer.
    • Shutdown immediately by typing “sudo shutdown -h now”
    • Schedule a shutdown time, for instance 11:30 PM, by typing “sudo shutdown -h 23:30”

ssh

  • The secure shell, or SSH, provides remote login access to another computer. On MacOS laptops, to access your Raspberry Pi open the terminal and type the following (example uses an IP address in the IllinoisNet_Guest local area network domain). Before proceeding, you’ll need to have written down the WiFi IP address of your Raspberry Pi, or determined it via the console using the USB to TTL cable.
  • On Windows, you can use PowerShell to SSH. You can also use PuTTY, the free and open source software which supports several network protocols for connecting two devices. Download PuTTY.

startx

  • When a Raspberry Pi or other Linux computer boots straight to a console window, you can prompt it to further boot to reach the GUI by typing:
sudo startx

sudo

  • Placing sudo in front of any of the above commands asserts increased authority if the user logged in has been granted such in the /etc/sudoers configuration file. Working in your home directory generally isn’t a problem. But often you’ll find running higher order control commands and editing of files in system-wide directories will require the running commands like ls, rm, mkdir and others with a leading sudo in front of it to exercise your advanced powers of control.

tail

  • The tail command writes the last ten lines of a file to the screen. Clear the screen and type:
tail science.txt

touch

  • The touch command allows you to create an empty file. This can be useful if you want to then fill the file with notes, output data from a command, or a range of other miscellany. You can specify a file extension (such as .txt or .py) or name the file without an extension.

traceroute

  • A core command to test the connection between two devices on a network, testing each intermediate connection between the two. For example:
traceroute illinois.edu

UP key

  • This is not a command typed into the terminal, but rather a key on the keyboard you can hit to list previous commands you’ve entered. Much simpler than fully typing in a command again to rerun it.

write

  • Open a chat session with another user connected via terminal, console, or other TTY (officially, teletype) connection. For instance, if a user named “visitor” has logged into the Raspberry Pi using SSH, you could say hello to them by typing the following:
write visitor
Hello, visitor. How can I help?
  • This chat could then be continued back and forth until you end the write session by holding down the CTRL key and hitting the letter C on your keyboard. This is known as CTRL-C, stated as “control c.”

License

Icon for the Creative Commons Attribution-ShareAlike 4.0 International License

A Person-Centered Guide to Demystifying Technology by Dinesh Rathi, Henry Grob, Vandana Singh, and Martin Wolske is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License, except where otherwise noted.

Share This Book