本文为 Bash Reference Manual 第4章:Shell Builtin Commands 第1节:Bourne Shell Builtins 的读书笔记。

本章开头的概述部分也放到了这里。

内置命令包含在 shell 本身中。当内置命令的名称作为简单命令的第一个词使用时(参见简单命令),shell 会直接执行该命令,而无需调用其他程序。内置命令对于实现那些使用独立工具无法实现或不便实现的功能是必要的。

本节简要介绍了 Bash 从 Bourne Shell 继承的内建命令,以及 Bash 独有或经过扩展的内建命令。

💡 Bourne Shell 和 Bash是不同的。Bourne Shell (sh) 1977年由Stephen Bourne在贝尔实验室开发,是Unix的原始shell。而Bash (Bourne-Again SHell) 1989年由Brian Fox为GNU项目开发,是Bourne Shell的增强替代品。

其他章节中描述了若干内建命令:提供 Bash 对作业控制功能接口的内建命令(见作业控制内建命令)、目录栈(见目录栈内建命令)、命令历史(见 Bash 历史内建命令)以及可编程补全功能(见可编程补全内建命令)。

许多内建命令已被 POSIX 或 Bash 所扩展。

除非另有说明,每个内置命令如果文档中说明可以接受以‘-’开头的选项,也接受‘–’来表示选项的结束。:、true、false 和 test/[ 内置命令不接受选项,也不对‘–’进行特殊处理。exit、logout、return、break、continue、let 和 shift 内置命令可以接受并处理以‘-’开头的参数,而无需‘–’。其他接受参数但未特别说明可以接受选项的内置命令,将以‘-’开头的参数解释为无效选项,并需要使用‘–’以防止这种解释。

以下 shell 内置命令继承自 Bourne Shell。这些命令按照 POSIX 标准实现。


:(冒号)

: [arguments]

除了扩展参数和执行重定向外,不要执行任何操作。返回状态为零。

$ :
$ echo $?
0

由于:的返回状态为0,因此也可用以表示状态为真。

i=1
while : ; do
        echo $i
        ((i++))
        sleep 1
done

.(句号)

. [-p path] filename [arguments]

. 命令在当前 shell 环境中读取并执行 filename 参数中的命令。

如果 filename 不包含斜杠,. 会搜索它。如果提供了 -p,. 会将 path 视为以冒号分隔的目录列表,在这些目录中查找 filename;否则,. 使用 PATH 中的目录来查找 filename。filename 不需要是可执行的。当 Bash 不处于 POSIX 模式时,如果在 $PATH 中未找到 filename,会搜索当前目录,但如果提供了 -p,则不会搜索当前目录。如果 sourcepath 选项(参见 The Shopt Builtin)被关闭,. 不会搜索 PATH。

如果提供了任何arguments,它们将在执行 filename 时成为位置参数。否则,位置参数保持不变。

如果启用了 -T 选项,. 会继承任何 DEBUG 上的陷阱;如果没有启用,任何 DEBUG 陷阱字符串会在调用 . 之前保存,在执行 . 时取消 DEBUG 陷阱,并在调用完成后恢复。如果未设置 -T,而被导入的文件更改了 DEBUG 陷阱,则新值会在 . 完成后继续生效。返回状态是从 filename 执行的最后一个命令的退出状态,如果没有执行任何命令,则为零。如果未找到 filename,或者无法读取,则返回状态非零。此内建命令等同于 source。

$ . --help
.: . filename [arguments]
    Execute commands from a file in the current shell.

    Read and execute commands from FILENAME in the current shell.  The
    entries in $PATH are used to find the directory containing FILENAME.
    If any ARGUMENTS are supplied, they become the positional parameters
    when FILENAME is executed.

    Exit Status:
    Returns the status of the last command executed in FILENAME; fails if
    FILENAME cannot be read.

$ cat a.sh
echo name is $name

$ name=tommy
$ ./a.sh
name is
$ . a.sh
name is tommy

break

break [n]

从 for、while、until 或 select 循环中退出。如果提供了 n,break 将退出第 n 个包含的循环。n 必须大于或等于 1。返回状态为零,除非 n 小于 1。


cd

cd [-L] [-@] [directory]
cd -P [-e] [-@] [directory]

将当前工作目录更改为directory。如果未提供directory,则使用 HOME shell 变量的值作为目录。如果存在 shell 变量 CDPATH,并且directory不是以斜杠开头,cd 将其用作搜索路径:cd 会在 CDPATH 中的每个目录名称中搜索 directory,CDPATH 中的备用目录名称以冒号(‘:’)分隔。CDPATH 中的空目录名称表示与当前目录相同。

-P 选项意味着不跟随符号链接:在 cd 遍历directory并在处理directory中的 … 实例之前,会解析符号链接。

默认情况下,或者提供 -L 选项时,directory中的符号链接会在 cd 处理directory中的 … 实例之后解析。

如果directory中出现 …,cd 会通过删除紧挨其前的路径名组件(直到斜杠或directory开头)来处理它,并验证在移除该路径名组件后,cd 已处理的directory部分仍然是有效的目录名。如果不是有效的目录名,cd 会返回非零状态。

如果在使用 -P 的同时提供了 -e 选项,并且 cd 在成功更改目录后无法成功确定当前工作目录,则返回非零状态。

如果你已进入的目录被删除或被修改了权限,使用-e选项就会检测到此错误并返回非0:

先看一个权限被修改的示例:

$ cat a.sh
#!/bin/bash

test_dir=$(mktemp -d)
sub_dir="$test_dir/subdir"
mkdir -p "$sub_dir"
cd "$sub_dir"
echo "Current dir: $(pwd)"

chmod a-x "$test_dir"

echo "Test cd -P -e:"
if cd -P -e .; then
    echo "Succeed!"
else
    echo "Failed,status: $?"
fi

chmod a+x "$test_dir"
rm -rf "$test_dir"

$ ./a.sh
Current dir: /tmp/tmp.cArRYAlYOl/subdir
Test cd -P -e:
Failed,status: 1

如果把脚本中的if cd -P -e .改为if cd -P .,则输出为:

$ ./a.sh
Current dir: /tmp/tmp.5weq723ACi/subdir
Test cd -P -e:
Succeed!

再看一个目录被删除的示例,本例使用了两个终端(利用tmux):
在这里插入图片描述

在支持的系统上,-@ 选项会将与文件关联的扩展属性显示为目录。

如果目录为‘-’,则在尝试更改目录之前会将其转换为 $OLDPWD。

如果 cd 使用来自 CDPATH 的非空目录名,或者 ‘-’ 是第一个参数,并且目录更改成功,cd 会将新工作目录的绝对路径写入标准输出。

如果目录更改成功,cd 会将 PWD 环境变量的值设置为新目录名,并将 OLDPWD 环境变量设置为更改前当前工作目录的值。

如果目录成功更改,返回状态为零,否则为非零。

示例:

$ cd -
-bash: cd: OLDPWD not set
$ cd /usr/include
$ cd -
/home/vagrant
$ pwd
/home/vagrant

另一个示例:

$ mkdir a
$ ln -s a b
$ cd b
$ pwd
/home/vagrant/b
$ cd -
/home/vagrant
$ cd -P b
$ pwd
/home/vagrant/a
$ cd -
/home/vagrant
$ rmdir a
$ rm b

CDPATH有点chroot的意思:

$ export CDPATH=/home/vagrant/test
$ pwd
/home/vagrant
$ mkdir a b
$ cd a
$ pwd
/home/vagrant/a
$ echo $CDPATH
/home/vagrant/test
$ cd $CDPATH
$ mkdir a
$ cd
$ pwd
/home/vagrant
$ cd a
/home/vagrant/test/a

continue

continue [n]

continue 会继续执行外层 for、while、until 或 select 循环的下一次迭代。如果提供了 n,Bash 会继续执行第 n 层外层循环。n 必须大于或等于 1。返回状态为零,除非 n 小于 1。


eval

eval [arguments]

这些参数被连接成一个单一的命令,用空格分隔。Bash 然后读取并执行该命令,并将其退出状态作为 eval 的退出状态返回。如果没有参数或只有空参数,则返回状态为零。

示例:

# 简单情况下,有无eval并无区别
$ cmd="echo Hello"
$ $cmd
Hello
$ eval $cmd
Hello

# 涉及到变量时。eval可正确处理
$ cmd="echo Hello \$name"
$ name=Amigo
$ $cmd
Hello $name
$ eval $cmd
Hello Amigo

💡 eval的使用要小心,例如若eval的命令为rm -fr就完蛋了。

看一个稍微复杂点的例子:

$ option='-name "*.h" -type f'
$ find /usr/include $option |wc -l
0
$ eval find /usr/include $option |wc -l
1610

进入调试模式,可以看到区别:

$ set -x
++ printf '\033k%s@%s:%s\033\' vagrant ol9-vagrant '~'
$ find /usr/include $option |wc -l
+ find /usr/include -name '"*.h"' -type f
+ wc -l
0
++ printf '\033k%s@%s:%s\033\' vagrant ol9-vagrant '~'
$ eval find /usr/include $option |wc -l
+ wc -l
+ eval find /usr/include -name '"*.h"' -type f
++ find /usr/include -name '*.h' -type f
1610
++ printf '\033k%s@%s:%s\033\' vagrant ol9-vagrant '~'
$ set +x
+ set +x

本例中,若 option='-name *.h -type f',则使用eval与否都无区别。


exec

exec [-cl] [-a name] [command [arguments]]

如果提供了command ,它将取代当前的 shell 而不会创建新的进程。command 不能是 shell 内置命令或函数。arguments 将成为命令的参数。如果提供了 -l 选项,shell 会在传递给命令的第零个参数的开头添加一个短横。这也是login程序的做法。-c 选项会导致命令在空环境下执行。如果提供了 -a,shell 会将 name 作为第零个参数传递给command 。

如果由于某种原因command 无法执行,非交互式 shell 将退出,除非启用了 execfail shell 选项。在这种情况下,它返回非零状态。如果文件无法执行,交互式 shell 会返回非零状态。如果 exec 失败,子 shell 会无条件退出。

如果未指定命令,可以使用重定向来影响当前的 shell 环境。如果没有重定向错误,返回状态为零;否则返回状态为非零。

exec不会生成子shell,而是直接替换掉当前的shell:

$ (exec ls; echo I am here) 
a.sh  b.sh  X.pdf           
$ (ls; echo I am here)      
a.sh  b.sh  X.pdf           
I am here 

3.6 Redirections,我们已经知道exec可用于文件描述符操作。这也是其最常用的场景。

示例:

$ tty
/dev/pts/3
## 注意最前面的dash
$ echo $0
-bash

## -a option
$ ps -o pid,cmd $$
    PID CMD
  26857 -bash
$ exec -a "MyCustomShell" bash
$ ps -o pid,cmd $$
    PID CMD
  26857 MyCustomShell

## -a option 伪装进程名
$ exec -a "NOTSLEEP" sleep 120 &
[1] 26934
$ ps -ef|grep sleep|grep -v grep
$ ps -ef|grep SLEEP|grep -v grep
vagrant    26934   26857  0 08:53 pts/0    00:00:00 NOTSLEEP 120

## -c option
$ export MYVAR=myvar
$ cat a.sh
#!/bin/bash

echo number of envs is $(env|wc -l)

echo MYVAR is $MYVAR

$ ./a.sh
number of envs is 85
MYVAR is myvar
$ (exec -c ./a.sh)
number of envs is 3
MYVAR is

exit

exit [n]

退出 shell,并向 shell 的父进程返回状态 n。如果省略 n,则退出状态为最后执行的命令的状态。任何在 EXIT 上设置的 trap 都会在 shell 终止前执行。

$ (exit 10)
$ echo $?
10
$ (exit 10);(exit 11)
$ echo $?
11

export

export [-fn] [-p] [name[=value]]

将每个name 标记为要传递给随后在环境中执行的命令。如果提供了 -f 选项,这些name 指的是 shell 函数;否则指 shell 变量。

-n 选项表示取消导出每个名称:不再将其标记为导出。如果未提供name ,或者仅提供了 -p 选项,export 将在标准输出上显示所有已导出变量的名称列表。-p 和 -f 一起使用会显示已导出的函数。-p 选项以可重新用作输入的形式显示输出。

export 允许在导出或取消导出变量的同时设置变量的值,通过在变量名后跟 =value 来实现。这将在修改导出属性的同时将变量的值设置为指定值。

返回状态为零,除非提供了无效选项,名称之一不是有效的 shell 变量名,或者使用 -f 时名称不是 shell 函数。 …

$ export MYVAR=123
$ export|grep MYVAR
declare -x MYVAR="123"
$ export -n MYVAR
$ export|grep MYVAR
$

false

false

什么也不做;返回非零状态。

在复合语句中,until false等同于while truewhile :

$ false
$ echo $?
1
$ true
$ echo $?
0
$ :
$ echo $?
0
$ ! false
$ echo $?
0
$ ! :
$ echo $?
1

false的一些用途包括设置开关变量的初始值,影响函数的返回值,作为占位符表示功能尚未实现等。

## 初始值
var=false
$var && echo I was executed!
$var || echo I was executed!

feature_enabled=true

if $feature_enabled; then
    # 实现新功能逻辑
else
    false
    # 新功能尚未实现
fi

bash中并没有针对变量的取反(开关)操作,不过可以间接实现:

declare -A toggle=(
    [true]=false
    [false]=true
)

var=false
var=${toggle[$var]}
# 此时var=true
var=${toggle[$var]}
# 此时var=false

getopts

getopts optstring name [arg ...]

getopts 被 shell 脚本或函数用来解析位置参数,并获取选项及其参数。optstring 包含要识别的选项字符;如果某个字符后跟冒号,则该选项预期有一个参数,该参数应与选项通过空格分开。冒号(‘:’)和问号(‘?’)不能作为选项字符使用。

每次调用时,getopts 会将下一个选项放入 shell 变量 name 中,如果 name 不存在,则会初始化它,同时将下一个要处理的参数的索引放入变量 OPTIND 中。每次调用 shell 或 shell 脚本时,OPTIND 都会被初始化为 1。当某个选项需要一个参数时,getopts 会将该参数放入变量 OPTARG 中。

shell 不会自动重置 OPTIND;在同一次 shell 调用中多次调用 getopts 时,必须手动重置它,以使用新的一组参数。

当到达选项的末尾时,getopts 会以大于零的返回值退出。OPTIND 设置为第一个非选项参数的索引,name 则设置为 ‘?’。

getopts 通常解析位置参数,但如果提供了更多作为参数值的参数,getopts 会解析这些参数。

getopts 可以通过两种方式报告错误。如果 optstring 的第一个字符是冒号,getopts 会使用静默错误报告。在正常操作中,当 getopts 遇到无效选项或缺少选项参数时,会打印诊断信息。如果变量 OPTERR 设置为 0,即使 optstring 的第一个字符不是冒号,getopts 也不会显示任何错误信息。

如果 getopts 检测到无效选项,它会将 ‘?’ 放入 name 中,如果不是静默模式,还会打印错误信息并取消设置 OPTARG。如果 getopts 是静默的,它会将找到的选项字符赋给 OPTARG,并且不会打印诊断信息。

如果未找到必需的参数,并且 getopts 不是静默模式,它会将 name 的值设置为问号 (‘?’),取消设置 OPTARG,并打印诊断信息。如果 getopts 是静默的,它会将 name 的值设置为冒号 (‘:’),并将 OPTARG 设置为找到的选项字符。

如果找到指定或未指定的选项,getopts 返回 true。当遇到选项结束或发生错误时,它返回 false。

示例脚本procopts.sh:

#!/bin/bash

verbose=false
input=""
loglevel=0

usage() {
    echo "Usage: $0 [-v] -i infile [-l loglevel]"
    echo "options:"
    echo "  -v          verbose mode"
    echo "  -i infile   input file"
    echo "  -l loglevel log level"
    echo "  -h          display help"
    exit ${1:-0}
}

while getopts ":vi:l:h" opt; do
    case $opt in
        v) verbose=true ;;
        i) input="$OPTARG" ;;
        l) loglevel=$OPTARG ;;
        h) usage 0 ;;
        :) echo "Error: option -$OPTARG need a parameter" >&2; usage 1 ;;
        \?) echo "Error: Unknown option -$OPTARG" >&2; usage 1 ;;
    esac
