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.
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.
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
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.
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.
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.
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++))
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
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.
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
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
Bash has several specical variables like SECONDS, REPLY, RANDOM and etc.
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
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
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
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
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.
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
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]}
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
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 $
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
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 -"
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:
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
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:
Tip: This option pretty much limted to academic interest only. Too hard to remember all the options unless used very frequently.
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
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
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
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
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.
This example defines a simple function called foo(), which always returns the value 1:
#!/usr/bin/env bash
function foo() {
return 1
}
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 ##
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.
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.
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; }
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.
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
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.
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.
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.
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=$*
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
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
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"
Note: The CSI Esc [p;qH is the move to row,column command. The row number is specified first.
To Clear the current terminal window use the 2J CSI.
bash $ printf "\033[2J"
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
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.
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
Note: 1> file is the same as > file
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
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.
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
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.
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
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
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.
bash $ trap -l
Todo: More doco this option this required. Why and when would this option be used. Is it useful interactively or programatically?
bash $ trap -p
Todo: More doco this option this required. Why and when would this option be used. Is it useful interactively or programatically?
#!/usr/bin/env bash trap "rm -f somefile" EXIT
Todo: More doco this option this required. Why and when would this option be used.
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
Todo: More doco this option this required. Why and when would this option be used.
Note: See bash(1) Shell Grammar for a definition of a Simple Command.
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
Todo: More doco on this option is required. Why and when would this option be used.
Note: See bash(1) Shell Grammar for a definition of a Simple Command.
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
}
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
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)
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:
Note:: The bash completion rpm is configuration and is not platform nor architecture specific (packaged as .noarch)
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.
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.
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
Note #2: See also bash completion
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).
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
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:
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
Tip: We can pipeline the fuser(8) and kill(1) commands to terminate the proces: fuser 8889/tcp 2>/dev/null | kill -HUP
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).
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