| Chapter 37. Bash, versions 2, 3, and 4
37.1. Bash, version 2
The current version of Bash, the oneyou have running on your machine, is most likely version 2.xx.yy,3.xx.yy, or 4.xx.yy. bash$ echo $BASH_VERSION3.2.25(1)-release | The version 2 update of the classic Bash scripting languageadded array variables, string and parameter expansion, anda better method of indirect variable references, among otherfeatures. Example 37-1. String expansion #!/bin/bash# String expansion.# Introduced with version 2 of Bash.# Strings of the form $'xxx'#+ have the standard escaped characters interpreted. echo $'Ringing bell 3 times a a a' # May only ring once with certain terminals. # Or ... # May not ring at all, depending on terminal settings.echo $'Three form feeds f f f'echo $'10 newlines 'echo $'102141163150' # B a s h # Octal equivalent of characters.exit |
Example 37-2. Indirect variable references - the new way #!/bin/bash# Indirect variable referencing.# This has a few of the attributes of references in C++.a=letter_of_alphabetletter_of_alphabet=zecho "a = $a" # Direct reference.echo "Now a = ${!a}" # Indirect reference.# The ${!variable} notation is more intuitive than the old#+ eval var1=$$var2echot=table_cell_3table_cell_3=24echo "t = ${!t}" # t = 24table_cell_3=387echo "Value of t changed to ${!t}" # 387# No 'eval' necessary.# This is useful for referencing members of an array or table,#+ or for simulating a multi-dimensional array.# An indexing option (analogous to pointer arithmetic)#+ would have been nice. Sigh.exit 0# See also, ind-ref.sh example. | Example 37-3. Simple database application, using indirect variable referencing #!/bin/bash# resistor-inventory.sh# Simple database / table-lookup application.# ============================================================== ## DataB1723_value=470 # OhmsB1723_powerdissip=.25 # WattsB1723_colorcode="yellow-violet-brown" # Color bandsB1723_loc=173 # Where they areB1723_inventory=78 # How manyB1724_value=1000B1724_powerdissip=.25B1724_colorcode="brown-black-red" B1724_loc=24NB1724_inventory=243B1725_value=10000B1725_powerdissip=.125B1725_colorcode="brown-black-orange" B1725_loc=24NB1725_inventory=89# ============================================================== #echoPS3='Enter catalog number: 'echoselect catalog_number in "B1723" "B1724" "B1725" do Inv=${catalog_number}_inventory Val=${catalog_number}_value Pdissip=${catalog_number}_powerdissip Loc=${catalog_number}_loc Ccode=${catalog_number}_colorcode echo echo "Catalog number $catalog_number:" # Now, retrieve value, using indirect referencing. echo "There are ${!Inv} of [${!Val} ohm / ${!Pdissip} watt] resistors in stock." # ^ ^ # As of Bash 4.2, you can replace "ohm" with u2126 (using echo -e). echo "These are located in bin # ${!Loc}." echo "Their color code is "${!Ccode}"." breakdoneecho; echo# Exercises:# ---------# 1) Rewrite this script to read its data from an external file.# 2) Rewrite this script to use arrays,#+ rather than indirect variable referencing.# Which method is more straightforward and intuitive?# Which method is easier to code?# Notes:# -----# Shell scripts are inappropriate for anything except the most simple#+ database applications, and even then it involves workarounds and kludges.# Much better is to use a language with native support for data structures,#+ such as C++ or Java (or even Perl).exit 0 | Example 37-4. Using arrays and other miscellaneous trickery to deal four random hands from a deck of cards #!/bin/bash# cards.sh# Deals four random hands from a deck of cards.UNPICKED=0PICKED=1DUPE_CARD=99LOWER_LIMIT=0UPPER_LIMIT=51CARDS_IN_SUIT=13CARDS=52declare -a Deckdeclare -a Suitsdeclare -a Cards# It would have been easier to implement and more intuitive#+ with a single, 3-dimensional array.# Perhaps a future version of Bash will support multidimensional arrays.initialize_Deck (){i=$LOWER_LIMITuntil [ "$i" -gt $UPPER_LIMIT ]do Deck[i]=$UNPICKED # Set each card of "Deck" as unpicked. let "i += 1" doneecho}initialize_Suits (){Suits[0]=C #ClubsSuits[1]=D #DiamondsSuits[2]=H #HeartsSuits[3]=S #Spades}initialize_Cards (){Cards=(2 3 4 5 6 7 8 9 10 J Q K A)# Alternate method of initializing an array.}pick_a_card (){card_number=$RANDOMlet "card_number %= $CARDS" # Restrict range to 0 - 51, i.e., 52 cards.if [ "${Deck[card_number]}" -eq $UNPICKED ]then Deck[card_number]=$PICKED return $card_numberelse return $DUPE_CARDfi}parse_card (){number=$1let "suit_number = number / CARDS_IN_SUIT" suit=${Suits[suit_number]}echo -n "$suit-" let "card_no = number % CARDS_IN_SUIT" Card=${Cards[card_no]}printf %-4s $Card# Print cards in neat columns.}seed_random () # Seed random number generator.{ # What happens if you don't do this?seed=`eval date +%s`let "seed %= 32766" RANDOM=$seed# What are some other methods#+ of seeding the random number generator?}deal_cards (){echocards_picked=0while [ "$cards_picked" -le $UPPER_LIMIT ]do pick_a_card t=$? if [ "$t" -ne $DUPE_CARD ] then parse_card $t u=$cards_picked+1 # Change back to 1-based indexing (temporarily). Why? let "u %= $CARDS_IN_SUIT" if [ "$u" -eq 0 ] # Nested if/then condition test. then echo echo fi # Each hand set apart with a blank line. let "cards_picked += 1" fi done echoreturn 0}# Structured programming:# Entire program logic modularized in functions.#===============seed_randominitialize_Deckinitialize_Suitsinitialize_Cardsdeal_cards#===============exit# Exercise 1:# Add comments to thoroughly document this script.# Exercise 2:# Add a routine (function) to print out each hand sorted in suits.# You may add other bells and whistles if you like.# Exercise 3:# Simplify and streamline the logic of the script. |
37.2. Bash, version 3
On July 27, 2004, Chet Ramey released version 3 of Bash. This update fixed quite a number of bugs and added new features. Some of the more important added features:
A new, more generalized {a..z} brace expansion operator. #!/bin/bashfor i in {1..10}# Simpler and more straightforward than#+ for i in $(seq 10)do echo -n "$i " doneecho# 1 2 3 4 5 6 7 8 9 10# Or just . . .echo {a..z} # a b c d e f g h i j k l m n o p q r s t u v w x y zecho {e..m} # e f g h i j k l mecho {z..a} # z y x w v u t s r q p o n m l k j i h g f e d c b a # Works backwards, too.echo {25..30} # 25 26 27 28 29 30echo {3..-2} # 3 2 1 0 -1 -2echo {X..d} # X Y Z [ ] ^ _ ` a b c d # Shows (some of) the ASCII characters between Z and a, #+ but don't rely on this type of behavior because . . .echo {]..a} # {]..a} # Why?# You can tack on prefixes and suffixes.echo "Number #"{1..4}, "..." # Number #1, Number #2, Number #3, Number #4, ...# You can concatenate brace-expansion sets.echo {1..3}{x..z}" +" "..." # 1x + 1y + 1z + 2x + 2y + 2z + 3x + 3y + 3z + ... # Generates an algebraic expression. # This could be used to find permutations.# You can nest brace-expansion sets.echo {{a..c},{1..3}} # a b c 1 2 3 # The "comma operator" splices together strings.# ########## ######### ############ ########### ######### ################ Unfortunately, brace expansion does not lend itself to parameterization.var1=1var2=5echo {$var1..$var2} # {1..5}# Yet, as Emiliano G. points out, using "eval" overcomes this limitation.start=0end=10for index in $(eval echo {$start..$end})do echo -n "$index " # 0 1 2 3 4 5 6 7 8 9 10 doneecho | The ${!array[@]} operator, which expands to all the indices of a given array. #!/bin/bashArray=(element-zero element-one element-two element-three)echo ${Array[0]} # element-zero # First element of array.echo ${!Array[@]} # 0 1 2 3 # All the indices of Array.for i in ${!Array[@]}do echo ${Array[i]} # element-zero # element-one # element-two # element-three # # All the elements in Array.done |
The =~ Regular Expression matching operator within a double brackets test expression. (Perl has a similar operator.) #!/bin/bashvariable="This is a fine mess." echo "$variable" # Regex matching with =~ operator within [[ double brackets ]].if [[ "$variable" =~ T.........fin*es* ]]# NOTE: As of version 3.2 of Bash, expression to match no longer quoted.then echo "match found" # match foundfi | Or, more usefully: #!/bin/bashinput=$1if [[ "$input" =~ "[0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9]" ]]# ^ NOTE: Quoting not necessary, as of version 3.2 of Bash.# NNN-NN-NNNN (where each N is a digit).then echo "Social Security number." # Process SSN.else echo "Not a Social Security number!" # Or, ask for corrected input.fi | For additional examples of using the =~ operator, see Example A-29, Example 19-14, Example A-35, and Example A-24.
The new set -o pipefail option is useful for debugging pipes. If this option is set, then the exit status of a pipe is the exit status of the last command in the pipe to fail (return a non-zero value), rather than the actual final command in the pipe. See Example 16-43. | The update to version 3 of Bash breaks a few scripts that worked under earlier versions. Test critical legacyscripts to make sure they still work! As it happens, a couple of the scripts in the Advanced Bash Scripting Guide had to be fixed up (see Example 9-4, for instance). | 37.2.1. Bash, version 3.1The version 3.1 update of Bash introduces a number of bugfixes and a few minor changes. The += operator is now permitted in in places where previously only the =assignment operator was recognized.
a=1echo $a # 1a+=5 # Won't work under versions of Bash earlier than 3.1.echo $a # 15a+=Helloecho $a # 15Hello | Here, += functions as a stringconcatenation operator. Note that its behaviorin this particular context is different than within alet construct. a=1echo $a # 1let a+=5 # Integer arithmetic, rather than string concatenation.echo $a # 6let a+=Hello # Doesn't "add" anything to a.echo $a # 6 | Jeffrey Haemer points outthat this concatenation operator can be quiteuseful. In this instance, we append a directory to the$PATH.
bash$ echo $PATH/usr/bin:/bin:/usr/local/bin:/usr/X11R6/bin/:/usr/gamesbash$ PATH+=:/opt/binbash$ echo $PATH/usr/bin:/bin:/usr/local/bin:/usr/X11R6/bin/:/usr/games:/opt/bin |
37.2.2. Bash, version 3.2This is pretty much a bugfix update.
37.3. Bash, version 4
Chet Ramey announced Version 4 of Bash on the 20th of February, 2009. This release has a number of significantnew features, as well as some important bugfixes. Among the new goodies: Associative arrays. Example 37-5. A simple address database #!/bin/bash4# fetch_address.shdeclare -A address# -A option declares associative array.address[Charles]="414 W. 10th Ave., Baltimore, MD 21236" address[John]="202 E. 3rd St., New York, NY 10009" address[Wilma]="1854 Vermont Ave, Los Angeles, CA 90023" echo "Charles's address is ${address[Charles]}." # Charles's address is 414 W. 10th Ave., Baltimore, MD 21236.echo "Wilma's address is ${address[Wilma]}." # Wilma's address is 1854 Vermont Ave, Los Angeles, CA 90023.echo "John's address is ${address[John]}." # John's address is 202 E. 3rd St., New York, NY 10009.echoecho "${!address[*]}" # The array indices ...# Charles John Wilma | Example 37-6. A somewhat more elaborate address database #!/bin/bash4# fetch_address-2.sh# A more elaborate version of fetch_address.sh.SUCCESS=0E_DB=99 # Error code for missing entry.declare -A address# -A option declares associative array.store_address (){ address[$1]="$2" return $?}fetch_address (){ if [[ -z "${address[$1]}" ]] then echo "$1's address is not in database." return $E_DB fi echo "$1's address is ${address[$1]}." return $?}store_address "Lucas Fayne" "414 W. 13th Ave., Baltimore, MD 21236" store_address "Arvid Boyce" "202 E. 3rd St., New York, NY 10009" store_address "Velma Winston" "1854 Vermont Ave, Los Angeles, CA 90023" # Exercise:# Rewrite the above store_address calls to read data from a file,#+ then assign field 1 to name, field 2 to address in the array.# Each line in the file would have a format corresponding to the above.# Use a while-read loop to read from file, sed or awk to parse the fields.fetch_address "Lucas Fayne" # Lucas Fayne's address is 414 W. 13th Ave., Baltimore, MD 21236.fetch_address "Velma Winston" # Velma Winston's address is 1854 Vermont Ave, Los Angeles, CA 90023.fetch_address "Arvid Boyce" # Arvid Boyce's address is 202 E. 3rd St., New York, NY 10009.fetch_address "Bozo Bozeman" # Bozo Bozeman's address is not in database.exit $? # In this case, exit code = 99, since that is function return. | See Example A-53 for an interesting usage of an associative array. | Elements of the index array may include embedded space characters, or even leading and/or trailing space characters. However, index array elements containing only whitespace are not permitted. address[ ]="Blank" # Error! | | Enhancements to the case construct: the ;& and ;& terminators. Example 37-7. Testing characters #!/bin/bash4test_char (){ case "$1" in [[:print:]] ) echo "$1 is a printable character.";& # | # The ;& terminator continues to the next pattern test. | [[:alnum:]] ) echo "$1 is an alpha/numeric character.";& # v [[:alpha:]] ) echo "$1 is an alphabetic character.";& # v [[:lower:]] ) echo "$1 is a lowercase alphabetic character.";& [[:digit:]] ) echo "$1 is an numeric character.";& # | # The ;& terminator executes the next statement ... # | %%%@@@@@ ) echo "********************************"; # v# ^^^^^^^^ ... even with a dummy pattern. esac}echotest_char 3# 3 is a printable character.# 3 is an alpha/numeric character.# 3 is an numeric character.# ********************************echotest_char m# m is a printable character.# m is an alpha/numeric character.# m is an alphabetic character.# m is a lowercase alphabetic character.echotest_char /# / is a printable character.echo# The ;& terminator can save complex if/then conditions.# The ;& is somewhat less useful. | The new coproc builtin enables two parallel processes to communicate and interact. As Chet Ramey states in the Bash FAQ , ver. 4.01: There is a new 'coproc' reserved word that specifies a coprocess: an asynchronous command run with two pipes connected to the creating shell. Coprocs can be named. The input and output file descriptors and the PID of the coprocess are available to the calling shell in variables with coproc-specific names.
George Dimitriu explains, "... coproc ... is a feature used in Bash process substitution, which now is made publicly available." This means it can be explicitly invoked in a script, rather than just being a behind-the-scenes mechanism used by Bash. See http://linux010.blogspot.com/2008/12/bash-process-substitution.html. Coprocesses use file descriptors. File descriptors enable processes and pipes to communicate. #!/bin/bash4# A coprocess communicates with a while-read loop.coproc { cat mx_data.txt; sleep 2; }# ^^^^^^^# Try running this without "sleep 2" and see what happens.while read -u ${COPROC[0]} line # ${COPROC[0]} is thedo #+ file descriptor of the coprocess. echo "$line" | sed -e 's/line/NOT-ORIGINAL-TEXT/'donekill $COPROC_PID # No longer need the coprocess, #+ so kill its PID. | But, be careful! #!/bin/bash4echo; echoa=aaab=bbbc=ccccoproc echo "one two three" while read -u ${COPROC[0]} a b c; # Note that this loopdo #+ runs in a subshell. echo "Inside while-read loop: "; echo "a = $a"; echo "b = $b"; echo "c = $c" echo "coproc file descriptor: ${COPROC[0]}" done # a = one# b = two# c = three# So far, so good, but ...echo "-----------------" echo "Outside while-read loop: " echo "a = $a" # a =echo "b = $b" # b =echo "c = $c" # c =echo "coproc file descriptor: ${COPROC[0]}" echo# The coproc is still running, but ...#+ it still doesn't enable the parent process#+ to "inherit" variables from the child process, the while-read loop.# Compare this to the "badread.sh" script. | | The coprocess is asynchronous, and this might cause a problem. It may terminate before another process has finished communicating with it. #!/bin/bash4coproc cpname { for i in {0..10}; do echo "index = $i"; done; }# ^^^^^^ This is a *named* coprocess.read -u ${cpname[0]}echo $REPLY # index = 0echo ${COPROC[0]} #+ No output ... the coprocess timed out# after the first loop iteration.# However, George Dimitriu has a partial fix.coproc cpname { for i in {0..10}; do echo "index = $i"; done; sleep 1;echo hi > myo; cat - >> myo; }# ^^^^^ This is a *named* coprocess.echo "I am main"$' | |
|
| |