bash shell
bashshell 是可用于 Linux 的几个 shell 之一,也被称为 Bourne-again shell,是根据一个早期的 shell (/bin/sh) 的创建者 Stephen Bourne 来命名的。Bash 高度兼容 sh,但它在函数和编程功能上都提供了许多改进。它合并了来自 Korn shell (ksh) 和 C shell (csh) 的特性,想要成为一个符合 POSIX 的 shell。
在深入了解 bash 之前,回想一下 shell是一个接受和执行命令的程序。它还支持编程结构,允许使用更小的部件构建复杂的命令。这些复杂命令或 脚本可保存为文件,独自成为新命令。实际上,典型 Linux 系统上的许多命令 都是脚本。
Shell 有一些 内置命令,比如 cd
、break
和 exec
。其他命令是 外部命令。
Shell 也使用 3 种标准 I/O 流:
- stdin是 标准输入流,它向命令提供输入。
- stdout是 标准输出流,它显示来自命令的输出。
- stderr是 标准错误流,它显示来自命令的错误输出。
输入流向程序提供输入,这些输入通常来自终端击键。输出流打印文本字符,通常打印到终端。终端最初为 ASCII 打字机或显示终端,但现在通常是图形桌面上的窗口。
如果您使用的是没有图形桌面的 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 系统剪切并粘贴的代码示例,其中使用了这些系统的默认提示符。根用户提示符有一个结尾 #,所以您可以将它们与普通用户提示符(有一个结尾 $)区分开来。这种约定与许多有关这一主题的图书一致。如果某项功能似乎对您不起作用,您可以检查示例中的提示符。
环境变量
当您在 bash shell 中运行时,许多因素构成了您的 环境,比如您的提示符的形式、主目录、工作目录、shell 的名称、您打开的文件、您定义的函数,等等。您的环境包含许多可由 bash 或您设置的 变量。bash shell 还允许您拥有 shell 变量,您可以将这些变量 导出到环境中,供 shell 中运行的其他进程或您可能从当前 shell 衍生的其他 shell 使用。
环境变量和 shell 变量都有一个 名称。可以在名称前面加上 ‘ $ ’ 作为前缀来引用变量的值。您会遇到的一些常见的 bash 环境变量如 下表中所示。
一些常见的 bash 环境变量
名称 | 功能 |
---|---|
USER | 登录用户的名称 |
UID | 登录用户的用户 id 数字 |
HOME | 用户的主目录 |
PWD | 当前工作目录 |
SHELL | shell 的名称 |
$ | 运行的 bash shell(或其他)进程的进程 id(或 PID) |
PPID | 启动此进程的进程的进程 id(也就是父进程的 id) |
? | 上一个命令的退出代码 |
展示了您可能在其中一些常见 bash 变量中看到的结果
环境变量和 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
可通过键入一个名称后立即键入等号 (=) 来创建或 设置shell 变量。如果该变量存在,可以修改它来分配新值。变量是区分大小写的,所以 var1 和 VAR1 是不同的变量。根据约定,变量(特别是导出的变量)采用大写,但这不是必须的。从技术上讲,$$ 和 $? 是 shell 参数而不是变量。它们只能被引用;不能向它们分配值。
创建 shell 变量时,您通常希望将它 导出到环境中,以便从此 shell 启动的其他进程可以使用它。导出的变量 不可用于父 shell。可使用 export
命令导出变量名。作为 bash 中的快捷方式,可以在一个步骤中分配一个值并导出变量。
为了演示分配和导出过程,我们将在 bash shell 中运行该 bash 命令,然后从新的 bash shell 运行 Korn shell (ksh)。我们将使用 ps
命令显示正在运行的命令的信息。
[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 $SHELLvar1 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 已突出显示。
添加引号和 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 知道变量名会在找到另外的字符时结束。有时,您需要在含义模糊表达式中使用变量。在这些情况下,可以使用花括号来表示变量名,如 清单中所示。
将花括号用于变量名
[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,您需要安装它们来自行执行这些练习。
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 $-
显示当前设置的选项。
[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)。
使用 exec
[ian@atticf20 ~]$ echo $$
3175
[ian@atticf20 ~]$ bash
[ian@atticf20 ~]$ echo $$
4994
[ian@atticf20 ~]$ exec ksh
$ echo $$
4994
$ exit
[ian@atticf20 ~]$ echo $$
3175