Chapter 18. Working Faster by Typing Less

Despite all the improvements in processor speed, transmission rates, network speed, and I/O capabilities, there is still a limiting factor in many uses of bash—the typing speed of the user. Scripting has been our focus, of course, but interactive use of bash is still a significant part of its use and usefulness. Many of the scripting techniques we have described can be used interactively as well, but then you find yourself faced with a lot of typing, unless you know some shortcuts.

“Back in the day,” when Unix was first invented, there were teletype machines that could only crank out about 10 characters per second, and a good touch typist could type faster than the keyboard could handle it. It was in this milieu that Unix was developed, and some of its terseness is likely due to the fact that no one wanted to type more than absolutely necessary to get their commands across.

At the other end of the historical perspective (i.e., now), processors are so fast that they can be quite idle while waiting for user input, and can look back through histories of previous commands as well as in directories along your $PATH to find possible commands and valid arguments even before you finish typing them.

Combining techniques developed for each of these situations, we can greatly reduce the amount of typing required to issue shell commands—and not just out of sheer laziness. Rather, you’re likely to find these keystroke-saving measures useful because of the increased accuracy they provide, the mistakes they help you avoid, and the backups that you don’t need to reload.

18.1 Moving Quickly Among Arbitrary Directories

Problem

You find yourself moving frequently between two or more directories cd’ing here, then there, and then back again. The directories never seem to be close by, and you’re tired of always typing long pathnames.

Solution

Use the pushd and popd builtin commands to manage a stack of directory locations, and to switch between them easily. Here is a simple example:

$ cd /tmp/tank

$ pwd
/tmp/tank

$ pushd /var/log/cups
/var/log/cups /tmp/tank

$ pwd
/var/log/cups

$ ls
access_log error_log page_log

$ popd
/tmp/tank

$ ls
empty full

$ pushd /var/log/cups
/var/log/cups /tmp/tank

$ pushd
/tmp/tank /var/log/cups

$ pushd
/var/log/cups /tmp/tank

$ pushd
/tmp/tank /var/log/cups

$ dirs
/tmp/tank /var/log/cups

Discussion

Stacks are last in, first out mechanisms, which is how these commands behave. When you pushd to a new directory, it keeps the previous directory on a stack. Then when you popd, it pops the current location off of the stack and puts you back in that first location. When you change locations using these commands, they will print the values on the stack, left to right, corresponding to the top-to-bottom ordering of the stack.

If you use pushd without specifying a directory, it swaps the top item on the stack with the next one down, so that you can alternate between two directories using repeated pushd commands with no arguments. You can do the same thing using the cd - command.

You can still cd to locations—that will change the current directory, which is also the top of the directory stack. If you can’t remember what is on your stack of directories, use the dirs builtin command to echo the stack, left-to-right. For a more stack-like display, use the -v option:

$ dirs -v
 0 /var/tmp
 1 ~/part/me/scratch
 2 /tmp
$

The tilde (~) is a shorthand for your home directory. The numbers can be used to reorder the stack. If you pushd +2, then bash will put the #2 entry on the top of the stack (and cd you there) and push the others down:

$ pushd +2
/tmp /var/tmp ~/part/me/scratch
$ dirs -v
 0 /tmp
 1 /var/tmp
 2 ~/part/me/scratch
$

If you want that stack-like listing of directories, but without the numbers, use the -p option:

$ dirs -p
/tmp
/var/tmp
~/part/me/scratch
$

Once you get a little practice with these commands, you will find it much faster and easier to move repeatedly between directories.

18.2 Repeating the Last Command

Problem

You just typed a long and difficult command line, one with long pathnames and complicated sets of arguments. Now you need to run it again. Do you have to type it all again?

Solution

There are two very different solutions to this problem. First, just type two exclamation marks at the prompt, and bash will echo and repeat the previous command. For example:

$ /usr/bin/somewhere/someprog -g -H -yknot -w /tmp/soforthandsoon
...
$ !!
/usr/bin/somewhere/someprog -g -H -yknot -w /tmp/soforthandsoon
...
$

The other (more modern) solution involves using the arrow keys. Pressing the up-arrow key will scroll back through the previous commands that you have issued. When you find the one you want, just press the Enter key and that command will be run (again).

Description

The command is echoed when you type !! (sometimes called bang bang) so that you can see what is running.

Warning

