Posted on

Bash Command Lookup (\!)

I’ve recently found something relatively interesting that you can do in a bash terminal. I recently sent out an email talking about how to implement git completion’s wonderful self to work on macs.

Part of that endeavor meant diving into the way that the terminal displays its information to you on your prompt. Some of the things I found out were using the escape codes like \h to stand for host, \W for working directory w/o the path, etc.

So I set out to find out what some more of those escape characters were, and I found: \!

I’ve learned from Paul that doing a !! will repeat the last command that you put in. This \! will actually list a sequential number (to the last) on your prompt. So now when I’ve added it to my PS1 as before from the git completion tutorial, my prompt now displays:

(527)iMac:~ chase$ _

And when I put in a command, lets say I emptily type grep<enter>

(527)iMac:~ chase$ grep<enter>
Usage: grep [OPTION]... PATTERN [FILE]...
(528)iMac:~ chase$ _

Lets pretend that was some crucially complex command (you know the kind… that escapes you how to do it again later when you really need it) instead of an empty grep, and lets say that through the course of working I’ve since entered dozens or hundreds of other commands into the prompt. I have a few options available:

  • hit the up arrow repeatedly until I find the command (which it doesn’t list with the number next to it)
  • use the <ctrl>+R command and type in parts of the command I remember
  • grep the history
  • lots of things

or, if I’ve remembered that 527 was the line for that crucial command, I can simply type:

(8901)iMac:~ chase$ !527<enter>

And it will repeat the command from that line. The only downside to this, is that eventually if you come to rely on it for remembering several different sets of complex commands… you’ll have to end up remembering several different sets of numbers that corresponds to those lines. Also, this function doesn’t give you any type of “Are you sure?” type of moment to let you know what you’re about to do… so one transposed number or dropped digit could potentially mean catastrophe if you’ve ever run some iffy commands (rm -Rf) .

About This Article…

I pilfered this from “The List”, thanks Chase…
– Lobby

Posted on

Bash Tips & Tricks

bashYesterday on the-list I was asking about some weird behavior about `test`, so it made me want to write about some other Bash scripting pitfalls which I have fallen into before. Maybe some of these you’ve run into before, maybe some you haven’t. Anyways, I hope you find something here helpful. Note that these apply to Bash. If you write scripts with other shells like zsh or ksh, these may or may not apply.

Reading and Writing to the Same File

You cannot read and write to the same file in the same command. One of two things will happen:

  1. The file will be clobbered to zero bytes.
  2. The file will grow until it consumes all available space.

This means you cannot write things like

$ cat file | some_command > file

You can’t do this because the pipe tells the shell to read from the file, but then `some_command` is writing out to it. Which takes precedence? There is not a reliable standard on this matter. Your only option is to create a temporary file.

$ cat file > /tmp/file && some_command /tmp/file

This will guarantee that the contents from the `cat` will be written out (flushed in C library terms) before the other command.

The Error Status of `cd`

Changing directories seems harmless, but you *must* check the return value of the `cd` command. Please always do this! Consider this in a script:

$ cd foo; rm *.php

Now what if `foo` does not exist? The `cd` will fail and you will delete all of the PHP scripts in a directory that you didn’t intend. Whoops. A way to avoid this is to explicitly check the results of `cd` by exiting the script if it fails, e.g.:

$ cd foo || exit 1; rm *.php

This `||` means ‘either change to the directory foo, or if you can’t, exit completely’. I really suggest doing this with every `cd`. If your script depends on changing directories, you do not want that to fail; God forbid you run a series of ‘dangerous’ commands in an unexpected directory.

Looping Over File Names

You may see this a lot online, as a way to loop over file names:

for script in $(ls *.php); do 
    … 
done

Or even:

for script in $(find . -type f -name '*.php'); do 
    … 
done

Both of these forms will break in Bash for any file that has a space, because of word splitting rules. Let’s say you have a file called ‘user processor.php’. Both loops will execute twice, where the variable `$script` is equal to `user` and then `processor.php`. Which is not what you want in either case. The best thing to do is the same file glob:

for script in *.php; do 
    ... 
done

This will do the right thing.

Posted on

Changing Directories More Easily

Here is something I have in my Bash config that I have found useful these days. It defines a command called up that lets me move up a given number of directories. For example, up 2 is the same as cd ../.., and up 4 is cd ../../../.., and so on.

function up() {
     cd $(perl -e 'print join("/" => ("..") x shift)' $1)
}

I found this somewhere online, so I am not taking credit for it. The way this works is we use Perl to create the string ../../.., or however many dots and slashes we need to reach the right parent directory. We can create that string to go up three directories by using the code

("..") x 3

to create the list

(".." ".." "..")

We then use join to insert a slash between each set of dots. This gives us code very close to what is in the function above. The key difference is the use of shift. We don’t know ahead of time how many .. strings to create, since that depends on how many directories upward we want to move. What we want to do then is pass in the number of ..‘s to create as an argument to the Perl script. By default shift will pop off the first command-line argument to the script, which will be our number.