done

shift $((OPTIND - 1))

# remaining args (if any)
extra_args=("$@")

if [[ -z $input ]]; then
    echo "Error: Should specify the input file" >&2
    usage 1
fi

if [[ ! -f $input ]]; then
    echo "Error: Input file '$input' does not exist" >&2
    exit 1
fi

$verbose && echo Log level is $loglevel

echo "Begin to process file: $input"

num_extrargs=${#extra_args[@]}
if [[ $num_extrargs -gt 0 ]]; then
    [[ $verbose ]] && echo "Extra arguments($num_extrargs): ${extra_args[*]}"
fi

echo "Processing completed!"

输出如下:

$ ./procopts.sh -h
Usage: ./procopts.sh [-v] -i infile [-l loglevel]
options:
  -v          verbose mode
  -i infile   input file
  -l loglevel   log level
  -h          display help
$ ./procopts.sh -i infile
Error: Input file 'infile' does not exist
$ > infile
$ ./procopts.sh -i infile
Begin to process file: infile
Processing completed!
$ ./procopts.sh -i infile -v
Log level is 0
Begin to process file: infile
Processing completed!
$ ./procopts.sh -i infile -v -l 5
Log level is 5
Begin to process file: infile
Processing completed!
$ ./procopts.sh -i infile -v -l 5 aa bb cc
Log level is 5
Begin to process file: infile
Extra arguments(3): aa bb cc
Processing completed!

💡 getopts 本身并不支持强制选项,需要你在脚本里自行检查,如上例中的 -i 选项。


hash

hash [-r] [-p filename] [-dt] [name]

每次调用 hash 时,它都会记住作为 name 参数指定的命令的完整文件名,因此在后续调用时无需再次搜索这些命令。命令通过搜索 $PATH 中列出的目录来找到。与 name 关联的任何之前记住的文件名都会被丢弃。-p 选项会禁止路径搜索,并且 hash 会使用 filename 作为 name 的位置。

-r 选项会导致 shell 忘记所有已记住的位置。给 PATH 变量赋值也会清除所有已哈希的文件名。-d 选项会导致 shell 忘记每个 name 已记住的位置。

如果提供了 -t 选项,hash 会打印与每个 name 对应的完整路径名。如果使用 -t 提供了多个 name 参数,hash 会先打印每个 name,然后再打印对应的哈希完整路径。-l 选项以可被再次作为输入使用的格式显示输出。

如果没有提供参数,或仅提供了 -l,hash 会打印已记住命令的信息。-t、-d 和 -p 选项(作用于名称参数的选项)是互斥的。一次只能激活一个选项。如果提供了多个选项,-t 的优先级高于 -p,而 -p 和 -t 的优先级都高于 -d。

除非找不到名称或提供了无效选项,否则返回状态为零。

$ hash
hits    command
   1    /usr/bin/mv
   3    /usr/bin/vi
   5    /usr/bin/chmod
   5    /usr/bin/ls
   2    /usr/bin/cat
  10    /usr/bin/tmux
   1    /usr/bin/sudo
$ tmux
[exited]
$ hash
hits    command
   1    /usr/bin/mv
   3    /usr/bin/vi
   5    /usr/bin/chmod
   5    /usr/bin/ls
   2    /usr/bin/cat
  11    /usr/bin/tmux
   1    /usr/bin/sudo

$ hash -r
$ hash
hash: hash table empty

$ hash vi
$ hash
hits    command
   0    /usr/bin/vi
$ hash tmux
$ hash
hits    command
   0    /usr/bin/vi
   0    /usr/bin/tmux
$ hash pwd
$ hash
hits    command
   0    /usr/bin/vi
   0    /usr/bin/tmux

pwd

pwd [-LP]

打印当前工作目录的绝对路径名。如果提供了 -P 选项,或 set 内建命令(参见内建命令 set)启用了 -o physical 选项,则打印的路径名将不包含符号链接。如果提供了 -L 选项,则打印的路径名可能包含符号链接。除非在确定当前目录名称时发生错误或提供了无效选项,否则返回状态为零。

$ ls -l /bin
lrwxrwxrwx. 1 root root 7 Oct 26  2024 /bin -> usr/bin
$ cd /bin
$ pwd
/bin
$ pwd -P
/usr/bin
$ pwd -L
/bin

readonly

readonly [-aAf] [-p] [name[=value]] ...

将每个 name 标记为只读。这些名称的值不能通过后续的赋值或取消设置进行更改。如果提供了 -f 选项,每个名称都对应一个 shell 函数。-a 选项表示每个名称对应一个索引数组变量;-A 选项表示每个name 对应一个关联数组变量。如果同时提供两个选项,则 -A 优先。如果未提供name 参数,或提供了 -p 选项,则打印所有只读名称的列表。其他选项可用于将输出限制为只读名称集合的子集。-p 选项以可重用为输入的格式显示输出。

readonly 允许在修改只读属性的同时设置变量的值,只需在变量名后加上 =value。这会在修改只读属性的同时将变量的值设置为value。

返回状态为零,除非提供了无效选项,name参数之一不是有效的 shell 变量或函数名称,或者在使用 -f 选项时指定的名称不是 shell 函数。

💡 readonly 等同于 declare -r。

$ readonly
declare -r BASHOPTS="checkwinsize:cmdhist:complete_fullquote:expand_aliases:extquote:force_fignore:globasciiranges:histappend:hostcomplete:interactive_comments:login_shell:progcomp:promptvars:sourcepath"
declare -ar BASH_VERSINFO=([0]="5" [1]="1" [2]="8" [3]="1" [4]="release" [5]="x86_64-redhat-linux-gnu")
declare -ir EUID="1000"
declare -ir PPID="49907"
declare -r SHELLOPTS="braceexpand:emacs:hashall:histexpand:history:interactive-comments:monitor"
declare -ir UID="1000"

$ readonly|grep var[1-2]
declare -r var1

$ readonly var2=456

$ readonly|grep var[1-2]
declare -r var1
declare -r var2="456"

💡 只读变量不允许修改,不允许重置,也无法取消只读属性。

$ var1=456
-bash: var1: readonly variable

$ unset var1
-bash: unset: var1: cannot unset: readonly variable

return

return [n]

停止执行一个 shell 函数或被导入的文件,并将值 n 返回给调用者。如果未提供 n,则返回值为最后执行的命令的退出状态。如果 return 由 trap 处理程序执行,则用于确定状态的最后命令是 trap 处理程序之前执行的最后一个命令。如果 return 在 DEBUG trap 中执行,则用于确定状态的最后命令是 trap 处理程序在调用 return 之前执行的最后一个命令。

当 return 用于终止通过 .(source)内置命令执行的脚本时,它会将 n 或脚本中最后执行命令的退出状态作为脚本的退出状态返回。如果提供了 n,返回值为其最低有效的 8 位。

与 RETURN trap 关联的任何命令将在函数或脚本执行后恢复执行之前执行。

如果 return 提供了非数字参数,或在函数外使用且不是通过 . 或 source 执行脚本期间使用,则返回状态为非零。

$ return 5
-bash: return: can only `return' from a function or sourced script

$ ./ret.sh
./ret.sh: line 1: return: can only `return' from a function or sourced script

$ (exit 5)
$ . ./ret.sh
$ echo $?
5
$ . ./ret.sh 32
$ echo $?
32
$ . ./ret.sh 555
$ echo $?
43
$ printf '%x\n' 555
22b
$ printf '%d\n' 0x2b
43

shift

shift [n]

将位置参数向左移动 n 位:从 n 开始的各个位置参数 $n … $# 被重命名为 $1 … $#-n。由 $# 到 $#-n+1 表示的参数将被清除。n 必须是一个非负数,且小于或等于 $#。如果未提供 n,则默认 n 为 1。如果 n 为零或大于 $#, 位置参数不会发生变化。返回状态为零,除非 n 大于 $# 或小于零,否则返回非零。

$ set -- 1 2 3 4 5 6 7 8 9
$ echo $@
1 2 3 4 5 6 7 8 9
$ shift
$ echo $@
2 3 4 5 6 7 8 9
$ shift 4
$ echo $@
6 7 8 9
$ shift 100
$ echo $@
6 7 8 9
$ shift -1
-bash: shift: -1: shift count out of range
$ shift abc
-bash: shift: abc: numeric argument required

test
[

test expr

评估一个条件表达式 expr 并返回状态 0(真)或 1(假)。每个运算符和操作数必须是单独的参数。表达式由下面 Bash 条件表达式中描述的基本元素组成。test 不接受任何选项,也不接受带有 – 的参数来表示选项结束。在使用 [ 形式时,命令的最后一个参数必须是 ]。

表达式可以使用以下运算符组合,按优先级递减顺序列出。评估取决于参数的数量;详见下文。当参数数量在五个或更多时,test 会使用运算符优先级进行计算。

! expr
如果 expr 为假,则为真。

( expr )
返回 expr 的值。这可以用来覆盖正常的运算符优先级。

expr1 -a expr2
如果 expr1 和 expr2 都为真,则为真。

expr1 -o expr2
如果 expr1 或 expr2 中有一个为真,则为真。

test 和 [ 内置命令使用一套基于参数数量的规则来评估条件表达式。

0 个参数
表达式为假。

1 个参数
当且仅当参数不为 null 时,表达式为真。

2 个参数
如果第一个参数是 ‘!’,当且仅当第二个参数为 null 时,表达式为真。如果第一个参数是单目条件运算符之一(见 Bash 条件表达式),当单目测试为真时,表达式为真。如果第一个参数不是有效的单目运算符,表达式为假。

3 个参数

按列出的顺序应用以下条件。

  1. 如果第二个参数是二元条件运算符之一(参见 Bash 条件表达式),则表达式的结果是使用第一个和第三个参数作为操作数的二元测试的结果。当有三个参数时,‘-a’ 和 ‘-o’ 运算符被视为二元运算符。
  2. 如果第一个参数是 ‘!’,则值为使用第二个和第三个参数进行的两参数测试的否定。
  3. 如果第一个参数恰好是 ‘(’ 且第三个参数恰好是 ‘)’,则结果是对第二个参数进行的一参数测试。
  4. 否则,表达式为假。

4 个参数

以下条件按列出的顺序应用。

  1. 如果第一个参数是 ‘!’,则结果为由剩余三个参数组成的三参数表达式的否定。
  2. 如果第一个参数恰好是 ‘(’,且第四个参数恰好是 ‘)’,则结果为对第二个和第三个参数的两个参数测试。
  3. 否则,表达式将根据上述规则按优先级进行解析和求值。

5个或更多参数

该表达式根据上述列出的规则按优先级进行解析和评估。

如果 shell 处于 POSIX 模式,或者表达式是 [[ 命令的一部分,则 ‘<’ 和 ‘>’ 运算符按当前区域设置进行排序。如果 shell 不处于 POSIX 模式,test 和 ‘[’ 命令按 ASCII 顺序进行字典序排序。

当历史运算符优先级解析遇到看起来像主项的字符串时,使用 4 个或更多参数可能会导致歧义。POSIX 标准已经废弃了 -a 和 -o 主项以及将表达式括在括号中的做法。脚本不应再使用它们。更可靠的方法是将 test 调用限制为单个主项,并用 shell 的 && 和 || 列表运算符替换 -a 和 -o 的用法。例如,使用:

test -n string1 && test -n string2

而非:

test -n string1 -a -n string2

💡 [[ 属于Conditional Constructs,而 [test是builtin。简单来说,推荐使用[[,因其安全性和易用性更好。

具体的示例参见 6.4 Bash Conditional Expressions 的笔记。

Bash中除test builtin外,还有一个兼容POSIX标准的test命令。通常建议用builtin。

$ type -a test
test is a shell builtin
test is /usr/bin/test

$ time for i in {1..1000}; do /usr/bin/test -f /etc/passwd; done

real    0m1.509s
user    0m0.546s
sys     0m0.985s
$ time for i in {1..1000}; do test -f /etc/passwd; done

real    0m0.018s
user    0m0.009s
sys     0m0.008s

times

打印出 shell 及其子进程使用的用户时间和系统时间。返回状态为零。

$ times
0m0.095s 0m0.165s # 第一行:当前Shell进程的CPU时间
0m2.784s 0m1.802s # 第二行:所有子进程的累计CPU时间

$ times --help
times: times
    Display process times.

    Prints the accumulated user and system times for the shell and all of its
    child processes.

    Exit Status:
    Always succeeds.

trap

trap [-lpP] [action] [sigspec ...]

action 是一个命令,当 shell 接收到任意 sigspec 信号时会被读取并执行。如果 action 缺失(且只有一个 sigspec)或等于 ‘-’,则每个指定 sigspec 的处理方式会重置为 shell 启动时的值。如果 action 是空字符串,那么 shell 以及它调用的命令会忽略每个 sigspec 指定的信号。

$ trap -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
$ trap -p
$ trap 'echo Ctrl+C pressed!' SIGINT
$ trap -p
trap -- 'echo Ctrl+C pressed!' SIGINT

$ <- 此时按Ctrl+C键
Ctrl+C pressed!

# sleep命令按自己的方式处理SIGINT,因此不会打印信息
$ sleep 10
^C

## 忽略信号
$ trap '' SIGINT
$ trap -p
trap -- '' SIGINT
$ sleep 10
^C^C^C^C$

## 重置回默认处理方式
$ trap SIGINT
$ trap -p

如果没有提供参数,trap 会打印与每个被捕获信号相关联的操作,作为一组 trap 命令,可作为 shell 输入重新使用,以恢复当前的信号处理方式。

如果未提供操作并且使用了 -p,则 trap 会显示与每个 sigspec 相关的 trap 命令,或者如果未提供 sigspec,则显示所有已捕获信号的 trap 命令集,这些命令集可以作为 shell 输入重新使用以恢复当前信号处理行为。-P 选项的行为类似,但仅显示与每个 sigspec 参数相关的操作。-P 至少需要一个 sigspec 参数。-P 或 -p 选项可以在子 shell 环境中(例如命令替换)使用,只要在 trap 用于更改信号处理之前使用,就会显示其父 shell 捕获的信号状态。

-l 选项会打印信号名称及其对应的编号列表。每个 sigspec 可以是信号名称或信号编号。信号名称不区分大小写,并且 SIG 前缀是可选的。如果 -l 没有提供任何 sigspec 参数,它会打印有效信号名称的列表。

如果 sigspec 为 0 或 EXIT,当 shell 退出时会执行动作。如果 sigspec 为 DEBUG,在每个简单命令、for 命令、case 命令、select 命令、(( 算术命令、[[ 条件命令、算术 for 命令以及 shell 函数中的第一个命令执行之前,都会执行动作。有关其对 DEBUG trap 的影响,请参阅 extdebug shell 选项的描述(见 Shopt 内建命令)。如果 sigspec 为 RETURN,每次 shell 函数或通过 . 或 source 内建命令执行的脚本执行完毕后,都会执行动作。

如果 sigspec 为 ERR,每当管道(可能只包含一个简单命令)、列表或复合命令返回非零退出状态时,都会执行动作,但须遵守以下条件。如果失败的命令是紧随 until 或 while 保留字之后的命令列表的一部分,是 if 或 elif 保留字之后测试的一部分,是 && 或 || 列表中执行的命令(除了最后的 && 或 || 后的命令)、管道中除了最后一个的任何命令(取决于 pipefail shell 选项的状态)、或者命令的返回状态被 ! 取反,则不会执行 ERR trap。这些条件与 errexit (-e) 选项遵循的条件相同。

当 shell 处于非交互模式时,进入非交互 shell 时被忽略的信号无法被捕获或重置。交互式 shell 允许捕获进入时被忽略的信号。未被忽略的捕获信号在创建子 shell 或子 shell 环境时会重置为原始值。

返回状态为零,除非 sigspec 未指定有效信号;否则返回非零值。

3.7.6 Signals 的笔记中也有示例。


true

true

什么也不做,返回状态 0。


umask

umask [-p] [-S] [mode]

将 shell 进程的文件创建掩码设置为 mode。如果 mode 以数字开头,则将其解释为八进制数;否则,将其解释为类似 chmod 命令接受的符号模式掩码。如果省略 mode,umask 将打印掩码的当前值。如果在没有 mode 参数的情况下提供 -S 选项,umask 将以符号格式打印掩码;默认输出为八进制数。如果提供 -p 选项且省略 mode,则输出为可以复用为输入的形式。如果成功更改模式或未提供 mode 参数,则返回状态为零,否则为非零。

请注意,当模式被解释为八进制数时,umask 中的每个数字将从 7 中减去。因此,umask 为 022 会导致权限为 755。

$ umask
0022
$ umask -p
umask 0022
# 实际就是755
$ umask -S
u=rwx,g=rx,o=rx
$ > newfile
$ ls -l newfile
-rw-r--r--. 1 vagrant vagrant 0 Jan  8 04:28 newfile
$ mkdir newdir
$ ls -ld newdir
drwxr-xr-x. 2 vagrant vagrant 6 Jan  8 04:30 newdir
$ umask u=rwx,g=rx,o=rx
$ umask 0022

从上面例子可知,umask为022时,新建目录和新建文件的权限却不同。这是由于文件的默认基础权限是 666(无x,即执行权限),而目录默认是 777(有x,因为目录需要x才能进入)。umask是从默认基础权限中扣除,因此新建目录权限为 0777 & ~022 = 755,而新建文件权限为 0666 & ~022 = 644。

$ printf '%o\n' $(( 0777 & ~022 ))
755
$ printf '%o\n' $(( 0666 & ~022 ))
644

unset

unset [-fnv] [name]

移除每个变量或函数名。如果提供了 -v 选项,则每个名字表示一个 shell 变量,并且该变量将被移除。如果提供了 -f 选项,则这些名字表示 shell 函数,并且函数定义将被移除。如果提供了 -n 选项,并且名字是具有 nameref 属性的变量,则 name 将被取消设置,而不是它引用的变量。如果提供了 -f 选项,-n 不会起作用。如果未提供任何选项,则每个名字表示一个变量;如果没有该名字的变量,则会取消设置具有该名字的函数(如果有)。只读的变量和函数不能被取消设置。当变量或函数被移除时,它们也会从环境中被移除。有些 shell 变量不能被取消设置。有些 shell 变量在被取消设置后会失去其特殊行为;这种行为会在各个变量的描述中说明。返回状态为零,除非名字是只读的或不能被取消设置。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