Shell Customization

This chapter will discuss some of the bash features that you can use to customize the command line environment.

Environment Variables

From wikipedia: Environment variable:

An environment variable is a dynamic-named value that can affect the way running processes will behave on a computer. They are part of the environment in which a process runs. For example, a running process can query the value of the TEMP environment variable to discover a suitable location to store temporary files, or the HOME or USERPROFILE variable to find the directory structure owned by the user running the process.

See bash manual: Shell Variables for the complete list of bash variables. Some of them are presented below and some (HISTCONTROL for example) will be discussed later in this chapter.

  • HOME The current user's home directory; the default for the cd builtin command. The value of this variable is also used by tilde expansion
  • PS1 The primary prompt string. The default value is \s-\v\$
  • PS2 The secondary prompt string. The default value is >
  • PATH A colon-separated list of directories in which the shell looks for commands. A zero-length (null) directory name in the value of PATH indicates the current directory. A null directory name may appear as two adjacent colons, or as an initial or trailing colon
  • PWD The current working directory as set by the cd builtin
  • OLDPWD The previous working directory as set by the cd builtin
  • SHELL This environment variable expands to the full pathname of the shell

You can use the printenv command to display the name and value of all the environment variables. Providing arguments will display the values only for those variables.

$ printenv SHELL PWD HOME
/bin/bash
/home/learnbyexample/cli-computing
/home/learnbyexample

info warning It is recommended to use lowercase for user defined variable names to avoid potential conflict with environment variables. You might have noticed that I used only lowercase names in the Shell Scripting chapter.

info See also unix.stackexchange: How to correctly add a path to PATH?.

Aliases and Functions

To create an alias, use the appropriately named alias command. Without any arguments, it will list all the currently defined aliases. If you want to know what an existing alias does, provide one or more names as arguments. To actually create an alias, give a name, followed by = and then the command to be aliased. There should be no spaces around the = operator. Use type name to check if that name is already taken by some command. Here are some examples:

# mapping 'p' to the 'pwd' command
$ type p
bash: type: p: not found
$ alias p='pwd'
$ p
/home/learnbyexample/cli-computing

# adding '--color=auto' to 'ls' invocations
$ type -a ls
ls is /bin/ls
$ alias ls='ls --color=auto'
$ type -a ls
ls is aliased to 'ls --color=auto'
ls is /bin/ls

Here's how you can check what the above aliases do:

$ alias p ls
alias p='pwd'
alias ls='ls --color=auto'

info As seen above, aliases have higher precedence compared to commands in the PATH. You can use a \ prefix (for example \ls) if you want to avoid an alias and use the original command. You can also use command ls instead of the escape character.

If you need to pass arguments to your custom commands, use a function (or write a shell script). Here's an example function:

# prefix current path to the given arguments
$ ap() { for f in "$@"; do echo "$PWD/$f"; done; }

$ p
/home/learnbyexample
$ ap ip.txt mountain.jpg
/home/learnbyexample/ip.txt
/home/learnbyexample/mountain.jpg

info The aliases and functions created above will be valid only for that particular shell session. To load these shortcuts automatically, you need to add them to special files. See the next section for details.

You can use the unalias command to remove an alias. For functions, use the unset -f command.

$ unalias p

$ unset -f ap

$ type p ap
bash: type: p: not found
bash: type: ap: not found

Config files

You can add customizations to special configuration files so that those settings are automatically loaded when you start an interactive shell session.

.bashrc

From bash manual: Startup Files:

When an interactive shell that is not a login shell is started, Bash reads and executes commands from ~/.bashrc, if that file exists.

You'll likely have a ~/.bashrc file provided by the Linux distro you've installed, with useful settings like enabling bash programmable completion features, aliases and so on. I leave the distro provided settings alone, unless they are related to aliases and shell options that I want to customize.

Some of the shopt customizations I use are shown below. shopt was discussed briefly in the Shell Features chapter. See bash manual: Shopt Builtin for more details.

# append to history file instead of overwriting
shopt -s histappend