This is how we end up with

perl -e 'print join("/" => ("..") x shift)' $1

Here $1 refers to the first argument of the up shell function. So when we use up 3 we get

perl -e 'print join("/" => ("..") x shift)' 3

which gives us

print join("/" => ("..") x 3)
print join("/" => (".." ".." ".."))
print "../../.."

That string is finally returned as the argument to cd, which moves us up the right number of directories.

Related to this are some aliases I use to treat and as a stack of directories. Bash has two commands called pushd and popd. The former will change to the given directory and put it on the stack. The latter will pop the top of the stack and move to the directory that is now at the top. So I use these aliases to those commands:

alias bd="popd"
alias cd="pushd"
alias rd="popd -n"

The mnemonic for bd is to go ‘back a directory’. The rd alias ‘removes a directory’; it takes the top directory off the stack without switching to it. This is sometimes useful when I end up deleting a directory on the stack, because then ‘bd’ will complain with an error if I try to move back to it.

The command dirs will show you the stack, starting with the current directory on the left. Once you get used to it, I think this is a useful way of moving around directories.

Posted on

Easy Documentation for Git, MySQL, PHP, et cetera

This is what I do on my box to quickly find documentation, which you guys may find helpful.  Especially those of you on Linux—although you could do this on Windows too.

Most package managers make available ‘-doc’ packages, like php-doc, mysql-doc, and so on.  Install these for all the major software you use.

Next, install ‘screen’.

Now put this is your Bash config:

# Displays various types of documentation.

function doc() {
    case "$1" in
    'llvm')
        screen -t 'LLVM Documentation' w3m /usr/share/doc/llvm-doc/html/index.html ;;
    'erlang')
        screen -t 'Erlang Documentation' firefox /usr/share/doc/erlang-doc-html/html/doc/index.html ;;
    'python')
        screen -t 'Python Documentation' w3m /usr/share/doc/python3-doc/html/index.html ;;
    'php')
        screen -t 'PHP Documentation' w3m /usr/share/doc/php-doc/html/index.html ;;
    'ghc')
        firefox /usr/share/doc/ghc6-doc/index.html & ;;
    'postgresql')
        screen -t 'PostgreSQL Documentation' w3m /usr/share/doc/postgresql-doc-8.4/html/index.html ;;
    'mysql')
        screen -t 'MySQL Documentation' w3m /usr/share/doc/mysql-doc-5.0/refman-5.0-en.html-chapter/index.html ;;
    'apache')
        screen -t 'Apache Documentation' w3m /usr/share/doc/apache2-doc/manual/index.html ;;
    'j')
        screen -t 'J Documentation' w3m ~/Software/j602/help/index.htm ;;
    'lua')
        screen -t 'Lua Documentation' w3m /usr/share/doc/lua5.1-doc/doc/index.html ;;
    'git')
        screen -t 'Git Documentation' w3m /usr/local/share/doc/git-doc/index.html ;;
    'lighttpd')
        screen -t 'Lighttpd Documentation' w3m /usr/share/doc/lighttpd-doc/ ;;
    'plt-scheme')
        screen -t 'PLT Scheme Documentation' w3m /usr/share/plt/doc/index.html ;;
    'gambit')
        screen -t 'Gambit Documentation' w3m /usr/share/doc/gambit-doc/html/index.html ;;
    'tintin++')
        screen -t 'TinTin++ Documentation' zless /usr/share/doc/tintin++/tintin19.txt.gz ;;
    'sqlite')
        screen -t 'SQLite Documentation' w3m /usr/share/doc/sqlite3-doc/index.html ;;
    'django')
        screen -t 'Django Documentation' w3m /usr/share/doc/python-django-doc/html/index.html ;;
    'sbcl')
        screen -t 'SBCL Documentation' w3m /usr/share/doc/sbcl-doc/html/index.html ;;
    'boost')
        screen -t 'Boost Documentation' w3m /usr/share/doc/libboost-doc/HTML/index.htm ;;
    'smalltalk')
        screen -t 'GNU Smalltalk Documentation' info Smalltalk  ;;
    'haskell-tutorial')
        screen -t 'Haskell 98 Tutorial' w3m /usr/share/doc/haskell98-tutorial/html/index.html ;;
    'haskell-report')
        screen -t 'Haskell 98 Report' w3m /usr/share/doc/haskell98-report/html/index.html ;;
    'java')
        firefox "/home/eric/Documents/Books/Programming/Java SDK/index.html" & ;;
    esac
}

Replace ‘w3m’ with the browser you want to use.  And make sure the paths are correct.  If you’re on a Debian-based box, that’s where those doc packages will end up.

Now whenever you’re at the terminal you can simply run stuff like

$ doc git
$ doc postgresql

to browse through the official docs.

For Git in particular you will have to build the HTML docs.  In the Git source directory:

$ make html
$ sudo make install-html

There ya go, easy way to look up docs quickly.