Chet tells us that the csh-style bang history may no longer be enabled by default in a future version of bash because he has had multiple requests to turn that off. It would still be available as an option, however.

18.3 Running Almost the Same Command

Problem

After running a long and difficult-to-type command, you got an error message indicating that you’d made one tiny little typo in the middle of that command line. Do you have to retype the whole line?

Solution

The !! command that we discussed in Recipe 18.2 allows you to add an editing qualifier. How good are your sed-like skills? Add a colon after the bang bang and then a sed-like substitution expression, as in the following example:

$ /usr/bin/somewhere/someprog -g -H -yknot -w /tmp/soforthandsoon
Error: -H not recognized. Did you mean -A?
$ !!:s/H/A/
/usr/bin/somewhere/someprog -g -A -yknot -w /tmp/soforthandsoon
...
$

You can always just use the arrow keys to navigate your history and commands, as described in the previous recipe, but for long commands on slow links this syntax is great once you get used to it.

Discussion

If you’re going to use this feature, be careful with your substitutions. If you had tried to change the -g option by typing !!:s/g/h/ you would have ended up changing the first letter g, which is at the end of the command name, and you would be trying to run /usr/bin/somewhere/someproh.

If you want to change all occurrences of an expression in a command line, you need to precede the s with a g (for global substitution), as follows:

$ /usr/bin/somewhere/someprog -g -s -yknots -w /tmp/soforthandsoon
...
$ !!:gs/s/S/
/usr/bin/Somewhere/Someprog -g -S -yknotS -w /tmp/SoforthandSoon
...
$

Why does this g have to appear before the s and not after it, like in sed syntax? Well, anything that appears after the closing slash will be considered new text to append to the command—which is quite handy if you want to add another argument to the command when you run it again.

18.4 Quick Substitution

Problem

You’d like to know if there’s a simpler syntax for making substitutions in your previously executed command and running the modified result.

Solution

Use the caret (^) substitution mechanism:

$ /usr/bin/somewhere/someprog -g -A -yknot -w /tmp/soforthandsoon
...
$ ^-g -A^-gB^
/usr/bin/somewhere/someprog -gB -yknot -w /tmp/soforthandsoon
...

You can always just use the arrow keys to navigate your history and commands, but for long commands on slow links this syntax is great once you get used to it.

Discussion

Write the substitution on the command line by starting with a caret (^) and then the text you want replaced, then another caret and the new text. A trailing (third) caret is needed only if you want to add more text at the end of the line, as in:

$ /usr/bin/somewhere/someprog -g -A -yknot
...
$ ^-g -A^-gB^ /tmp^
/usr/bin/somewhere/someprog -gB -yknot /tmp
...

If you want to remove something, substitute an empty value; i.e., don’t put anything for the new text. Here are two examples:

$ /usr/bin/somewhere/someprog -g -A -yknot /tmp
...
$ ^-g -A^^
/usr/bin/somewhere/someprog -yknot /tmp
...
$ ^knot^
/usr/bin/somewhere/someprog -gA -y /tmp
...
$

The first example uses all three carets. The second example leaves off the third caret; since we want to replace the “knot” with nothing, we just end the line with a newline (the Enter key).

The use of caret substitution is just plain handy. Many bash users find it easier to use than the !!:s/…/…/ syntax demonstrated in Recipe 18.3. What do you think?

18.5 Reusing Arguments

Problem

Reusing the last command is easy with !!, but you don’t always want the whole command. How can you reuse just the last argument?

Solution

Use !$ to indicate the last argument of the preceding command. Use !:1 for the first argument on the command line, !:2 for the second, and so on.

Discussion

It is quite common to hand the same filename to a series of commands. One of the most common occurrences might be the way a programmer would edit and then compile, edit and then compile.… Here, the !$ comes in quite handy:

$ vi /some/long/path/name/you/only/type/once
...
$ gcc !$
gcc /some/long/path/name/you/only/type/once
...
$ vi !$
vi /some/long/path/name/you/only/type/once
...
$ gcc !$
gcc /some/long/path/name/you/only/type/once
...
$

Get the idea? It saves a lot of typing, but it also avoids errors. If you mistype the filename when you compile, then you are not compiling the file that you just edited. With !$ you always get the name of the file on which you just worked. If the argument you want is buried in the middle of the command line, you can get at it with the numbered “bang-colon” commands. Here’s an example:

$ munge /opt/my/long/path/toa/file | more
...
$ vi !:1
vi /opt/my/long/path/toa/file
...
$

