A Guide to Basic Command-Line Tasks

LJ300-July2019-DeepDive

A whirlwind tour of the "the user interface that wouldn't die". By Dave Taylor

Opening scene: a dusty 1950s-era computer room with spinning mag tapes and hundreds of flashing lights. There's an imposing grey teletype machine and empty chair front and center. To the right is a huge, messy pile of printouts clearly torn off the machine and marked up with pencils. It's late at night with minimal room lighting.

Cut to opening animation: "The User Interface That Wouldn't Die!"

Sf/x a scream as the screen bursts into a pure white...

Okay, so maybe using the command line isn't quite this dramatic as we move slowly but inexorably into the year 2020. Twenty twenty. Hard to believe we're already that far into the 21st century. And remember, the very first command lines were from the Multics era, if not earlier (Multics begat Unix which begat Linux many years later). We're talking about a user interface that's been around since the early 1960s.

This leads to the obvious question, "What's the darn appeal for people to use such an ancient interface when there are fancy graphical window managers, mice, touchscreens and swanky million-color displays?"

I occasionally ask myself this very question when I crack open a terminal window and start typing at the command line, just to realize that the command-line interface is still popular because it's so incredibly efficient.

Want to list only files that have a "z" in their name? Want to check the last time that user "maria" logged in? Need to change all the filenames in that archive from your supplier so that everything's in lowercase? Want to bulk-resize thousands of images? Create an encrypted backup and automatically copy it to a cloud server? Log in to a customer's system 7,500 miles away for remote diagnostics?

Those are just a few of the millions of tasks you can accomplish with the command line in Linux, and it's exactly why smart users still are finding that CLI and using it. My guess is that a significant subset of Linux Journal readers type in a command at least weekly, if not much more frequently.

But, how well do you actually know the command line, the user interface that wouldn't die? Let's have a refresher of the commands folks likely use the most often since this is the command-line issue of the magazine.

Note: I've been writing about the command line for eons. Books I've written on the subject include Teach Yourself Unix in a Week (which turned into Teach Yourself Unix in 24 Hours when a week seemed like too much time), Learning Unix for MacOS X and the ever-popular Wicked Cool Shell Scripts.

Editing Commands and Command History

Before you start typing in commands, it's really helpful to know how you can tweak and modify your command line. Start with history, and you'll see a list of your previous commands. Each is prefaced by a number, and you can repeat an earlier command quickly and easily with the shortcut !number, as shown:


$ history
    1  PS1="$ "
    2  uptime
    3  history 10
    4  ls -F
    5  find . -name "*.c" -print
    6  who
    7  date
    8  clear
    9  history
$ !2
uptime
 08:16:03 up  1:11,  1 user,  load average: 0.17, 0.18, 0.18
$ 

Notice that when I did !2 to repeat command #2, the shell then showed the command I'd matched (uptime) followed by the result of that command being invoked.

