Bash - Setup

We learn about setting up BASH for its running and coding environment. Here, you'll also learn the basic working mechanism for BASH programming language. You can use various editor to code your BASH scripts.

How BASH and Shell Works

To work with BASH, you need to understand its origin and how it works in *nix environment.


Pipeline Nature

BASH, like any other SHELL programming languages, is relying on *nix operating system pipeline management tool. In another word, it connects an input command, output of the command into another command's input like the way we join water pipes. This conforms the UNIX or Linux filesystem and its concepts. Here's an example:

Let's list all the files and directory using ls:

$ ls
bin  code_development  Desktop  Documents  Downloads  Music  Pictures  pkg  Public  snap  src  Templates  Videos

Next, let's pass it to a filter, say we want to remove "Desktop" from the ls output, we use pipe "|" command:

$ ls | grep -v "Desktop"
bin
code_development
Documents
Downloads
Music
Pictures
pkg
Public
snap
src
Templates
Videos

Next, let's say we want to modify the output to be a human dialog, we pass the filtered output again to xargs with echo command:

$ ls | grep -v "Desktop" | xargs -I {} echo "Here is a file:" {}
Here is a file: bin
Here is a file: code_development
Here is a file: Documents
Here is a file: Downloads
Here is a file: Music
Here is a file: Pictures
Here is a file: pkg
Here is a file: Public
Here is a file: snap
Here is a file: src
Here is a file: Templates
Here is a file: Videos

Notice the output has changed by piping the first command (ls) into the second command (grep) then lastly xargs-echo command. That's pipeline and almost all UNIX and LINUX system is based on this concept. BASH leverages this concept to reach its maximum potentials.


The Video Camera of Your Terminal Command

Another way to imagine shell scripts (including BASH) is like a video camera capturing an action moment. You can view the action repeatedly in the video and it will perform the exact same action every single time.

The difference here is that BASH is a terminal command capturing tool. It captures the list of commands (action) you want and packed it into a script. Then, you execute this script to repeat the set of commands again and again, consistently. Here's an example for scripting a Go programming language installation from their official site:

#!/bin/bash
go_version="$1"
arch="$(dpkg --print-architecture)"
go_pack="go${go_version}.linux-${arch}.tar.gz"
go_install_path="/usr/local/go"
go_pack_url="https://dl.google.com/go/$go_pack"
                                                                                                                                                                                                                   
sudo rm -rf "$go_install_path" > /dev/null
wget "$go_pack_url"
tar -xvf "$go_pack"
sudo mv go "$go_install_path"
rm "./$go_pack"
mkdir -p "${HOME}/src"
mkdir -p "${HOME}/pkg"
mkdir -p "${HOME}/bin" 

This way, every-time when there is a new version of Go releases, I can save my time to run this script instead of typing each line of commands in it.

Dependencies

BASH, by itself, doesn't have any working feature modules. It relies heavily on the executable programs available on a Unix system to get the job done.

