Owen Gage

Terminals and Shells 1: Fundamentals

This is part of a series covering 'glue' knowledge. This is stuff that may be difficult to find in any normal training material. If you're a new developer or programmer you will hopefully find it useful. I try to explain more of the implementation side if it helps understanding.

This particular part of the series is about terminal and shell usage. I focus mostly on Linux based shells.

Fundamentals

Basic terminology

You use a terminal to access a shell. There are many shells (programs) such as Bash, Zsh, Fish. Typically they end with 'sh' for 'shell'. There are many terminals such as Windows Terminal, Command Prompt, iTerm, just 'Terminal'.

Terminals typically let you control the colours, font, font size, etc. Most shells have special characters/codes that tell a terminal to display certain colours. Usually these codes are for named colours like 'blue' or 'red', and the terminal determines the exact colours they will take on, like #FF4444.

Paths and directories

In a shell, you have a current working directory, (often abbreviated cwd). This is the directory that you are 'in'. Any relative paths are typically relative to the cwd.

Paths are strings of characters representing where something may be in a filesystem. Your home directory might have the path /users/bob. Slashes are used to separate the directory and file names that make up the path. /users is a directory, containing a bob directory. To make the path we join them with a slash.

Paths beginning with a slash / are absolute. This means that they unambiguously point somewhere in the filesystem. This is in contrast to relative paths, such as src/test.js. This path could point to any test.js in any src directory, you could have many of these on your own system.

You can't actually create or manipulate things with relative paths. They always need to be converted to absolute paths in order to determine the exact place to manipulate. This is normally done by the executable you're calling. To convert relative paths, you need an absolute path to 'join' it with. Often this will be your current working directory.

If you refer to src/test.js when your cwd is /home/users/my-project, most programs will understand that path to refer to /home/users/my-project/src/test.js.

There are some 'special' parts of paths. If we call the parts between slashes segments, then there are three special segments:

If you are in my-project/src, you might refer to the README with ../README.md. You'll often find it useful to use . when referring to files relative to the current directory, like executing a local script with eg ./my-script.sh.

Paths can have spaces and other whitespace in them. You have to take care to quote paths if you're uncertain if they have spaces. This is more important when writing scripts and programs and will be talked about more then. You can quote paths using double ("/users/Bob Jones") or single ('/users/Bob Jones'). Shells do treat these differently, which we will get to later.

ls and argument conventions

ls ('el-ess') lists the 'directory entries' for the current directory. 'Directory entries' are things like files and directories. Linux generally refers to all of these things as 'files'...

Everything is a file

-Things that are mostly true on Linux

But 'file' in common parlance refers to what Linux calls a 'regular' file. These contain data, sometimes human readable, sometimes not. Other types of files exist, like block devices, pipes, and sockets.

ls will show you what's in your current working directory. But you can also pass it an argument, like ls my-project and it will instead list the contents of the that directory (ls will recognise it is a relative path, and make it absolute using cwd). You can also pass absolute paths like ls / and ls /home/users.

Most executables like ls or rm take arguments. These are a series of strings typed after the command name itself. Each argument is separated by spaces:

ls one two three

ls here is being called with three arguments, one, two and three. Executables can treat these arguments however they want, and it varies a lot. There are many conventions regarding arguments.

If what you want to pass as an argument containing spaces, you need to quote them:

# (this is wrong)
ls /users/Bob Jones

ls will see this as two separate arguments due to the space, /users/Bob and Jones. This isn't likely what you wanted, and you need to do ls "/users/Bob Jones" to get it to print the contents of Bob's home directory.

You can pass other arguments with dashes like ls -l to list more detail about each entry. This -l is called a 'short option' because it's a single character after the dash. There are usually 'long option' forms for each short option. ls doesn't have this, but if you wanted to print the version of Python you could do the short python3 -V or long python3 --version. Long options often use a double-dash, but not always. They typically start with the same letter, but not always.

