Chet Ramey 于 2009 年 2 月 20 日发布了 Bash 版本 4。此版本具有许多重要的新功能,以及一些重要的错误修复。
新功能包括
关联数组。[1]
示例 37-5. 一个简单的地址数据库
#!/bin/bash4
# fetch_address.sh
declare -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.
echo
echo "${!address[*]}" # The array indices ...
# Charles John Wilma |
示例 37-6. 一个稍微复杂的地址数据库
#!/bin/bash4
# fetch_address-2.sh
# A more elaborate version of fetch_address.sh.
SUCCESS=0
E_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. |
有关关联数组的有趣用法,请参阅示例 A-53。
![]() | 索引数组的元素可以包含嵌入的空格字符,甚至前导和/或尾随空格字符。但是,包含仅空白的索引数组元素是不允许的。
|
case 结构增强:;;&和;&终止符。
示例 37-7. 测试字符
#!/bin/bash4
test_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
}
echo
test_char 3
# 3 is a printable character.
# 3 is an alpha/numeric character.
# 3 is an numeric character.
# ********************************
echo
test_char m
# m is a printable character.
# m is an alpha/numeric character.
# m is an alphabetic character.
# m is a lowercase alphabetic character.
echo
test_char /
# / is a printable character.
echo
# The ;;& terminator can save complex if/then conditions.
# The ;& is somewhat less useful. |
新的 coproc 内置命令使两个并行进程能够通信和交互。正如 Chet Ramey 在Bash FAQ [2] ver. 4.01 中所述
有一个新的 'coproc' 保留字,用于指定协进程
一个异步命令,运行并带有两个管道连接到创建它的
shell。协进程可以被命名。输入和输出文件描述符
以及协进程的 PID 在调用 shell 中可用
变量,这些变量带有特定于协进程的名称。
George Dimitriu 解释说,
"... coproc ... 是 Bash 进程替换中使用的功能,
现在已公开可用。"
这意味着它可以在脚本中显式调用,而不是
仅仅是 Bash 在幕后使用的机制。
协进程使用文件描述符。文件描述符使进程和管道能够通信。
#!/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 the
do #+ file descriptor of the coprocess.
echo "$line" | sed -e 's/line/NOT-ORIGINAL-TEXT/'
done
kill $COPROC_PID # No longer need the coprocess,
#+ so kill its PID. |
但是,请小心!
#!/bin/bash4
echo; echo
a=aaa
b=bbb
c=ccc
coproc echo "one two three"
while read -u ${COPROC[0]} a b c; # Note that this loop
do #+ 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. |
![]() | 协进程是异步的,这可能会导致问题。它可能在另一个进程完成与其通信之前终止。
|
新的 mapfile 内置命令使得加载包含文本文件内容的数组成为可能,而无需使用循环或命令替换。
#!/bin/bash4
mapfile Arr1 < $0
# Same result as Arr1=( $(cat $0) )
echo "${Arr1[@]}" # Copies this entire script out to stdout.
echo "--"; echo
# But, not the same as read -a !!!
read -a Arr2 < $0
echo "${Arr2[@]}" # Reads only first line of script into the array.
exit |
read 内置命令进行了一些小的改进。-t timeout 选项现在接受(十进制)小数值 [3],并且-i选项允许预加载编辑缓冲区。[4] 不幸的是,这些增强功能仍在开发中,尚未(还)在脚本中可用。
参数替换获得了大小写修改运算符。
#!/bin/bash4
var=veryMixedUpVariable
echo ${var} # veryMixedUpVariable
echo ${var^} # VeryMixedUpVariable
# * First char --> uppercase.
echo ${var^^} # VERYMIXEDUPVARIABLE
# ** All chars --> uppercase.
echo ${var,} # veryMixedUpVariable
# * First char --> lowercase.
echo ${var,,} # verymixedupvariable
# ** All chars --> lowercase. |
declare 内置命令现在接受-l 小写和-c 首字母大写选项。
#!/bin/bash4 declare -l var1 # Will change to lowercase var1=MixedCaseVARIABLE echo "$var1" # mixedcasevariable # Same effect as echo $var1 | tr A-Z a-z declare -c var2 # Changes only initial char to uppercase. var2=originally_lowercase echo "$var2" # Originally_lowercase # NOT the same effect as echo $var2 | tr a-z A-Z |
花括号展开有更多选项。
递增/递减,在花括号内的最后一项中指定。
#!/bin/bash4
echo {40..60..2}
# 40 42 44 46 48 50 52 54 56 58 60
# All the even numbers, between 40 and 60.
echo {60..40..2}
# 60 58 56 54 52 50 48 46 44 42 40
# All the even numbers, between 40 and 60, counting backwards.
# In effect, a decrement.
echo {60..40..-2}
# The same output. The minus sign is not necessary.
# But, what about letters and symbols?
echo {X..d}
# X Y Z [ ] ^ _ ` a b c d
# Does not echo the \ which escapes a space. |
零填充,在花括号内的第一项中指定,在输出中的每一项前缀相同数量的零。
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
|
位置参数的子字符串提取现在以 $0 作为零索引开始。(这纠正了位置参数处理中的不一致性。)
#!/bin/bash
# show-params.bash
# Requires version 4+ of Bash.
# Invoke this scripts with at least one positional parameter.
E_BADPARAMS=99
if [ -z "$1" ]
then
echo "Usage $0 param1 ..."
exit $E_BADPARAMS
fi
echo ${@:0}
# bash3 show-params.bash4 one two three
# one two three
# bash4 show-params.bash4 one two three
# show-params.bash4 one two three
# $0 $1 $2 $3 |
新的 ** globbing 运算符递归地匹配文件名和目录。
#!/bin/bash4
# filelist.bash4
shopt -s globstar # Must enable globstar, otherwise ** doesn't work.
# The globstar shell option is new to version 4 of Bash.
echo "Using *"; echo
for filename in *
do
echo "$filename"
done # Lists only files in current directory ($PWD).
echo; echo "--------------"; echo
echo "Using **"
for filename in **
do
echo "$filename"
done # Lists complete file tree, recursively.
exit
Using *
allmyfiles
filelist.bash4
--------------
Using **
allmyfiles
allmyfiles/file.index.txt
allmyfiles/my_music
allmyfiles/my_music/me-singing-60s-folksongs.ogg
allmyfiles/my_music/me-singing-opera.ogg
allmyfiles/my_music/piano-lesson.1.ogg
allmyfiles/my_pictures
allmyfiles/my_pictures/at-beach-with-Jade.png
allmyfiles/my_pictures/picnic-with-Melissa.png
filelist.bash4 |
新的 $BASHPID 内部变量。
有一个新的 builtin 错误处理函数,名为 command_not_found_handle。
#!/bin/bash4
command_not_found_handle ()
{ # Accepts implicit parameters.
echo "The following command is not valid: \""$1\"""
echo "With the following argument(s): \""$2\"" \""$3\""" # $4, $5 ...
} # $1, $2, etc. are not explicitly passed to the function.
bad_command arg1 arg2
# The following command is not valid: "bad_command"
# With the following argument(s): "arg1" "arg2" |
编者评论 关联数组?协进程?我们已经了解和喜爱的精简 Bash 发生了什么?它是否正在遭受(可怕的!)“功能蔓延”?或者甚至可能是 Korn shell 羡慕? 给 Chet Ramey 的注释:请在未来的 Bash 版本中仅添加必要的功能——也许是 for-each 循环和对多维数组的支持。[5] 大多数 Bash 用户不需要、不会使用,并且可能不会非常欣赏复杂的“功能”,例如内置调试器、Perl 接口和外挂式火箭助推器。 |
Bash 版本 4.1 于 2010 年 5 月发布,主要是错误修复更新。
在双括号内,> 和 < 字符串比较运算符现在符合locale。由于 locale 设置可能会影响字符串表达式的排序顺序,因此这对 [[ ... ]] 表达式内的比较测试具有副作用。
read 内置命令现在接受-N选项(read -N chars),这会导致 read 在读取 chars 个字符后终止。
嵌入在 $( ... ) 命令替换构造中的 Here 文档 可以用简单的 ) 终止。
示例 37-9. 使用here 文档设置变量
#!/bin/bash # here-commsub.sh # Requires Bash version -ge 4.1 ... multi_line_var=$( cat <<ENDxxx ------------------------------ This is line 1 of the variable This is line 2 of the variable This is line 3 of the variable ------------------------------ ENDxxx) # Rather than what Bash 4.0 requires: #+ that the terminating limit string and #+ the terminating close-parenthesis be on separate lines. # ENDxxx # ) echo "$multi_line_var" # Bash still emits a warning, though. # warning: here-document at line 10 delimited #+ by end-of-file (wanted `ENDxxx') |
Bash 版本 4.2 于 2011 年 2 月发布,除了错误修复外,还包含许多新功能和增强功能。
Bash 现在支持\u和\U Unicode 转义。
echo -e '\u2630' # Horizontal triple bar character.
# Equivalent to the more roundabout:
echo -e "\xE2\x98\xB0"
# Recognized by earlier Bash versions.
echo -e '\u220F' # PI (Greek letter and mathematical symbol)
echo -e '\u0416' # Capital "ZHE" (Cyrillic letter)
echo -e '\u2708' # Airplane (Dingbat font) symbol
echo -e '\u2622' # Radioactivity trefoil
echo -e "The amplifier circuit requires a 100 \u2126 pull-up resistor."
unicode_var='\u2640'
echo -e $unicode_var # Female symbol
printf "$unicode_var \n" # Female symbol, with newline
# And for something a bit more elaborate . . .
# We can store Unicode symbols in an associative array,
#+ then retrieve them by name.
# Run this in a gnome-terminal or a terminal with a large, bold font
#+ for better legibility.
declare -A symbol # Associative array.
symbol[script_E]='\u2130'
symbol[script_F]='\u2131'
symbol[script_J]='\u2110'
symbol[script_M]='\u2133'
symbol[Rx]='\u211E'
symbol[TEL]='\u2121'
symbol[FAX]='\u213B'
symbol[care_of]='\u2105'
symbol[account]='\u2100'
symbol[trademark]='\u2122'
echo -ne "${symbol[script_E]} "
echo -ne "${symbol[script_F]} "
echo -ne "${symbol[script_J]} "
echo -ne "${symbol[script_M]} "
echo -ne "${symbol[Rx]} "
echo -ne "${symbol[TEL]} "
echo -ne "${symbol[FAX]} "
echo -ne "${symbol[care_of]} "
echo -ne "${symbol[account]} "
echo -ne "${symbol[trademark]} "
echo |
![]() | 上面的示例使用了 $' ... ' 字符串展开构造。 |
当lastpipeshell 选项被设置时,管道中的最后一个命令不在子 shell 中运行。
示例 37-10. 将输入通过管道传递给 read
#!/bin/bash # lastpipe-option.sh line='' # Null value. echo "\$line = "$line"" # $line = echo shopt -s lastpipe # Error on Bash version -lt 4.2. echo "Exit status of attempting to set \"lastpipe\" option is $?" # 1 if Bash version -lt 4.2, 0 otherwise. echo head -1 $0 | read line # Pipe the first line of the script to read. # ^^^^^^^^^ Not in a subshell!!! echo "\$line = "$line"" # Older Bash releases $line = # Bash version 4.2 $line = #!/bin/bash |
负数组索引允许从数组末尾向后计数。
示例 37-11. 负数组索引
#!/bin/bash
# neg-array.sh
# Requires Bash, version -ge 4.2.
array=( zero one two three four five ) # Six-element array.
# 0 1 2 3 4 5
# -6 -5 -4 -3 -2 -1
# Negative array indices now permitted.
echo ${array[-1]} # five
echo ${array[-2]} # four
# ...
echo ${array[-6]} # zero
# Negative array indices count backward from the last element+1.
# But, you cannot index past the beginning of the array.
echo ${array[-7]} # array: bad array subscript
# So, what is this new feature good for?
echo "The last element in the array is "${array[-1]}""
# Which is quite a bit more straightforward than:
echo "The last element in the array is "${array[${#array[*]}-1]}""
echo
# And ...
index=0
let "neg_element_count = 0 - ${#array[*]}"
# Number of elements, converted to a negative number.
while [ $index -gt $neg_element_count ]; do
((index--)); echo -n "${array[index]} "
done # Lists the elements in the array, backwards.
# We have just simulated the "tac" command on this array.
echo
# See also neg-offset.sh. |
子字符串提取使用负长度参数来指定距目标字符串末尾的偏移量。
示例 37-12. 字符串提取构造中的负参数
#!/bin/bash
# Bash, version -ge 4.2
# Negative length-index in substring extraction.
# Important: It changes the interpretation of this construct!
stringZ=abcABC123ABCabc
echo ${stringZ} # abcABC123ABCabc
# Position within string: 0123456789.....
echo ${stringZ:2:3} # cAB
# Count 2 chars forward from string beginning, and extract 3 chars.
# ${string:position:length}
# So far, nothing new, but now ...
# abcABC123ABCabc
# Position within string: 0123....6543210
echo ${stringZ:3:-6} # ABC123
# ^
# Index 3 chars forward from beginning and 6 chars backward from end,
#+ and extract everything in between.
# ${string:offset-from-front:offset-from-end}
# When the "length" parameter is negative,
#+ it serves as an offset-from-end parameter.
# See also neg-array.sh. |
| [1] | 更具体地说,Bash 4+ 对关联数组具有有限的支持。这是一个简陋的实现,并且缺少其他编程语言中此类数组的大部分功能。但是请注意,Bash 中的关联数组似乎比数字索引数组执行得更快更有效。 |
| [2] | 版权 1995-2009 Chester Ramey。 |
| [3] | 这仅适用于管道和某些其他特殊文件。 |
| [4] | 但这仅与readline结合使用,即从命令行。 |
| [5] | 当您这样做时,请考虑修复臭名昭著的管道 read 问题。 |