You also can repeat a command by using that same ! followed by a letter or two, so I easily could repeat the find command (command #5) with this:


$ !f
find . -name "*.c" -print
...

That's the majority of my command-line manipulation, actually. On many systems, you also can use the cursor up and cursor down arrows to scroll through your command history, but that almost feels like cheating if I'm talking about a throwback to the teletype machine.

Trivia: /dev/tty is named after the teletype system, and it's still the mnemonic for your terminal session all these decades later. That teletype hasn't completely vanished!

There's quite a bit more you can do with the command line, including pulling arguments from one command into another and so on, but those things are a bit more obscure and probably not as helpful as just knowing that you always can check and access your history with just a keystroke or two. In fact, when developing a script, I often find myself in a cycle of !v to edit and !. to execute it once I've typed in the full commands a single time.

Navigating the Filesystem

The most rudimentary use of the command line is to explore the filesystem. You surely already know that the Linux filesystem is an inverted hierarchical tree, right? Yes, and don't call me Shirley!

As a result, the key commands to learn for filesystem navigation are to identify your current location in the filesystem (pwd) and how to move around (cd):


$ pwd
/home/taylor
$ cd /home
$ pwd
/home

You can see that I started out with a present working directory of /home/taylor, then used the change directory command to move to /home, at which point my pwd is now, logically enough, /home.

There are two additional shortcuts for navigating the filesystem in Linux, and those are .. to back up one level in the system and ~ as a shortcut for your home directory. Your home directory, just to be clear, is where you start when you've logged in to the system.

This should all make sense:


$ cd ~
$ pwd
/home/taylor
$ cd ..
$ pwd
/home

That's the basics of moving around. Pretty darn easy, really.

Listing Files and Directories

Moving around in the filesystem is of limited value if you can't actually look at what's in your current location. That's what the ls command is for, and if there's one command that you should grow to love, it's ls. This is also the first command I'm considering here that has arguments and command flags—lots of them actually. But to start, at its most basic:


$ cd ~
$ ls
Desktop    Downloads         Music     Public     Videos
Documents  examples.desktop  Pictures  Templates

You can see that there are eight entries in my home directory (remember, ~ is a shortcut for home), but what are they? My notational convention is to have directories start with an uppercase letter and have files all be lowercase, but that doesn't mean I'm 100% consistent.

To find out, add the -F flag to the ls command, which appends a symbol suffix to indicate file or directory type:


$ ls -F
Desktop/    Downloads/        Music/     Public/     Videos/
Documents/  examples.desktop  Pictures/  Templates/

Everything with a trailing / is a directory.

This is a good moment to point out something pretty important. Although we see directories and files as being quite different, the Linux filesystem views them all as just objects or entities. You can rename a directory the same way you can rename a file, for example, and directories and files can move around in just about identical fashions too. In fact, Linux has a sloppy habit of using the filename suffix to identify the type of file too, so rename "resume.docx" to "resume.mp3", and suddenly it'll try to play your résumé instead of letting you edit it.

The -F flag is useful, but -l (that's a lowercase L) is much more useful as it offers up what's known as a "long listing" format:


$ ls -l
total 44
drwxr-xr-x 2 taylor taylor 4096 Mar  9 06:48 Desktop
drwxr-xr-x 2 taylor taylor 4096 Mar  9 06:48 Documents
drwxr-xr-x 2 taylor taylor 4096 Mar  9 06:48 Downloads
-rw-r--r-- 1 taylor taylor 8980 Mar  9 06:44 examples.desktop
drwxr-xr-x 2 taylor taylor 4096 Mar  9 06:48 Music
drwxr-xr-x 2 taylor taylor 4096 Mar  9 06:48 Pictures
drwxr-xr-x 2 taylor taylor 4096 Mar  9 06:48 Public
drwxr-xr-x 2 taylor taylor 4096 Mar  9 06:48 Templates
drwxr-xr-x 2 taylor taylor 4096 Mar  9 06:48 Videos

Now you can see permissions, ownership, group ownership, size (sort of), last modified dates and the file or directory name. Confusingly, the meaning of these values is different based on the type of file or object represented. All directories are shown with basically the same size (4K here), which is basically useless, but notice that the lone file, examples.desktop, has a different value. That's its actual size: 8,980 bytes.

It's the permissions string that's more interesting, however. Linux permissions model UNIX permissions and are based on three concentric circles of access: owner, group and everyone. To decode one of the permissions strings, know that the first character indicates type, then each three-character set is read/write/execute permission status for each of the three circles of access.

The directory Desktop, for example, has a d as its first letter. Then the owner of the file (taylor, as shown in column 4) has read+write+execute permission as denoted by rwx. The group to which the directory is assigned (also taylor in this example, as shown in column 5) has read+execute permission, as denoted by r-x. Finally, everyone else on the system has read+execute only permission, meaning that only the owner of the directory can edit or change it.

Similarly, the file examples.desktop has read+write permission for its owner, taylor, and everyone else has read-only permission and cannot edit or change it or its content. That's what -rw-r--r-- means. Focus on the three-letter sequence * three access levels, and it shouldn't be too intimidating. But there's no way around it, permissions for directories are a bit more confusing. Suffice it to say, if you want someone to have access, make it r-x, and if you want them to be able to change, add and edit, use rwx. Close it off? That's what -- is for.

Let's check out the topmost directory for some more interesting examples of permissions:


$ cd /
$ ls -l
total 970072
drwxr-xr-x   2 root root      4096 Mar  9 06:52 bin
drwxr-xr-x  19 root root      4060 Mar  9 06:56 dev
drwxr-xr-x 124 root root     12288 Mar  9 07:03 etc
drwxr-xr-x   3 root root      4096 Mar  9 06:44 home
lrwxrwxrwx   1 root root        33 Mar  9 06:54 initrd.img ->
boot/initrd.img-4.18.0-16-generic
drwxr-xr-x  21 root root      4096 Mar  9 06:46 lib
drwxr-xr-x   2 root root      4096 Oct 17 16:23 lib64
drwx------   2 root root     16384 Mar  9 06:42 lost+found
dr-xr-xr-x 258 root root         0 Mar  9 06:56 proc
drwx------   3 root root      4096 Oct 17 16:34 root
drwxr-xr-x  31 root root       880 Apr  8 08:05 run
drwxr-xr-x   2 root root      4096 Oct 17 16:23 sry
-rw-------   1 root root 993244160 Mar  9 06:42 swapfile

I've pruned the above just a bit to make it less overwhelming, but notice how much variation there is! The first letter of the permissions string can also be an "l" (lowercase L) to indicate a symbolic link. In this instance, it shows that the entry /initrd.img is actually a link to /boot/initd.img-4.18.0-16-generic. Symbolic links are super small too, as they need to contain only the destination filename. This one's 33 bytes in size.

Notice the permission for lost+found: rwx for owner (root) and completely shut off for everyone else. Oh, and see that swapfile? How big is it?

Fortunately, there's one more ls flag worth mentioning: -h. That offers a "human-friendly" size. Add that to the ability to specify a directory or even a single file on the command line, and you can figure it out quickly:


$ ls -lh /swapfile
-rw------- 1 root root 948M Mar  9 06:42 /swapfile

Between cd and ls, you now can move around the filesystem quickly.

Tip: when you're typing in a file or directory name, try pressing the Tab key to expand it. As long as it will expand to a unique word or name, that's all you need to do, so typing /sw and then pressing Tab would work fine in the above example. Handy indeed!

Moving and Copying Files and Directories

Next up in this quick tour of the command line are the commands that let you move and copy files and folders, and the command that lets you create new directories. Moving and copying at some level are the same task, a stream of bytes moving into a new file container, but the big difference is what happens to the original file. With a move, the source file is deleted.

Let's jump back to my home directory and move that examples.desktop file to the Desktop directory:


$ cd ~
$ ls -l examples.desktop 
-rw-r--r-- 1 taylor taylor 8980 Mar  9 06:44 examples.desktop
$ mv examples.desktop Desktop/
$ ls -F
Desktop/  Documents/  Downloads/  Music/  Pictures/  Public/  
Templates/  Videos/

There's another handy shortcut demonstrated here. If you move or copy a file into a new directory, you can just specify the destination directory, and it'll retain its name. This means that, as shown, there's no longer a file called examples.desktop in my home directory.

You can rename it as you move or copy a file or folder, of course, so this would work fine:


$ mv Videos Desktop/my-video-archive
$

And this would work too:


$ cp Desktop/examples.desktop my-example
$

In fact, if you want to rename a file, it's the mv command you'll use, even if you're "moving" it within the same directory:


$ mv my-example demo-example.txt
$

There's no rename command in Linux, nor do you need one.

Of Wildcards, Asterisks and Quotes

In the old days, UNIX commands were all lowercase and filenames also were all lowercase and never had spaces within them. So it was easy to mv test test.c and know it'd work properly. Modern graphical interfaces work fine with more complex names, so it's no surprise that there now are multiword directories and filenames.

To deal with complex names, quote things, ideally with the double quotes:


$ ls
examples.desktop
$ mv examples.desktop "My Favorite Examples"
$ ls -l My\ Favorite\ Examples 
-rw-r--r-- 1 taylor taylor 8980 Mar  9 06:44 'My Favorite 
 ↪Examples'

Specific characters that cause trouble can be escaped with a prefacing backslash, as the second ls command shows. Better yet, that's from me doing a Tab expansion, so the shell's smart enough to do this all by itself. Helpful!

The shell also has a cool wildcard feature, which is great if you want to do any sort of bulk moving. For example, C program files are denoted with a ".c" suffix, so a directory full of C source files might look like this:


$ ls -F
demoprog*  libalt.c  library.c  main1.c  main2.c  main.c  
Makefile  README  utilities.c

Notice in this instance that the -F flag has given a "*" suffix to indicate that demoprog is executable.

To reference all the C source files en masse, you could use *.c as the notation. Copy them all into a backup directory? Easy:


$ cp *.c ../backupdir

This proves generally true, so doing this:


$ cp * ../backupdir

will copy everything from this directory, including the executable. There's lots more to the wildcard language of the shell, including ? to match a single letter (so main?.c will match main1.c and main2.c, but not main.c) and sets like [MR]* to match M* and R*, but the asterisk wildcard will definitely get you started.

Pipes and Redirects

Think about this: every command invoked on the command line automatically has an input, an output and an error device associated. By default, the input is the keyboard, and the output and error output are both the screen or terminal. But, you can change them.

To demonstrate, I'm going to introduce another command: wc. The wc command does not point you to the closest restroom when you're in the UK (yeah, bad joke!), it offers up the word count for a file. For example:


$ wc main.c
  44  129 1172 main.c

This shows you that there are 44 lines, 129 words and 1172 characters in the C source file main.c—useful. Add -w, and it'll just report words too: wc -w main.c.

But, this command also can use the < symbol to redirect input. Notice how it subtly changes the results:


$ wc -w < main.c
129

Because I used redirection, it didn't know the name of the input file. You can use < to redirect input, but you also can use > to redirect the output of the command and even >> to redirect output and append it to an existing file. Consider this:


$ date > uptime.log
$ uptime >> uptime.log
$ 
$ cat uptime.log
Mon Apr  8 12:17:49 MDT 2019
 12:17:53 up 14 min,  1 user,  load average: 0.23, 0.41, 0.45
$ 

Oops, the cat program—short for concatenate—dumps the contents of a file—any file, even one that's not actually readable. In the above, the date command created the output file (or overwrote it if it already existed, which is a danger if you mess up input and output files), then the second command had its output appended. Finally, cat showed what's in uptime.log.

If you can change the input and output of a command, can you have one command's output be the input to another command's input? That's what pipes are for, and it's one of the most powerful—and wicked cool—capabilities of the command line. There are hundreds of commands with thousands of flags and options, but once you realize that one command's output can be another's input, and that output can be hooked to yet another, well, then you realize there are millions of different commands you can form with a few dozen keystrokes.

At its most simple, how about this:


$ uptime | wc
      1      10      61
$ uptime | wc | wc 
      1       3      24

See what I did in that second command? I used wc the second time to count the number of words, lines and characters that were the output of the first wc invocation. It's kind of a weird example, but it highlights that any command that's in a pipe acts independently, so the second wc couldn't know that it was being invoked twice.

The combination of pipes and redirection offers an extraordinarily rich environment within which you can create just about any possible command you can imagine. That's where a lot of the fun of working with the Linux command line comes from. The other thing that makes it great fun for me is...

Shell Script Programming

Yep, that should be no surprise since I have been writing the Shell Script Programming column here in Linux Journal for more years than I want to remember. Imagine that everything you can do on the command line you can easily do within a simple script. Add conditional statements, functions and variables—the sky's the limit on what you can do with shell scripts, and I've written hundreds of different scripts, ranging from a few lines to hundreds of lines of complex code.

You can see 101 of my best and most interesting in my book Wicked Cool Shell Scripts, or just read my column here in the magazine. Or even better, do both!

I'm way out of space, which is too bad, because I could keep going for many, many more pages. Indeed, I have, which is why I've written an introduction to the Linux command line three times in different books. None were shorter than 300 pages either. Still, I hope this has whetted your appetite to learn more about the fantastic Linux command line, and remember that both MacOS X and Windows have command-line interfaces too.

It really is "the User Interface that Wouldn't Die" after all.

About the Author

Dave Taylor has been hacking shell scripts on UNIX and Linux systems for a really long time. He's the author of Learning Unix for Mac OS X and Wicked Cool Shell Scripts. You can find him on Twitter as @DaveTaylor, and you can reach him through his tech Q&A site: Ask Dave Taylor.

Dave Taylor