Short options can often be grouped together, but not always. ls -h -l will show extra detail for each entry (-l) but with human-readable file sizes (-h). This will show things like '3.1M' for 3.1 Megabytes, rather than eg '3250588'. These options can be grouped together like ls -lh. It knows they are two options, not a single long option, because of the single dash. The order typically doesn't matter eg ls -hl is the same.

How relative paths, short options, and long options are treated is up to the executable. ls and most other common commands follow these conventions. But not always!

man and help

At this point you should be frustrated about the 'but not always'. Thankfully, you can use the man command to learn about other commands! man ls will tell you all about ls. man cd will tell you all about cd... well actually it won't, you'll get something explaining 'built-ins'. Ignore this.

man does work for a lot of programs, though, so it's worth a shot. The pages that appear are called 'manpages', short for 'manual pages'. This is what they are: manuals. They're not written to explain the command, but to document. Searching online will generally give better introductions.

Commands without manpages will often have a 'help' option, like -h or --help or -help. Sometimes running the command with no arguments will show help. It's all a mess of inconsistency.

cd

You probably already know that you can change your current directory with cd. It's good to know you can do several directory levels at once: cd my-project/src/thing. You can also 'go back' to the last directory you cd'd into using a single dash: cd -

You can use absolute paths, you can use .. several times as well: cd ../../.. to go up three levels of directories.

Aside: cd isn't really an executable. It's a 'built-in' of the shell. Changing the current working directory isn't something an executable could do for you. This will be more clear later when we talk about processes.

Tab tab tab

Any time you're typing a command or a path... hit tab! Tab will generally autocomplete the current command/path if its not ambiguous. If it is ambiguous mashing tab a few times will tell you the things that it could be. You only need to type enough to make it 'biguous'.

Tab. Hit it.

Aside: Some terminals will cycle through options if it is ambiguous, rather than show all potential choices.

More useful commands

Lets cover some commands you should definitely be aware of.

pwd will print your cwd. You can think of it meaning 'print working directory', or 'present working directory'. Figuring out why its a 'p' is a rabbit hole of history.

cat will print out files you specify to the terminal. 'cat' is a abbreviation of 'concatenate'. This is because cat a.txt b.txt will output the two files one after the other, ie concatenating them. Outside of scripts and 'pipelines' its usually for just showing the contents of a file.

head and tail are two commands that also show the contents of a file, but only the first few lines for head, or the last few lines for tail. Useful if a file is massive. Takes lots of arguments to control how much they print.

mkdir will make a new directory. It takes one or more paths that specify where to make them, so mkdir one two three will make three directories in the current directory. You can do mkdir one/blah, but the one directory needs to already exist. Some shells support -p that will make parents as needed, so mkdir -p a/b/c/d/e/f/g would make all of the needed directories to get to making g.

rmdir is the opposite, it will remove a directory, if it is empty. If you expect what you're deleting is empty, it's good to use this to not accidentally delete a directory of stuff you did want.

rm will delete the files at the paths given to it. rm -r will recursively delete, meaning if you delete a directory, it will delete everything in it (by recursing into it). You often need to throw an -f on there, rm -rf. You can't undo this. Use with caution.

echo will take its arguments and print them out. The usefulness of this becomes apparent when you learn the rest of the shell features.

Useful shortcuts

Many terminals will have keyboard shortcuts to do certain things, but mostly they will forward keystrokes to the shell. The shell itself has a bunch of keyboard shortcuts.

Some of these shortcuts work on most shells, these work for Bash and Zsh which are likely the ones you're using. There are many more; these are the ones I find useful and can actually remember consistently.

Ctrl-a moves your cursor (where you're typing) to the start of the command you're typing.

Ctrl-e moves to the end of the command you're typing.

Ctrl-r lets you search through the commands in your command history. You can type any part of a previous command and find it. Pressing Ctrl-r again will jump further back in history to the next match. Ctrl-c will get you out of it.

Ctrl-c will interrupt the currently running command, normally causing it to exit and let you type commands again. More on this later.