BASH as a Command Line Interface

Change login shell

Run chsh (change shell) (or ypchsh if prompted).

It will check /etc/shells (man "shells"). If the new shell is not valid, you'll get something like this:

/bin/bash is unacceptable as a new shell.

If failed changing the default login shell, workaround like the following example:

# For csh, put the following snippet into your ~/.login (which is loaded by
# login shell only)
if ( -t 0 ) then # interactive shell
    setenv SHELL `which bash`
    exec $SHELL --login

Input and key binding

Getting <Delete> and <Backspace> to work just right

It is nontrivial, but the first thing you should try is:

stty ^?

If that does not work, read the following links for more details

To disable enable XON/XOFF flow control

By default, the flow control is enabled. That is, if C-s is pressed, the output will "freeze". Note that the terminal is still accepting inputs, it is just the output that is paused. A C-q will restart the output.

Nowadays, we are using terminal emulators most of the time and this feature only services to obfuscate and confuse. Hence, turn it off with the following command:

stty -ixon

To enter special characters in Bash CLI

Use C-q or C-v to escape the characters. For example, to enter ^M, i.e. '\r' (CR: Carriage Return, ascii 0x0d):

  1. Press C-q (or C-v if C-q is interpreted as XOFF)
  2. Then, press C-m

Customize key bindings

  • To list available key-bindings
    bind -p
  • Refer to inputrc section in my profile

Session types

  • "login shell" versus "non-login shell"
  • "interactive shell" versus "non-interactive shell"
  • An example

    ssh remote_host cmd starts an interactive but non-login shell (hence source in .bashrc but not .bash_profile) and run 'cmd' rather then just run 'cmd' directly. In this case, the shell is, even though indeed a non-interactive shell, technically an ephemeral interactive but NO terminal shell, .

    This case also implies, in my opinion, that [ -t 0 ] is better than [ -n "$PS1" ] in terms of testing if a shell is really interactive since [ -t 0 ] always fails in this case.

(Hints: .bash_profile, .bashrc, alias etc.)

Commands types

To find out what a "command" really is

  • type [-a] cmd
  • help cmd (for shell builtin, like man for non-builtins)
  • which cmd
  • whereis cmd
$ type printf
printf is a shell builtin

$ type -a printf
printf is a shell builtin
printf is /usr/bin/printf

$ help printf
printf: printf [-v var] format [arguments]
     printf formats and prints ARGUMENTS under control of the FORMAT. FORMAT

$ which printf

$ whereis printf
printf: /usr/bin/printf /usr/include/printf.h ...

To escape aliases

Prepend the command with a backslash. Say, your ls is an alias to ls --color=auto, and now you want the original ls. just \ls.

NOTE that, in bash, aliases are disabled automatically in non-interactive sessions (scripts).

Navigating among directories

shell options

  • autocd
  • cdspell
  • dirspell


For bash, check (info "(bash) Directory Stack Builtins") for usage of pushd, popd, dirs. Refer to my profile for a simple wrapper which makes it even more convenient.

directory bookmark

Also, take a look at the lsdm, dN, gN (and dumpdm, loaddm) in my profile. This solution is complementary to pushd. It facilitates bookmarking directories.

bookmark current directory as gN
goto corresponding bookmarked directory
list current directory bookmark

