I'LL GET AROUND TO FORMATTING THIS THIS WEEKEND
Introduction
Bash (Bourne Again SHell) is a GNU replacement for the standard Unix Bourne shell, it executes commands from the command line or a bash shell script. It has extended the original Bourne shell extensively adding bits from C shell and Ksh and adding a number of handy elements all it's own. Because of its ease of use it has become the de-facto default shell for many users and is installed on almost all Unix systems.
Tab completion
Tab completion in bash allows you to enter part of a filename or command and have the rest of it filled out automatically on hitting the tab key, if there are more than one way of completing the string bash will complete it up to the point at which the available values differ if you hit tab again, it will show you the available values, a very handy feature.
Editing on the command line
The command line can be thought of as an Emacs editor buffer of your .bash_history file with your current command buffer appended, While all of emacs isn't present there are a lot of useful editing commands available, the following keys are the most useful shortcuts (in my opinion)
Emacs keys on the command line
Movement
Ctrl-F Forward one char
Alt-F Forward one word
Ctrl-B Back one char
Alt-B Back one word
Ctrl-A Go to beginning of line
Ctrl-E Go to end of line
Ctrl-R Search for string in previous command history
Editing
Ctrl-D Cuts the following character
Ctrl-U Cuts everything to the left
Ctrl-K Cuts everything to the right
Ctrl-W Cuts the word to the left
Ctrl-Y Yanks the current buffer (Paste)
Ctrl-T Swap previous character with preceeding character
Alt-T Swap words with current cursor position as axixs
ALT-DOT Insert the last "word" from the previous command at the current cursor position
Support Files
Bash uses a number of hidden files, the three most important are
- .bash_profile
- .bashrc
- .bash_history
.bash_profile
Your bash profile file and .bashrc are similar oin that they are used to set up your environment, the difference is that your profile is executed when you login whereas your rc file is executed for non-login calls to bash, ~(eg. su or sudo bash, xterms etc).
My .bash_profile looks as follows
if [ -f ~/.bashrc ]
. ~/.bashrc
fi
This simply tests ([]) for the existence of a file (-f) in my home directory (~/) called .bashrc and if it is present executes it (. ~/.bashrc)
.bashrc
Here you set up any environment variables you wish to configure, any aliases you prefer to use and run any initial command you wish, it is recommended that you export these values so that they are inherited by all sub-shells and software running in your shell. A useful template .bashrc
# import system wide defaults
if [ -f /etc/bashrc ]
. /etc/bashrc
fi
# extend our path to other useful commands
export PATH=$PATH:/sbin:/usr/bin:/usr/sbin/:/usr/local/bin:/usr/local/sbin:$HOME/bin:/usr/java/bin
# increase the size of our history buffer
export HISTFILESIZE=1000
# maximise the utility of our history file
export HISTCONTROL=ignoreboth:erasedups
# if vim is installed prefer it over vi
if [ -x /usr/bin/vim ]
export EDITOR=/usr/bin/vim
export VISUAL=/usr/bin/vim
export FCEDIT=/usr/bin/vim
fi
# Set up a couple of useful shortcuts
export alias j='jobs'
export alias l.='ls -d .*'
export alias ll='ls -l '
export alias la='ls -lA'
export alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'
.bash_history
In your home folder there is a hidden file called .bash_history, this contains your previous $HISTFILESIZE commands from previous sessions (default is 500), The current session is saved in the running bash process and available to search also, it is commited to the file when the session exits. You can alter the way this mechanism behaves by adding the following values to the environment variable HISTCONTROL (as a colon seperated list) otherwise all commends are saved to the list
ignorespace lines which begin with a space character are not saved in the history list.
ignoredups causes lines matching the previous history entry to not be saved.
ignoreboth shorthand for ignorespace and ignoredups.
erasedups remove all previous lines matching the current line to be removed from the history list before that line is saved.
The built in command fc command (fix command) can be used in conjuntion with the history file to help you automate common actions in shell scripts
Environment Variables
Environment variables can be used to control a number of settings in the shell, we have seen above how the various settings of HISTCONTROL can influence the behaviour of this module, the same is true for many other modules and applications. They typically indicate a prefered helper application or location for a program to use:
A few common environmental variables
CLASSPATH Directories to search for other classes when running a java application, colon sepearated list
MANPATH A clon seperated list of directories to look in for manual pages
PATH A colon seperated list of directories to search for executable commands
LD_LIBRARY_PATH A colon seperated list of directories to look in when looking for shared object libraries
EDITOR The user's prefered editor (eg. when you hit v in less)
These values can be set thus
VARIABLE=value
and the value is returned if you pre-pend a $(mnemonic, "value of"), eg.
echo $VARIABLE
In order to inherit the setting in sub-shells you must export the variable
export VARIABLE
Writing scripts in bash
- Variables
- Input Field Separator and command substitution
- Conditions
- looping
- Redirection, pipes and tee
- Command substitution
- logical operators
As with environmental variables variables can be assigned a value thus
variable=value
and the value of the variable accessed as $variable. You can use the for loop in bash as follows
for i in *; do
found=0
grep "The missing value" $i && found=1
if [ found -eq 0 ]
rm $i
else
echo $i >>foundIn
fi
done
There are a number of ways to join simple commands together to make a powerful one line script using the redirection opperators (|, >, >>and <).
| (pipe) forwards the output of the previous command into the next command. eg
grep $threadID $file|wc -l
The if statement can be used with any command, if the command returns true the commands contained in the if (elif|fi) block will be executed otherwise the commands in the elif fi block, eg.
if [ 1 -gt 2 ] then
echo "This shouldn't happen"
elif [ 1 -eq 2 ] then
echo "This shouldn't happen either"
else
echo "one is not greater than two"
fi
This example uses the shorthand for the built in test command bash provides which can be used for conditional statements, the following two statements are equivalent, but the second is easier to read in my opinion.
if test 1 -lt 2 ; then echo "one is less than two" ; fi
if [ 1 -lt 2 ] ; then echo "one is less than two" ; fi
The -lt modifier tests that the left hand side is less than the right hand side for numeric expressions, the following tests are also available:
Usage Explanation
-d file True if file exists and is a directory.
-e file True if file exists.
-f file True if file exists and is a regular file.
-x file True if file exists and is executable.
-O file True if file exists and is owned by the effective user id.
-G file True if file exists and is owned by the effective group id.
-g file True if file exists and is set-group-id.
-h file True if file exists and is a symbolic link.
-k file True if file exists and its sticky bit is set.
-p file True if file exists and is a named pipe (FIFO).
-r file True if file exists and is readable.
-s file True if file exists and has a size greater than zero.
-u file True if file exists and its set-user-id bit is set.
-w file True if file exists and is writable.
-L file True if file exists and is a symbolic link.
-N file True if file exists and has been modified since it was last read.
file1 -nt file2 True if file1 is newer (according to modification date) than file2, or if file1 exists and file2 does not.
file1 -ot file2 True if file1 is older than file2, or if file2 exists and file1 does not.
file1 -ef file2 True if file1 and file2 refer to the same device and inode numbers.
-z string True if the length of string is zero.
-n string True if the length of string is non-zero.
string1 == string2 True if the strings are equal. = may be used in place of == for strict POSIX compliance.
string1 != string2 True if the strings are not equal.
string1 < string2 True if string1 sorts before string2 lexicographically in the current locale.
string1 > string2 True if string1 sorts after string2 lexicographically in the current locale.
arg1 OP arg2 OP is one of -eq, -ne, -lt, -le, -gt, or -ge. These arithmetic binary operators return true if arg1 is equal to, not equal to, less than, less than or equal to, greater than, or greater than or equal to arg2, respectively. Arg1 and arg2 may be positive or negative integers.
The output of a command can be treated as a value using command substitution, eg
for i in $(ls ~/src/*/*.pl); do grep -C3 flock $i;done
here the $(ls ~/src/*/*.pl) returns a list of files which the for loop iterates through (this is actually a poor example as bash's globbing would do this anyway)
If you carry out a process regularly and want to script it, a handy tool is fc (fix command)
Called on it's own it opens the previous command in your chosen editor and runs the contents of the buffer on exit, however you can call it with a number of previous commands to include in the buffer allowing you to then write out the edited buffer as a shell script.eg.
philip@luggage:~/src/vim-6.1.405$ ./configure
philip@luggage:~/src/vim-6.1.405$ make
philip@luggage:~/src/vim-6.1.405$ sudo make install
philip@luggage:~/src/vim-6.1.405$ fc -3 -1
And then in your editor ....
#!/bin/bash
./configure
make
sudo make install
...
:w ~/bin/buildit.sh
The fc command can either use a count of the number of commands to include in the edit or it can take the command names themselves, eg
~/$ fc grep ls
This command will import all lines from the most recent grep to the most recent ls command, this is probably the most common fc -10 -1 Import the previous 10 commands into an editor ($FCEDIT) and run the resulting file on quit, you can save from within your editor to resulting in a shell script for subsequesnt use. fc -l -10 List the previous 10 commands You can make the execution of a command dependant on the exit status of the previous command through the use of the logical opperators (||,and &&, OR and AND)
And Finally a handy construct:
for i in *.txt;do mv $i ${i%.txt}.bak;done
which I think of as a text mode modulus it substututes the ending of the file, a very handy command and more efficient than calling sed for each itteration of the loop