# extended wildcard functionality
shopt -s extglob

# helps to recursively match files within a specified path
shopt -s globstar

I prefer a simple prompt PS1='$ ' instead of fancy colors. See bash manual: Controlling the Prompt for customization options. See also starship which is a minimal, blazing-fast, and infinitely customizable prompt for any shell.

Some history customizations are shown below. See bash manual: History Facilities for more details. See also unix.stackexchange: common history across sessions.

# ignorespace prevents lines starting with space from being saved in history
# erasedups deletes previous history entries matching the current one
HISTCONTROL=ignorespace:erasedups

# maximum number of history lines in the current shell session
# older entries will be overwritten if the size is exceeded
# use a negative number for unlimited size
HISTSIZE=2000

# maximum number of lines in the history file
HISTFILESIZE=2000

For aliases and functions, I use a separate file named ~/.bash_aliases to reduce clutter in the .bashrc file. This is not a file that is loaded automatically, so you need to add source ~/.bash_aliases command in the .bashrc file.

Some of my favorite aliases and functions are shown below. See my .bash_aliases file for more.

alias c='clear'
alias p='pwd'
alias e='exit'

alias c1='cd ../'
alias c2='cd ../../'
alias c3='cd ../../../'

alias ls='ls --color=auto'
alias l='ls -ltrhG'
alias la='l -A'

alias grep='grep --color=auto'

# save the last command from history to a reference file
alias sl='fc -ln -1 | sed "s/^\s*//" >> ~/.saved_cmds.txt'
alias slg='< ~/.saved_cmds.txt grep'

# case insensitive file search
# fs search is same as find -iname '*search*'
fs() { find -iname '*'"$1"'*' ; }

info You can use source with .bashrc or .bash_aliases files as arguments to apply changes from such files to the current shell session.

.inputrc

You can add custom key bindings to the ~/.inputrc file. See bash manual: Readline Init File for more details.

A few examples from my ~/.inputrc file are shown below:

$ cat ~/.inputrc
# use up/down arrow to match history based on starting text of the command
"\e[A": history-search-backward
"\e[B": history-search-forward
# use history-substring-search-backward and history-substring-search-forward
# if you want to match anywhere in the command line

# ignore case for filename matching and completion
set completion-ignore-case on

# single Tab press will complete if there's only one match
# multiple completions will be displayed otherwise
set show-all-if-ambiguous on

info You can use bind -f ~/.inputrc or press Ctrl+x Ctrl+r to apply changes from the .inputrc file to the current shell session.

Further Reading

Readline shortcuts

Quoting from bash manual: Readline Interaction:

Often during an interactive session you type in a long line of text, only to notice that the first word on the line is misspelled. The Readline library gives you a set of commands for manipulating the text as you type it in, allowing you to just fix your typo, and not forcing you to retype the majority of the line. Using these editing commands, you move the cursor to the place that needs correction, and delete or insert the text of the corrections.

By default, the command line editing bindings are styled after Emacs (a text editor). You can switch to Vi mode (another text editor) if you wish. This section will discuss some of the often used Emacs-style key bindings.

Tab completion

The tab key helps you complete commands, aliases, filenames and so on, depending on the context. If there is only one possible completion, it will be done on single tab press. Otherwise, you can press the tab key twice to get a list of possible matches (if there are any).

Use set show-all-if-ambiguous on as seen earlier in the .inputrc section to combine the single and double tab presses into a single action.

info See bash manual: Programmable Completion for more details.

Searching history

You can use Ctrl+r to search through the command history. After pressing this key sequence, type characters you wish to match from history, then press the Esc key to return to the command prompt or press Enter to execute the command.

You can press Ctrl+r repeatedly to move backwards through matching entries and Ctrl+s to move forwards. If Ctrl+s is not working as expected, see unix.stackexchange: disable ctrl-s.

As discussed in the .inputrc section, you can use custom key mappings instead of the default offerings.

Moving the cursor