Here is an example of how to use it:

  • Remember directories and jump to them easily
    [994] 10:05:46 lungangfang@qdbuild2 ~
    $ cd sandbox/main/TelicaRoot/components/sp_system/scripts_rhx86/
    [995] 10:05:59 lungangfang@qdbuild2 ~/sandbox/main/TelicaRoot/components/sp_system/scripts_rhx86
    $ d0
    [1000] 10:06:02 lungangfang@qdbuild2 ~/sandbox/main/TelicaRoot/components/sp_system/scripts_rhx86
    $ lsdm
    g0='cd /home/lungangfang/sandbox/main/TelicaRoot/components/sp_system/scripts_rhx86'
    [1001] 10:06:05 lungangfang@qdbuild2 ~/sandbox/main/TelicaRoot/components/sp_system/scripts_rhx86
    $ cd ~/tmp
    [1002] 10:06:11 lungangfang@qdbuild2 ~/tmp
    $ d1
    [1003] 10:06:51 lungangfang@qdbuild2 ~/tmp
    $ cd ~/doc/sdm
    [1004] 10:06:59 lungangfang@qdbuild2 ~/doc/sdm
    $ d2
    [1005] 10:07:03 lungangfang@qdbuild2 ~/doc/sdm
    $ lsdm
    g0='cd /home/lungangfang/sandbox/main/TelicaRoot/components/sp_system/scripts_rhx86'
    g1='cd /home/lungangfang/tmp'
    g2='cd /home/lungangfang/doc/sdm'
    [1006] 10:07:05 lungangfang@qdbuild2 ~/doc/sdm
    $ g0
    [1007] 10:07:08 lungangfang@qdbuild2 ~/sandbox/main/TelicaRoot/components/sp_system/scripts_rhx86
    $ g1
    [1008] 10:07:11 lungangfang@qdbuild2 ~/tmp
    $ g2
    [1009] 10:07:13 lungangfang@qdbuild2 ~/doc/sdm
  • Persist and load directory marks between sessions
    [1030] 10:09:24 lungangfang@qdbuild2 ~/sandbox/fid16665
    $ cat ~/.dir_mark
    alias g0='cd /home/lungangfang/sandbox/aluac'
    alias g1='cd /home/lungangfang/sandbox/BRANCH-8-3-0-5'
    alias g2='cd /home/lungangfang/sandbox/fid16665'
    [1031] 10:09:11 lungangfang@qdbuild2 ~/sandbox/fid16665
    $ loaddm
    [1030] 10:09:22 lungangfang@qdbuild2 ~/sandbox/fid16665
    $ lsdm
    g0='cd /home/lungangfang/sandbox/aluac'
    g1='cd /home/lungangfang/sandbox/BRANCH-8-3-0-5'
    g2='cd /home/lungangfang/sandbox/fid16665'
    [1032] 10:13:11 lungangfang@qdbuild2 ~/sandbox/fid16665
    $ d0
    [1033] 10:14:28 lungangfang@qdbuild2 ~/sandbox/fid16665
    $ dumpdm
    [1034] 10:14:38 lungangfang@qdbuild2 ~/sandbox/fid16665
    $ cat ~/.dir_mark
    alias g0='cd /home/lungangfang/sandbox/fid16665'
    alias g1='cd /home/lungangfang/sandbox/BRANCH-8-3-0-5'
    alias g2='cd /home/lungangfang/sandbox/fid16665'

    If you are using bash, maybe you can put "dumpdm" to .bash_logout to automatically dump directory marks. and put "loaddm" into .bash_profile (or .bashrc) to load directory marks automatically.


# remember to put the '.' at the beginning

cd to a similar hierarchy

Say, you are in "/home/lgfang/1.0/demo/test" and you want to change directory to "/home/lgfang/2.0/demo/test".

In ksh, you can just cd 1.0 2.0, but Bash does not provide this. Here is small function to simulate that.

