If you’re an experienced developer who knows your system inside out, this guide is not for you. However, if you’ve only ever used desktop tools, or you’re just starting out as a developer, you may not have needed to work at what is variously called “the command line”, “a console,” or “a terminal”. If these terms are unfamiliar to you, or they kind of ring a bell but you’re not sure what they are, read on!
Back in the dim and distant past, computers were controlled with typed commands. Then we evolved the Graphical User Interface (GUI) and typing was superseded by pointing and clicking. That’s how most folks work with computers today, though often they use a finger on a touchscreen rather than a mouse on a desk.
Computers can still be controlled by entering commands on the keyboard and, as you’ll have guessed, this takes place at the command line. Windows, macOS and Linux all have facilities for presenting you with a command line. Typically these applications are called consoles or terminals.
On macOS, for example, the app is literally Terminal. You’ll find it in Applications > Utilities. Different versions of Linux offer different terminal apps, but they’ll be called something like Terminal and be accessible from the desktop. Windows 10 has Windows Terminal; older versions have a Command Prompt entry in the Start menu which calls up their own terminal app. Microsoft also has Windows Subsytem for Linux, which implements a Linux command line in Windows 10.
A terminal is just a presentation mechanism; the software doing the real work is called a “shell”. That’s why you might see PowerShell in your terminal window on a Windows PC. The shell is the real interface between you and the computer; the terminal just displays the shell’s output in a window and sends your key presses to the shell.
If you’ve used a Linux computer that has been configured not to start up a desktop screen, you’ve been using the command line all along and communicated directly with a shell. Terminal apps are generally only used from a GUI’s desktop.
Seasoned developers have favorite shells, but at the start it’s best to make use of the one your operating system provides. Once you’ve got the gist of how it works, and have begun making use of it, you can start to explore the alternatives.
However you invoke a shell, you’ll see what’s called a “prompt”, the shell’s cue to you to type in a command. When the command has completed, you’ll see the prompt appear again: the shell is telling you that it’s ready for its next task.
There are many different shells so you may not alway see the same prompt when you use multiple machines. The most common Linux shell is called Bash; its standard prompt is the
$ symbol. macOS recently began using the Z Shell, which uses
% as its prompt. On Windows, the prompt is usually a
>. Whatever the symbol, and whatever information is displayed alongside it — often the current directory but you might also see the drive letter, the name of the logged in user, or the date — the prompt tells you the shell is waiting for input.
Current directory? This is another common term read in discussions concerning the command line. You might also see it called the “working directory”. “Directory” is just shell-speak for folder, “working” the one you’re currently viewing in the terminal.
The sequence of directories in which a file or a directory is located is called its “path”. Here’s a macOS example:
This is an “absolute” path: it identifies precisely where the file
imageprep.sh is to be found. Linux and macOS use the
/symbol, as shown above, to separate files and directories in the path, but Windows uses
\ so just replace one with the other in this and the following examples if you’re using Windows Terminal or PowerShell.
Depending on your shell, you may be able to use “relative” paths — paths that depend on where you are starting from. These use the following markers:
.— the current directory.
..— a parent directory.
So if we’re working in the directory
/Users/Twilio/Documents/websource/ then we can access the script — the
.sh file — mentioned above using the relative path:
This means ‘go up to the parent directory’ (ie.
Documents/), ‘go up to the next parent directory’ (
Twilio/), then ‘go down to
If you’re wondering what the
/ right at the start of the first path is, it’s special: it’s the Linux and macOS “root” directory, ie. the top-most directory from which all others branch.
You may see a
~ (called a tilde) in a path. It’s one of the Unix shells’ shorthand codes: it means the current user’s home directory and you can use it anywhere you might otherwise write your home directory’s path out in full. It’s also a good way of referring to home directories in scripts that will be used by different folks: they all have different home directories, but
~ will be read by the shell as the right one every time.
To run the script
imageprep.sh, you just provide its name at the prompt. If it’s in a different directory from the one in which you’re working, provide an absolute or relative path. If it’s in the working directory, you can omit the path. However, you will need to tell the shell that the file is here: prefix it with
./ to indicate the current directory:
It’s outside of the scope of this guide, but it’s useful to know that Linux and macOS shells have a variable called
$PATH which tells them where to look for files: it contains a list of directory paths. If you type just
imageprep.sh, the shell will only look for the file in the directories included in
$PATH. That’s why we need the
./ above —
/Users/Twilio/GitHub/scripts is not listed in
When referencing shell variables (though not when setting them), always prefix their names with the
If you open up a terminal now, when the prompt appears enter one of the following commands:
These commands tell the shell to list the files and sub-directories within the current directory, also known as the working directory. How can you see what that is? With another command of course:
To create a new directory, use the following command:
If the value you put in place of
<directory_path> is just the new directory’s name, it will be created in the working directory.
To move or “change” to another directory, use the following commands:
There are literally dozens of commands you can enter, far too many to list here. There are lots of online resources that list them — just use your favorite search engine.
Most commands are more than a simple word — you can provide extra information in the form of “arguments” placed after the command and separated by at least one space. Arguments might include target filenames or the location of the directory where the command’s results will be placed.
The need to separate arguments with spaces means you need to take care with file and directory names that contain spaces. Telling a shell to treat included spaces as part of an argument and not a separator is called “quoting”. For example, to stop the second space in the command
cat /Users/Twilio/my file.txt
from causing errors (specifically
cat: /Users/Twilio/my: no such file or directory and
cat: file.txt: no such file or directory), you wrap the argument in quotes:
cat "/Users/Twilio/my file.txt"
This correctly display the contents of the named file.
Other arguments govern how the command works: they’re called “switches”, “flags” or “options” and are prefixed with
--. For example, you might issue the
ls command mentioned above with these two switches:
ls -l -a
Respectively, these cause
ls to output its list in columns (
-l) and to include hidden files (
ls command allows you to combine switches into a single statement:
This has the same effect as the previous example, but is more compact. Many commands work this way, but not all do. Many commands have the switches
--help which will cause them to output guidance — these will tell you if the command supports combining switches and flags this way.
mkdir command we mentioned earlier? If you provide a full path as an argument and that includes directories that don’t exist,
mkdirwill display an error message. However, if you also include the
mkdir will also create any ‘missing’ directories within the path.
Some shell commands are built into the shell itself, but many more are programs in their own right. Others are “shell scripts”. These are what make the command line so attractive to developers. Scripts are a way to combine multiple commands with variables and flow logic to automate key tasks.
Or you might write a script to extract certain data points from a stack of log files. Or to package apps ready for distribution. Or... well, the options are endless. Shell scripting is a truly powerful way of automating processes.
Let’s look at the first example. Here’s the code:
# Rebuild Hugo-generated website 2.3.1 # Set variables source="$GIT/website" # Move to source directory cd "$source" || exit 1 # Zap any existing build rm -rf public # Generate CSS "$source/scripts/css" # Build the site if hugo; then cd public rm $(find . -name .DS_Store) gsutil rsync -d -R . gs://www.example.com fi
Most lines in a script are read and handled as if you’d keyed in at the prompt; a couple of others determine how the script is run, delimit code structures, or are comments. You can spot all the comment lines: they start with
# — shells ignore any text after that symbol, up to the end of the line. Let’s go through the rest.
The first real line sets the value of a new variable,
source. Don’t read the quote marks as string delimiters — they’re being used for quoting. Why? Because the value of the variable
GIT may contain spaces and we want the shell to handle them correctly.
GIT is a variable set outside of the script; to read it back we prefix it with
$, as we do when we later read back
source. Not including spaces on either side of the assignment operator,
=, is a requirement in shell scripting.
cd we know already: it makes the named directory (the value of
source) the current directory. The
|| symbol stands for ‘logical OR’ and essentially tells the shell to check the outcome of command and, if it failed, to run the rest of the line. In this case, that means we
exit the script with an exit code of 1, i.e., a failure. The exit code for success is 0. Why check? Because if the target directory doesn’t exist, we want to make sure we don’t continue.
rm is short for ‘remove” — it deletes the named file or directory. Because
public is a directory (which we might have made earlier), we need to include the switches
-r to force
rm to delete the folder if it contains any files and to go into and directories it contains and delete them too. You’ll need
-r whenever you
rm a directory.
Remember, lines in a script are processed as if they were entered at the prompt.
"$source/scripts/css" just runs the named script,
css, to prepare the sites style sheets.
The last section shows some control logic: the script runs the program
hugo and if the program runs successfully — its exit code is 0 — the lines inside the
if... fi structure are executed.
hugo runs successfully — it’s a static website builder — the script switches to the
public directory (
hugo is responsible for creating this) and then removes some files that may have been copied in but which we don’t want. The
$(...) formatting tells the shell to run the code in the brackets and return whatever that code outputs. The output, which is either nothing or a list of files, is passed to
rm as an argument — the files we want deleted. The
find command is passed
. to tell it to look in the current directory; the
-name flag and its value,
find to look for files of that name.
Finally, we call Google’s
gsutil tool with a number of arguments to sync the contents of public, ie. the build website files, with a Google Cloud Platform web server.
And we’re done — we’ve build our static website and synced any new files with the web server!
If the script is called
deploy.sh, we run it, as we saw before, by entering
./deploy.sh at the prompt. If you’d just created this script in a text editor, you’d first need to make it executable — for which you use the
chmod command and its
+x (for ‘add execute permission’) argument :
chmod +x deploy.sh
We’ve really just scratched the surface of the command line, shells and scripts. Hopefully, you now understand what these terms mean, and why developers make them key components of their toolkits. You may already be thinking about how you can do the same.
To learn more about specific commands, Linux and macOS provide a shell-accessible command called
man which you provide a command as an argument. It will output the “manual” for the specified command — hence the name. Use the arrow keys to move up and down through the text, and hit Q to quit.
We can’t wait to see what you script!