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 |
多个语句 |
**=
赋值运算符。