The documentation uses Meta (M- prefix) and notes that this key is labeled as Alt on many keyboards. The documentation also mentions that you can also use the Esc key for such combinations.

  • Alt+b move the cursor to the start of the current or previous word
  • Alt+f move the cursor to the end of the next word
  • Ctrl+a or Home move the cursor to the beginning of the command line
  • Ctrl+e or End move the cursor to the end of the command line

info One difference between Alt and Esc combinations is that you can keep pressing b or f while holding the Alt key down. The Esc combinations are two different key presses, whereas Alt has to be kept pressed down for the shortcut to take effect.

Deleting characters

  • Alt+Backspace (or Esc+Backspace) delete backwards up to word boundary
  • Ctrl+w delete backwards up to whitespace boundary
  • Ctrl+u delete from the character before the cursor till the start of the line
  • Ctrl+k delete from the cursor location to the end of the command line

Clear screen

  • Ctrl+l preserve whatever is typed and clear the terminal screen

info Note that Ctrl+l doesn't try to remove the scrollback buffer altogether. Use the clear command for that purpose.

Swap words and characters

  • Alt+t (or Esc+t) swap the previous two words
  • Ctrl+t swap the previous two characters
    • for example, if you typed sp instead of ps, press Ctrl+t when the cursor is to the right of sp

Insert arguments

  • Alt+. (or Esc+.) insert the last argument from the previous command, multiple presses will traverse through second last command and so on
    • for example, if cat temp.txt was the last command used, pressing Alt+. will insert temp.txt
    • you can also use !$ to represent the last argument from the previous command

Further Reading

Copy and paste

Shortcuts for copy-paste operations in the terminal are shown below. You might be able to customize these shortcuts in the terminal preferences.

  • Shift+Ctrl+c copy the highlighted portion to the clipboard
  • Shift+Ctrl+v paste clipboard contents
  • Shift+Insert paste the last highlighted portion (not necessarily the clipboard contents)

You can also press the middle mouse button instead of the Shift+Insert shortcut. This is not limited to the terminal, works in many other applications too. You can use the xinput command to enable/disable mouse button clicks. First, use xinput without any arguments and spot the number corresponding to your mouse. As an example, assuming the device number is 11, you can use the following commands:

  • xinput set-button-map 11 1 0 3 to disable middle button click
  • xinput set-button-map 11 1 2 3 to enable middle button click

Exercises

1) Which command would you use to display the name and value of all or specific environment variables?

2) If you add an alias for an already existing command (ls for example), how would you invoke the original command instead of the alias?

3) Why doesn't the alias shown below work? What would you use instead?

# doesn't work as expected
$ alias ext='echo "${1##*.}"'
$ ext ip.txt
 ip.txt

# expected output
$ ext ip.txt
txt
$ ext scores.csv
csv
$ ext file.txt.txt
txt

4) How would you remove a particular alias/function definition for the current shell session?

$ alias hw='echo hello world'
$ hw
hello world
# ???
$ hw
hw: command not found

$ hw() { echo hello there ; }
$ hw
hello there
# ???
$ hw
hw: command not found

5) Write an alias and a function to display the contents of the PATH environment variable on separate lines by changing : to the newline character. Sample output is shown below.

$ echo "$PATH"
/usr/local/bin:/usr/bin:/bin:/usr/games

# alias
$ a_p
/usr/local/bin
/usr/bin
/bin
/usr/games

# function
$ f_p
/usr/local/bin
/usr/bin
/bin
/usr/games

6) Will a login shell read and execute ~/.bashrc automatically?

7) What should be the value assigned to HISTSIZE if you wish to have unlimited history entries?

8) What does the binding set completion-ignore-case on do?

9) Which shortcut helps you interactively search the command history?

10) What do the shortcuts Alt+b and Alt+f do?

11) Are there differences between the Ctrl+l shortcut and the clear command?

12) Which shortcut will you use to delete characters before the cursor till the start of the line?

13) What do the shortcuts Alt+t and Ctrl+t do?

14) Is there a difference between the Shift+Insert and Shift+Ctrl+v shortcuts?