function cdr {
    # cdr from to
    if [ $# -ne 2 ]; then
        echo "Usage: cdr from to" >&2
        return 1

    cd "${PWD/$1/$2}"

Command history

Recall previous command

In addition to C-r (suppose you have not set -o vi) to search backward in command history, this section gives some examples for other frequently used features.

For more detailed and more accurate info, refer to (info "(bash)Using History Interactively").

  • Before try the techniques described below, you might want to add this to profile so that you get a chance to look at the resulted command instead of running it directly.
    shopt -s histverify
  • !N:xxx to retrieve the N^th command and tweak it.

    Suppose the 1025^th command is this:

    $ to -a -s bono08


    | input                | result          | comment                  |
    | !1025                | to -a -s bono08 | the whole command        |
    | !1025:*              | -a -s bono08    | all parameters           |
    | !1025:^              | -a              | first parameter          |
    | !1025:$              | bono08          | last parameter           |
    | !1025:2-3            | -s bono08       | 2nd-3rd parameters       |
    | !1025:s/bono08/hp25/ | to -a -s hp25   | replace bono08 with hp25 |
  • !str:xxx works the same way except it designates the last command which starts with string "str".
  • !#:xxx for current command line typed so far
  • !!:xxx for the last command
  • !-N:xxx for command N lines back (!-1 == !!).
  • To print history number in PS1
    export PS1='[\!]\$ '
  • Refer to my profile for the usage of history-search-backward.

To disable history temporarily

Ignore commands started with spaces

export HISTCONTROL="ignorespace"

Issue of this method is that you tend to forget to prepend a space to all commands.

Ignore all commands within current session



export HISTFILE=/dev/null

The advantages of this way over previous one are:

  1. The history is still available until the session end. It is just not saved on exit. To be accurate, it is saved to nowhere (/dev/null).
  2. This can be done afterwards. That is, you may run some commands before you aware that you want to hide them. It does not matter. Just make sure you run unset "HISTFILE" before you close the session.

NOTE: unset is NOT inherited by child shells. Therefore, you unset HISTFILE before starting a tmux session does not take effect. Hence, unset HISTFILE is a good interim solution (autocompletion of var) while an alias of export HISTFILE=/dev/null seems a good candidate for long-term approach.

To edit command history

By editing history file

Even though BASH provides history -d xxx to delete history entries, I found it is way more convient and straightforward to just edit the history file.

  1. If there are other shells, quit them or unset their HISTFILE.

    Every shell load history on start and write history on quit. This step is to ensure existing shells will not overwrite the history file.

  2. Unset HISTFILE in current shell.

    Do not overwrite history file with current shell's history either.

  3. Edit and save history file in current shell.

Using history -d

If you do want to use history -d, here are my two cents

  1. Note that the history numbers shift every time you delete a command history. (Consequently, you may want to delete commands bottom up.)
  2. Note that the history -d xxx itself also gets into the history.

Related shell options

Refer to my profile

Related key binding

Refer to my profile

Command completion

Completion scripts

Usually, we do not write scripts of programmable completion by ourselves. We just google and download ready-made scripts.

To "install" those scripts,

  • Simply put them file into a standard completion directory such as /etc/bash_completion.d/. Or,
  • Put them to anywhere you see as appropriate and source it in your bashrc.
    if [ -d $HOME/.bash_completion.d ]; then
        for each in $HOME/.bash_completion.d/*; do
            source $each

Write your own simple complete

To write your own auto-completion, it is really easy to get started. Here is an example:

complete -W "list of all words for an automatic completion" your_command

Refer to following resources for more details

Make use of "Job Control"

  • Assuming you have only one session and you are debugging a script. You are constantly switching between vim and CLI. You do not have to enter and quit vim again and again. Just put the vim to background using C-z to run command in CLI. After that, bring vim to foreground using fg.


Colors for ls

  • One way to change them as indicated by /etc/profile.d/ is to create your own version of .dir_colors.$TERM.
    [ -e "/etc/DIR_COLORS.$TERM" ] && COLORS="/etc/DIR_COLORS.$TERM"
    [ -e "$HOME/.dircolors" ] && COLORS="$HOME/.dircolors"
    [ -e "$HOME/.dir_colors" ] && COLORS="$HOME/.dir_colors"
    [ -e "$HOME/.dircolors.$TERM" ] && COLORS="$HOME/.dircolors.$TERM"
    [ -e "$HOME/.dir_colors.$TERM" ] && COLORS="$HOME/.dir_colors.$TERM"
    # ...
    eval `dircolors --sh "$COLORS" 2>/dev/null`
  • An simpler way is, if you only change one color or two, is to put the following line into your .bashrc:
    export LS_COLORS='di=38;5;33'
    # NOTE: this will wipe out other LS_COLORS settings

Colors for PS1

  • Format:
  • Example:
    # for 256-color
    PS1='\n\[\e[38;5;10m\][\!] \t \[\e[38;5;106m\]\u@\h'
    PS1=$PS1' \[\e[38;5;33m\]\w\n\[\e[32m\]\$\[\e[0m\] '
    # a similar one for 8-color
    PS1='\n\[\e[32m\][\!] \t \[\e[33m\]\u@\h'
    PS1=$PS1' \[\e[34m\]\w\[\e[32m\]\n\$\[\e[0m\] '


Also refer to /etc/DIR_COLORS or /etc/DIR_COLORS.256color for detailed explaination of codes for colors. Even though it is for ls, the codes also applicable to PS1.

To show each one of 256 colors in bash

for i in {0..255};do echo -e "\x1b[38;5;${i}mcolour${i}"; done


for i in {0..255};do printf "\x1b[48;5;${i}m ";done;printf "\x1b[0m\n"


for i in {0,1,29,30,31};do
    printf "$((i*8))\t"
    for j in {0..7}; do
        printf "\x1b[48;5;$((i*8+j))m  "
    printf "\x1b[0m\n"
for red in {0..5}; do
    printf "$((16+red*36))\t"
    for green in {0..5};do
        for blue in {0..5}; do
            printf "\x1b[48;5;$((16+red*36+green*6+blue))m  "
        printf "\x1b[0m "
    printf "\x1b[0m\n"

The safer "rm"

alias rm='rm -I' # This is way better than 'rm -i' and 'rm'

Why "cannot access parent directories"

If you keep in a directory while it is deleted and recreated in another session, you may observe "wierd" phenomenon.

  1. ls always show "old content".
  2. For some commands, you may get the following error:

    Shell-init: error retreieving current directory: getcwd: cannot access parent directories: No such file or directory.

Why command output does not show up realtime

Stdout are usually line-buffered when connected to display and block-buffered when redirected into files. For the later case, the ouput may not show up timely or even may not appear at all. Some examples:

  1. If you redirect logs into a log file and tail the log, there is usually a delay.
  2. "tcpdump -i ethN | grep some_thing" may not work as you expect (matched lines may not show at all or there will be huges delays). You should use option "-l" to make tcpdump stdout line buffered.

My profile

Created: 2015-12-09 Wed 15:06 by Emacs 24.5.1 (Org mode 8.2.10)

comments powered by Disqus