In another word, BASH potentials become very limited if the core executables are not available, like coreutils or busybox. These libraries are the one responsible for facilitating commands like echo, [[, eval, grep, etc. Hence, always keep in mind that every SHELL script depends heavily on the operating system's facilities.

That being said, on the contrary, it very easy to learn BASH since there is only a little handful of things and conventions for you to remember.


Resolving Dependencies

Thankfully, most UNIX system already packed the core packages into it. You can try using which command to identify a particular command's availability.

$ which echo
/bin/echo
$

If you want to know which package is responsible for this executable, use your package manager to identify it. Here is an example checking echo command from debian package manager using dpkg:

$ dpkg -S /bin/echo 
coreutils: /bin/echo

$ dpkg -s coreutils 
Package: coreutils
Essential: yes
Status: install ok installed
Priority: required
Section: utils
Installed-Size: 15103
Maintainer: Michael Stone <mstone@debian.org>
Architecture: amd64
Multi-Arch: foreign
Version: 8.26-3
Replaces: mktemp, realpath, timeout
Pre-Depends: libacl1 (>= 2.2.51-8), libattr1 (>= 1:2.4.46-8), libc6 (>= 2.17), libselinux1 (>= 2.1.13)
Conflicts: timeout
Description: GNU core utilities
 This package contains the basic file, shell and text manipulation
 utilities which are expected to exist on every operating system.
 .
 Specifically, this package includes:
 arch base64 basename cat chcon chgrp chmod chown chroot cksum comm cp
 csplit cut date dd df dir dircolors dirname du echo env expand expr
 factor false flock fmt fold groups head hostid id install join link ln
 logname ls md5sum mkdir mkfifo mknod mktemp mv nice nl nohup nproc numfmt
 od paste pathchk pinky pr printenv printf ptx pwd readlink realpath rm
 rmdir runcon sha*sum seq shred sleep sort split stat stty sum sync tac
 tail tee test timeout touch tr true truncate tsort tty uname unexpand
 uniq unlink users vdir wc who whoami yes
Homepage: http://gnu.org/software/coreutils

$

In case you need a particular command, you'll need to search it out and install it into the system. There are various ways to install an open-source software. Perhaps, you can start with package manager.

Shell Configuration Files

BASH is one of the shell languages. It runs on a environment settings or a profile. As part of the core UNIX system, shell settings and profiles conformed to the Filesystem Hierarchy Standards. That also means for:

  • User specific settings - the configuration file is in $HOME or ~.
  • System-wide settings - the configuration file is in /etc/.


Profile vs. RC

There are 2 types of configuration files: the profile, and the rc. They both have their different "trigger points". Hence, you should know the "trigger-point" for your settings and put them correctly into the correct file. Keep in mind that for MacOS, due to their modification in the operating system architecture, every terminal is a new login instance. That said: you need to study your operating system to know which configuration files make sense. Here is a summary of their respective differences:

  • profile (profile, .bash_profile)

Profile are the setting files configured for every login attempts before prompting. This is only executed once after you login into a machine whether locally or remotely (e.g. SSH).

  • rc (bash.rc, .bashrc)

rc are the setting files configured for every new interactive terminal prompt. This means that you already login into a system and you initiate a new terminal instance, like calling a new /bin/bash terminal for execution.

  • logout (.bash_logout)

Opposite to profile script, this script run for every logout. This is very useful for automatic cleanup work right before you logout from a machine whether locally or remotely (e.g. SSH).


List of Common Configuration Files

There are other configuration files with different trigger points like completion However, here, we cover these 5 main configuration files since it is commonly known and maintained by any level of users. These are the 5 common configuration files:

  • /etc/profile, /etc/profile.d/* — system-wide terminal initialization file.
  • /etc/bash.bashrc — system-wide per-interactive configuration file.
  • $HOME/.bash_profile — user-specific terminal initialization file.
  • $HOME/.bashrc — user-specific per-interactive configuration file.
  • $HOME/.bash_logout — user-specific terminal logout file.

You should know the "area of effect" for your settings before inserting them into the configuration file. System-wide means it affects every single terminal level regardless user; user-specific means it only affects that particular user. The operating system first reads the system-wide configurations file, and then reads the user-specific configuration file, performing any necessary overrides from the latter to the former.


Master Copy Location

Now that you know the location of the configuration files, you should also know the master copy of them, just in case you screwed up somewhere and urgently need to restore it.

All UNIX system provides a backup user-specific configuration copy in /etc/skel directory.

That being said, you do not edit the files inside there; you should only use the correct file to restore your user-specific configuration file accordingly.


Commonly Use

For beginner and (most) expert usage, we try to maintain 1 setting script as much as possible: $HOME/.bashrc. This way, it easier to backup and grow the settings as soon as your Unix experience grows. There are other forms of settings files like $HOME/.zshrc for different terminal types. In those scenarios, we apply symbolic link to it. Example:

$ ln -s $HOME/.bashrc $HOME/.zshrc

This way, ZSH terminal uses the BASH terminal configurations without much modifications.

NOTE: Since $HOME/.bashrc is usable across different shell terminals, you must keep it POSIX compliant to maintain portability.


It is very rare cases we apply system-wide configurations in /etc/profile. However, if you really need to do so, we create an independent bash script and put it inside /etc/profile.d/. The main reason is that /etc/profile loops over the /etc/profile.d/ directory and execute each scripts inside it. The caveat however, is that your setting file must be stateless and independent since you can't sequentially guess which script runs first.

Necessary Configurations

Now that you know where to place your configurations. It's time to review each of the necessary settings and apply it into your $HOME/.bashrc. Again, in case you screwed up, you can always restore from the master copy.


PS1 Prompt

This is governed by the $PS1 environment variable. This is the one that is responsible for printing the "prompt". The default is:

  • \s-\v\$ - produces a bash-4.1$ prompt like:
bash-4.1$ 

You can modify accordingly to the manual. Example: say we change it to \u@\h:\w$. This gives us:

holloway@localhost:/var/log$

To change it, simply export PS1 environment variable:

bash-4.1$ export PS1="\u@\h:\w$"
holloway@localhost:/var/log$

If you like it and you want to make it available permanently, save the command (export PS1="\u@\h:\w$") into your $HOME/.bashrc.

Most UNIX operating system has PS1 pre-configured. Here is an example from the default Debian $HOME/.bashrc:

if [ "$color_prompt" = yes ]; then
        PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u\[\033[00m\]:\[\033[01;34m\]\W\[\033[00m\]\$ '
else
        PS1='${debian_chroot:+($debian_chroot)}\u:\W\$ '
fi
unset color_prompt force_color_prompt

# If this is an xterm set the title to user@host:dir
case "$TERM" in
xterm*|rxvt*)
        PS1="\[\e]0;${debian_chroot:+($debian_chroot)}\u@\h: \w\a\]$PS1"
        ;;
*)
        ;;
esac

Debian set PS1 based on the condition whether the terminal has color supports and then terminal type. This yields a short and colorful prompt like (at this moment, Google New Site doesn't support color text):

holloway:~$

We don't overwrite the default settings. You can save your PS1 command after this initialization. It will override whatever the default settings did.


Tool-Specific Environment Variables

These are the environment variables for each tools to initialize before use. If you start from a freshly installed operating system, there isn't much to do (since you haven't install any tool yet). Here is an example for Go programming language:

# Go
export GOROOT="/usr/local/go"
export GOPATH="${HOME}"
export GOBIN="${GOPATH}/bin"
PATH="${GOROOT}/bin:${GOBIN}:$PATH"
if [[ "$(which godoc)" != "" &&
        "$(lsof -ni:"6060" -sTCP:LISTEN -t)" == "" ]]; then
        godoc -http :6060 -play &
fi

These instructions came from Go programming language official site and is to append into $HOME/.bashrc. You can also append any tool changes or instructions into the configuration files.


Aliases

Aliases, as a name said, is a re-representation of a command. This means that for commands that you commonly used but always comes with a set of argument settings, you can alias it. Example:

alias ll='ls -alF'
alias la='ls -A'
alias l='ls -CF'

All 3 aliases are ls command with different arguments. Aliases can also form a new command based on the old command like:

# Nvidia
alias nvidia-off="sudo tee /proc/acpi/bbswitch <<<OFF"
alias nvidia-on="sudo tee /proc/acpi/bbswitch <<<ON"

However, we usually refrain from doing it since it's a kind of abuse and can cause confusion if an actual executable is available. A general rule of thumb for aliases is to use them with discipline and restricted to command alteration, not creation.


Path

PATH is a special environment variable for terminal to search for executables in the specified location. You should not use it for your bash operations.

PATH contains a list of default paths for search, they are:

  • /bin
  • /usr/bin
  • /sbin
  • /usr/sbin

You can expand the search directories by appending new paths into it. Example, say I want to have my user-specific bin folder and a current directory bin folder:

export PATH="${PATH}:./bin"        # current directory bin folder
export PATH="${PATH}:${HOME}/bin"  # user-specific bin folder

You basically append path using the colon (:) separator onto the $PATH variable itself.


Application Launcher

In UNIX, we use a Desktop Entry Launcher as an application launcher (or "shortcut" in Microsoft Windows). Just like PATH, XDG_DATA_DIRS is another special environment variable for terminal to search for application launcher and populate it into your start menu or showing in your GUI. You should not use it for your bash operations. XDG_DATA_DIRS contains a list of default paths for search, they are:

  • /usr/local/share
  • /usr/share
  • /usr/share/gdm
  • /var/lib/menu-xdg
  • /usr/local/share/
  • /usr/share/
  • /usr/local/share/
  • /usr/share/
  • /usr/share/gdm/
  • /var/lib/menu-xdg/

You can expand the path similar to PATH, like for example, I have all my launchers stored in ${HOME}/launchers folder:

export XDG_DATA_DIRS="${XDG_DATA_DIRS}:${HOME}/launchers"  # user-specific launchers folder

You basically append path using the colon (:) separator onto the $XDG_DATA_DIRS variable itself.

There are other settings as well but these would be suffice for the basic bare-minimum usage. You can proceed to the next topic.