bash — 标准 Shell
在处理 ebuild 时,深入理解bash编程至关重要。
Bash 条件语句
多重选择
可以使用else和elif进行多重选择。
if something ; then
	do_stuff
elif something_else ; then
	do_other_stuff
elif full_moon ; then
	howl
else
	turn_into_a_newt
fi
if some_stuff ; then
	# A statement is required here. a blank or a comment
	# isn't enough!
else
	einfo "Not some stuff"
fi
如果您真的不想重构代码块,可以使用单个冒号(:)作为空语句。
if some_stuff ; then
	# Do nothing
	:
else
	einfo "Not some stuff"
fi
选择测试
要进行比较或文件属性测试,需要使用[[ ]](首选)或[ ]代码块。
# is ${foo} zero length?
if [[ -z ${foo} ]] ; then
	die "Please set foo"
fi
# is ${foo} equal to "moo"?
if [[ ${foo} == "moo" ]] ; then
	einfo "Hello Larry"
fi
# does ${ROOT}/etc/deleteme exist?
if [[ -f ${ROOT}/etc/deleteme ]] ; then
	einfo "Please delete ${ROOT}/etc/deleteme manually!"
fi
Bash 中的单括号与双括号
[[ ]]形式通常比[ ]更安全,应在所有新代码中使用。POSIX 兼容性对于 ebuild 来说不是问题,因为它们的解释器保证是 GNU Bash。POSIX 风格的测试具有不同的语义,而使用常见的测试形式符合最小惊讶原则。大多数开发人员习惯于 Bash 测试的语义和行为,在 ebuild 中偏离这一点可能会令人困惑。
这是因为[[ ]]是 bash 的语法结构,而[ ]是一个程序,恰好被实现为内部程序——因此,前者可以实现更简洁的语法。举个简单的例子,考虑一下
bash$ [ -n ${foo} ] && [ -z ${foo} ] && echo "huh?"
huh?
bash$ [[ -n ${foo} ]] && [[ -z ${foo} ]] && echo "huh?"
bash$
Bash 中的字符串比较
字符串比较的一般形式是string1 operator string2。以下操作符可用
| 操作符 | 用途 | 
|---|---|
| ==(也可用=) | 字符串相等 | 
| != | 字符串不相等 | 
| < | 字符串字典序比较(在…之前) | 
| > | 字符串字典序比较(在…之后) | 
| =~ | 字符串正则表达式匹配 | 
Bash 中的整数比较
整数比较的一般形式是int1 -operator int2。以下操作符可用
| 操作符 | 用途 | 
|---|---|
| -eq | 整数相等 | 
| -ne | 整数不相等 | 
| -lt | 整数小于 | 
| -le | 整数小于或等于 | 
| -gt | 整数大于 | 
| -ge | 整数大于或等于 | 
Bash 中的文件测试
文件测试的一般形式是-operator "filename"。以下操作符可用(摘自man bash)
| 操作符 | 用途 | 
|---|---|
| -a file | 存在(使用 -e代替) | 
| -b file | 存在且为块特殊文件 | 
| -c file | 存在且为字符特殊文件 | 
| -d file | 存在且为目录 | 
| -e file | 存在 | 
| -f file | 存在且为普通文件 | 
| -g file | 存在且设置了 set-group-id 位 | 
| -h file | 存在且为符号链接 | 
| -k file | 存在且设置了粘滞位 | 
| -p file | 存在且为命名管道(FIFO) | 
| -r file | 存在且可读 | 
| -s file | 存在且大小大于零 | 
| -t fd | 描述符 fd 已打开且引用终端 | 
| -u file | 存在且设置了 set-user-id 位 | 
| -w file | 存在且可写 | 
| -x file | 存在且可执行 | 
| -O file | 存在且由有效用户 ID 拥有 | 
| -G file | 存在且由有效组 ID 拥有 | 
| -L file | 存在且为符号链接 | 
| -S file | 存在且为套接字 | 
| -N file | 存在且自上次读取以来已被修改 | 
Bash 中的文件比较
文件比较的一般形式是"file1" -operator "file2"。以下操作符可用
| 操作符 | 用途 | 
|---|---|
| file1 -nt file2 | file1 比 file2 新(根据修改日期),或者 file1 存在而 file2 不存在 | 
| file1 -ot file2 | file1 比 file2 旧,或者 file2 存在而 file1 不存在 | 
| file1 -ef file2 | file1 是 file2 的硬链接 | 
Bash 中的布尔代数
有一些可用于布尔代数(“与”,“或”和“非”)的结构。这些结构在[[ ]]代码块**外部**使用。对于操作符优先级,使用( )。
| 结构 | 效果 | 
|---|---|
| first || second | first 或 second(短路) | 
| first && second | first 与 second(短路) | 
| ! condition | 非 condition | 
[[ ]]结构**内部**起作用,并且在测试之前使用!非常常见。[[ ! -f foo ]] && bar是可以的。但是,也有一些注意事项——[[ -f foo && bar ]]**将无法**正常工作,因为无法在[[ ]]代码块内运行命令。在[ ]代码块内,可以使用几个-test风格的布尔操作符。应避免使用这些操作符,而应使用[[ ]]和上述操作符。
Bash 迭代结构
Bash 提供了一些简单的迭代结构。其中最有用的是for循环。它可以用于对多个项目执行相同的任务。
for myvar in "the first" "the second" "and the third" ; do
	einfo "This is ${myvar}"
