Monday, March 23, 2009

Episode #14 - Command Line Shortcuts

Paul Writes In:

Since the gloves are coming off, I would like to highlight a couple of features in the UNIX/Linux command line that save me some time and help foster my loving relationship with Bash (just don't tell my wife). As I've stated before, I really hate to type, so I tend to use as many shortcuts as I can, for example:

If I run the following command:

$ ls -l /home/windows

It will list the contents of the /home/windows directory (don't worry it gets better). If I've copied files into that directory and want to run that command again I can type:

$ !ls

Which will execute the last command that I ran containing the string "ls". Lets keep the fun going and run the following command:

$ ^windows^linux

This command will run my previous command and replace the string "windows" with "linux" (some would say "upgrade" my command). If I want to run my previous command I can use the following:

$ !!

I also use the following technique quite often:

$ ls !$

The !$ is the last parameter of your previous command. Now, you may feel like a hotshot using this Kung Fu, however try not to use them in conjunction with the "rm" command (like !rm) because you may be in the "/" directory instead of "/tmp" :)

Hal Says:

Strictly speaking, "!ls" means run the last command line that starts with "ls", not the last command that contains "ls":

$ ls -l /usr/bin/file
-rwxr-xr-x 1 root root 16992 May 24 2008 /usr/bin/file
$ file /bin/ls
/bin/ls: ELF 64-bit LSB executable, AMD x86-64, ...
$ !ls
ls -l /usr/bin/file
-rwxr-xr-x 1 root root 16992 May 24 2008 /usr/bin/file

As Paul points out, however, it can be dangerous to just pop off with "!somecommand" when you're not sure what's in your history. Luckily, there's the ":p" option which prints the command that would be executed without actually running the command:

# !/etc:p
/etc/init.d/named restart
# !!
/etc/init.d/named restart
Stopping named: ......... [ OK ]
Starting named: [ OK ]

Notice that ":p" puts the command into the "last command" buffer so that you can immediately execute it with "!!" if it turns out to be the command you want.

But what if it isn't the command you want? Did you know you can search your history interactively? Just hit <Ctrl>-R and start typing characters that match some string in your command history-- unlike "!command", the matching happens anywhere in the string, not just at the front of the command line. Additional <Ctrl>-R's will search backwards from the current match using the given search string. Once you find the command you want, just hit <Enter> to execute it, or use the normal command-line editing tools to change it. Use <Ctrl>-C to abort the command without executing anything.

By the way, while "!!" gives you the previous command, "!-2" gives you the command before the previous command, "!-3" goes three commands back, and so on. For example, suppose I was watching the size of a log file and making sure the partition it was in was not running out of space, I might alternate two commands:

# ls -l mail.20090312
-rw------- 1 root root 392041 Mar 12 14:14 mail.20090312
# df -h .
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/Root-Var 20G 3.7G 15G 20% /var
# !-2
ls -l mail.20090312
-rw------- 1 root root 392534 Mar 12 14:16 mail.20090312
# !-2
df -h .
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/Root-Var 20G 3.7G 15G 20% /var
# !-2
ls -l mail.20090312
-rw------- 1 root root 393068 Mar 12 14:20 mail.20090312
# !-2
df -h .
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/Root-Var 20G 3.7G 15G 20% /var

By the way, the "!<n>" thing also works with argument substitutions like "!$":

# cp ~hal/.profile ~hal/.bashrc ~paul
# chown paul ~paul/.profile ~/.bashrc
# cd !-2$
cd ~paul

And speaking of "!$", did you know that "!*" means "use all previous arguments"? For example:

# touch file1 file2 file3
# chmod 600 !*
chmod 600 file1 file2 file3
# chown hal !-2*
chown hal file1 file2 file3

The replacement operator ("^foo^bar") is awesomely useful. Here's a cool trick that I use a lot:

# make -n install
# ^-n
make install

In other words, I used a null substitution to remove the "-n" operator from the previous command line once I had assured myself that "make" was going to do the right thing.

The only problem with the replacement operator is that it only works on the first instance of the string in the previous command:

# cp passwd
# ^passwd^shadow
cp shadow

Obviously, this is not what we want. The following syntax works:

# cp passwd
# !:gs/passwd/shadow/
cp shadow

I'm just not sure that's any quicker than manually editing the previous command.

Ed comments:

Holy frijoles! That's a lot of fu, Hal. Lot of fu.

On Windows, accessing shell history isn't quite as fine-grained, but we've got some useful options in cmd.exe.

With a cmd.exe on the screen, we can hit the F7 key to see our command history.

Scroll up or down, and you can then hit the Enter key to re-run the command. Hit the right or left arrow, and it'll retype the command, letting you edit it and then re-run it.

See those numbers next to the commands when you hit F7? If you hit the F9 key, it prompts you for a command number to re-execute. Type in the appropriate number, hit Enter, and your command is re-typed, letting you edit it.

To clear this history, you can hit ALT-F7.

If you want to see your recent history (up to 50 commands stored by default) in standard output, run:

C:\> doskey /history
So, it's not as scrumdiliously awesome as what you guys have over on Linux, but it's very workable. Heck, some might even say that it's usable... barely. ;)