Runtime Environment

Description

This document primarily serves to describe features of the interactive Bash shell. There are two important things to do:

Root, non-root and sudo

As a "normal", or "non-root" user on a system, with no administrative privileges, virtually everything you create or edit are files are within your home directory:
/home/LOGIN
From the shell perspective, indication of your non-root status is the usage of the "$" marker as the prompt suffix. As system administrator, most things you do are done directly or indirectly as root user. Any time your actions affect a file outside your home directory, you must be root via: Running commands as root via sudo is quite straightforward:
$ sudo some-command-as-root
[sudo] password for LOGIN:
For example, try doing a full listing of the protected home directory of root:
$ ls -a /root
$ sudo ls -a /root
To run a root shell, one uses sudo to invoke the root shell. There are several different ways to do so which we will explore later. For now, the easiest way to become root is the run the su program through sudo:
$ sudo su
#
A root shell appears with the "#" prompt, a feature built into the bash program itself which actually interprets "$" as "#" when the user is root. You can see a discussion of it in the bash manual in the section:
PROMPTING

Notational conventions

There are three common symbols used when presenting Linux commands:
$   prompt for a non-root command
#   prompt for a root command
~   (tilde) the user's home directory, used only for a non-root command
The point in greatest need of emphasize is that you should not be using a root shell for command with the ~ symbol. We will always make the root's home explicit as:
/root

File Editing/Creation

For almost all system files you will need to be root to edit the file. The two ways of achieving are through a root shell or through sudo, e.g.
$ sudo su
# nano SOME-SYSTEM-FILE
# gedit SOME-SYSTEM-FILE &
or
$ sudo nano SOME-SYSTEM-FILE
$ sudo gedit SOME-SYSTEM-FILE &
Try to remember the implications of statements like
"create the file so-and-so," or
"edit the file so-and-so."
The most important thing is to decide whether you need to be root or not.

Usually a file with a path name starting with "/", it is a system file and you need to be root. For example, "edit the file /etc/skel/.bash_aliases" means, possibly, one of:
$ sudo nano /etc/skel/.bash_aliases
$ sudo gedit /etc/skel/.bash_aliases &
If a file starts with "~", the implications is that it resides in your home directory and you do not want to be root. For example, "edit the file ~/.bashrc" means, possibly, one of: This might be achieved by this:
$ nano ~/.bashrc
$ gedit ~/.bashrc &

Shell Environment

The shell environment is the set of pre-defined features available to the shell user. These features are of these types: The creation of these settings is begun by the login process which is controlled by several key administrative files, primarily these: A validated user process runs the /bin/bash executable by virtue of the user's entry in the /etc/passwd file. The user obtains a login environment which you can see by running either of the commands:
$ printenv
$ export
One of the most important of these environment variables is PATH, for which you can see the current value by:
$ printenv PATH
$ echo $PATH
The PATH variable controls where to find executable programs requested from the shell.

Interactive Shell Features

What we think of as a normal shell is called interactive because it is prompt-driven, waiting on user input and running some internal or external command. It would seem that all shell executions are interactive, but not so; for example, this remote command runs in a non-interactive shell from taz:
$ ssh taz export
versus, running from an interactive shell on taz:
[taz]$ export
An interactive shell also provides variables specific to the interactive shell, like these
PS1:        initial shell prompt
SHELLOPTS:  interactive shell options
COLUMNS:    number of columns in terminal (80)
One can contrast, say, PATH with COLUMNS by executing:
$ echo $COLUMNS
$ printenv COLUMNS      (nothing)
All variables and functions can be seen by running the set command. The output of set can be large, so you might want to page through it:
$ set | more          (type blank to continue, "q" to quit)

How it appears in ~/.profile

These are all done as normal (non-root) user. Start by creating a directory for your personal executables:
$ mkdir ~/bin
The ~/.profile script is activated whenever you log in. Take a look at how this gets applied: Here it is:

~/.profile
...
if [ -n "$BASH_VERSION" ]; then
    # include .bashrc if it exists
    if [ -f "$HOME/.bashrc" ]; then
	  . "$HOME/.bashrc"
    fi
fi
 
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi
In particular, when you log in, your personal ~/.bashrc is loaded and your ~/bin directory is added to your page. The implication of the outer if block:
if [ -n "$BASH_VERSION" ]; then
  ...
fi
is that presumably ~/.profile can be used by other shell programs (like /bin/sh), but we only want to load ~/.bashrc if the shell is a BASH shell.

Add $HOME/bin to the PATH

The user ~/bin directory is intended for executables only accessible to the user. Ubuntu recognizes the need for this with the lines in the ~/.profile file:

~/.profile
# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin:$PATH"
fi
This says: "if there is a ~/bin subdirectory, add it to the PATH." There is really no need to embed this PATH addition into an if statement, but to have this take effect
$ mkdir ~/bin
The file ~/.profile takes effect only when you log in.

Log out, log back in and check:
$ printenv PATH
/home/LOGIN/bin:...

Same for root and new users

