bash — 标准 Shell

在处理 ebuild 时,深入理解bash编程至关重要。

Bash 条件语句

基本选择

基本的条件操作符是if语句

if something ; then
	do_stuff
fi

多重选择

可以使用elseelif进行多重选择。

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 中的字符串测试

字符串测试的一般形式是-operator "string"。以下操作符可用

操作符 用途
-z "string" 字符串长度为零
-n "string" 字符串长度不为零

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

[ ]代码块内,可以使用几个-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 开始。 offsetlength 都是算术表达式。

当偏移量 offset 为正数时,第一个形式返回从偏移量 offset 指定的字符开始到字符串末尾的子字符串。如果偏移量为负数,则偏移量相对于字符串的末尾计算。

第二个形式返回从 offset 开始的 ${var} 值的前 length 个字符。如果 offset 为负数,则偏移量相对于字符串的末尾计算。 length 参数不能小于零。同样,负 offset 值必须以表达式的形式给出。

bash 命令替换

$(command) 结构可用于运行命令并将输出(stdout)作为字符串捕获。

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}

要仅在 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 多个语句