[ Prev ] [ Index ] [ Next ]

Bash

Created Thursday 24/8/2006

Bash (Bourne-Again SH) is a POSIX shell and has a rich programing interface as well as an easily usable interactive shell.

1. Fixing screen size

Shell terminal windows can sometimes get a bit confused. This may be caused, in some older environments, by simply doing a resize on the terminal window. The more recent incarnations of the bash(1) shell can deal automatically with terminal window resizing. However, there are still circumstances where the environment can become unusuable or unreadable and it is useful to know how to recover from this without having to close the terminal.

1.A. Fix vim half-screen

To fix the case where only about half the terminal is used or when there's no color in say, vim(1), make sure that TERM has a sensible value, such as "xterm" (e.g., export TERM=xterm) and then eval(1) the resize(1) command:

bash$ eval $(resize)

Note that when using screen(1), the terminal needs to first be detached (ctrl+a D) before executing the eval(1). After executing the resize, reattach the screen with the -r option, and the terminal size should be correct.

bash$ screen -T xterm -r

1.B Fix terminal weirdness

If the terminal is displaying weird and unreadable characters (such as -␉▒⎽␤ ⎽≤⎽␌⎺┼°␋±), which might happen if a binary executable is cat(1)'d to stdout, then executing a terminal reset(1) can recover the terminal:

bash$ reset

If the terminal is showing readable characters, then it may not be possible to read the content of the commands being typed. In this case, clear the terminal with ctrl+u and then carefully type reset and press enter. Executing the reset ensures that the terminal serial characteristics (i.e., the stty(1) options) are reset to their defaults. Plain text characters should now appear on the terminal.

2. Incrementing a value in a shell script

There are a number of ways to do this. The most portable, across different shells, is to use expr(1), but it's slow. However, bash(1) has a veritable pantheon of ways to perform variable arithmetic or simiple incrementation. These range from using ++, +, and the declare -i options.

2.A. Using expr(1)

This is pretty straight forward and uses the + operator. For example, if a variable i is assigned the value 1, then:

bash$ i=$(expr $i + 1)

Which performs the arithmetic operation i+1 and assign the result back to i.

2.B. The ++ operator

One option for incrementing a variable in bash(1) is to use the c-style post-increment operator. Note, double parenthesis must be used for the ++ operator. The operator can be used in evaluating (and returning) an expression or it can simply be used to perform variable incrementation.

bash$ j=$((i++))  

In the above, i is incremented and the resulting expression is assinged to j. If the value of i was 1, then both i and j are 2 after the above operation. If the intention was to simply increment i, then the following does the trick:

bash$ ((i++))     

2.C. The + operator

Simple arithmetic addition can be performed with the + operator, enclosed in the evaluate syntax $(). The $() evaluates the result and so must be assigned to a variable. If this is not desirable, then double-parenthesis solves the issue:

bash$ i=$(i + 1)    # perform i+1 and assign back to 1
bash$ $((i + 1))    # perform i+1 and throw the result away

2.D. Using ''declare'' or ''typeset''

