总览
1. 什么是 Linux
Linux 可划分为以下四部分:
- Linux 内核
- GNU 工具
- 图形化桌面环境
- 应用软件
1.1 内核
通常来讲,计算机硬件是由运算器、控制器、存储器、I/O 设备等共同组成的,而让各种硬件设备各司其职又能协同运行的东西就是系统内核。Linux 系统的内核负责完成对硬件资源的分配、调度等管理任务。
内核主要负责以下 4 种功能:
系统内存管理:内核通过硬盘上的存储空间来实现虚拟内存,这块区域称为交换空间(swap space);
软件程序管理:Linux 操作系统将运行中的程序称为进程;
硬件设备管理:
- 驱动程序代码相当于应用程序和硬件设备的中间人,允许内核与设备之间交换数据;
- Linux 系统将硬件设备当成特殊的文件,称为设备文件,设备文件有 3 种分类:字符型设备文件、块设备文件、网络设备文件;
- Linux 为系统上的每个设备都创建一种称为节点的特殊文件;
文件系统管理:
Linux 服务器所访问的所有硬盘都必须格式化成表 1-1 所列文件系统类型中的一种;
Linux 内核采用虚拟文件系统(Virtual File System, VFS)作为和每个文件系统交互的接口,这为 Linux 内核同任何类型文件系统通信提供了一个标准接口。
1.2 GNU
GNU组织(GNU 是 GNU's Not Unix 的缩写)开发了一套完整的 UNIX 工具,但没有可以运行它们的内核系统,这些工具是在名为开源软件(open source software, OSS)的软件理念下开发的,将 Linus 的 Linux 内核和 GNU 操作系统工具整合起来,就产生了一款完整的、功能丰富的免费操作系统。因此,尽管通常将 Linux 内核和 GNU 工具的结合体称为 Linux,但在互联网上也常有一些 Linux 纯粹主义者将其称为 GNU/Linux 系统,藉此向 GNU 组织所作的贡献致意。
GNU 工具主要由两部分组成:核心 GNU 工具、SHELL
- 核心 GNU 工具(coreutils, core utilities)软件包由三部分组成:
- 用以处理文件的工具
- 用以操作文本的工具
- 用以管理进程的工具
- SHELL:也称为终端或壳,其充当的是人与内核(硬件)之间的翻译官,用户把一些命令“告诉”终端,它就会调用相应的程序服务去完成某些工作
1.3 图形化桌面
Linux桌面环境:“完成工具的方式不止一种,Linux 一直以来都以此而闻名。”
X Window 系统
KDE 桌面
GNOME 桌面:GNOME(the GNU Network Object Model Environment, GNU 网络对象模型环境),现已成为许多 Linux 发行版默认的桌面环境
Unity 桌面:其宗旨是为工作站、平板电脑以及移动设备提供一致的桌面体验,Ubuntu 即为 Unity 桌面
1.4 Linux发行版
Linux 发行版:我们将完整的 Linux 系统包称为发行版。不同的 Linux 发行版通常归为 3 种:
核心 Linux 发行版:提供了一站式的完整 Linux 安装
版本 特点 Slackware 最早的 Linux 发行版中的一员,在 Linux 极客中比较流行 Red Hat 主要用于 Internet 服务器的商业发行版 Fedora 从 Red Hat 分离出的家用发行版 Gentoo 为高级 linux 用户设计的发行版,仅包含 linux 源代码 openSUSE 用于商用和家用的发行版 Debian 在 linux 专家和商用 linux 产品中流行的发行版 特定用途的发行版:通常基于某个主流发行版,但仅包含主流发行版中一小部分用于某种特定用途的应用程序
版本 特点 CentOS 基于 Red Hat 企业版 linux 源代码构建的免费发行版 Ubuntu 用于学校和家庭的免费发行版 PCLinuxOS 用于家庭和办公的免费发行版 Mint 用于家庭娱乐的免费发行版 dyne:bolic 用于音频和 MIDI 应用的免费发行版 Puppy Linux 用于老旧 PC 的小型免费发行版 LiveCD 测试发行版:......
2. Linux 存储结构
2.1 目录结构划分
Linux 将文件存储在单个目录结构中,这个目录被称为虚拟目录(virtual directory),其将安装在计算机上的所有存储设备的文件路径纳入单个目录结构中。
具体而言,在这个单个目录中,Linux 系统中的一切文件都是从根(/
)目录开始的,并按照文件系统层次化标准(FHS)采用树形结构来存放文件,并定义了常见目录的用途:
本质上,FHS 对于用户来讲只是一种道德上的约束,有些用户就是懒得遵守,总会把文件到处乱放,有些甚至从来没有听说过它。所以,大家在日常工作学习中要灵活运用所学的知识,不要只讲死道理。
Linux 系统中常见的目录名称及其相应内容:
目录名称 | 含义 | 应放置文件的内容 |
---|---|---|
/ | 虚拟目录的根目录 | 通常不会在这里存储文件 |
/boot | 启动目录 | 开机所需文件——内核、开机菜单以及所需配置文件等 |
/bin | 二进制目录 | 存放单用户模式下还可以操作的命令 |
/dev | 设备目录 | Linux 在这里创建设备节点,以文件形式存放任何设备与接口 |
/etc | 系统配置目录 | 存放系统配置文件的目录 |
/home | 用户家目录/主目录 | linux 在这里创建用户目录 |
/lib | 库目录 | 存放系统和应用程序的库文件 |
/media | 媒体目录 | 可移动媒体设备的常用挂载点 |
/mnt | 挂载目录 | 另一个可移动媒体设备的常用挂载点 |
/opt | 可选目录 | 常用于存放第三方软件包和数据文件 |
/proc | 进程目录 | 存放现有硬件及当前进程的相关信息等 |
/root | 系统管理员的家目录 | 顾名思义 |
/sbin | 系统二进制目录 | 存放许多 GNU 管理员级工具,如开机过程中需要的命令 |
/run | 运行目录 | 存放系统动作时的运行时数据 |
/srv | 服务目录 | 存放一些网络服务的数据文件 |
/sys | 系统目录 | 存放系统硬件信息的相关文件 |
/tmp | 临时目录 | 存放任何人均可使用的临时工作文件 |
/usr | 用户二进制目录 | 存放大量用户级的 GNU 工具和数据文件 |
/var | 可变目录 | 用以存放经常变化的文件,如日志 |
/usr/local | 用户自行安装的软件 | |
/usr/sbin | Linux 系统开机时不会使用到的软件/命令/命令 | |
/usr/share | 帮助与说明文件,也可放置共享文件 | |
/lost+found | 当文件系统发生错误时,将一些丢失的文件片段存放在这里 |
2.2 虚拟目录之“虚”
Linux虚拟目录中比较复杂的部分是它如何协调管理各个存储设备。在Linux PC上安装的第一块硬盘称为根驱动器。根驱动器包含了虚拟目录的核心,其他目录都是从那里开始构建的。
Linux会在根驱动器上创建一些特别的目录,我们称之为挂载点(mount point)。挂载点是虚拟目录中用于分配额外存储设备的目录。虚拟目录会让文件和目录出现在这些挂载点目录中,然而实际上它们却存储在另外一个驱动器中。
通常系统文件会存储在根驱动器中,而其他的用户文件则可能存储在另一驱动器中,如下图所示:
2.3 绝对/相对路径
绝对路径指从根目录(/
)开始写起的文件或目录名称,相对路径则指的是相对于当前路径的写法。
Linux 中有两个特殊字符可用于相对路径:
- 单点符(
.
):表示当前目录 - 双点符(
..
):表示当前目录的父目录
2.4 Linux 中的文件
关于文件:
- Linux 系统中的文件和目录名称是严格区分大小写的,并且文件名称中不得包含斜杠
/
; - Linux 系统中以点
.
开头的文件均代表隐藏文件,它们不会在ls
命令执行时被显示出来,这些文件大多数为系统服务文件; - Linux 系统中一切都是文件,对服务程序进行配置自然也就是编辑程序的配置文件。
3. 命令行CLI
在图形化桌面出现之前,与 unix 系统进行交互的唯一方式就是借助由 shell 所提供的文本命令行界面(command line interface, CLI),CLI 只能接受文本输入,也只能显示出文本和基本的图形输出。
进入 CLI 主要有两种方式:控制台终端、图形化终端。
3.1 控制台终端
控制台终端:让 Linux 系统退出图形化桌面模式,进入文本模式,这样在显示器上就只有一个简单的 shell CLI,跟图形化桌面出现以前一样,这种模式称作 Linux 的控制台。在 Linux 控制台中无法运行任何图形化程序。
Linux系统启动后,它会自动创建出一些虚拟控制台。虚拟控制台是运行在Linux系统内存中的终端会话,大多数Linux发行版会启动5~6个(有时会更多)虚拟控制台,你在一台计算机的显示器和键盘上就可以访问它们。
- 在大多数Linux发行版中,你可以使用 $Ctrl+Alt+Fn$ 快捷键来访问某个Linux虚拟控制台,比如功能键F1生成虚拟控制台1,F2键生成虚拟控制台2,F3键生成虚拟控制台3,依次类推,通常可至F7。
- 另一方面,Linux发行版通常使用 $Ctrl+Alt$ 配合 F1 或 F7 来进入图形界面。比如Ubuntu使用F7,而RHEL则使用F1。所以最好还是测试一下自己所使用的发行版是如何进入图形界面的。
文本模式的虚拟控制台采用全屏的方式显示文本登录界面,如下展示了一个虚拟控制台的文本登录界面:
注意,图中第一行文本的最后有一个词 tty2,这个词中的2表明这是虚拟控制台2,可以通过$Ctrl+Alt+F2$组合键进入;
tty代表电传打字机(teletypewriter)。这是一个古老的名词,指的是一台用于发送消息的机器。
不是所有的Linux发行版都会在登录界面上显示虚拟控制台的tty号。
一旦登录完成,你可以保持此次登录的活动状态,然后在不中断活动会话的同时切换到另一个虚拟控制台。你可以在所有虚拟控制台之间切换,拥有多个活动会话。在使用CLI时,这个特性为你提供了巨大的灵活性。
3.2 图形化终端
图形化终端:Linux 图形化桌面环境中的终端仿真包,其会在一个桌面图形化窗口中模拟控制台终端的使用,我们平常所指的命令行界面实际上就是这个。
图形化终端仿真只负责Linux图形化体验的一部分,完整的体验效果需要借助多个组件来实现,其中就包括图形化终端仿真软件,下表展示了Linux图形化桌面环境的不同组成部分:
名称 | 例子 | 描述 |
---|---|---|
图形化终端仿真软件 | 图形化终端仿真器,桌面环境,网络浏览器 | 请求图形化服务的应用 |
显示服务器 | Mir,Wayland Compositor,Xserver | 负责管理显示(屏幕)和输入设备(键盘、鼠标、触摸屏) |
窗口管理器 | Compiz,Metacity,Kwin | 为窗口加入边框,提供窗口移动和管理功能 |
部件库 | Athenal(Xaw),X Intrinsics | 为桌面环境中的客户端添加菜单以及外观项 |
要想在桌面中使用命令行,关键在于图形化终端仿真器。可以把图形化终端仿真器看作GUI中(in the GUI)的CLI终端,将虚拟控制台终端看作GUI以外(outside the GUI)的CLI终端。
图形化终端的仿真 CLI 有大量可用的仿真器,每个软件包都有各自独特的特性及选项,但主要的有三种:
- GNOME Terminal:GNOME Terminal 是 GNOME 桌面环境的默认终端仿真器中,很多发行版如 RHEL、Fedora 和 CentOS 默认采用的都是 GNOME 桌面环境,但一些其他桌面环境如 Ubuntu Unity,也采用了 GNOME Terminal 作为默认的终端仿真软件包,它使用起来非常简单,是 Linux 新手的不错选择。
- Konsole Terminal
- xterm
4. shell
4.1 走进 shell
所有主流 Linux 发行版默认使用的 shell 都是 Bash(Bourne-Again Shell) 解释器。
常见 shell 命令的格式为命令名称 <命令参数> <命令对象>
:
命令对象一般是指要处理的文件、目录、用户等资源;
命令参数可以用长格式(完整的选项名称),也可以用短格式(单个字母的缩写),两者分别用
--
与-
作为前缀。# 示例 man --help # 长格式 man -h # 短格式
长格式和长格式之间不能合并,长格式和短格式之间也不能合并,但短格式和短格式之间是可以合并的,合并后仅保留一个
-
即可另外,ps 命令可允许参数不加减号(
-
),因此可直接写成ps aux
的样子。
shell 不会就相同的功能采用不同大小写的参数,只有极少的例外,如
rm -r dir
与rm -R dir
功能相同。shell 命令输出条目的字段之间用冒号分隔,如
christine:x:501:501:Christine Bresnahan:/home/christine:/bin/bash
。shell 中的转义字符为反斜杠(
\
)——使反斜杠后面的一个变量变为单纯的字符串。
管道命令符,用来把前一个命令原本要输出到屏幕的标准正常数据当作是后一个命令的标准输入,即将两个或多个命令像管道一样连接起来,省去写很多行的命令的形式。其格式为命令A|命令B
。如ls -l
命令产生了目录中所有文件的长列表,对包含大量文件的目录来说,这个列表会相当长,通过将输出管道连接到more
命令,可以强制输出在一屏数据显示后停下来,即ls -l | more
。
在 shell 中有关文件名的输入时,往往可以使用模糊的文件名,这实则是一个文件名过滤器,这种情况输入的名字可能会匹配多个文件/目录。
符号 | 含义 |
---|---|
? | 可使用?代表一个字符 |
* | 代表零或多个字符 |
[ai] | 代表多选,[ai]即 a 或 i |
[a-i] | 字母范围 a-i |
[!a] | 不允许有 a |
4.2 父子 shell
概念
shell 的父子结构:
- 在一个 shell 的 CLI 提示符后输入/bin/bash 命令或其他等效的 bash 命令时,会创建一个新的 shell 程序,这个 shell 程序被称为子 shell,原来的 shell 则称为父 shell。可通过
ps -f
命令可查看创建子 shell 前后的进程变化。子 shell 可以嵌套,可以利用exit
命令一层一层地退出 shell - 在生成子 shell 进程时,只有部分父进程的环境被复制到子 shell 环境中,这会对包括变量在内的一些东西造成影响。
- 在 shell 脚本中,经常使用子 shell 进行多进程处理,但是采用子 shell 的成本不菲,会明显拖慢处理速度,在交互式的 CLI shell 会话中,子 shell 同样存在问题。这并非真正的多进程处理,因为终端控制着子 shell 的 I/O
shell 命令可以组成命令列表,只需要在命令之间加入分号;
即可,这些命令会依次执行:
将命令列表的形式用圆括号括住,则构成了进程列表。进程列表的不同之处在于它会生成一个子 shell 来执行对应的命令:
echo $BASH_SUBSHELL
可返回子 shell 的数量,为 0 意味着没有创建子 shell。
后台模式
在交互式 shell 中,一个高效的子 shell 用法是使用后台模式,在后台模式中可以在处理命令的同时让出 CLI,以供他用,在命令后加上&
即可让命令切换到后台执行。另外与后台模式相关的经典命令是sleep
命令。
将进程列表置入后台
在CLI中运用子shell的创造性方法之一就是将进程列表置入后台模式——你既可以在子shell中进行繁重的处理工作,同时也不会让子shell的I/O受制于终端。
使用 tar
创建备份文件是有效利用后台进程列表的一个更实用的例子:
$ (tar -cf Rich.tar /home/rich ; tar -cf My.tar /home/christine)&
[3] 2423
$
协程
协程可以同时做两件事:它在后台生成一个子shell,并在这个子shell中执行命令。
要进行协程处理,得使用coproc
命令,还有要在子shell中执行的命令,如:
$ coproc sleep 10
[1] 2544
$
除了会创建子shell之外,协程基本上就是将命令置入后台模式。当输入coproc
命令及其参数之后,你会发现启用了一个后台作业,屏幕上会显示出后台作业号(1)以及进程ID(2544),此时用jobs
命令能够显示出协程的处理状态:
$ jobs
[1]+ Running coproc COPROC sleep 10 &
$
在上面的例子中可以看到在子shell中执行的后台命令是
coproc COPROC sleep 10
,其中COPROC
是coproc
命令给进程起的名字。
可以使用命令的扩展语法自己设置协程的名字,如将协程的名字设为My_Job
:
$ coproc My_Job { sleep 10; }
[1] 2570
$
$ jobs
[1]+ Running coproc My_Job { sleep 10; } &
$
这里要注意的是,扩展语法写起来有点麻烦。必须确保在第一个花括号({)和命令名之间有一个空格,还必须保证命令以分号(;)结尾,另外,分号和闭花括号(})之间也得有一个空格。
当然,你可以发挥才智,将协程与进程列表结合起来产生嵌套的子shell。只需要输入进程列表,然后把命令coproc放在前面就行了,比如coproc ( sleep 10; sleep 2 )
。
4.3 内建/外部命令
shell 内建命令与外部命令:
内建命令是解释器内部的指令,它们已经和 shell 编译成了一体,作为 shell 工具的组成部分存在,不需要借助外部程序文件而被直接执行;内建命令不会创建子进程,因而其执行速度更快。
外部命令也称为文件系统命令,它们并不是 shell 程序的一部分,外部命令将在
PATH
中寻找其位置然后执行;外部命令执行时,会创建一个子进程,被称为衍生(forking) 。外部命令程序通常位于
/bin
、/usr/bin
、/sbin
或/usr/sbin
中。
例如,cd
和 exit
命令都内建于 bash shell,而 ps
命令是一个外部命令。可以这么理解:有些命令不是 shell 必须的,却在日常中经常使用,故而在 Linux 发行版中配套了相关的外部命令可以直接使用。
要判断某命令是内建命令还是外部命令,使用可以type command
命令,如:
$ type cd
cd is a shell builtin # 内建命令
$ type ps
ps is /usr/bin/ps # 外部命令
$ type -a pwd # 有的命令既存在内建实现,又存在外部实现,可用 -a 参数来全部列出。此时若想要使用该命令的外部实现,通过直接指明其对应的文件就可以了,例如 `/bin/pwd`。
pwd is a shell builtin
pwd is /usr/bin/pwd
pwd is /bin/pwd
4.4 命令行参数
全量而论,linux 中的各种命令,有可能支持三种风格类型的参数:
- BSD 风格:前面不加破折线;
- Unix 风格:前面加单破折线
-
; - GNU 风格:前面加双破折线
--
,属于长参数。
Unix 和 GNU 风格应该是用得最多的。
5. 环境变量
在 Linux 的 shell 中可定义一些变量,它们存储着有关 shell 会话和工作环境的信息,因而 Linux 的变量也叫做环境变量。
变量由固定的变量名与用户/系统设置的变量值两部分组成,我们完全可以自行创建变量,来满足工作需求。
一个相同的变量会因为用户身份的不同而具有不同的值。
5.1 全局/局部环境变量
在 shell 中,环境变量分为两类:
- 全局变量:对当前 shell 会话和所有生成的子 shell 都可见;
- 局部变量:只对创建它们的 shell 可见,亦即只能在定义它们的进程中可见,尽管它们是局部的,但是和全局环境变量一样重要。
Linux系统在你开始bash会话时就内置了一些全局环境变量和局部环境变量,用户也可以选择定义自己的全局/局部变量,这些变量被称为用户自定义全局/局部变量。
Linux 系统的自带环境变量的名称一般是全大写的,这是一种约定俗成的规范,以区别普通用户的自定义变量。Linux 中最重要的 10 个系统环境变量为:
变量名称 | 作用 |
---|---|
HOME | 用户的主目录(即家目录) |
SHELL | 用户在使用的 shell 解释器名称 |
PATH | shell 查找外部命令路径列表,由冒号分隔 |
HISTSIZE | 输出的历史命令记录条数 |
HISTFILESIZE | 保存的历史命令记录条数 |
MAIL | 邮件保存路径 |
LANG | 系统语言、语系名称 |
RANDOM | 生成一个随机数字 |
PSL | bash 解释器的提示符 |
EDITOR | 用户默认的文本编辑器 |
要查看全局变量,可以使用env
或printenv
命令;遗憾的是,在Linux系统并没有一个只显示局部环境变量的命令,此时可以使用set
命令,其会显示为某个特定进程设置的所有环境变量,包括局部变量、全局变量、以及用户定义变量。
对于
set
命令另外需要多嘴一句的是,不是所有的系统环境变量都会在运行set
命令时列出,因为尽管那些确实是默认环境变量,但并不是每一个默认环境变量都必须有一个值。
5.2 用户自定义变量
可以在bash shell中直接设置自己的变量,习惯上用户自定义的变量使用小写字母。
创建局部变量
给变量赋值的形式是variable=value
:
- 注意等号两边不能有空格;
- 变量值可能是数值或字符串,若字符串内需要含有空格,则将字符串放入引号之内。
$ echo $my_variable
$ my_variable=Hello
$
$ echo $my_variable
Hello
$
$ my_variable=Hello World
-bash: World: command not found
$
$ my_variable="Hello World"
$
$ echo $my_variable
Hello World
$
数组变量:要给某个变量设置多个值,可以把值放在括号里,值与值之间用空格分隔。
对于数组变量,
echo $variable
只能显示其第一个值,要引用其他数组元素,使用echo ${variable[index]}
,数组变量的索引从 0 开始要显示整个数组变量,可使用通配符:
echo ${variable[*]}
可使用索引直接改变某个元素的值:
variable[index]=new_value
删除某个数组元素时使用
unset (unset variable[index])
,但此操作其实只是将该索引位置置为空了,原来的每个元素的索引是没有变的:$ unset mytest[2] $ $ echo ${mytest[*]} one two four five $ echo ${mytest[2]} $ echo ${mytest[3]} four
设置了局部环境变量后,就能在当前shell进程的任何地方使用它了。但是,如果该shell进程生成了一个子shell,它在子shell中是不可用的:
类似地,如果你在子进程中设置了一个局部变量,那么一旦你退出了子进程,那个局部环境变量就不复存在了。总结而言,局部环境变量只能在当前shell进程可用。
$ my_variable="Hello World"
$
$ bash
$
$ echo $my_variable
$ exit
exit
$
$ echo $my_variable
Hello World
$
创建全局变量
由前所述,对于全局变量而言,在设定全局环境变量的进程所创建的子进程中,该变量都是可见的。
创建全局变量的方法是先创建一个局部变量,然后通过 export
将它导出到全局环境中。如下:
$ my_variable="I am Global now"
$
$ export my_variable
$
$ echo $my_variable
I am Global now
$
$ bash
$
$ echo $my_variable
I am Global now
$
$ exit
exit
$
$ echo $my_variable
I am Global now
$
需要注意的是,修改/删除子 shell 中的全局变量并不会影响到父 shell 中该变量的值:
$ my_variable="I am Global now"
$ export my_variable
$
$ echo $my_variable
I am Global now
$
$ bash
$
$ echo $my_variable
I am Global now
$
$ my_variable="Null"
$
$ echo $my_variable
Null
$
$ exit
exit
$
$ echo $my_variable
I am Global now
$
删除变量
既然可以创建新的环境变量,自然也能删除已经存在的环境变量,可以用unset
命令完成这个操作。
在unset
命令中引用环境变量时,记住不要使用$
:
$ echo $my_variable
I am Global now
$
$ unset my_variable
$
$ echo $my_variable
$
在涉及环境变量名时,什么时候该使用
$
,什么时候不该使用$
,实在让人摸不着头脑。记住一点就行了:如果要用到变量,使用$
;如果要操作变量,不使用$
。这条规则的一个例外就是使用printenv
显示某个变量的值。
对于全局变量,和对变量作修改时一样,在子shell中删除全局变量后,你无法将效果反映到父shell中。
无须多言,unset variable
也可删除整个数组。
5.3 PATH 环境变量
作为一名态度谨慎、有经验的运维人员,在接手了一台 Linux 系统后一定会在执行命令前先检查
PATH
变量中是否有可疑的目录。
当你在shell命令行界面中输入一个外部命令时,shell必须搜索系统来找到对应的可执行程序,PATH
环境变量即定义了这个用于进行命令和程序查找的目录。如果命令或程序的位置没有包括在 PATH
变量中,只有使用绝对路径才能在 shell 中调用。
$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:
/sbin:/bin:/usr/games:/usr/local/games
$
修改 PATH
环境变量:
临时修改:要将某目录添加到现有
PATH
变量中,只需使用echo
引用原来的PATH
值,然后添加新目录即可,但注意这种修改在退出 shell 或重启系统之后会失效持久修改:永远修改环境变量最好的办法是在
/etc/profile.d
目录中创建一个以.sh
结尾的文件,把所有新的或修改过的全局环境变量设置放在这个文件中
5.4 系统环境变量的存储位置
在你登入Linux系统启动一个bash shell时,默认情况下bash会在几个文件中查找命令,这些文件叫作启动文件或环境文件。
bash 检查的启动文件取决于你启动 bash shell 的方式,主要有3种:
- 登录 linux 系统时作为默认登录的 shell
- 作为非登录shell的交互式shell
- 作为运行脚本的非交互shell
登录shell
当你登录Linux系统时,bash shell会作为登录shell启动。登录shell会从5个不同的启动文件里读取命令:
/etc/profile
:系统上默认的bash shell的主启动文件,系统上的每个用户登录时都会执行这个启动文件。不同的Linux发行版在这个文件里放了不同的命令
在本书所用的Ubuntu Linux系统上,它看起来是这样的:
# /etc/profile: system-wide .profile file for the Bourne shell (sh(1)) # and Bourne compatible shells (bash(1), ksh(1), ash(1), ...). if [ "${PS1-}" ]; then if [ "${BASH-}" ] && [ "$BASH" != "/bin/sh" ]; then # The file bash.bashrc already sets the default PS1. # PS1='\h:\w\$ ' if [ -f /etc/bash.bashrc ]; then . /etc/bash.bashrc fi else if [ "`id -u`" -eq 0 ]; then PS1='# ' else PS1='$ ' fi fi fi if [ -d /etc/profile.d ]; then for i in /etc/profile.d/*.sh; do if [ -r $i ]; then . $i fi done unset i fi
注意,上述文件中存在一个
for
语句来遍历/etc/profile.d
目录下的所有文件,这实际上为Linux系统提供了一个放置特定应用程序启动文件的地方,从而当用户登录时,shell就会去执行这些文件。比如,笔记所用 ubuntu wsl 的相应目录中就含有如下文件:$ ll /etc/profile.d/ total 48 drwxr-xr-x 2 root root 4096 Jul 25 18:43 ./ drwxr-xr-x 107 root root 4096 Dec 6 16:39 ../ -rw-r--r-- 1 root root 96 Dec 5 2019 01-locale-fix.sh -rw-r--r-- 1 root root 1557 Feb 17 2020 Z97-byobu.sh -rwxr-xr-x 1 root root 3417 Apr 20 2021 Z99-cloud-locale-test.sh* -rwxr-xr-x 1 root root 873 Apr 20 2021 Z99-cloudinit-warnings.sh* -rw-r--r-- 1 root root 835 Jun 15 2021 apps-bin-path.sh -rw-r--r-- 1 root root 729 Feb 2 2020 bash_completion.sh -rw-r--r-- 1 root root 1003 Aug 13 2019 cedilla-portuguese.sh -rw-r--r-- 1 root root 1107 Nov 4 2019 gawk.csh -rw-r--r-- 1 root root 757 Nov 4 2019 gawk.sh -rwxr-xr-x 1 root root 898 Sep 24 2020 update-motd.sh*
顾名思义,有些文件与系统中的特定应用有关。大部分应用都会创建两个启动文件:一个供bash shell使用(使用.sh扩展名),一个供c shell使用(使用.csh扩展名)。
$HOME
系列:其作用相同,都是提供一个用户专属的启动文件来定义该用户所用到的环境变量,大多数Linux发行版只用其中一个。shell会按照下列顺序,运行第一个被找到的文件,余下的则被忽略:$HOME/.bash_profile
$HOME/.bash_login
$HOME/.profile
值得注意的是,常说的
$HOME/.bashrc
与上述3个并非并列关系,前者的存在性是因为上述3个文件中的某个在执行的过程中引用了$HOME/.bashrc
,比如笔者 wsl 中的$HOME/.profile
:# if running bash if [ -n "$BASH_VERSION" ]; then # include .bashrc if it exists if [ -f "$HOME/.bashrc" ]; then . "$HOME/.bashrc" fi fi # set PATH so it includes user's private bin if it exists if [ -d "$HOME/bin" ] ; then PATH="$HOME/bin:$PATH" fi # set PATH so it includes user's private bin if it exists if [ -d "$HOME/.local/bin" ] ; then PATH="$HOME/.local/bin:$PATH" fi
交互式shell
如果你的bash shell不是登录系统时启动的,比如你是在命令行提示符下敲入bash时启动,那么你启动的shell叫作交互式shell。交互式shell不会像登录shell一样运行,但它依然提供了命令行提示符来输入命令。
如果bash是作为交互式shell启动的,它就不会访问/etc/profile
文件,只会检查用户家目录中的.bashrc
文件,该文件看起来像这样:
$ cat .bashrc
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# User specific aliases and functions
$
.bashrc
文件有两个作用:
- 一是查看
/etc
目录下通用的bashrc
文件; - 二是为用户提供一个定制自己的命令别名和私有脚本函数的地方。
非交互式shell
系统执行shell脚本时用的就是这种shell,其不同的地方在于它没有命令行提示符。显然当你在系统上运行脚本时,也希望能够运行一些特定启动的命令,为了处理这种情况,bash shell提供了BASH_ENV
环境变量。
当shell启动一个非交互式shell进程时,它会检查BASH_ENV
环境变量来查看要执行的启动文件,如果有指定的文件,shell会执行该文件里的命令,其中通常包括shell脚本变量设置。
在笔者所用的 wsl 中,变量BASH_ENV
并未被设置。那如果BASH_ENV
变量没有设置,shell脚本到哪里去获得它们的环境变量呢?别忘了有些shell脚本是通过启动一个子shell来执行的,此时子shell可以继承父shell导出过的变量。
环境变量持久化
由上已经了解了各种shell进程以及对应的环境文件,找出永久性环境变量就容易多了,也可以利用这些文件创建自己的永久性全局变量或局部变量。
对全局环境变量来说(Linux系统中所有用户都需要使用的变量):
- 你可能下意识里倾向于将新的或修改过的变量设置放在
/etc/profile
文件中,但这并非好的策略主意,因为一旦你升级了所用的Linux发行版,这个文件也会跟着更新,那你所有定制过的变量设置就都荡然无存了; - 最好的方式是在
/etc/profile.d
目录中创建一个以.sh
结尾的文件,把所有新的或修改过的全局环境变量设置放在这个文件中。
对于个人用户性的永久变量:
- 在大多数发行版中,存储个人用户永久性 bash shell 变量的地方是
$HOME/.bashrc
文件,这一点适用于所有类型的 shell 进程; - 但如果设置了
BASH_ENV
变量,那么需要注意,除非它指向的是$HOME/.bashrc
,否则你应该将非交互式 shell 的用户变量放在别的地方。
5.5 相关命令汇总
env
:查看所有全局系统变量。env
printenv
:查看所有/某全局系统变量。printenv <globle_variable>
echo
echo $<variable>
:引用某变量的值;若该变量不存在,则创建一个空值的变量。echo str
:引用字符串的值echo <command>
:显示命令执行结果$ echo `date` Thu Jul 24 10:08:46 CST 2014
set
:显示某进程的所有变量,返回结果按字母顺序排序。unset
:删除某变量。unset variable
export
:将变量提升为全局变量。export variable
6. 输入输出重定向
输入重定向是指把文件导入到命令中,输出重定向则是指把原本要输出到屏幕的数据信息写入到指定文件中。输出重定向又分为标准输出重定向和错误输出重定向两种不同的技术,以及清空写入与追加写入两种模式。
重定向 | 简写 | 文件描述符 | 默认情况 |
---|---|---|---|
标准输入重定向 | STDIN | 0 | 默认从键盘输入,也可从其他文件或命令中输入 |
标准输出重定向 | STDOUT | 1 | 默认输出到屏幕 |
错误输出重定向 | STDERR | 2 | 默认输出到屏幕 |
输入重定向中用到的符号及其作用:
符号 | 作用 |
---|---|
命令 < 文件 | 将文件作为命令的标准输入 |
命令 << 分界符 | 从标准输入中读入,直到遇见分界符停止 |
命令 < 文件 1 > 文件 2 | 将文件 1 作为命令的标准输入并将标准输出到文件 2 |
输出重定向中用到的符号及其作用:
符号 | 作用 |
---|---|
命令 > 文件 | 将标准输出重定向到一个文件中(清空原有文件的数据) |
命令 2> 文件 | 将错误输出重定向到一个文件中(清空原有文件的数据) |
命令 >> 文件 | 将标准输出重定向到一个文件中(追加到原有内容的后面) |
命令 2 >> 文件 | 将错误输出重定向到一个文件中(追加到原有内容的后面) |
命令 >> 文件 2>&1 或 命令 &>> 文件 | 将标准输出与错误输出共同写入到文件中(追加到原有内容的后面) |