done
还有一种for循环的形式,可用于重复执行某个事件指定次数。
for (( i = 1 ; i <= 10 ; i++ )) ; do
	einfo "i is ${i}"
done
还有一个while循环,尽管它通常在 ebuild 中用处不大。
while hungry ; do
	eat_cookies
done
这最常用于迭代文件中的行
while read myline ; do
	einfo "It says ${myline}"
done < some_file
请参阅die 和子 shell,了解为什么应使用while read < file而不是cat file | while read。
Bash 变量操作
Bash 中有许多特殊的${}结构,这些结构要么操作变量,要么根据变量返回信息。这些结构可以用来代替对sed及其同类程序的昂贵(或在全局范围内是非法的)外部调用。
Bash 字符串长度
${#somevar}结构可用于获取字符串变量的长度。
somevar="Hello World"
echo "${somevar} is ${#somevar} characters long"
Bash 变量默认值
如果变量未设置或长度为零,则可以使用多种方法使用默认值。${var:-value}结构如果${var}已设置且不为空,则扩展为${var}的值,否则扩展为value。${var-value}结构类似,但仅检查变量是否已设置。
${var:=value}和${var=value}形式还将在var未设置(以及已设置但对于:=形式为空)时将value赋值给var。
${var:?message}形式将message显示到标准错误输出,然后在var未设置或为空时退出。这通常不应在 ebuild 中使用,因为它不使用die机制。还有一个${var?message}形式。
如果变量 var 已设置且不为空,则 ${var:+value} 形式扩展为 value;否则扩展为空字符串。还存在 ${var+value} 形式。
bash 子字符串提取
可以使用 ${var:offset} 和 ${var:offset:length} 结构获取子字符串。字符串索引从 0 开始。 offset 和 length 都是算术表达式。
当偏移量 offset 为正数时,第一个形式返回从偏移量 offset 指定的字符开始到字符串末尾的子字符串。如果偏移量为负数,则偏移量相对于字符串的末尾计算。
${var:0-1}。 ${var:-1} 将不会起作用。第二个形式返回从 offset 开始的 ${var} 值的前 length 个字符。如果 offset 为负数,则偏移量相对于字符串的末尾计算。 length 参数不能小于零。同样,负 offset 值必须以表达式的形式给出。
bash 命令替换
$(command) 结构可用于运行命令并将输出(stdout)作为字符串捕获。
`command` 结构也能做到这一点,但为了清晰起见,以及易于阅读和嵌套的目的,应避免使用它,而应使用 $(command)。myconf="$(use_enable acl) $(use_enable nls) --with-tlib=ncurses"
bash 字符串替换
有三种基本的字符串替换形式可用: ${var#pattern}、 ${var%pattern} 和 ${var/pattern/replacement}。前两个分别用于从字符串的开头和结尾删除内容。第三个用于将匹配项替换为不同的内容。
${var#pattern} 形式将返回 var,其中删除了 var 值开头与 pattern 最短匹配的部分。如果无法进行匹配,则给出 var 的值。要删除开头处的最长匹配项,请改用 ${var##pattern}。
${var%pattern} 和 ${var%%pattern} 形式与此类似,但分别删除 var 末尾处的最短和最长匹配项。
% 和 # 是非贪婪形式)。这可能是不正确的,但这些术语相当接近。${var/pattern/replacement} 结构扩展为 var 的值,其中将 pattern 的第一个匹配项替换为 replacement。要替换所有匹配项,可以使用 ${var//pattern/replacement}。
man bash 错误地描述了将匹配的内容。在所有可能的左匹配项中,将采用最长的匹配项。是的,确实是最长的,即使它涉及偏好后面的组或后面的分支。这不像 perl 或 sed。有关详细信息,请参阅 IEEE Std 1003.1-2017,第 9.1 节。要仅在 pattern 出现在 var 的值开头时匹配,则模式应以 # 字符开头。要仅在末尾匹配,则模式应以 % 开头。
如果 replacement 为空,则删除匹配项,并且可以省略 pattern 后面的 /。
pattern 可以包含许多用于模式匹配的特殊元字符。
| 字符 | 含义 | 
|---|---|
| * | 匹配任何字符串,包括空字符串 | 
| ? | 匹配任何单个字符 | 
| [...] | 匹配括号中包含的任何一个字符 | 
有关这些字符的更多详细信息和注意事项,请参阅 Bash 参考手册。
如果启用了 extglob shell 选项,则可以使用许多其他结构。这些结构有时非常有用。在下表中, pattern-list 是一个由一个或多个模式组成的列表,这些模式用 | 分隔。
| 结构 | 含义 | 
|---|---|
| ?(pattern-list) | 匹配给定模式的零个或一个出现 | 
| *(pattern-list) | 匹配给定模式的零个或多个出现 | 
| +(pattern-list) | 匹配给定模式的一个或多个出现 | 
| @(pattern-list) | 匹配给定的模式之一 | 
| !(pattern-list) | 匹配除给定模式之一之外的任何内容 | 
bash 算术扩展
$(( expression )) 结构可用于整数算术运算。 expression 是一个类似 C 语言的算术表达式。支持以下运算符(表格按优先级顺序排列,从高到低)
| 运算符 | 效果 | 
|---|---|
| var++、var-- | 变量后置增量、后置减量 | 
| ++var、--var | 变量前置增量、前置减量 | 
| -,+ | 一元负号和正号 | 
| !,~ | 逻辑非、按位非 | 
| ** | 幂运算 | 
| *,/,% | 乘法、除法、取余 | 
| +,- | 加法、减法 | 
| <<,>> | 左移、右移 | 
| <=,>=,<,> | 比较:小于等于、大于等于、严格小于、严格大于 | 
| ==,!= | 相等、不相等 | 
| & | 按位与 | 
| ^ | 按位异或 | 
| | | 按位或 | 
| && | 逻辑与 | 
| || | 逻辑或 | 
| expr ? expr : expr | 条件运算符 | 
| =,*=,/=,%=,+=,-=,<<=,>>=,&=,^=,|= | 赋值 | 
| expr1 , expr2 | 多个语句 | 
**= 赋值运算符。