You might be tempted to try to use !$, but in this instance it would yield more, which is not the name of the file that you want to edit.

See Also

18.6 Finishing Names for You

Problem

Sometimes pathnames get pretty long. This is a computer that bash is running on… can’t it help?

Solution

When in doubt, press the Tab key. bash will try to finish the pathname for you. If it does nothing, it may be because there are no matches, or because there is more than one. Press the Tab key a second time and it will list the choices and then repeat the command up to where you stopped typing, so that you can continue. Type a bit more (to disambiguate), then press the Tab key again to have bash finish off the argument for you.

Discussion

bash is even smart enough to limit the selection to certain types of files. If you type unzip and then the beginning of a pathname, and then you press the Tab key, it will only finish off with files that end in .zip even if you have other files whose names match as much as you have typed. For example:

$ ls
myfile.c     myfile.o     myfile.zip
$ ls -lh myfile<tab><tab>
myfile.c     myfile.o     myfile.zip
$ ls -lh myfile.z<tab>ip
-rw-r--r--     1 me mygroup 1.9M 2006-06-06 23:26 myfile.zip
$ unzip -l myfile<tab>.zip
...
$
Warning

That last example for unzip requires the bash-completion package we discussed in Recipe 16.19. If bash is not able to offer completion suggestions, make sure that package is installed.

18.7 Playing It Safe

Problem

It’s so easy to type the wrong character by mistrake (see!). Even for simple bash commands this can be quite serious—you could move or remove the wrong files. When pattern matching is added to the mix, the results can be even more exciting, as a typo in the pattern can lead to wildly different-than-intended consequences. What’s a conscientious person to do?

Solution

You can use these history features and keyboard shortcuts to repeat arguments without retyping them, thereby reducing the chance of typos. If you need a tricky pattern match for files, try it out with echo to see that it works, and then when you’ve got it right use !$ to use it for real. For example:

$ ls
ab1.txt  ac1.txt  jb1.txt  wc3.txt
$ echo *1.txt
ab1.txt ac1.txt jb1.txt
$ echo [aj]?1.txt
ab1.txt ac1.txt jb1.txt
$ echo ?b1.txt
ab1.txt jb1.txt
$ rm !$
rm ?b1.txt
$

Discussion

echo is a way to see the results of your pattern match. Once you’re convinced it gives you what you want, then you can use it for your intended command. Here we removed the named files—not something that one wants to get wrong.

Also, when you’re using the history commands, you can add a :p modifier and it will cause bash to print but not execute the command—another handy way to see if you got your history substitutions right. At the end of the example in the Solution section, we could have done this:

$ echo ?b1.txt
ab1.txt jb1.txt
$ rm !$:p
rm ?b1.txt
$

The :p modifier causes bash to print but not execute the command—but notice that the argument is ?b1.txt and is not expanded to the two filenames. This option shows you what will be run, but only when it is run will the shell expand that pattern to the two filenames. If you want to see how it will be expanded, use the echo command.

See Also

18.8 Big Changes, More Lines

Problem

What if the changes that need to be made are too complicated for a single substitution or span several command lines? Sometimes you find yourself running the history command and redirecting the output to a file, editing that file, and running those commands as a script after editing. Is there an easier way?

Solution

The fc command will put the most recent command (or a range of commands) in a temporary file, invoke your editor and let you edit the command(s) any way you see fit, and then automatically rerun the edited version of the command(s) when you exit the editor.

Discussion

Which editor will it invoke? It will use the one defined in the shell variable FCEDIT, if it is set. If that one is empty it will use the more general EDITOR variable, and if that is also empty it will use vi.

Which line(s) will appear in the editor? If you invoke fc with no arguments, it will use only the last line. You can also specify a particular line with a single argument: fc 1004 will use line 1004 from your command history, whereas fc -5 will use the line five previous from the most recent command. Similarly, you can specify a range of arguments, such as fc 1001 1005, which will let you edit lines 1001 through 1005 inclusive in your command history, or fc -5 -1, which allows you to edit the last five commands that you ran.

Warning

When the fc command has invoked the editor it will rerun whatever commands are left in the file when you exit—even if you exit without making any changes. What if you change your mind and don’t want to execute any commands? Then you should delete all the lines in the file and write that empty file out before exiting. Don’t try to suspend the editor, either. That will leave the shell and terminal in limbo.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.196.217