Shell 的命令

Mr.Hope ... 2020-6-3 大约 5 分钟

# Shell 的命令

# 命令的类别

Bash 可以使用的命令分成四类。

  • 可执行程序
  • Shell 提供的命令
  • Shell 函数
  • 前三类命令的别名

# type, whatis

type 命令可以显示命令类型。

type command
1

下面是几个例子。

$ type type
type is a shell builtin

$ type ls
ls is aliased to `ls --color=tty'

$ type cp
cp is /bin/cp
1
2
3
4
5
6
7
8

whatis 命令显示指定命令的描述。

$ whatis ls
ls (1) - list directory contents
1
2

# apropos

apropos 命令返回符合搜索条件的命令列表。

$ apropos floppy
create_floppy_devices (8) - udev callout to create all possible
fdformat (8) - Low-level formats a floppy disk
floppy (8) - format floppy disks
gfloppy (1) - a simple floppy formatter for the GNOME
mbadblocks (1) - tests a floppy disk, and marks the bad
mformat (1) - add an MSDOS filesystem to a low-level
1
2
3
4
5
6
7

# alias, unalias

alias 命令用来为命令起别名。

$ alias foo='cd /usr; ls; cd -'

$ type foo
foo is aliased to `cd /usr; ls ; cd -'
1
2
3
4

上面命令指定 foo 为三个命令的别名。以后,执行 foo 就相当于一起执行这三条命令。

注意,默认情况下,别名只在当前 Session 有效。当前 Session 结束时,这些别名就会消失。

alias 命令不加参数时,显示所有有效的别名。

$ alias
alias l.='ls -d .* --color=tty'
alias ll='ls -l --color=tty'
alias ls='ls --color=tty'
1
2
3
4

unalias 命令用来取消别名。

$ unalias foo
$ type foo
bash: type: foo: not found
1
2
3

# which

which 命令显示可执行程序的路径。

$ which ls
/bin/ls
1
2

which 命令用于 Shell 内置命令时(比如 cd),将没有任何输出。

# help,man

help 命令用于查看 Shell 内置命令的帮助信息,man 命令用于查看可执行命令的帮助信息。

help cd
man ls
1
2

man 里面的文档一共有 8 类,如果同一个命令,匹配多个文档,man 命令总是返回第一个匹配。如果想看指定类型的文档,命令可以采用下面的形式。

man 5 passwd
1

# script

script 命令会将输入的命令和它的输出,都保存进一个文件。

script [file]
1

如果没有指定文件名,则所有结果会保存进当前目录下 typescript 文件。结束录制的时候,可以按下 Ctrl + d

# export

export 命令用于将当前进程的变量,输出到所有子进程。

# 命令的连续执行

多个命令可以写在一起。

Bash 提供三种方式,定义它们如何执行。

# 第一个命令执行完,执行第二个命令
command1; command2

# 只有第一个命令成功执行完(退出码0),才会执行第二个命令
command1 && command2

# 只有第一个命令执行失败(退出码非0),才会执行第二个命令
command1 || command2
1
2
3
4
5
6
7
8

上面三种执行方法的退出码,都是最后一条执行的命令的退出码。

bash 允许把命令组合在一起。可以通过两种方式完成;要么用一个 group 命令,要么用一个子 shell。这里是每种方式的语法示例:

组命令:

{ command1; command2; [command3; ...] }
1

子 shell

(command1; command2; [command3;...])
1

这两种形式的不同之处在于,组命令用花括号把它的命令包裹起来,而子 shell 用括号。值得注意的是,鉴于 bash 实现组命令的方式, 花括号与命令之间必须有一个空格,并且最后一个命令必须用一个分号或者一个换行符终止。

那么组命令和子 shell 命令对什么有好处呢? 它们都是用来管理重定向的。

{ ls -l; echo "Listing of foo.txt"; cat foo.txt; } > output.txt
1

使用一个子 shell 是相似的。

(ls -l; echo "Listing of foo.txt"; cat foo.txt) > output.txt
1

组命令和子 shell 真正闪光的地方是与管道线相结合。当构建一个管道线命令的时候,通常把几个命令的输出结果合并成一个流是很有用的。组命令和子 shell 使这种操作变得很简单。

{ ls -l; echo "Listing of foo.txt"; cat foo.txt; } | lpr
1

这里我们已经把我们的三个命令的输出结果合并在一起,并把它们用管道输送给命令 lpr 的输入,以便产生一个打印报告。

虽然组命令和子 shell 看起来相似,并且它们都能用来在重定向中合并流,但是两者之间有一个很重要的不同。然而,一个组命令在当前 shell 中执行它的所有命令,而一个子 shell(顾名思义)在当前 shell 的一个 子副本中执行它的命令。这意味着运行环境被复制给了一个新的 shell 实例。当这个子 shell 退出时,环境副本会消失, 所以在子 shell 环境(包括变量赋值)中的任何更改也会消失。因此,在大多数情况下,除非脚本要求一个子 shell, 组命令比子 shell 更受欢迎。组命令运行很快并且占用的内存也少。

当我们发现管道线中的一个 read 命令 不按我们所期望的那样工作的时候。为了重现问题,我们构建一个像这样的管道线:

echo "foo" | read
echo $REPLY
1
2

该 REPLY 变量的内容总是为空,是因为这个 read 命令在一个子 shell 中执行,所以它的 REPLY 副本会被毁掉, 当该子 shell 终止的时候。因为管道线中的命令总是在子 shell 中执行,任何给变量赋值的命令都会遭遇这样的问题。幸运地是,shell 提供了一种奇异的展开方式,叫做进程替换,它可以用来解决这种麻烦。进程替换有两种表达方式:

一种适用于产生标准输出的进程:

<(list)
1

另一种适用于接受标准输入的进程:

>(list)
1

这里的 list 是一串命令列表:

为了解决我们的 read 命令问题,我们可以雇佣进程替换,像这样。

read < <(echo "foo")
echo $REPLY
1
2

进程替换允许我们把一个子 shell 的输出结果当作一个用于重定向的普通文件。事实上,因为它是一种展开形式,我们可以检验它的真实值:

[me@linuxbox ~]$ echo <(echo "foo")
/dev/fd/63
1
2

通过使用 echo 命令,查看展开结果,我们看到子 shell 的输出结果,由一个名为 /dev/fd/63 的文件提供。