Set up a private bin directory for the root user as well. In this case we have to add a PATH modification line into /root/.profile.
$ sudo mkdir /root/bin
$ sudo nano /root/.profile
Append the following line:
PATH=/root/bin:$PATH
Likewise, do the same for all new users:
$ sudo mkdir /etc/skel/bin

Aliases and functions

A bash shell also provides other features other than variables: aliases defined by the bash alias command, which offer simple substitutions for commands; and functions, which are more complex command substitutions. Aliases can be seen by running the Bash command:
$ alias
Among the listed aliases you'll see:
alias l='ls -CF'
alias la='ls -A'
alias ll='ls -alF'
alias ls='ls --color=auto'
Experiment with these a bit:
$ /bin/ls           normal ls
$ ls                colorized ls
$ which ls          doesn't express alias
/bin/ls
$ la                shows hidden files
$ ll                long listing with hidden files
As you see the redefinition of ls through the last alias is picked up by previous aliases, making them all give a colorized listing.

For the most part, aliases, in which arguments can be added to the end, serve to extend your Bash scripting needs, but occasionally you need a function. For example key this (artificial) example into your shell (the >'s are prompts}
tazweb() {
> ssh taz ls "$@" ~/public_html
> }
Then try
$ tazweb
$ tazweb -la
This added function shows up as the last entry in
$ set
and can be removed by
$ unset tazweb

Add aliases

Ubuntu provides an easy "hook" to add aliases for interactive shells. An interactive bash shell sources the file

~/.bashrc
...
# Alias definitions.
# You may want to put all your additions into a separate file like
# ~/.bash_aliases, instead of adding them here directly.
# See /usr/share/doc/bash-doc/examples in the bash-doc package.
 
if [ -f ~/.bash_aliases ]; then
    . ~/.bash_aliases
fi
...
The syntax says "if ~/.bash_aliases exists, source it." We will explain the idea of sourcing a file, but the suggestion is that additions are best placed into the file:
~/.bash_aliases
Towards this end create the file:
$ nano ~/.bash_aliases
and add this content:

~/.bash_aliases
#!/bin/bash
 
# make rm, mv, cp "interactive" in that they will query the user
# if the outcome would be to delete/modify an existing file
# the "-i" option means "interactive"
alias rm='rm -i'
alias mv='mv -i'
alias cp='cp -i'
 
alias path='printenv PATH'  # a convenience for checking PATH
alias more='less'           # less is a better pager than more
 
# set the "noclobber" option so that output redirection to an 
# existing file with ">" is must be overwritten with ">|"
set -C
select
These aliases will go into effect for all subsequent shells. You can put them into effect for the current shell by sourcing this file:
$ . ~/.bashrc
or
$ source ~/.bashrc
Verify the outcome by:
$ alias
$ path
The "set -C" shows up as the "noclobber" shell option in:
$ echo $SHELLOPTS

Change for all users

Put this into effect for other new users as well as the root user, run these administrative commands:
$ sudo cp ~/.bash_aliases /etc/skel
$ sudo cp ~/.bash_aliases /root

Color prompt (optional)

Edit the ~/.bashrc file. As normal (non-root) user, make the following editing changes to ~/.bashrc; only the first one is required. Our TERM is xterm, which you can verify by typing:
$ echo $TERM
It clearly supports color, but ~/.bashrc script doesn't turn color on automatically. Fix this by editing ~/.bashrc and looking for these lines:
# uncomment for a colored prompt, if the terminal has the capability; turned
# off by default to not distract the user: the focus in a terminal window
# should be on the output of commands, not on the prompt
#force_color_prompt=yes
As suggested, uncomment the last line:
force_color_prompt=yes
The actual color settings are done later in this block:
if [ "$color_prompt" = yes ]; then
    PS1=...
else
    PS1=...
fi
Color/style settings are created by the "special strings" like "\[\033[01;32m\]" which you see in the prompt definition. Some alternative color/styles you can try are these:
\[\033[01;31m\]            (bold/red)
\[\033[01;34m\]            (normal/reddish)
Just make sure that the last color change string is "\[\033[00m\]".

Shortened Prompt (optional)

The standard Bash prompt can be "overly long", leaving little space for the command when you get sufficiently deep into the directory structure. Fix by editing ~/.bashrc and look for these lines:
if [ "$color_prompt" = yes ]; then
    PS1='... \w\[\033[00m\]\$ '
else
    PS1='${debian_chroot:+($debian_chroot)}\u@\h:\w\$ '
fi
Replacing the occurrences of "\w" by "\W" (lower case to upper case) will show only the basename of the current working directory.

Prompt on line following full directory name

Another common alternative is to make the prompt complete on the line following the printing of the full directory. Do this by inserting a newline character "\n" just before the end of the prompt string, i.e, make "\$" into "\n\$".

Adding functions to add to ~/.bash_aliases

Remember that you can add your own user-defined functions to the ~/.bash_aliases file. Here is one which might server well. Recall from the Ubuntu Desktop Installation in the GUI Editors section, we suggested this complex command to use to edit a file, disconnecting the editor from the terminal shell:
$ nohup gedit ~/.bashrc 2>/dev/null &
Make this call convenient by adding the following function (name it as you like):

~/.bash_aliases (added)
function ge() {
  nohup gedit "$@" >/dev/null 2>&1 &
}
Then, simply call:
$ ge ~/.bashrc
The meaning of the syntax is:

Being root

Limitations of sudo usage

Although we are implicitly invoking root powers when we use sudo its usage has certain limitations compared to running a root shell. Try this little test scenario:
$ sudo su
# touch /root/test1 /root/test2
# ls /root/test*
# exit
$ sudo ls /root/test*
The final sudo fails even though we could list each of the test files individually. The issue is that the "*" creates a glob expansion at the level of non-root user, and cannot list the two root files.

A second, perhaps more ominous problem is this:
$ sudo rm /root/test1
$ sudo su 
# rm /root/test2
You will see that the first rm command goes ahead without interactive confirmation whereas the second rm command forces confirmation of removal because this alias is in effect:
alias rm='rm -i'

Root shell access operations

It is sometimes convenient to work directly in a root shell, if for nothing else, the convenience of not having to start every line with sudo. These compare the alternatives:
$ path               Observe your home-bin in PATH

$ sudo su
# path               no /root/bin in PATH
# pwd                working directory unchanged
# exit

$ sudo su -
# path               PATH has /root/bin
# pwd                working directory is /root
# exit

$ sudo -H bash -l
# path               PATH has /root/bin
# pwd                working directory unchanged
# exit
The last of these has the advantage of setting a complete root login PATH while "staying where you are." To make most the effective use of the last alternative, a good idea is to create an alias for it such as:

~/.bash_aliases
...
alias besu='sudo -H bash -l'

Execution/Sourcing, Shell Environment

A bash script something.sh can be executed or sourced. Executing a bash script is most commonly thought of as what is done with any program
$ bash something.sh
It need not be executable, but if so, we can also do:
$ ./something.sh
sourcing something.sh also executes the commands within. It is done in one of these two synonymous ways:
$ source something.sh      or      $ . something.sh
What is the difference? It has to do with what environment variables and settings are available to the executed vs. the sourced script and changes to the environment.

Start with this simple example. Create these two simple files in your home directory:

vars1.sh
x=11
echo "x=$x"

vars2.sh
echo "x=$x"
The correct understanding is that an executed script runs in a sub-shell of the interactive shell and a sourced script runs becomes part of the interactive shell per se. Thus we observe this about the x variable:

Open a shell and run this test:
$ bash vars1.sh
x=11
x set when executed in subshell, its value is printed
$ echo $x x is unset in the interactive shell
$ . vars1.sh
x=11
x set when executed in interactive shell
$ echo $x
11
x now set in the interactive shell
$ bash vars2.sh
x=
x not available to scripts executed in a subshell
$ . vars2.sh
x=11
x is set for a sourced script the interactive shell
$ unset x x is now undefined

Exported environment variables

It is important to be able to set variables in the interactive shell, like PATH and have them available to scripts executed within this shell; this is termed exporting the variables. Such variables must use the export declarator. In Bash, the export can be done before, after or during its definition, i.e., the outcome of these are all the same:
export MYVAR
MYVAR=33
MYVAR=33
export MYVAR
export MYVAR=33
Try this experiment. Create these two Bash scripts:

exp1.sh
export MYVAR=33
echo "MYVAR=$MYVAR"

exp2.sh
echo "MYVAR=$MYVAR"
Then run these commands:
$ bash exp1.sh
MYVAR=33
MYVAR defined by execution in a subshell and printed
$ echo $MYVAR MYVAR undefined in interactive shell
$ . exp1.sh
MYVAR=33
MYVAR defined in interactive shell by sourcing
$ echo $MYVAR
33
confirm that MYVAR defined in interactive shell
$ bash exp2.sh
MYVAR=33
MYVAR now defined in all subshells executed from the interactive shell
$ unset MYVAR un-define the variable

Aliases only work in interative shell

This is easy to verify. Create the script:

foo.sh
alias foo='echo FOO'
foo
Then try this:
$ bash foo.sh                              (1)
foo.sh: line 2: foo: command not found
$ . foo.sh                                 (2)
FOO
$ foo                                      (3)
FOO
$ unalias foo                              (4)
A synopsis of the outcome:
  1. aliases only available for interactive shell
  2. sourcing can define and uses the alias
  3. once defined, an alias is available to the interactive shell
  4. undefine the alias

Functions are usable in subshell but not exportable

Create these script bar.sh:

bar.sh
function bar() {
  echo "calling bar"
}
bar
select

callbar.sh
# call bar
bar
Then try this experiment which tries to mimic what an exported variable achieves:
$ bash bar.sh                                 (1)
calling bar
$ . bar.sh                                    (2)
calling bar
$ bar                                         (3)
calling bar
$ . callbar.sh                                (4)
calling bar
$ bash callbar.sh                             (5)
callbar.sh: line 1: bar: command not found
A synopsis of the outcome:
  1. unlike aliases, functions can be defined and used in executed scripts
  2. sourcing defines bar function within interactive shell
  3. bar function available
  4. bar function available to sourced script
  5. bar function not exported to subshells


© Robert M. Kline