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 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 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 terminal app. Microsoft also has Windows Subsystem 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, when 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 been 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 always 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 the 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:
/Users/Twilio/GitHub/scripts/imageprep.sh
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:
../../GitHub/scripts/imageprep.sh
This means 'go up to the parent directory' (ie. Documents/
), 'go up to the next parent directory' (Twilio/
), then 'go down to GitHub/scripts/imageprep.sh
'.
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 other branches.
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 must 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:
./imageprep.sh
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
that 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
not listed in $PATH
.
When referencing shell variables (though not when setting them), always prefix their names with the $
sign.
If you open up a terminal now, when the prompt appears enter one of the following commands:
Platform | Command |
---|---|
macOS/Linux | ls |
Windows Terminal | DIR |
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:
Platform | Command |
---|---|
macOS/Linux | pwd |
Windows Terminal | CHDIR |
To create a new directory, use the following command:
Platform | Command |
---|---|
macOS/Linux/Windows Terminal | mkdir <directory_path> |
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:
Platform | Command |
---|---|
macOS/Linux | cd <directory_path> |
Windows Terminal | CHDIR <directory_path> |
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 displays the contents of the named file.
Other arguments govern how the command works: they're called "switches", "flags" or "options" and are prefixed with -
or --
. 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 (-a
). The ls
command allows you to combine switches into a single statement:
ls -la
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 -h
and/or --help
which will cause them to output guidance — these will tell you if the command supports combining switches and flags this way.
Remember the mkdir
command we mentioned earlier? If you provide a full path as an argument and that includes directories that don't exist, mkdir
will display an error message. However, if you also include the -p
switch, 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.
Here's an example: if you're building a website, you might write a script to automate all the repetitive setup work you need to do when you test a change: process the style sheets, minify your CSS and JavaScript files, organize the content and transfer it to the location from which it will be served, and to start up the HTTP server.
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:
1# Rebuild Hugo-generated website 2.3.123# Set variables4source="$GIT/website"56# Move to the source directory7cd "$source" || exit 189# Zap any existing build10rm -rf public1112# Generate CSS13"$source/scripts/css"1415# Build the site16if hugo; then17cd public18rm $(find . -name .DS_Store)19gsutil rsync -d -R . gs://www.example.com20fi
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 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.
The command 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 the command and, if it fails, 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.
The command 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 -f
and -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 site's 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.
If 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, .DS_Store
, tell 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 built 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 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.
The Linux Documentation Project has a great guide to getting started with the Bash shell and a more detailed guide covering advanced scripting. There is also the Bash Manual.
For Mac users, there's an equivalent manual for the Z Shell. Check out Apple's Terminal User Guide too.
Microsoft has its own guides for Windows Terminal, PowerShell, and Windows Subsystem for Linux.
We can't wait to see what you script!