The bash variable declarion keywords declare (or it's equivelant typeset) can be used to associated a semantic to a variable. Arithmetic evaluation semantic can be given to a variable with declare -i, e.g.,

bash$ declare -i i  # define an arithmetic variable called "i"
bash$ i=1+2         # the semantic of "i" evaluates the arithmetic of 1+2

Consider that without the declare -i, the value of i will have the text string 1+2. Instead i is assigned the value 3.

3. launch an XPM splash screen

The program splash(1) can be used to display an xpm file as a splash screen. The script takes an optional command after the xpm file pathname, which can be used to run some activity while the splash is active. The following example shows an xpm image for 5 seconds.

bash$ splash /usr/share/pixmaps/sample.xpm sleep 5

4. Exit status

A bash shell function will return an exit code equal to the last command executed within the function body (if no explicit return value is provided). A bash shell function can provide a specific exit code set by using the return keyword. To set the exit code to 3, add return 3 as the last line of the function. The following examples illustrate this:

# function "foo" returns the exit code of the "grep" command...
foo() {
    echo foo | grep "foo"
}
# function "bar" always returns an exit code of 1
bar() {
    return 1
}

The return code is accessed with $?, in the same way as for exit codes. For example, if we invoke the above bar() function, we can evaluate the return code:

#!/usr/bin/env bash
bar
[ $? == 0 ] && echo all ok || echo not ok

5. Special variables

Bash has several specical variables like SECONDS, REPLY, RANDOM and etc.

5.A SECONDS

The SECONDS variable is the number of seconds elapsed since the script/process started. E.g.,

#!/bin/bash
sleep 1
echo $SECONDS # 1 second has elapsed, so 1 is printed to stdout

5.B REPLY

The REPLY variable contains the text from read(1), if read is invoked without and arguments. E.g.,

#!/bin/bash
read # user enters abc
echo $REPLY # text "abc" is written to stdout

5.C RANDOM

The RANDOM variable holds a random value between 0 and 32767. Accessing this variable causes a value between this range to be generated and assigned to RANDOM. This variable can be initialized by assigning a value to it. If this variable is unset, then the random values are no longer assigned (even if RANDOM is re-init'd). E.g.,

#!/bin/bash
echo $RANDOM # prints between 0 and 32767
echo $RANDOM # prints between 0 and 32767
RANDOM=100   # init
echo $RANDOM # prints between 0 and 32767
echo $RANDOM # prints between 0 and 32767
unset RANDOM # stop generation
echo $RANDOM # prints nothing
echo $RANDOM # prints nothing
RANDOM=100   # fix random to 100
echo $RANDOM # prints 100
echo $RANDOM # prints 100

See also #20 Generating Random Numbers in bash

6. Base representation - hex, dec, oct, bin

Bash can represent numbers in a variety of bases. By default, numbers are represented in base 10. However bases between 2 and 64 are supported. Bases 10, 8 and 16 have specical shortcut syntax, whilst the other bases use the base#number format. For the base#number format to work, assignment to a variable using let must be used. E.g.,

#!/bin/bash
let "dec = 10"
let "hex = 0x10" # base 16 shortcut, specify with prefix of 0x
let "oct = 010" # base 8 shortcut, specify with prefix of 0
let "bin = 2#10" # base 2 
let "pen = 5#10" # base 5
let "six4 = 64#10" # base 64, max base supported
echo dec=$dec # prints 10
echo oct=$oct # prints 8
echo hex=$hex # prints 16
echo bin=$bin # prints 2
echo pen=$pen # prints 5
echo six4=$six4 # prints 64p

7. Array variables

Bash has array variables which can be extended dynamically and are indexed starting from 0. All array variable operations must be done with the variable enclosed in the ${} syntax.

7.A Array initialization

Creating and initializing an array variable can be done by assingment using parenthesis or alternatively, any existing array can be extended simply by assigning to a subscript at the end of the array. Here's an example of each:

bash$ anArray=(a b c)
bash$ aSecondArray[0]=a

7.B Array access

Accessing an element of an array variable is done using the [subscript] syntax, again with the array variable enclosed in ${}. The following example accesses the second element of an array:

bash$ echo ${anArray[1]}

7.C Array length

The length of an array variable can be determined with the ${#array[subscript]} where subscript can be @ or * (note both are specical positional parameters and expand to the number of words in the parameter (with the word boundary defined by the first character of the IFS special variable). For example:

bash$ anArray=(a b c)
bash$ echo ${#anArray[@]}
3

8. C-like for loop

In addition to the standard for i $list bash has a for-loop that is pretty much the same as that found in C. Syntax from bash(1) describes this loop as:

for (( expr1 ; expr2 ; expr3 )) ; do list ; done

Where expr1, expr2 and expr3 are arithmetic expressions. The loop terminates when expr2 is 0 and the exit status of the loop is the exit status of the list command in list. As is typical with c-like for-loops, expr1 is an initialization component and is executed once at the start of the loop and expr3 is executed each time through the loop and thus can be used as an incrementing component. All the elements expr{1,2,3} are optional and if absent evaluate to 1.

The following is an example of a for-loop to print the contents of an array variable:

bash$ anArray=(a b c)
bash$ for ((i=0; $i < ${#anArry[@]}; i++))
> do
>    printf "element %d = %s\n" $i ${anArray[$i]}
>done
a
b
c
bash $

9. Using egrep in an "if" statement

It is possible to use the return value of egrep(1) as an if test. The return value from egrep(1) is 0 if the grep matches or 1 if the expression doesn't. When using this form, do not use the test(1) (or [] command) as the return value of egrep is required for the if statement evaluation and not the return value of test(1).

# Example 1: Print yes if the file /tmp/file contains the text foo at the start of a line
bash$ echo "foo" > /tmp/foo
bash$ if egrep -qi '^foo.*$' /tmp/foo; then echo yes; fi
yes
# Example 2: Print ok if the file /tmp/foo does not contain the text foo
bash$ echo "bar" > /tmp/foo
bash$ if ! egrep -qi 'foo' /tmp/foo; then echo OK; fi

10. Selective History

The bash specical variable HISTIGNORE defines which values to leave out of the history list. It's possible to define partial matches of command strings and many match definitions can be provided.

In it's most simple form, HISTIGNORE can be set simply to ignore duplicates so that ls;ls only records one ls entry. Clearly, the HISTIGNORE value should be configured in the .bash_profile. For example, to ignore duplicates:

bash $ export HISTIGNORE="&"

In addition to ignoring duplicates, HISTIGNORE can be configured to ignore specific commands such that they don't appear in the history list at all. These can be partial matches or exact string matches. The following example tells the history fc(1) mechanism to ignore duplicates, to never record exit, nor bg nor fg and to ignore ls entries with parameter options:

bash $ export HISTIGNORE="&:exit:[bf]g:ls -"

11. Shell options

Shell options can be specified with shopt(1), whose "-s" option takes a parameter that controls many aspects of the shell command invocation. These shell options should be configured in .bash_profile. All of the shopt parameters described below should be executed (or added into .bash_profile) using shopt, with takes the form shopt -s parameter . For example, to set the cdspell shell option:

bash $ shopt -s cdspell

The following describe some of the available shell options and usages:

11.A. cdpsell

This shopt(1) parameter will correct typos for the cd builtin. These corrections relate to simple typos like transposed characters. For example, with this option set, entering something like:

bash $ cd /hoem

Translates to:

bash $ cd /home

11.B. extglob

This parameter allows for ksh-88 egrep-style extended pattern matching which basically means that the shell supports an extended globing syntax that supports an rx style command syntax. For example, with "extglob" set, executing something like:

bash $ ls /s@(r|y)* 

Which says "match a '/', immediatedly followed by 's', then followed by exactly one occurence of either 'r' or 'y', followed by anything". The previous rx for example matches /srv and /sys but not /sbin nor /selinux.

There are many of these extended rx operators in addition to "@" which matches exactly once. The syntax for the extended glob expression must have an operator followed by a pattern expression which must be enclosed in parenthesis:

image Tip: This option pretty much limted to academic interest only. Too hard to remember all the options unless used very frequently.

11.C. Signalling "end of options"

The end of options can be signalled to a bash process using the "--" syntax. After encountering the end of options "--", the bash(1) shell treats all subsequent characters as filename and arguments. In addition to being understood by "shopt", the "--" end of options notifier can be used with (almost all) commands run through the bash shell. E.g.,

bash $ ls -lAF -- /tmp

12. Control variables && and ||

The control varible && denotes an AND list and the variable || is an OR list. These can be combined with the bash test(1) statement (and it's abbreviation "[") to provide if-then-fi and if-then-else-fi conditional evaluation.

The test(1) statement evaluates to 0 if the expression being evaluated is true and evaluates to 1 if it's the expression is false. A test(1) expression can be combined with the && or || control variables to provide a more concise conditional statement than the more traditional if-then-fi and if-then-else-fi built-ins. The following are examples using AND-list and OR-lists combined with test(1):

# Print "yes" to stdout if the variable "foo" is defined:
bash $ [ -n $foo ] && echo yes
# Print the word "failed" if the variable "foo" is not "bar":
bash $ [ "$foo" == "bar" ] || echo failed

In addition to these simple if-then-fi constructs, the && and || can be combined to produce an if-then-else-fi:

# Print "yes" if foo is defined, otherwise print "no"
bash $ [ $foo ] && echo yes || echo no

13. Expresion comparision: test, [ and [[

image Note: See bash test commands

Bash provides the capacity for boolean logic and expression comparision via test, [ and [[. Essentially test is a synonym for [ and is used to evaluate simple boolean expressions. There are a number of different evaluation types, such as for file, directory, character special, string, numeric comparisions. The following example prints a message if the directory /tmp/foo does not exist:

bash $ [ ! -d /tmp/foo ] && echo directory /tmp/foo not found

Multiple comparision types can be combined in a single test by grouping the test expressions in parenthisis (which must be quoted) and then using bash conjunction (-a) or disjunction (-o) operators. In the following example, a message is printed if /tmp/foo does not exist as a file nor directory:

bash $ [ \( ! -d /tmp/foo \) -a \( ! -f /tmp/foo \) ] && echo directory /tmp/foo not found

Which can be reformulated as:

bash $ [ ! \( -d /tmp/foo \) -o \( -f /tmp/foo \) ] && echo directory /tmp/foo not found

14. The "caller" statement

This bash(1) feature can be used for debug and executing caller within a script will print the line number and function currently being executed.

#!/bin/bash
myfunction() {
    caller
    echo This is \"myfunction\"
}
echo starting \"caller\" test
myfunction

15. Shell functions

Bash supports shell functions, which can be defined within the interactive shell environment or as a function scoped for the life of a process (i.e., from within a shell script). Shell functions have return codes and can embedded other functions, i.e., a function foo() may define another function bar(). A function does not have to return a value, nor does it have to start with the keyword function. A shell function defined in this way is like a typical procedure. Also as discussed in #15, below, shell functions can define locally scoped and typed variables or they may access global variables.

15.A Function returning a value

This example defines a simple function called foo(), which always returns the value 1:

#!/usr/bin/env bash
function foo() {
    return 1
}

15.B An embedded function

This example defines a function that defines an embedded function. The main reason for defining an embedded function is for scoping. In this example, a function is implemented to sent mail based on a condition flag sent to the outer function. The inner function mailspacelow() formats a message and then invokes another function which uses the unix mail(1) program.

#!/usr/bin/env bash
MAIL_RCPT_TO=foo@bar.com
## send mail: Sends the piped input of the function to receipient $MAIL_RCPT_TO
sendmailnow () {
    HOSTNAME=${HOSTNAME:-$(hostname)}
    subject="$HOSTNAME alert: $1"
    mail -s "$subject" "$MAIL_RCPT_TO"
}
## The mail check: perform a function based on arg1. E.g., "domailcheck spacelow"
domailcheck() {
    spacelow () {
            { cat << EOF
                level: $1 freeKB: $2 free%: $3
                 date: $(date)"
                 host: $(hostname)
              EOF
            } | sendmailnow "Freespace Low!"
    }
    foo () {
        sendmailnow "!! FOO TEST !!"
    }
    [ $1 == spacelow ] && spacelow 4 200K 42
    [ $1 == mailfoo ] && foo
}
## start of processing ##
domailcheck
## EOF: mxcheck ##

15.C Shell function tips

There are many tips and tricks for shell function programming as the shell provides a powerful environment which allows the script writer to build up more complicated processing from simpler components. This is indeed the unix philosophy: everything in the enviroment is a file and is composible.

15.C.1 Simple testing facility

A simple testing facility can be implemented by defining a variable, say $debug to be the string "echo" (itself a program, or shell built-in). In the following a fuction performs a task or, if debug is set, prints what it would do:

#!/usr/bin/env bash
debug=echo      # comment this out to run in "real" mode
# execute the command given in args as a shell command, unless
dostuff() { 
    $debug $*
}
dostuff /bin/ls -laF /tmp

In the previous scriplet, the arguments to the dostuff function is an ls(1) command. However, because the variable $debug is set to be echo, the nett effect of the scriplet is to print the text: /bin/ls -laF /tmp, rather than to execute it. To execute the command, simply comment or remove the $debug variable.

15.C.2 Matching user input

Often a script will use the readline function read(1) to get user input from the terminal. It is sometimes useful to map a range of user input to the same behaviour, e.g., case insensitive user input for "Y" or "y". The test built-in can do this quite easily, note the double-test "[[" brackets

#!/usr/bin/env bash
read -n 1 -p "Do stuff [y/n]? "         # read a single character
[[ $REPLY == [yY] ]] \
    && { echo Pressed Y; } \
    || { echo Did not press Y or y; }

16. Local scoping

Recent additions to the bash shell grammer make it possible to restrict the definition of a variable to the scope of a function. Normally, the scoping rules for a bash(1) a variable assigned a value in a function make the value available outside the scope of that function. Using the local keyword in the context of a variable declaration in a function, changes the default scope, making the variable availabe only to the function where is is declared. The options to local are the same as that for declare (and it's equivelant, keyworad typedef), thereby making it possible to define an arithmetic semantic for a locally scopped variable. The following example defines a funtion with a local integer variable:

bash $ myfunc() {
>    foo() {
>        local -i i
>        i=$1+$1
>        j=$i
>        echo when inside context of local scoping i=$i, j=$j
>    }
>    foo 2
>    echo but outside context of local scoping i=$i, j=$j
>}
bash $ myfunc
when inside context local scoping i=4, j=4
but outside context of local scoping i=, j=4

The previous shell function and console output shows that within the scope of function foo, the value of i and j are both 4. However, outside the scope of foo the value of i is unavailable but the value of j (which is not scoped as local) is still 4. The value of j, assigned from within foo, is still avaiable in the context of function myfunc.

17. Command line - with getopts

The bash(1) shell includes getopts(1) as a shell built-in. This can be used to parse a command line of simple flag arguments and option arguments, in both long and short form. The getopts built-in can operate in silent and diagnostic mode, which reports errors for absent options. The getopts built-in uses a number of special variables. These are:

The following sub-sections are an overview of GNU Getopts and bash built-in implementation

17.A. Getopts syntax

The general syntax of getopts(1) is getopts "optstring" "name" where "optstring" is a specificiation of the options and flags that getopts(1) must parse. The value of "name", which is OPTION by convention, will take on the value of the command line option currently being processed. If getopts encounters an absent argument to an option then "name" is assigned the "?" character.

16.B. Quiet mode

If the getopts(1) "optstring" starts with a colon ":", then getopts operates in quiet mode. Additionally, if OPTERR is set to 0, then getopts enters quiet mode regardless of the first character being a colon or not.

17.C. Defining optstring

The options that getopts should parse for are specified by a string, with each character representing a short option. If an option character is followed by a colon ":", then the option is defined as expecting an argument. If getopts does not detect an argument when processing the command line, then it will report a simple error message such as "-a expected one argument". This can be surpresed by either setting the first character of the option string as a colon or by setting OPTERR to 0.

17.D. Getops example

The following parses for 2 short options. One option, called "-a", expects and argument and the other "-b" does not. Getopts is configured to be quiet by setting OPTERR=0 (note, using ":" as the first character of the option string has the same affect). The OPTION will take on the current command line option being processed. Getopts will set this value to "?" if it encounters an absent argument to an option.

#!/usr/bin/env bash
# invoke with something like "testname -a x -b -a x and others"
OPTERR=1
while getopts ":a:b" option
do
  case $option in
    a) echo "[cmdline entry # $OPTIND] Parameter option "A" set to value $OPTARG" ;;
    b) echo "[cmdline entry # $OPTIND] Flag option "B" is now set" ;;
    ?) echo "[cmdline entry # $OPTIND] error, expected option \"-$OPTARG\" to have an argument"; exit 1;;
    *) ;;
  esac
done
# We shift the argument pointer to next non-getopt arg. This means that
# "$*" will now point to any command line values specified after the
# getopts args.
shift $(($OPTIND - 1))
echo cmd=$*

18. CSI-Escape sequences

This is not really a bash issue, but it does relate to shells running in a terminal. The CSI-Escape codes are used control the terminal. The provide features for setting terminal attributes like color and for moving the cursor (to row, column positions). The current cursor position can be saved and restored, the screen can be cleared and etc.

The documentation page for console_codes(1) describes the CSI-Escape codes, which are the common codes for performing these types of functions within shells currenting within tty terminals:

bash $ man console_codes
bash $ man unicode
bash $ man iso-8859-1

The following sections provide examples

image Note: ^[ is ESC, which can be entered (in vim) by typing CTRL+v followed by the ESCape key, where as "[" is a literal bracket character

18.A. Print text at row and column

image Tip: The CSI Esc 7 saves the current row and column and the CSI Esc 8 restores it

The H CSI can be used to move to a particular row and column and then move back to the cursor position. For example, to move to row 10, column 50 and print hello,world, then return to current cursor position, use the following CSI escapes

bash $ printf "\0337\033[10;50Hhello,world\0338"

image Note: The CSI Esc [p;qH is the move to row,column command. The row number is specified first.

18.B. Clear the terminal window

To Clear the current terminal window use the 2J CSI.

bash $ printf "\033[2J"

image Note: \033 is the escape character, the same as typing CTRL+v ESC in vim. The backslash \ form is for entering octal (033 base-8 is 27 base-10). 0x for hex

19. Redirecting stderr/stdout

There are a number of ways of redirecting I/O for stdout, stderr and stdin within in a POSIX shell. I/O redirection can be performed on a per command basis (see #18.A and #18.B), per process (#18.C) and per block (#18.D). An example of per block redirection is where a section of executing code is redirected and then then restored to the original file descriptor.

19.A. Redirecting stdout and stderr to separate files

This is the simplest and most common I/O redirection, where the > (overwrite) or >> (append) directives are specified with the command being executed. The > (and >>) directives can have an optional file descriptor, which defaults to 1 (stdout). In the following, 1> says redirect stdout to path1, overwriting its contents and 2>path2 says redirect stderr to path2, overwriting its contents.

#!/bin/bash
some-command 1> path1 2> path2

image Note: 1> file is the same as > file

19.B. Redirecting stderr to stdout

This example ensures that stderr will be sent to the same file descriptor as stdout. This means that if stdout is redirect to a file then stderr to be written to that same file. This is a variation of #18.A, above.

#!/bin/bash
some-command 1> path1 2>&1

This above redirects any errors to the same location as stdout, the converse is possible too (redirect stdout to stderr as in some-cmd 1>&2

19.C. Per-Process redirection

The redirection in #18.B above is done on a per-command basis, that is, for each command, I/O must be redirected as required. the POSIX shell allows for a redirection command that will redirct all I/O to the location specified for all commands executing within the current shell process:

#!/bin/bash
exec 2>&1
cmd1
cmd2

The above redirects all output sent to stderr by cmd1 and cmd2 to wherever stdout is being sent. It's obviously possible to redirect both to the same place, each to a different location or to redirect just stdout or just stderr.

19.D. Alternate I/O redirection syntax

The bash(1) shell provides for the same result with the syntax [n]>&digit- which says Redirect filedescriptor 'digit' do filedescriptor n, or to the stdout if n is unspecified. So, the following is equivelant to #18.C, above. This is a bit confusing because it in #18.C we say exec 2>&1, which we can read as fd2 moves to the address of fd1. In the alternate syntax we say exec 1>&2-, which, in my opinion doesn't read as well.

#!/bin/bash
exec 1>&2-
cmd1
cmd2

19.E. Conditional I/O redirection

It is also possible to perform i/o redirection only if the specified descriptor IS NOT already redirected. The -t option to test(1) allows this by:

#!/bin/bash
[ -t 2 ] && { exec 2>&1; }
cmd1
cmd2

Where the argument to the -t option of test(1) is the file number of the I/O of interest (i.e., 0=stdin, 1=stdout, 2=stderr). In the above example, the redirection of stderr only occurs if stderr is not already being redirected.

19.F. Create file descriptor duplicates

In this example, stdout is duplicated and then redirected to /dev/null. A few simple commands are executed and then stdout is restored (from the duplicate). This type of example is useful when the outputs of commands need to be captured (or surpressed).

#!/usr/bin/env bash
exec 1>&5   # duplicate stdout to another fd (so we can restore later)
exec 2>&1   # ...and send stderr to the same location
cmd1        # some command that generates output
cmd2        # same as above
echo This does _not_ show on the stdout
exec 5>&1   # restore original stdout
echo This _does_ show on the stdout

19.G. I/O Redirection timing bug

The following causes the final cat(1) to fail in some circumstances due to the i/o redirection and timing issues. about 10% of the time, the i/o has not completed by the time that the pipe exeutes, so that the cat(1) command has to content. Interestly enough, the redirect-with-override (the > commands) truncate the content of file bar so that any previous content is lost, however the new content which should be available from the stdin redirection of the initial cat(1) command is (sometimes) not available.

bash $ cat <<< FOO 1>& bar 1>&2 | cat < bar

20. Signal traps

Shell scripts can implement signal traps for any of the standard signal.h signal specs. Multiple signal traps can be defined, each performing a specific set of commands for a range of signals. The trap(1) built-in is used for this purpose and can be used to both define signal traps and to print any traps currently defined. The following examples are shown as script fragments, but they can also be issued directly from the shell just as easily.

Signal specs can be specified either numerically or by name (as defined in signal.h). The signal names are not case sensitive and the "SIG" prefix is optional (i.e., SIGUSR1 and USR1 and 10 are interchangeable). Clearly, referencing signal specs by name is preferable.

20.A. Print a list of known signals

bash $ trap -l

image Todo: More doco this option this required. Why and when would this option be used. Is it useful interactively or programatically?

20.B. Print a list of current signal traps

bash $ trap -p

image Todo: More doco this option this required. Why and when would this option be used. Is it useful interactively or programatically?

20.C. Simple trap to cleanup on exit

#!/usr/bin/env bash
trap "rm -f somefile" EXIT

image Todo: More doco this option this required. Why and when would this option be used.

20.D. Error trap

The following example shows show an error handler can be implemented using trap(1). The signal spec ERR sets a trap that fires when a simple command, returns a non-zero exit status:

#!/usr/bin/env bash
trap "errorHandler" ERR

image Todo: More doco this option this required. Why and when would this option be used.

image Note: See bash(1) Shell Grammar for a definition of a Simple Command.

20.E. Debug trap

The following shows an example of a debug trap, which fires before and after every simple command. See the section on Shell Grammar in bash(1) for a definition of a Simple Command.

#!/usr/bin/env bash
trap "debugTrap" DEBUG

image Todo: More doco on this option is required. Why and when would this option be used.

image Note: See bash(1) Shell Grammar for a definition of a Simple Command.

20.F. Multiple traps

By now this is pretty obvious, but is possible to setup a number of traps within the same script to perform different options depending on which signals are received.

#!/usr/bin/env bash
trap "interuptTrap" SIGINT SIGUSR1   # only on ctrl+c (2) or usr1 (10)
trap "doCleanup" EXIT               # whenever the script terminates, 
                                    # by normal or abnormal action.
trap -p         # print a list of the current signal traps
interuptTrap() {
    echo Received interupt or usr1 signal
}
doCleanup() {
    caller      # print the call stack
    echo Finalization - script is terminating
}

21. Command line parser using "while"

A simple command line parse can be written using a case construct within a while loop. This simple algorithm desructively shifts through the command line and passes the results through a case statement for the option processing:

#!/usr/bin/env bash
echo cmdline args are: $*
while [ $# != 0 ]
do
    case $1 in
        -p) pmode=1 ;;               	# An example "no argument option". I.e., a flag or switch
        -f) shift ; filename="$1" ;; 	# Example "option with arg". An extra shift is needed to access 'arg'
        *) ;;				   	# Catch-all. Use this for unhandled options or for a default.
    esac
    shift   				   	# Move to next command line item. Variable $1 is moved on
done
echo cmdline args are now: $*        	# The loop is descructive, afterwards $* has no value

Another (not so desirable) way to process through the command line is to use the same style case construct, but within a bash for loop. The bash shell assigns the command line $@ to the for loop if the loop variable is named for a non-existent variable. Significantly, this style does not need to execute a shift operation within the loop (c.f., the while loop version) and so non-descructively processes through the command line (again, compare with the while loop implementation, above):

#!/usr/bin/env bash
echo cmdline args are: $*
for $p
do
    case $1 in
        -p) pmode=1;; 			   	# Example as before, a "no-arg option" (e.g., a flag)
        *) echo "Don;t know of $1";;  	# fault handling, or supported option handling
    esac
done
echo cmdline args are still: $*      	# non-destructive processing through cmdline

22. The linux object linker

The Linux object linker ld(1) uses the the value of the environment varibles $LD_LIBRARY_PATH and $LD_RUN_PATH. The ld(1) documentation tells us the following:

The environment variable LD_LIBRARY_PATH may be used to specify library search directories. In the most general case, it will contain two directory lists separated by a semicolon: dirlist1;dirlist2

Thus, if ld is called with the following occurrences of -L:

ld . . . -Lpath1 . . . -Lpathn . . . -lx

then the search path ordering for the library x (libx.so or libx.a) is:

dirlist1 path1 . . . pathn dirlist2 LIBPATH

LD_LIBRARY_PATH is also used to specify library search directories to the dynamic linker at run time. That is, if LD_LIBRARY_PATH exists in the environment, the dynamic linker will search the directories it names before its default directory for shared objects to be linked with the program at execution.

Thus, you can choose within LD_LIBRARY_PATH which directories to search both before and after the -L directories. It also says:

Additionally, the environment variable LD_RUN_PATH (which also contains a directory list) may be used to specify library search directories to the dynamic linker. If present and not empty, it is passed to the dynamic linker by ld via data stored in the output object file. LD_RUN_PATH is ignored if building a shared object. The paths it specifies are searched by the dynamic linker before those specified by LD_LIBRARY_PATH. Note that LD_RUN_PATH is obsolete. Its use is discouraged in favor of the -R option to ld(1). If -R is specified, then the value of LD_RUN_PATH is ignored.

Todo: Describe LD_RUN_PATH and the -R path option of ld(1)

23. Bash command completion

The bash(1) shell has extensive command completion, which is user entensible. A set of pre-defined command completions for applications such as like p4(1), make(1) and etc can be installed via the bash-completion rpm (yum search bash-completion). Clearly, the most recent rpm should be chosen for the current O/S and platform). The maintainer for Bash Completion is www.caliban.org and a packaged Fedora rpm can be installed via yum.

See also:

image Note:: The bash completion rpm is configuration and is not platform nor architecture specific (packaged as .noarch)

24. BASH_PROMPT (pre-primary prompt command)

The bash(1) variable PROMPT_COMMAND can be used to execute a command prior to issuing each primary prompt (PS1). This is useful for doing things like setting the current directory in the terminal emulator's title bar, executing CSI escapes in the primary prompt (like changing color when in certain directories) and so on.

The standard /etc/bashrc configuration for Fedora looks for a file in /etc/sysconfig called bash-prompt-$TERM (e.g., /etc/sysconfig/bash-prompt-xterm). If this is not found, then the primary prompt PS1 is set to a default command string defined /etc/bashrc. If the value of $TERM does not match xterm or screen, then /etc/sysconfig/bash-prompt-default is used.

image Important: The bash-prompt-* files must have their execution bits set. See chmod(1)

Typically, these files do not exist and are not distributed with Fedora. A simple example bash-prompt-default might look for the existence $HOME/.bash_prompt and, if it exists, might source it's content, falling back to the /etc/bashrc PS1 defaults if not.

The following is an example bash prompt command file that could be installed to /etc/sysconfig (must be mode 755). This example prompt file checks for the existence of a specific file in the user's home and executes it if found, falling back to the traditional bash PS1 value, which sets the username and hostname in the window title string (e.g., "user@host"). The PROMPT_COMMAND variable is actually set in /etc/bashrc to, for example, /etc/sysconfig/bash-prompt-xterm (if $TERM is xterm), so be certain that the contents of bash-prompt-xterm (or -default) does not set PROMPT_COMMAND variable itself. The prompt files should simply execute any relevant commands, such as setting the primary prompt PS1 string.

The following scriptlet is the standard foonix shell command for bash prompt command file. A preference hierarchy is established for which files should be source'd. A fallback command string is printed to stdout, which uses CSI-escape's to set the window title string to the current username and hostname (without dommainname). The characte 7-ascii is required to terminate the window title CSI string. The scriplet does not require tha the user prompt files exist, but if they do, they must be mode 755 otherwise they are not effected.

image Note #1: Cut-n-paste the following scriptlet into /etc/sysconfig/bash-prompt-xterm (requires root).

#!/usr/bin/env bash
if [ -e $HOME/.bash_prompt.${TERM} ]; then
	$HOME/.bash_prompt.${TERM}
elif [ -e $HOME/.bash_prompt ]; then
	$HOME/.bash_prompt
else 
	echo -ne "\033]0;${USER}@${HOSTNAME%%.*}:${PWD/#$HOME/~}\007"
fi

image Note #2: See also bash completion

25. Parameter Expansion

Bash variables have quite a powerful syntax, which allow a variable to be manipulated (assinged a default value if null, substring'd, partial replacement and so on).

25.A. Replacing/Removing a substring

The bash(1) parameter expansion syntax includes an option for removing a substring. Consider:

bash $ x=foobaroof
bash echo ${x/oo/}
fbaroof

The above uses the ${var/pattern/repl} syntax to search for the first occurance of pattern within the variable var, replacing it with repl (which can be empty). The global replace option can be specified by using a double-slash "//" instead of a single slash "/". For example, ${var//pattern/repl} specifies that a global replace all occurences of pattern with repl. The following two examples illustrate using parameter expansion to remove the first occurance and all occurences of a pattern:

bash $ x=foobaroof
bash $ echo ${x//oo/}
fbarf
bash $ echo ${x/oo/}
fbaroof

Clearly, string replacement is performed by providing a non-empty replacement string. In the following, the double-o "oo" is replaced with the string "XX":

bash $ echo ${x//oo/XX}
fXXbarXXf

26. Cleaning up dead processes still attached to a socket

image Note: Use fuser(1) to locate the process holding onto a socket. E.g., 'fuser 8889/tcp''

If a process is attached to a socket and dies without cleaning up the socket connection (or has forked off a child that is still attached to the socket), then it's possible to locate the process and kill it. The fuser(1) program can be used to identify the process ID of any active (or zombie) process using a particular socket.

When given a port and protocol (e.g., 8888/tcp), fuser(1) will display the ID of the process that has attached to that particular socket (via the indicated protocol). For example, imagine that a process has created a TCP connection to port 8889 and we would like to terminate the process so that the socket is freed. We will use fuser(1) to locate the process ID and netstat(8) to determine of the socket is still connected.

bash $ netstat -atn | grep 8889
tcp        0      0 0.0.0.0:8889                0.0.0.0:*                   LISTEN 

The above netstat(8) output shows that there is an active (LISTEN'ing) connection on TCP socket 8889. Using fuser(1), we can locate the process that is holding this particular socket connection:

image Warning: fuser(1) directs the process ID to stdout and the port/protocol (the argument on the fuser command line) to stderr

bash $ fuser 8889/tcp
8889/tcp:            27142

The fuser(1) output shows us that the process with ID 27142 is the culprit. Using a combination of ps(1) and grep(1) we can find out the command which started process 27142:

bash $ ps -elf | grep 27142 | grep -v grep
0 S foo  27142 27096  1  80  0 - 365990 futex_ Apr08 pts/1 00:11:44 /usr/local/jdk/bin/java -Xrunjdwp:transport=dt_socket,address=8889,server=y,suspend=n -Xdebug -Xnoagent -Xmx768M -classpath .:/src

The ps(1) and grep(1) output, above, shows us that a JDK process is holding onto our 8889 TCP socket. This is in fact confirmed by the JDK option -Xrunjwp where we see that the transport argument is dt_socket on address=8889 (if no hostname is specified in the address, then the localhost is assumed). Now that we know which process holds our socket, we can either close the program down in it's recommended way (perhaps exiting a gui or terminating a command line app). If this is not possible (as might be the case if the socket is held by a zombie process), then we can always use kill(1) to clean up the process

bash $ kill -HUP 27142

Next, we'll use netstat(8) to confirm that the socket has been freed. If the process has relinquished the socket, then there should be no output generated by the following netstat command:

bash $ netstat -atn | grep 8889

image Tip: We can pipeline the fuser(8) and kill(1) commands to terminate the proces: fuser 8889/tcp 2>/dev/null | kill -HUP

27. Directory listing ls(1)

image Todo: move this to gnu

The ls(1) command displays the contents of directories and provides options for producing many different output formats. This section describes some of the more obscure options and is specfic to Gnu Linux ls(1).

27.A The 'version' sort (-v option)

The version sort is specified by either the -v (sort option) or the --sort=version (long option) and tells ls(1) to take into account the fact that file names frequently include indices or version numbers. Standard sorting functions usually do not produce an expected ordering because comparisons are made on a character-by-character basis. The version sort addresses this problem and is especially useful when browsing directories that contain many files with indices/version numbers in their names:

           > ls -1            > ls -1v
           foo.zml-1.gz       foo.zml-1.gz
           foo.zml-100.gz     foo.zml-2.gz
           foo.zml-12.gz      foo.zml-6.gz
           foo.zml-13.gz      foo.zml-12.gz
           foo.zml-2.gz       foo.zml-13.gz
           foo.zml-25.gz      foo.zml-25.gz
           foo.zml-6.gz       foo.zml-100

Note also that numeric parts with leading zeroes are considered as fractional one:

           > ls -1            > ls -1v
           abc-1.007.tgz      abc-1.007.tgz
           abc-1.012b.tgz     abc-1.01a.tgz
           abc-1.01a.tgz      abc-1.012b.tgz

Stuart Moorfoot © 24 Aug 2006 foo@bund.com.au


Backlinks: :Home :a2ps :debian :readline