Advanced Bash-Scripting Guide: An in-depth exploration of the art of shell scripting | ||
---|---|---|
Prev | Chapter 36. Bash, versions 2, 3, and 4 | Next |
Chet Ramey announced Version 4 of Bash on the 20th of February, 2009. This release has a number of significant new features, as well as some important bugfixes.
Among the new goodies:
Example 36-5. A simple address database
1 #!/bin/bash4 2 # fetch_address.sh 3 4 declare -A address 5 # -A option declares associative array. 6 7 address[Charles]="414 W. 10th Ave., Baltimore, MD 21236" 8 address[John]="202 E. 3rd St., New York, NY 10009" 9 address[Wilma]="1854 Vermont Ave, Los Angeles, CA 90023" 10 11 12 echo "Charles's address is ${address[Charles]}." 13 # Charles's address is 414 W. 10th Ave., Baltimore, MD 21236. 14 echo "Wilma's address is ${address[Wilma]}." 15 # Wilma's address is 1854 Vermont Ave, Los Angeles, CA 90023. 16 echo "John's address is ${address[John]}." 17 # John's address is 202 E. 3rd St., New York, NY 10009. |
Example 36-6. A somewhat more elaborate address database
1 #!/bin/bash4 2 # fetch_address-2.sh 3 # A more elaborate version of fetch_address.sh. 4 5 SUCCESS=0 6 E_DB=99 # Error code for missing entry. 7 8 declare -A address 9 # -A option declares associative array. 10 11 12 store_address () 13 { 14 address[$1]="$2" 15 return $? 16 } 17 18 19 fetch_address () 20 { 21 if [[ -z "${address[$1]}" ]] 22 then 23 echo "$1's address is not in database." 24 return $E_DB 25 fi 26 27 echo "$1's address is ${address[$1]}." 28 return $? 29 } 30 31 32 store_address "Charles Jones" "414 W. 10th Ave., Baltimore, MD 21236" 33 store_address "John Smith" "202 E. 3rd St., New York, NY 10009" 34 store_address "Wilma Wilson" "1854 Vermont Ave, Los Angeles, CA 90023" 35 # Exercise: 36 # Rewrite the above store_address calls to read data from a file, 37 #+ then assign field 1 to name, field 2 to address in the array. 38 # Each line in the file would have a format corresponding to the above. 39 # Use a while-read loop to read from file, sed or awk to parse the fields. 40 41 fetch_address "Charles Jones" 42 # Charles Jones's address is 414 W. 10th Ave., Baltimore, MD 21236. 43 fetch_address "Wilma Wilson" 44 # Wilma Wilson's address is 1854 Vermont Ave, Los Angeles, CA 90023. 45 fetch_address "John Smith" 46 # John Smith's address is 202 E. 3rd St., New York, NY 10009. 47 fetch_address "Bozo Bozeman" 48 # Bozo Bozeman's address is not in database. 49 50 exit $? # In this case, exit code = 99, since that is function return. |
Enhancements to the case construct: the ;;& and ;& terminators.
Example 36-7. Testing characters
1 #!/bin/bash4 2 3 test_char () 4 { 5 case "$1" in 6 [[:print:]] ) echo "$1 is a printable character.";;& # | 7 # The ;;& terminator continues to the next pattern test. | 8 [[:alnum:]] ) echo "$1 is an alpha/numeric character.";;& # v 9 [[:alpha:]] ) echo "$1 is an alphabetic character.";;& # v 10 [[:lower:]] ) echo "$1 is a lowercase alphabetic character.";;& 11 [[:digit:]] ) echo "$1 is an numeric character.";& # | 12 # The ;& terminator executes the next statement ... # | 13 %%%@@@@@ ) echo "********************************";; # v 14 # ^^^^^^^^ ... even with a dummy pattern. 15 esac 16 } 17 18 echo 19 20 test_char 3 21 # 3 is a printable character. 22 # 3 is an alpha/numeric character. 23 # 3 is an numeric character. 24 # ******************************** 25 echo 26 27 test_char m 28 # m is a printable character. 29 # m is an alpha/numeric character. 30 # m is an alphabetic character. 31 # m is a lowercase alphabetic character. 32 echo 33 34 test_char / 35 # / is a printable character. 36 37 echo 38 39 # The ;;& terminator can save complex if/then conditions. 40 # 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 [1] , 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.
1 #!/bin/bash4 2 # A coprocess communicates with a while-read loop. 3 4 5 coproc { cat mx_data.txt; sleep 2; } 6 # ^^^^^^^ 7 # Try running this without "sleep 2" and see what happens. 8 9 while read -u ${COPROC[0]} line # ${COPROC[0]} is the 10 do #+ file descriptor of the coprocess. 11 echo "$line" | sed -e 's/line/NOT-ORIGINAL-TEXT/' 12 done 13 14 kill $COPROC_PID # No longer need the coprocess, 15 #+ so kill its PID. |
But, be careful!
1 #!/bin/bash4 2 3 echo; echo 4 a=aaa 5 b=bbb 6 c=ccc 7 8 coproc echo "one two three" 9 while read -u ${COPROC[0]} a b c; # Note that this loop 10 do #+ runs in a subshell. 11 echo "Inside while-read loop: "; 12 echo "a = $a"; echo "b = $b"; echo "c = $c" 13 echo "coproc file descriptor: ${COPROC[0]}" 14 done 15 16 # a = one 17 # b = two 18 # c = three 19 # So far, so good, but ... 20 21 echo "-----------------" 22 echo "Outside while-read loop: " 23 echo "a = $a" # a = 24 echo "b = $b" # b = 25 echo "c = $c" # c = 26 echo "coproc file descriptor: ${COPROC[0]}" 27 echo 28 # The coproc is still running, but ... 29 #+ it still doesn't enable the parent process 30 #+ to "inherit" variables from the child process, the while-read loop. 31 32 # 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.
|
The new mapfile builtin makes it possible to load an array with the contents of a text file without using a loop or command substitution.
1 #!/bin/bash4 2 3 mapfile Arr1 < $0 4 # Same result as Arr1=( $(cat $0) ) 5 echo "${Arr1[@]}" # Copies this entire script out to stdout. 6 7 echo "--"; echo 8 9 # But, not the same as read -a !!! 10 read -a Arr2 < $0 11 echo "${Arr2[@]}" # Reads only first line of script into the array. 12 13 exit |
The read builtin got a minor facelift. The -t timeout option now accepts (decimal) fractional values [2] and the -i option permits preloading the edit buffer. [3] Unfortunately, these enhancements are still a work in progress and not (yet) usable in scripts.
Parameter substitution gets case-modification operators.
1 #!/bin/bash4 2 3 var=veryMixedUpVariable 4 echo ${var} # veryMixedUpVariable 5 echo ${var^} # VeryMixedUpVariable 6 # * First char --> uppercase. 7 echo ${var^^} # VERYMIXEDUPVARIABLE 8 # ** All chars --> uppercase. 9 echo ${var,} # veryMixedUpVariable 10 # * First char --> lowercase. 11 echo ${var,,} # verymixedupvariable 12 # ** All chars --> lowercase. |
The declare builtin now accepts the -l lowercase and -c capitalize options.
1 #!/bin/bash4 2 3 declare -l var1 # Will change to lowercase 4 var1=MixedCaseVARIABLE 5 echo "$var1" # mixedcasevariable 6 # Same effect as echo $var1 | tr A-Z a-z 7 8 declare -c var2 # Changes only initial char to uppercase. 9 var2=originally_lowercase 10 echo "$var2" # Originally_lowercase 11 # NOT the same effect as echo $var2 | tr a-z A-Z |
Brace expansion has more options.
Increment/decrement, specified in the final term within braces.
1 #!/bin/bash4 2 3 echo {40..60..2} 4 # 40 42 44 46 48 50 52 54 56 58 60 5 # All the even numbers, between 40 and 60. 6 7 echo {60..40..2} 8 # 60 58 56 54 52 50 48 46 44 42 40 9 # All the even numbers, between 40 and 60, counting backwards. 10 # In effect, a decrement. 11 echo {60..40..-2} 12 # The same output. The minus sign is not necessary. 13 14 # But, what about letters and symbols? 15 echo {X..d} 16 # X Y Z [ ] ^ _ ` a b c d 17 echo {X..d..2} 18 # X Z ^ ` b d 19 # It seems to work for upper/lowercase letters, 20 #+ but the increment is a bit inconsistent on symbols. |
Zero-padding, specified in the first term within braces, prefixes each term in the output with the same number of zeroes.
bash4$ echo {010..15} 010 011 012 013 014 015 bash4$ echo {000..10} 000 001 002 003 004 005 006 007 008 009 010 |
Substring extraction on positional parameters now starts with $0 as the zero-index. (This corrects an inconsistency in the treatment of positional parameters.)
1 #!/bin/bash4 2 # show-params.bash4 3 4 # Invoke this scripts with at least one positional parameter. 5 6 E_BADPARAMS=99 7 8 if [ -z "$1" ] 9 then 10 echo "Usage $0 param1 ..." 11 exit $E_BADPARAMS 12 fi 13 14 echo ${@:0} 15 16 # bash3 show-params.bash4 one two three 17 # one two three 18 19 # bash4 show-params.bash4 one two three 20 # show-params.bash4 one two three 21 22 # $0 $1 $2 $3 |
The new ** globbing operator matches filenames and directories recursively.
1 #!/bin/bash4 2 # filelist.bash4 3 4 shopt -s globstar # Must enable globstar, otherwise ** doesn't work. 5 # The globstar shell option is new to version 4 of Bash. 6 7 echo "Using *"; echo 8 for filename in * 9 do 10 echo "$filename" 11 done # Lists only files in current directory ($PWD). 12 13 echo; echo "--------------"; echo 14 15 echo "Using **" 16 for filename in ** 17 do 18 echo "$filename" 19 done # Lists complete file tree, recursively. 20 21 exit 22 23 Using * 24 25 allmyfiles 26 filelist.bash4 27 28 -------------- 29 30 Using ** 31 32 allmyfiles 33 allmyfiles/file.index.txt 34 allmyfiles/my_music 35 allmyfiles/my_music/me-singing-60s-folksongs.ogg 36 allmyfiles/my_music/me-singing-opera.ogg 37 allmyfiles/my_music/piano-lesson.1.ogg 38 allmyfiles/my_pictures 39 allmyfiles/my_pictures/at-beach-with-Jade.png 40 allmyfiles/my_pictures/picnic-with-Melissa.png 41 filelist.bash4 |
The new $BASHPID internal variable.
There is a new builtin error-handling function named command_not_found_handle.
1 #!/bin/bash4 2 3 command_not_found_handle () 4 { # Accepts implicit parameters. 5 echo "The following command is not valid: \""$1\""" 6 echo "With the following argument(s): \""$2\"" \""$3\""" # $4, $5 ... 7 } # $1, $2, etc. are not explicitly passed to the function. 8 9 bad_command arg1 arg2 10 11 # The following command is not valid: "bad_command" 12 # With the following argument(s): "arg1" "arg2" |
Editorial comment Associative arrays? Coprocesses? Whatever happened to the lean and mean Bash we have come to know and love? Could it be suffering from (horrors!) "feature creep"? Or perhaps even Korn shell envy? Note to Chet Ramey: Please add only essential features in future Bash releases -- perhaps for-each loops and support for multi-dimensional arrays. [4] Most Bash users won't need, won't use, and likely won't greatly appreciate complex "features" like built-in debuggers, Perl interfaces, and bolt-on rocket boosters. |
[1] | Copyright 1995-2009 by Chester Ramey. |
[2] | This only works with pipes and certain other special files. |
[3] | But only in conjunction with readline, i.e., from the command-line. |
[4] | And while you're at it, consider fixing the notorious piped read problem. |