概述
本教程将简要介绍 bash shell 的一些主要特性,涵盖以下主题:
- 使用命令行与 shell 和命令交互
- 使用有效的命令和命令序列
- 定义、修改、引用和导出环境变量
- 访问命令历史和编辑工具
- 调用路径内和路径外的命令
- 使用手册页了解命令
bash shell
bashshell 是可用于 Linux 的几个 shell 之一,也被称为 Bourne-again shell,是根据一个早期的 shell (/bin/sh) 的创建者 Stephen Bourne 来命名的。Bash 高度兼容 sh,但它在函数和编程功能上都提供了许多改进。它合并了来自 Korn shell (ksh) 和 C shell (csh) 的特性,想要成为一个符合 POSIX 的 shell。
除非另行说明,本教程中的示例使用的是 Fedora 22 和 4.0.4 内核。您在其他系统上的结果可能有所不同。
关于本系列
本教程系列可以帮助您学习 Linux 系统管理任务。您还可以使用这些教程中的资料来对 Linux Professional Institute 的 LPIC-1:Linux 服务器专业认证考试进行应考准备。
请参阅 “学习 Linux,101:LPIC-1 学习路线图”,查看本系列中每部教程的描述和链接。这个路线图正在开发之中,它反映了 2015 年 4 月 15 日更新的 4.0 版 LPIC-1 考试目标。在完成这些教程后,我们会将它们添加到路线图中。
本教程帮助针对 Linux Server Professional (LPIC-1) 考试 101 的主题 103 中的目标 103.1 进行应考准备。该目标的权重为 4。
前提条件
要从本系列教程中获得最大收获,您应该拥有 Linux 的基本知识和一个正常工作的 Linux 系统,您可以在这个系统上实践本教程中涵盖的命令。有时程序的不同版本会得到不同的输出格式,所以您的结果可能并不总是与这里给出的清单和图完全相同。
在深入了解 bash 之前,回想一下 shell是一个接受和执行命令的程序。它还支持编程结构,允许使用更小的部件构建复杂的命令。这些复杂命令或 脚本可保存为文件,独自成为新命令。实际上,典型 Linux 系统上的许多命令 都是脚本。
Shell 有一些 内置命令,比如 cd
、break
和 exec
。其他命令是 外部命令。
Shell 也使用 3 种标准 I/O 流:
- stdin是 标准输入流,它向命令提供输入。
- stdout是 标准输出流,它显示来自命令的输出。
- stderr是 标准错误流,它显示来自命令的错误输出。
输入流向程序提供输入,这些输入通常来自终端击键。输出流打印文本字符,通常打印到终端。终端最初为 ASCII 打字机或显示终端,但现在通常是图形桌面上的窗口。有关如何重定向这些标准 I/O 流的更多细节,将会在本 系列的另一篇教程中介绍。
在本教程的剩余部分中,我们假设您知道如何获取 shell 提示符。如果您不知道,developerWorks®教程 “新 Linux 用户的基本任务” 会介绍如何执行此操作和其他操作。
如果您使用的是没有图形桌面的 Linux 系统,或者在图形桌面上打开一个终端窗口,那么您会看到一个提示符,它可能类似于 清单 1中所示的 3 个提示符之一。
清单 1. 一些典型的用户提示符
[ian@atticf20 ~]$
jenni@atticf20:data
$
请注意,这 3 个提示符都来自我的测试系统 atticf20,但面向的是不同用户。前两个是 bash 提示符,二者都显示了已登录的用户、系统名和当前工作目录。第三个提示符是我的系统上针对 ksh shell 的默认提示符。不同发行版和不同 shell 默认使用不同的提示符,所以如果您的发行版看起来有所不同,不要恐慌。我们将在本系列的另一篇教程中介绍如何更改提示符。
如果您以根用户(或超级用户)身份登录,您的提示符可能类似于 清单 2中所示的提示符之一。
清单 2. 超级用户或根用户提示符示例
[root@atticf20 ~]#
atticf20:~#
根用户有很大的权力,所以使用该身份时请小心。在拥有根用户特权时,大部分提示符都包含结尾的井号 (#)。普通用户特权通常使用不同的字符表示,这个字符通常为美元符号 ($)。您的实际提示符可能看起来与本教程中的示例有所不同。您的提示符可能包含您的用户名、主机名、当前目录、日期或打印该提示符的时间,等等。
备注:一些系统(比如 Debian)和基于 Debian 的发行版(比如 Ubuntu)不允许根用户登录,要求所有特权(根用户)命令都使用 sudo
命令执行。在这种情况下,您的提示符不会发生更改,但您应该知道需要使用 sudo
执行普通用户无权执行的命令。
这些教程包含从真实的 Linux 系统剪切并粘贴的代码示例,其中使用了这些系统的默认提示符。根用户提示符有一个结尾 #,所以您可以将它们与普通用户提示符(有一个结尾 $)区分开来。这种约定与许多有关这一主题的图书一致。如果某项功能似乎对您不起作用,您可以检查示例中的提示符。
命令和序列
现在您已经有一个提示符,让我们看看可以使用它做什么。shell 的主要功能是解释您的命令,以便您可以与 Linux 系统交互。在 Linux(和 UNIX®)系统上,命令有一个 命令名,然后拥有 选项和 参数。一些命令既没有选项也没有参数,一些命令只有其中一种。
如果某一代码行包含一个 # 字符,那么该行上的所有剩余字符都会被忽略。所以 # 字符既可以表示注释,也可以表示根用户提示符。从上下文可以明显看出它应表示什么。
Echo
echo
命令将其参数打印(或回送)到终端,如 清单 3中所示。
清单 3. Echo 示例
[ian@atticf20 ~]$ echo Word
Word
[ian@atticf20 ~]$ echo A phrase
A phrase
[ian@atticf20 ~]$ echo Where are my spaces?
Where are my spaces?
[ian@atticf20 ~]$ echo "Here are my spaces." # plus comment
Here are my spaces.
在 清单 3的第 3 个示例中,所有额外的空格在输出中都被压缩为单个空格。为避免这种情况,需要使用双引号 (") 或单引号 (') 给字符串 加上引号。Bash 使用 空白(比如空格、制表符和换行符)将输入行分割为 记号 (token),然后传递给命令。给字符串加上引号会保留额外的空白,使整个字符串成为一个记号。在上面的示例中,命令名后的每个记号都是一个参数,所以我们分别有 1、2、4 和 1 个参数。
echo 命令有两个选项。通常,echo 会在输出的末尾处附加一个换行符。可使用 -n
选项禁止此行为。可使用 -e
选项让某些经过反斜杠转义的字符具有特殊的含义。一些字符如 表 1中所示。
表 1. Echo 和转义字符
转义字符序列 | 功能 |
---|---|
\a | 警报(闹钟) |
\b | 退格 |
\c | 禁止结尾的换行符(等效于 -n 选项) |
\f | 换页(清除视频显示器上的屏幕) |
\n | 换行 |
\r | 回车 |
\t | 水平制表符 |
转义和续行
在 bash 中使用反斜杠有一个小问题。当为给反斜杠字符 (\) 加上引号时,它可以充当一个转义字符来告诉 bash 本身保留后面的字符的字面含义。这对特殊的 shell 元字符很有必要,我们稍后会介绍元字符。此规则有一个例外:反斜杠后跟一个换行符会导致 bash 合并两个字符,将该序列视为一个续行请求。这样可以很方便地分割较长的行,尤其是在 shell 脚本中。
要让 echo
命令或其他许多使用类似转义控制字符的命令正确处理上述序列,必须将转义序列放在引号中,或者作为引号中的字符串的一部分,除非您使用了第二个反斜杠让 shell 将其中一个保留给命令。清单 4给出了 \ 的各种用法的一些示例。
清单 4. 更多 echo 示例
[ian@atticf20 ~]$ echo -e "No new line\c"
No new line[ian@atticf20 ~]$ echo "A line with a typed
> return"
A line with a typed
return
[ian@atticf20 ~]$ echo -e "A line with an escaped\nreturn"
A line with an escaped
return
[ian@atticf20 ~]$ echo "A line with an escaped\nreturn but no -e option"
A line with an escaped\nreturn but no -e option
[ian@atticf20 ~]$ echo -e Doubly escaped\\n\\tmetacharacters
Doubly escaped
metacharacters
[ian@atticf20 ~]$ echo Backslash \
> followed by newline \
> serves as line continuation.
Backslash followed by newline serves as line continuation.
请注意,当您键入的一行中包含不匹配的引号时,bash 会显示一个特殊提示符 (>)。您的输入字符串会延续到第二行,并包含换行符。
Bash shell 元字符和控制运算符。
Bash 有多个 元字符,在未加引号时,它们的作用是将输入分解为单词。除了空格之外,元字符还包括:
- |
- &
- ;
- (
- )
- <
- >
我们将在本教程的其他部分更详细讨论其中一些元字符。但现在需要注意的是,如果想要包含元字符作为文本的一部分,则必须将它放在引号中或使用反斜杠 (\) 转义,如 清单 4中所示。
换行符和某些元字符或元字符对也可以用作 控制运算符。它们是:
- ||
- &&
- &
- ;
- ;;
- |
- (
- )
这些控制运算符中的部分运算符可用于创建命令 序列或 列表。
最简单的命令序列是由一个分号 (;) 分开的两个命令。每个命令按顺序执行。在任何可编程环境中,命令都会返回成功或失败指示;Linux 命令通常返回 0 值表示成功,在失败时返回非 0 值。可以使用 && 和 || 控制运算符向列表中引入一些条件处理。如果使用控制运算符 && 将两个命令分开,当且仅当第一个命令返回退出值 0 时,才会执行第二个命令。如果使用 || 分离这些命令,那么只在第一个命令返回非 0 的退出代码时,才会执行第二个命令。清单 5给出了一些使用 echo 命令的命令序列。这些命令序列不是很有趣,因为 scho 返回了 0,但是在后面有更多命令可用时,您会看到更多示例。
清单 5. 命令序列
[ian@atticf20 ~]$ echo line 1;echo line 2; echo line 3
line 1
line 2
line 3
[ian@atticf20 ~]$ echo line 1&&echo line 2&&echo line 3
line 1
line 2
line 3
[ian@atticf20 ~]$ echo line 1||echo line 2; echo line 3
line 1
line 3
Exit
可以使用 exit
命令终止 shell。可以提供一个退出代码作为参数,此操作是可选的。如果在图形桌面上的终端窗口中运行 shell,您的窗口将会关闭。类似地,如果使用 ssh 或 telnet(举例而言)连接到远程系统,您的连接将终止。在 bash shell 中,还可以按住 Ctrl键并按下 d键来退出。
让我们看看另一个控制运算符。如果将一个命令或命令列表放在括号中,该命令或序列会在一个子 shell 中执行,所以 exit 命令会退出子 shell,而不是退出您所在的 shell。清单 6给出了一个结合使用 &&、|| 和两个不同的退出代码的简单示例。
清单 6. 子 shell 和序列
[ian@atticf20 ~]$ (echo In subshell; exit 0) && echo OK || echo Bad exit
In subshell
OK
[ian@atticf20 ~]$ (echo In subshell; exit 4) && echo OK || echo Bad exit
In subshell
Bad exit
请在本教程后面继续学习更多命令序列。
环境变量
当您在 bash shell 中运行时,许多因素构成了您的 环境,比如您的提示符的形式、主目录、工作目录、shell 的名称、您打开的文件、您定义的函数,等等。您的环境包含许多可由 bash 或您设置的 变量。bash shell 还允许您拥有 shell 变量,您可以将这些变量 导出到环境中,供 shell 中运行的其他进程或您可能从当前 shell 衍生的其他 shell 使用。
环境变量和 shell 变量都有一个 名称。可以在名称前面加上 ‘ $ ’ 作为前缀来引用变量的值。您会遇到的一些常见的 bash 环境变量如 表 2中所示。
表 2. 一些常见的 bash 环境变量
名称 | 功能 |
---|---|
USER | 登录用户的名称 |
UID | 登录用户的用户 id 数字 |
HOME | 用户的主目录 |
PWD | 当前工作目录 |
SHELL | shell 的名称 |
$ | 运行的 bash shell(或其他)进程的进程 id(或 PID) |
PPID | 启动此进程的进程的进程 id(也就是父进程的 id) |
? | 上一个命令的退出代码 |
清单 7展示了您可能在其中一些常见 bash 变量中看到的结果
清单 7. 环境变量和 shell 变量
[ian@atticf20 ~]$ echo $USER $UID
ian 1000
[ian@atticf20 ~]$ echo $SHELL $HOME $PWD
/bin/bash /home/ian /home/ian
[ian@atticf20 ~]$ (exit 0);echo $?;(exit 4);echo $?
0
4
[ian@atticf20 ~]$ echo $$ $PPID
3175 2457
未使用 bash ?
bash shell 是大部分 Linux 发行版上的默认 shell。如果您未在 bash shell 下运行,可以考虑采用以下方式之一来练习使用 bash shell。
- 使用
chsh -s /bin/bash
命令更改默认 shell。默认 shell 会在您下次登录时生效。 - 使用
su - $USER -s /bin/bash
命令创建另一个进程作为当前 shell 的子进程。新进程将是一个使用 bash 的登录 shell。 - 使用 bash shell 的默认设置创建一个 id,将其用于 LPI 备考。
可通过键入一个名称后立即键入等号 (=) 来创建或 设置shell 变量。如果该变量存在,可以修改它来分配新值。变量是区分大小写的,所以 var1 和 VAR1 是不同的变量。根据约定,变量(特别是导出的变量)采用大写,但这不是必须的。从技术上讲,$$ 和 $? 是 shell 参数而不是变量。它们只能被引用;不能向它们分配值。
创建 shell 变量时,您通常希望将它 导出到环境中,以便从此 shell 启动的其他进程可以使用它。导出的变量 不可用于父 shell。可使用 export
命令导出变量名。作为 bash 中的快捷方式,可以在一个步骤中分配一个值并导出变量。
为了演示分配和导出过程,我们将在 bash shell 中运行该 bash 命令,然后从新的 bash shell 运行 Korn shell (ksh)。我们将使用 ps
命令显示正在运行的命令的信息。本 系列的另一篇教程会更详细地介绍 ps
。
清单 8. 更多环境变量和 shell 变量
[ian@atticf20 ~]$ ps -p $$ -o "pid ppid cmd"
PID PPID CMD
3175 2457 bash
[ian@atticf20 ~]$ bash
[ian@atticf20 ~]$ ps -p $$ -o "pid ppid cmd"
PID PPID CMD
4325 3175 bash
[ian@atticf20 ~]$ VAR1=var1
[ian@atticf20 ~]$ VAR2=var2
[ian@atticf20 ~]$ export VAR2
[ian@atticf20 ~]$ export VAR3=var3
[ian@atticf20 ~]$ echo $VAR1 $VAR2 $VAR3
var1 var2 var3
[ian@atticf20 ~]$ echo $VAR1 $VAR2 $VAR3 $SHELL
var1 var2 var3 /bin/bash
[ian@atticf20 ~]$ ksh
$ ps -p $$ -o "pid ppid cmd"
PID PPID CMD
4427 4325 ksh
$ export VAR4=var4
$ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL
var2 var3 var4 /bin/bash
$ exit
[ian@atticf20 ~]$ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL
var1 var2 var3 /bin/bash
[ian@atticf20 ~]$ ps -p $$ -o "pid ppid cmd"
PID PPID CMD
4325 3175 bash
[ian@atticf20 ~]$ exit
exit
[ian@atticf20 ~]$ ps -p $$ -o "pid ppid cmd"
PID PPID CMD
3175 2457 bash
[ian@atticf20 ~]$ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL
/bin/bash [ian@echidna ~]$ ps -p $$ -o "pid ppid cmd"
PID PPID CMD
2559 2558 -bash
[ian@echidna ~]$ bash
[ian@echidna ~]$ ps -p $$ -o "pid ppid cmd"
PID PPID CMD
2811 2559 bash
[ian@echidna ~]$ VAR1=var1
[ian@echidna ~]$ VAR2=var2
[ian@echidna ~]$ export VAR2
[ian@echidna ~]$ export VAR3=var3
[ian@echidna ~]$ echo $VAR1 $VAR2 $VAR3
var1 var2 var3
[ian@echidna ~]$ echo $VAR1 $VAR2 $VAR3 $SHELL
var1 var2 var3 /bin/bash
[ian@echidna ~]$ ksh
$ ps -p $$ -o "pid ppid cmd"
PID PPID CMD
2840 2811 ksh
$ export VAR4=var4
$ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL
var2 var3 var4 /bin/bash
$ exit
[ian@echidna ~]$ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL
var1 var2 var3 /bin/bash
[ian@echidna ~]$ ps -p $$ -o "pid ppid cmd"
PID PPID CMD
2811 2559 bash
[ian@echidna ~]$ exit
exit
[ian@echidna ~]$ ps -p $$ -o "pid ppid cmd"
PID PPID CMD
2559 2558 -bash
[ian@echidna ~]$ echo $VAR1 $VAR2 $VAR3 $VAR4 $SHELL
/bin/bash
备注:
- 在此序列的开头,bash shell 拥有 PID 3175。
- 第二个 bash shell 拥有 PID 4325,它的父 shell 为 PID 3175(原始的 bash shell)。
- 我们在第二个 bash shell 中创建了 VAR1、VAR2 和 VAR3,但仅导出了 VAR2 和 VAR3。
- 在 Korn shell 中,我们创建了 VAR4。
echo
命令仅显示了 VAR2、VAR3 和 VAR4 的值,可以确认 VAR1 未导出。您看到 SHELL 变量的值未发生改变是否感到很奇怪,因为提示符已发生改变?您不能始终依靠 SHELL 来告诉您在哪些 shell 下运行,但ps
命令会告诉您实际命令。请注意,ps
在第一个 bash shell 前面放入了一个连字符 (-),表明这是一个 登录 shell。 - 返回到第二个 bash shell,我们可以看到 VAR1、VAR2 和 VAR3。
- 最后,当返回到原始 shell 时,所有新变量都不存在。
之前对引号的讨论中已提到,可以使用单引号或双引号。它们之间有一个重要区别。shell 会扩展双引号之间的 shell 变量,但使用单引号 (') 时不会扩展。在上一个示例中,我们在 shell 中启动了另一个 shell 并获得了一个新进程 id。使用 -c
选项,您可以将一个命令传递给其他 shell,该 shell 将执行该命令并返回。如果传递一个带引号的字符串作为命令,外部 shell 将消除引号并传递字符串。如果使用双引号,变量扩展会在传递字符串 之前执行,所以结果可能不符合预期。该 shell 和命令将在另一个进程中运行,所以它们将拥有另一个 PID。清单 9演示了这些概念。* bash shell 的 PID 已突出显示。
清单 9. 添加引号和 shell 变量
[ian@atticf20 ~]$ echo "$SHELL" '$SHELL' "$$" '$$'
/bin/bash $SHELL 3175$$
[ian@atticf20 ~]$ bash -c "echo Expand in parent $$ $PPID"
Expand in parent 31752457
[ian@atticf20 ~]$ bash -c 'echo Expand in child $$ $PPID'
Expand in child 4541 3175
目前为止,我们的所有变量引用都以空白终止,所以很清楚变量名在何处结束。事实上,变量名仅能由字母、数字或下划线组成。shell 知道变量名会在找到另外的字符时结束。有时,您需要在含义模糊表达式中使用变量。在这些情况下,可以使用花括号来表示变量名,如 清单 10中所示。
清单 10. 将花括号用于变量名
[ian@atticf20 ~]$ echo "-$HOME/abc-"
-/home/ian/abc-
[ian@atticf20 ~]$ echo "-$HOME_abc-"
--
[ian@atticf20 ~]$ echo "-${HOME}_abc-"
-/home/ian_abc-
Env
没有任何选项或参数的 env
命令显示当前环境变量。也可以使用它在自定义环境中执行命令。-i
(或者只是 -
)选项在运行命令前清除当前环境,而 -u
选项取消设置您不希望传递的环境变量。
清单 11显示了没有任何参数的 env
命令的部分输出,然后给出了 3 个调用没有父环境的不同 shell 的示例。在我们讨论它们之前请仔细看看它们。
备注:如果您的系统未安装 ksh (Korn) 或 tcsh shell,您需要安装它们来自行执行这些练习。
清单 11. env 命令
[ian@atticf20 ~]$ env
XDG_VTNR=2
XDG_SESSION_ID=1
HOSTNAME=atticf20
GPG_AGENT_INFO=/run/user/1000/keyring/gpg:0:1
SHELL=/bin/bash
TERM=xterm-256color
XDG_MENU_PREFIX=gnome-
VTE_VERSION=4002
HISTSIZE=1000
GJS_DEBUG_OUTPUT=stderr
WINDOWID=35651982
GJS_DEBUG_TOPICS=JS ERROR;JS LOG
QT_GRAPHICSSYSTEM_CHECKED=1
USER=ian
..._=/usr/bin/env
OLDPWD=/home/ian/Documents
[ian@atticf20 ~]$ env -i bash -c 'echo $SHELL; env'
/bin/bash
PWD=/home/ian
SHLVL=1
_=/usr/bin/env
[ian@atticf20 ~]$ env -i ksh -c 'echo $SHELL; env'
/bin/sh
_=*3175*/usr/bin/env
PWD=/home/ian
SHLVL=1
_AST_FEATURES=UNIVERSE - ucb
A__z="*SHLVL
[ian@atticf20 ~]$ env -i tcsh -c 'echo $SHELL; env'
SHELL: Undefined variable.
当 bash 设置 SHELL 变量时,会将其导出至环境中。新 bash shell 已在环境中创建了 3 个其他变量。在 ksh 示例中,我们有 5 个环境变量,但我们尝试回送 SHELL 变量值时,获得了输出 /bin/sh。一些更早的 ksh 版本仅提供一个空白行来表明 SHELL 变量未设置。最后,tcsh 未创建任何环境变量,并在我们尝试引用 SHELL 的值时生成了一个错误。
取消设置和设置
清单 11显示了 shell 处理变量和环境的不同行为。尽管本教程重点介绍的是 bash,但也有必要知道不是所有 shell 都具有相同的行为。此外,shell 将根据它是否是 登录 shell来采取不同行为。就目前而言,我们仅假设登录 shell 是您登录到系统时获得的 shell;如果愿意,您还可以启动其他 shell 来充当登录 shell。上面使用 env -i
启动的 3 个 shell 都不是登录 shell。尝试将 -l
选项传递给 shell 命令本身,看看您使用登录 shell 会获得哪些区别。
让我们尝试在这些非登录 shell 中显示 SHELL 变量的值:
- 当 bash 启动时,它会设置 SHELL 变量,但不会自动将其导出到环境中。
- 当 ksh 启动时,它会将其 SHELL 变量的视图设置为 /bin/sh。相较而言,在之前的示例中,ksh 继承了从调用 bash shell 导出的 /bin/bash 值。
- 当 tcsh 启动时,没有设置 SHELL 变量。在这种情况下,默认行为与 ksh(和 bash)不同,因为在我们尝试使用一个不存在的变量时报告了错误。
可以使用 unset
命令取消设置变量,并从 shell 变量列表中删除它。如果变量已导出到环境中,此命令还会从环境中删除它。可以使用 set
命令控制 bash(或其他 shell)的工作方式的许多方面。Set 是一个 shell 内置命令,所以各种选项都是特定于 shell 的。在 bash 中,-u
选项导致 bash 报告变量未定义的错误,而不是将它们视为已定义但是空的。可以使用 -
打开 set
的各种选项,使用 +
关闭它们。可以使用 echo $-
显示当前设置的选项。
清单 12. 取消设置和设置
[ian@atticf20 ~]$ echo $-
himBH
[ian@atticf20 ~]$ echo $VAR1 [ian@atticf20 ~]$ set -u;echo $-
himuBH
[ian@atticf20 ~]$ echo $VAR1
bash: VAR1: unbound variable
[ian@atticf20 ~]$ VAR1=v1;echo $VAR1
v1
[ian@atticf20 ~]$ unset VAR1;echo $VAR1
bash: VAR1: unbound variable
[ian@atticf20 ~]$ set +u;echo $VAR1;echo $- himBH
如果使用没有任何选项的 set
命令,它会显示所有 shell 变量和它们的值(如果有)。还有另一个 declare
命令,可用于创建、导出和显示 shell 变量的值。可以使用手册页了解许多剩余的 set
选项和 declare
命令。本教程后面部分将讨论 手册页。
Exec
最后一个要介绍的命令是 exec
。可使用 exec
命令运行另一个程序来 取代当前 shell。清单 13启动了一个子 bash shell,然后使用 exec
将它替换为一个 Korn shell。从 Korn shell 退出时,会返回到原始 bash shell(在本例中为 PID 2852)。
清单 13. 使用 exec
[ian@atticf20 ~]$ echo $$
3175
[ian@atticf20 ~]$ bash
[ian@atticf20 ~]$ echo $$
4994
[ian@atticf20 ~]$ exec ksh
$ echo $$
4994
$ exit
[ian@atticf20 ~]$ echo $$
3175
使用 uname 获取系统信息
uname
命令打印您的系统及其内核的信息。清单 14给出了 uname
的各种选项和结果信息;每个选项均在 表 3中定义。
清单 14. uname 命令
[ian@atticf20 ~]$ uname
Linux
[ian@atticf20 ~]$ uname -s
Linux
[ian@atticf20 ~]$ uname -n
atticf20
[ian@atticf20 ~]$ uname -r
4.0.4-303.fc22.x86_64
[ian@atticf20 ~]$ uname -v
#1 SMP Thu May 28 12:37:06 UTC 2015
[ian@atticf20 ~]$ uname -m
x86_64
[ian@atticf20 ~]$ uname -o
GNU/Linux
[ian@atticf20 ~]$ uname -a
Linux atticf20 4.0.4-303.fc22.x86_64 #1 SMP Thu May 28 12:37:06 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux
表 3. uname 的选项
选项 | 描述 |
---|---|
-s | 打印内核名称。如果未指定选项,那么这就是默认选项。 |
-n | 打印节点名或主机名。 |
-r | 打印内核的发行版。此选项通常用于模块处理命令。 |
-v | 打印内核的版本。 |
-m | 打印机器的硬件 (CPU) 名称。 |
-o | 打印操作系统名称。 |
-a | 打印所有上述信息。 |
清单 14来自一个在 64 位 AMD®CPU 上运行的 Fedora 22 系统。uname
命令可用于大部分 UNIX®和类 UNIX 系统,以及 Linux。由于 Linux 发行版和版本不同,以及您所在的机器类型不同,打印的信息也将不同。清单 15显示了来自一个老旧 Intel®32 位系统的输出,该系统在即时模式下从 DVD 运行 Ubuntu 14.04。
清单 15. 为另一个系统使用 uname
ian@Z61t-u14:~$ uname -a
Linux Z61t-u14 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:45:15 UTC
2015 i686 i686 i686 GNU/Linux
命令历史
如果您在阅读过程中键入了命令,您可能会注意到,通常一个命令会被使用许多次,无论是原封不动地使用还是稍微更改一下再使用。好消息是,bash shell 可维护命令 历史。默认情况下,历史功能已开启。可以使用命令 set +o history
关闭它,使用 set -o history
命令再次打开它。环境变量 HISTSIZE 会告诉 bash 保留多少行历史记录。其他一些设置可控制如何使用和管理历史记录。请参阅 bash 手册页了解完整细节。
可用于历史记录工具的一些命令包括:
- history
- 显示完整历史记录。
- history N
- 显示历史记录中的最后 N行。
- history -d N
- 从历史记录中删除第 N行;举例而言,如果该行包含一个密码,您可以这么做。
- !!
- 您最近使用的命令。
- !N
- 第 N个历史记录命令。
- !-N
- 历史记录中的 N个命令之前的命令(!-1 等效于 !!)。
- !#
- 您键入的当前命令。
- !string
- 以 string开头的最新的命令。
- !?string?
- 包含 string的最新命令。
也可以使用一个冒号 (:) 后跟一些值来访问或修改历史记录中的一部分或一个命令。清单 16演示了一些历史功能。
清单 16. 处理历史
[ian@atticf20 ~]$ echo $$
3175
[ian@atticf20 ~]$ env -i bash -c 'echo $$'
6737
[ian@atticf20 ~]$ !!
env -i bash -c 'echo $$'
6744
[ian@atticf20 ~]$ !ec
echo $$
3175
[ian@atticf20 ~]$ !en:s/$$/$PPID/
env -i bash -c 'echo $PPID'
3175
[ian@atticf20 ~]$ history 6
263 history -d259
264 echo $$
265 env -i bash -c 'echo $$'
266 echo $$
267 env -i bash -c 'echo $PPID'
268 history 6
[ian@atticf20 ~]$ history -d266
[ian@atticf20 ~]$ !-2
history 6
264 echo $$
265 env -i bash -c 'echo $$'
266 env -i bash -c 'echo $PPID'
267 history 6
268 history -d266
269 history 6
清单 16中的命令执行以下操作:
- 回送当前 shell 的 PID。
- 在新 shell 中运行一个 echo 命令并回送该 shell 的 PID。
- 重新运行最后一个命令。
- 重新运行最后一个以 “ec” 开头的命令;这会返回此示例中的第一个命令。
- 重新运行最后一个以 ‘ en ’ 开头的命令,但使用 ‘ $PPID ’ 替代 ‘ $$ ’,所以显示了父 PID。
- 在终端输出上放上一条注释。
- 显示历史记录中的最后 6 条命令。
- 删除历史条目 266,也就是最后一个 echo 命令。
- 重新显示历史记录中的最新的 6 条命令。
也可以交互式地编辑历史记录。bash shell 使用 readline 库来管理命令编辑和历史记录。默认情况下,用于在历史记录中移动或编辑行的键和组合键与 GNU Emacs 编辑器中使用的类似。Emacs 组合键通常表示为 C-x或 M-x,其中 x是一个常规键,C和 M分别是 控制键和 元键。在典型的 PC 系统上,Ctrl键被用作 Emacs 控制键,Alt键被用作元键。表 4总结了一些可用的历史记录编辑功能。除了 表 4中所示的组合键之外,光标移动键(比如向左、向右、向上和向下箭头)及 Home 和 End 键通常被设置为按逻辑方式操作。可以在手册页中了解其他功能,以及如何使用 readline init 文件(通常是您的主目录中的 inputrc)来自定义这些选项。
表 4. 使用 emacs 命令编辑历史记录
命令 | 常见 PC 键 | 描述 |
---|---|---|
C-f | 向右箭头 | 右移一个空格。 |
C-b | 向左箭头 | 左移一个空格。 |
C-p | 向上箭头 | 在历史记录中向前移一条命令。 |
C-n | 向下箭头 | 在历史记录中向后移一条命令。 |
C-r | 递增地逆向搜索。键入一个或多个字母来反向搜索一个字符串。再次按 C-r 会搜索前一次出现相同字符串的命令。 | |
M-f | Alt-f | 移到下一个单词的开头;GUI 环境通常使用此组合键来打开窗口的 File菜单。 |
M-b | Alt-b | 移到前一个单词的开头。 |
C-a | Home | 移到行首。 |
C-e | End | 移到行末。 |
Backspace | Backspace | 删除光标前一个字符。 |
C-d | Del | 删除光标处的字符(Del 和 Backspace 功能可配置为相反的含义)。 |
C-k | Ctrl-k | 删除(结束)到行末,保存删除的文本供以后使用。 |
M-d | Alt-d | 删除(结束)到单词结尾,保存删除的文本供以后使用。 |
C-y | Ctrl-y | 恢复使用 kill 命令删除的文本。 |
如果您更喜欢使用类似 vi 的编辑模式处理历史记录,可以使用命令 set -o vi
切换到 vi 模式。可使用 set -o emacs
切换回 emacs 模式。当在 vi 模式下检索命令时,最初会处于 vi 的插入模式。本系列的另一篇教程中将会介绍 vi 编辑器。(请参阅 参考资料了解系列学习路线图)。
当您关闭一个 shell 会话或从 shell 注销时,会将主目录中最后的 $HISTSIZE 历史记录行保存在一个名为 .bash_history (~/.bash_history) 的文件中。再次登录时,会从此文件加载您的历史记录。如果打开了多个会话,那么在每个会话关闭时都会重写历史记录文件。来自每个会话的数据不会合并。
路径 - 我的命令在何处?
一些 bash 命令是内置命令,而其他命令是外部命令。我们现在看看外部命令,如何运行它们,以及如何确定命令是内部的。
shell 在何处查找命令?
外部命令是文件系统中的文件。本系列的另一篇教程将会介绍基本文件管理。(请参阅 参考资料了解系列学习路线图)。在 Linux 和 UNIX 系统上,会在一个以 / 为根的大型树中访问所有文件。在我们目前为止的示例中,当前目录是用户的主目录。非根用户在 /home 目录内通常有一个主目录,比如我的主目录为 /home/ian。根用户的主目录通常为 /root。如果您键入一个命令名称,bash 会在您的 路径上查找该命令,该路径是 PATH 环境变量中的一个冒号分隔的目录列表。
如果想知道在键入某个特定字符串时将执行哪个命令,可使用 which
或 type
命令。清单 17显示了我的默认路径和多个命令的位置。
清单 17. 查找命令位置
[ian@atticf20 ~]$ echo $PATH
/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/ian/.local/bin:/home/ian/bin
[ian@atticf20 ~]$ which bash env zip xclock echo set ls
alias ls='ls --color=auto'
/usr/bin/ls
/usr/bin/bash
/usr/bin/env
/usr/bin/zip
/usr/bin/xclock
/usr/bin/echo
/usr/bin/which: no set in (/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:
/home/ian/.local/bin:/home/ian/bin)
[ian@atticf20 ~]$ type bash env zip xclock echo set ls
bash is hashed (/usr/bin/bash)
env is hashed (/usr/bin/env)
zip is /usr/bin/zip
xclock is /usr/bin/xclock
echo is a shell builtin
set is a shell builtin
ls is aliased to `ls --color=auto'
注意,该路径中的目录大部分都位于 /bin 中。这是一种常见约定,但不是一种要求。which
命令报告 ls
命令是一个 别名,set
命令无法找到。在这种情况下,我们将此解释为该命令不存在,或者它是一个内置命令。type
命令报告 ls
命令是一个 别名,但将 set
命令识别为 shell 内置命令。它还报告有一个内置的 echo
命令,which
也在 /bin 下找到了一个命令。这些命令按不同的顺序生成输出。
我们可以看到,ls
命令(用于列出目录内容)是一个 别名。别名是一种配置一些命令来使用不同的默认设置集合或为一个命令提供替代名称的方便方式。在我们的示例中,因为选择了 --color=tty
选项,所以会依据文件或目录的类型对目录清单进行颜色编码。尝试运行 dircolors --print-database
来查看如何控制颜色编码,以及对哪种文件使用了哪种颜色。
每个命令都有更多选项。根据您的需求,可以使用任一个命令。在能够合理地确定我将找到一个可执行文件,我只需要它的完整路径规范时,我倾向于使用 which
。我发现 type
为我提供了更准确的信息,shell 脚本中有时需要这些信息。
运行其他命令
我们在 清单 17中看到,可执行文件具有以根目录 / 开头的完整路径。例如,xclock 程序实际上为 /usr/bin/xclock,是一个位于 /usr/bin 目录的文件。在旧系统上,您可能在 /usr/X11R6/bin 目录中找到此文件。如果某个命令 不在您的 PATH 规范中,您仍可以指定一个路径和命令名来运行它。可以使用两种类型的路径:
- 绝对路径以 / 开头,比如我们在 清单 17中看到的路径(/bin/bash、/bin/env 等)。
-
相对路径与
pwd
命令所报告的您的 当前工作目录相对。这些命令不以 / 开头,但包含至少一个 /。
无论当前工作目录是什么,都可以使用绝对路径,但仅在某个命令在您的当前目录附近时才可能使用相对路径。假设您正在主目录的一个子目录 mytestbin 中开发经典 “Hello World!” 程序的一个新版本。可以使用相对路径以 mytestbin/hello
形式运行您的命令。可以在一个路径中使用两个特殊名称;一个点 (.) 指代当前目录,两个点 (..) 指代当前目录的父目录。因为您的主目录通常不在您的路径上(而且一般不应在这),您需要显式提供您想从主目录运行的任何可执行文件的路径。例如,如果主目录中有一个 hello 程序的副本,可以使用命令 ./hello
运行它。. 和 .. 都可以用在绝对路径中,但一个 . 在这种情况下不是很有用。也可以使用波浪符号 (~) 表示您自己的主目录,使用 ~username表示名为username的用户的主目录。一些示例如 清单 18中所示。
清单 18. 绝对路径和相对路径
[ian@atticf20 ~]$ /bin/echo Use echo command rather than builtin
Use echo command rather than builtin
[ian@atticf20 ~]$ /usr/../bin/echo Include parent dir in path
Include parent dir in path
[ian@atticf20 ~]$ /bin/././echo Add a couple of useless path components
Add a couple of useless path components
[ian@atticf20 ~]$ pwd # See where we are
/home/ian
[ian@atticf20 ~]$ ../../bin/echo Use a relative path to echo
Use a relative path to echo
[ian@atticf20 ~]$ myprogs/hello # Use a relative path with no dots
bash: myprogs/hello: No such file or directory
[ian@atticf20 ~]$ mytestbin/hello # Use a relative path with no dots
Hello World!
[ian@atticf20 ~]$ cp mytestbin/hello . # Copy hello program to home
[ian@atticf20 ~]$ ./hello
Hello World!
[ian@atticf20 ~]$ ~/mytestbin/hello # run hello using ~
Hello World!
[ian@atticf20 ~]$ ../hello # Try running hello from parent
bash: ../hello: No such file or directory
更改工作目录
就像可以从系统中的各种目录执行程序一样,也可以使用 cd
命令更改当前工作目录。cd
的参数必须是目录的绝对或相对路径。对于命令,可以在路径中使用 .、..、~ 和 ~username。如果使用没有参数的 cd
,则会更改到您的主目录。使用一个连字符 (-) 作为参数,表示更改到之前的工作目录。您的主目录存储在 HOME 环境变量中,上一个目录存储在 OLDPWD 变量中,所以单独的 cd
等效于 cd $HOME
,cd -
等效于 cd $OLDPWD
。通常,我们说 更改目录,而不会完整地说出 更改当前工作目录。
对于命令,也有一个环境变量 CDPATH,它包含一个应在解析相对路径时搜索到的冒号分隔的目录集(除当前工作目录外)。如果解析时使用了一个来自 CDPATH 的路径,cd
将打印结果目录的完整路径作为输出。通常,成功的目录更改没有输出,而是会出现一个新的且可能已更改的提示符。一些示例如 清单 19中所示。
清单 19. 更改目录
[ian@atticf20 ~]$ cd /;pwd
/
[ian@atticf20 /]$ cd /usr/local;pwd
/usr/local
[ian@atticf20 local]$ cd ;pwd
/home/ian
[ian@atticf20 ~]$ cd -;pwd
/usr/local
/usr/local
[ian@atticf20 local]$ cd ~ian/..;pwd
/home
[ian@atticf20 home]$ cd ~;pwd
/home/ian
[ian@atticf20 ~]$ export CDPATH=~
[ian@atticf20 ~]$ cd /;pwd
/
[ian@atticf20 /]$ cd mytestbin
/home/ian/mytestbin
手册页
本教程的最后一个主题将介绍如何通过手册页或其他文档来源获取 Linux 命令的文档。
手册页和各部分
主要(且传统)的文档来源是 手册页,可使用 man
命令访问它们。图 1展示了 grep
命令的实际运用。可使用命令 man grep
显示此信息。
图 1. grep 命令的手册页
- 一个标题,其中包含命令的名称,其后的括号中是各部分的编号。
- 该命令的名称和同一个手册页上描述的任何相关命令。
- 适用于该命令的选项和参数概要。
- 命令的简短描述。
- 每个选项的详细信息。
您可能会找到其他有关用法、如何报告错误、作者信息和相关命令列表的部分。相关命令及相应部分通常会在 SEE ALSO 下找到。例如,man
的手册页列出了相关命令,如 清单 20中所示。
清单 20. 与 man 命令相关的命令
SEE ALSO
apropos(1), groff(1), less(1), manpath(1), nroff(1),
troff(1), whatis(1), zsoelim(1), setlocale(3), man ‐
path(5), ascii(7), latin1(7), man(7), catman(8),
mandb(8), the man-db package manual, FSSTND
手册页有 8 个常见的部分。手册页通常在安装包时安装,如果没有安装包,可能就没有该包的手册页。类似地,手册的某些部分可能是空的或几乎是空的。手册的常见部分及一些示例内容如下:
- 用户命令(env、ls、echo、mkdir、tty)
- 系统调用或内核函数(link、sethostname、mkdir)
- 库例程(acosh、asctime、btree、locale、XML::Parser)
- 设备相关信息(isdn_audio、mouse、tty、zero)
- 文件格式描述(keymaps、motd、wvdial.conf)
- 游戏(请注意,许多游戏现在都是图形化的,而且在手册页系统外拥有图形帮助)
- 杂项(arp、boot、regex、unix utf8)
- 系统管理(debugfs、fdisk、fsck、mount、renice、rpm)
您可能找到的其他部分包括 9表示 Linux 内核文档,n表示新文档,o表示旧文档,l表示本地文档。
一些条目会出现在多个部分中。下面的 清单 21中的示例显示了第 1 和 2 部分都包含 mkdir,第 1 和 4 部分都包含 tty。可以指定某个特定的部分,例如 man 4 tty
或 man 2 mkdir
,或者可以指定 -a
选项来列出所有适用的手册部分。
man
命令有许多选项和相关命令,您可以自行探索。现在我们快速看看与 man
相关的 “另请参见” 命令。
另请参见
与 man
相关的两个重要命令是 whatis
和 apropos
。whatis
命令在手册页中搜索您提供的名称,并显示来自合适的手册页的名称信息。apropos
命令对手册页执行关键词搜索,并列出包含您的关键词的命令。
清单 21. Whatis 和 apropos 示例
[ian@atticf20 ~]$ whatis man
man (7) - macros to format man pages
man (1p) - display system documentation
man (1) - an interface to the on-line reference m...
[ian@atticf20 ~]$ whatis mkdir
mkdir (3p) - make a directory relative to directory ...
mkdir (1p) - make directories
mkdir (2) - create a directory
mkdir (1) - make directories
[ian@atticf20 ~]$ apropos mkdir
gvfs-mkdir (1) - Create directories
mkdir (1) - make directories
mkdir (1p) - make directories
mkdir (2) - create a directory
mkdir (3p) - make a directory relative to directory ...
mkdirat (2) - create a directory
man
命令使用一个分页程序分页显示输出。在大部分 Linux 系统上,该程序可能是 less
程序。另一种选择可能是更古老的 more
程序。如果您希望打印该页,可指定 -t
选项,使用 groff
或 troff
程序格式化页面以便打印。
less
分页程序有多个命令,可帮助您在显示的输出中搜索字符串。可使用 man less
查找 /(向前搜索)、?(向后搜索)和 n(重复上次搜索)等其他许多命令的更多信息。
Bash help
bash 的手册页非常长,搜索它可能会花一些时间,甚至使用 less
分页程序也是如此。幸运的是,如果您希望快速获得与某个 bash 内置命令相关的帮助,可以使用 help
内置命令,如 清单 22中所示。使用没有参数的 help
会获得可用帮助列表,使用 help help
可了解如何使用 help
。
清单 22. 获取 Bash 内置命令的帮助
[ian@atticf20 ~]$ type help # See that help is a builtin
help is a shell builtin
[ian@atticf20 ~]$ help cd
cd: cd [-L|[-P [-e]] [-@]] [dir]
Change the shell working directory. Change the current directory to DIR. The default DIR is the value of the
HOME shell variable. The variable CDPATH defines the search path for the directory containing
DIR. Alternative directory names in CDPATH are separated by a colon (:).
A null directory name is the same as the current directory. If DIR begins
with a slash (/), then CDPATH is not used. If the directory is not found, and the shell option `cdable_vars' is set,
the word is assumed to be a variable name. If that variable has a value,
its value is used for DIR. Options:
-L force symbolic links to be followed: resolve symbolic links in
DIR after processing instances of `..'
-P use the physical directory structure without following symbolic
links: resolve symbolic links in DIR before processing instances
of `..'
-e if the -P option is supplied, and the current working directory
cannot be determined successfully, exit with a non-zero status
-@ on systems that support it, present a file with extended attributes
as a directory containing the file attributes The default is to follow symbolic links, as if `-L' were specified.
`..' is processed by removing the immediately previous pathname component
back to a slash or the beginning of DIR. Exit Status:
Returns 0 if the directory is changed, and if $PWD is set successfully when
-P is used; non-zero otherwise.
其他文档来源
除了可从命令行访问的手册页,Free Software Foundation 还创建了一些使用 info程序处理过的 info文件。这些文件提供了丰富的导航工具,包括能够跳到其他部分。尝试使用 man info
或 info info
获得更多信息。不是所有命令都使用 info 进行了归档,所以如果您成为 info 用户,就会发现可以同时使用 man 和 info。
手册页也有一些图形界面,比如 xman
(来自 XFree86 项目)和 yelp
(Gnome 2.0 帮助浏览器)。
如果无法找到某个命令的帮助,可以尝试使用 --help
选项运行该命令。这可以提供该命令的帮助,或者它可以告诉您如何获取需要的帮助。