BASH as a Command Line Interface
Table of Contents
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 endif
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):
- Press
C-q
(orC-v
ifC-q
is interpreted as XOFF) - 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, likeman
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 /usr/bin/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
pushd
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.
- dN
- bookmark current directory as
gN
- gN
- goto corresponding bookmarked directory
- lsdm
- 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.
CDPATH
# remember to put the '.' at the beginning CDPATH=.:/home/lgfang:/local/home/lgfang
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 fi 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
Then:
| 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
unset HISTFILE
or
export HISTFILE=/dev/null
The advantages of this way over previous one are:
- 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
). - 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.
- 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.
- Unset HISTFILE in current shell.
Do not overwrite history file with current shell's history either.
- Edit and save history file in current shell.
Using history -d
If you do want to use history -d
, here are my two cents
- Note that the history numbers shift every time you delete a command history. (Consequently, you may want to delete commands bottom up.)
- 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 done fi
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 http://www.gnu.org/software/bash/manual/html_node/Programmable-Completion-Builtins.html#Programmable-Completion-Builtins http://www.debian-administration.org/article/316/An_introduction_to_bash_completion_part_1 http://www.debian-administration.org/article/316/An_introduction_to_bash_completion_part_2
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 usingfg
.
Colors
Colors for ls
- One way to change them as indicated by
/etc/profile.d/colorls.sh
is to create your own version of.dir_colors.$TERM
.COLORS=/etc/DIR_COLORS [ -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:
\[\e[color_code+m\]
- 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\] '
Reference:
http://misc.flogisoft.com/bash/tip_colors_and_formatting
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
or
for i in {0..255};do printf "\x1b[48;5;${i}m ";done;printf "\x1b[0m\n"
or
#!/bin/bash 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 " done printf "\x1b[0m\n" done 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 " done printf "\x1b[0m " done printf "\x1b[0m\n" done
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.
ls
always show "old content".- 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:
- If you redirect logs into a log file and tail the log, there is usually a delay.
- "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.