01-Shell基础知识¶
Shell是什么¶
现在我们使用的操作系统(Windows、Mac OS、Android、iOS 等)都是带图形界面的,简单直观,容易上手,对专业用户(程序员、网管等)和普通用户(家庭主妇、老年人等)都非常适用;计算机的普及离不开图形界面。
然而在计算机的早期并没有图形界面,我们只能通过一个一个地命令来控制计算机,这些命令有成百上千之多,且不说记住这些命令非常困难,每天面对没有任何色彩的“黑屏”本身就是一件枯燥的事情;这个时候的计算机还远远谈不上炫酷和普及,只有专业人员才能使用。
用户界面和命令行就是这个另外开发的程序,就是这层“代理”。在Linux下,这个命令行程序叫做 Shell。
Shell 是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核,这就是 Shell 的本质。
Shell 本身并不是内核的一部分,它只是站在内核的基础上编写的一个应用程序,它和 QQ、迅雷、Firefox 等其它软件没有什么区别。然而 Shell 也有着它的特殊性,就是开机立马启动,并呈现在用户面前;用户通过 Shell 来使用 Linux,不启动 Shell 的话,用户就没办法使用 Linux。
Shell 并不是简单的堆砌命令,我们还可以在 Shell 中编程,这和使用 C++、C#、Java、Python 等常见的编程语言并没有什么两样。
Shell 虽然没有 C++、Java、Python 等强大,但也支持了基本的编程元素,例如:
- if...else 选择结构,case...in 开关语句,for、while、until 循环;
- 变量、数组、字符串、注释、加减乘除、逻辑运算等概念;
- 函数,包括用户自定义的函数和内置函数(例如 printf、export、eval 等)。
站在这个角度讲,Shell 也是一种编程语言,它的编译器(解释器)是 Shell 这个程序。我们平时所说的 Shell,有时候是指连接用户和内核的这个程序,有时候又是指 Shell 编程。
Shell 主要用来开发一些实用的、自动化的小工具,而不是用来开发具有复杂业务逻辑的中大型软件,例如检测计算机的硬件参数、搭建 Web 运行环境、日志分析等,Shell 都非常合适。
使用 Shell 的熟练程度反映了用户对 Linux 的掌握程度,运维工程师、网络管理员、程序员都应该学习 Shell。
尤其是 Linux 运维工程师,Shell 更是必不可少的,是必须掌握的技能,它使得我们能够自动化地管理服务器集群,否则你就得一个一个地登录所有的服务器,对每一台服务器都进行相同的设置,而这些服务器可能有成百上千之多,会浪费大量的时间在重复性的工作上。
任何代码最终都要被“翻译”成二进制的形式才能在计算机中执行。
有的编程语言,如 C/C++、Pascal、Go语言、汇编等,必须在程序运行之前将所有代码都翻译成二进制形式,也就是生成可执行文件,用户拿到的是最终生成的可执行文件,看不到源码。
这个过程叫做编译(Compile),这样的编程语言叫做编译型语言,完成编译过程的软件叫做编译器(Compiler)。
而有的编程语言,如 Shell、JavaScript、Python、PHP等,需要一边执行一边翻译,不会生成任何可执行文件,用户必须拿到源码才能运行程序。程序运行后会即时翻译,翻译完一部分执行一部分,不用等到所有代码都翻译完。
这个过程叫做解释,这样的编程语言叫做解释型语言或者脚本语言(Script),完成解释过程的软件叫做解释器。
编译型语言的优点是执行速度快、对硬件要求低、保密性好,适合开发操作系统、大型应用程序、数据库等。
脚本语言的优点是使用灵活、部署容易、跨平台性好,非常适合 Web 开发以及小工具的制作。
Shell 就是一种脚本语言,我们编写完源码后不用编译,直接运行源码即可。
Shell运维必会¶
Linux 运维人员就是负责 Linux 服务器的运行和维护。随着互联网的爆发,Linux 运维在最近几年也迎来了春天,出现了大量的职位需求,催生了一批 Linux 运维培训班。
如今的 IT 服务器领域是 Linux、UNIX、Windows 三分天下,Linux 系统可谓后起之秀,特别是“互联网热”以来,Linux 在服务器端的市场份额不断扩大,每年增长势头迅猛,开始对 Windows 和 UNIX 的地位构成严重威胁。
下图是 2016 年初国内服务器端各个操作系统的市场份额:
可以看出来,Linux 占 80% 左右(包括 CentOS、Ubuntu 等),Windows 占 12.8%,Solaris 占 6.2%。在未来的服务器领域,Linux 是大势所趋。
Linux 在服务器上的应用非常广泛,可以用来搭建 Web 服务器、数据库服务器、负载均衡服务器(CDN)、邮件服务器、DNS 服务器、反向代理服务器、VPN 服务器、路由器等。用 Linux 作为服务器系统不但非常高效和稳定,还不用担心版权问题,不用付费。
正是由于 Linux 服务器的大规模应用,才需要一批专业的人才去管理,这群人就是 Linux 运维工程师(OPS)。
OPS 的主要工作就是搭建起运行环境,让程序员写的代码能够高效、稳定、安全地在服务器上运行,他们属于后勤部门。OPS 的要求并不比程序员低,优秀的 OPS 拥有架设服务器集群的能力,还会编程开发常用的工具。
OPS 这项工作的细节内容包括:
- 安装操作系统,例如 CentOS、Ubuntu 等。
- 部署代码运行环境,例如网站后台语言采用 PHP,就需要安装 Nginx、Apache、MySQL、PHP 运行时等。
- 及时修复漏洞,防止服务器被攻击,这包括 Linux 本身漏洞以及各个软件的漏洞。
- 根据项目需求升级软件,例如 PHP 7.0 在性能方面获得了重大突破,如果现在服务器压力比较大,就可以考虑将旧版的 PHP 5.x 升级到 PHP 7.0。
- 监控服务器压力,别让服务器宕机。例如淘宝双十一的时候就会瞬间涌入大量用户,导致部分服务器宕机,网页没法访问,甚至连支付宝都不能使用。
- 分析日志,及时发现代码或者环境的问题,通知相关人员修复。
这些任务只要登录远程服务器,或者去机房连接服务器(下图所示)就能够完成,为什么要用 Shell 编程呢?
因为 OPS 面对的是成千上万台的服务器,不是十台八台,你总不能把同样的工作重复成千上万遍吧,那时估计黄花菜都凉了,市场也成一片红海了。
服务器一旦多了,这些人力工作都需要自动化起来,跑一段代码就能在成千上万台服务器上完成相同的工作,例如服务的监控、代码快速部署、服务启动停止、数据备份、日志分析等。
Shell 脚本很适合处理纯文本类型的数据,而 Linux 中几乎所有的配置文件、日志文件(如 NFS、Rsync、Httpd、Nginx、MySQL 等),以及绝大多数的启动文件都是纯文本类型的文件。
下面的手链形象地展示了 Shell 在运维工作中的地位:
运维“手链”的组成:每颗“珍珠”都是一项服务,将珍珠穿起来的“线”就是 Shell。
Shell 脚本是实现 Linux 系统自动管理以及自动化运维所必备的工具,Linux 的底层以及基础应用软件的核心大都涉及 Shell 脚本的内容。每一个合格的 Linux 系统管理员或运维工程师,都应该能够熟练的编写 Shell 脚本,只要这样才能提升运维人员的工作效率,减少不必要的重复劳动,为个人的职场发展奠定较好的基础。
除了 Shell,能够用于 Linux 运维的脚本语言还有 Python 和 Perl。
1) Perl 语言
Perl 比 Shell 强大很多,在 2010 年以前很流行,它的语法灵活、复杂,在实现不同的功能时可以用多种不同的方式,缺点是不易读,团队协作困难。
Perl 脚本已经成为历史了,现在的 Linux 运维人员几乎不需要了解 Perl 了,最多可以了解一下 Perl 的安装环境。
2) Python 语言
Python 是近几年非常流行的语言,它不但可以用于脚本程序开发,也可以实现 Web 程序开发(知乎、豆瓣、YouTube、Instagram 都是用 Python 开发),甚至还可以实现软件的开发(大名鼎鼎的 OpenStack、SaltStack 都是 Python 语言开发)、游戏开发、大数据开发、移动端开发。
现在越来越多的公司要求运维人员会 Python 自动化开发,Python 也成了运维人员必备的技能,每一个运维人员在熟悉了 Shell 之后,都应该再学习 Python 语言。
3) Shell
Shell 脚本的优势在于处理偏操作系统底层的业务,例如,Linux 内部的很多应用(有的是应用的一部分)都是使用 Shell 脚本开发的,因为有 1000 多个 Linux 系统命令为它作支撑,特别是 Linux 正则表达式以及三剑客 grep、awk、sed 等命令。
对于一些常见的系统脚本,使用 Shell 开发会更简单、更快速,例如,让软件一键自动化安装、优化,监控报警脚本,软件启动脚本,日志分析脚本等,虽然 Python 也能做到这些,但是考虑到掌握难度、开发效率、开发习惯等因素,它们可能就不如 Shell 脚本流行以及有优势了。对于一些常见的业务应用,使用 Shell 更符合 Linux 运维简单、易用、高效的三大原则。
Python 语言的优势在于开发复杂的运维软件、Web 页面的管理工具和 Web 业务的开发(例如 CMDB 自动化运维平台、跳板机、批量管理软件 SaltStack、云计算 OpenStack 软件)等。
我们在开发一个应用时,应该根据业务需求,结合不同语言的优势以及自己擅长的语言来选择,扬长避短,从而达到高效开发、易于自己维护的目的。
常用的Shell¶
Linux 是一个开源的操作系统,由分布在世界各地的多个组织机构或个人共同开发完成,每个组织结构或个人负责一部分功能,最后组合在一起,就构成了今天的 Linux。例如:
- Linux 内核最初由芬兰黑客 Linus Torvalds 开发,后来他组建了团队,Linux 内核由这个团队维护。
- GNU 组织开发了很多核心软件和基础库,例如 GCC 编译器、C语言标准库、文本编辑器 Emacs、进程管理软件、Shell 以及 GNOME 桌面环境等。
- VIM 编辑器由荷兰人 Bram Moolenaar 开发。
Windows、Mac OS、Android 等操作系统不一样,它们都由一家公司开发,所有的核心软件和基础库都由一家公司做决定,容易形成统一的标准,一般不会开发多款功能类似的软件。
而 Linux 不一样,它是“万国牌”,由多个组织机构开发,不同的组织机构为了发展自己的 Linux 分支可能会开发出功能类似的软件,它们各有优缺点,用户可以自由选择。Shell 就是这样的一款软件,不同的组织机构开发了不同的 Shell,它们各有所长,有的占用资源少,有的支持高级编程功能,有的兼容性好,有的重视用户体验。
Shell 既是一种脚本编程语言,也是一个连接内核和用户的软件。
常见的 Shell 有 sh、bash、csh、tcsh、ash 等。
进入Shell的方式¶
一:进入 Linux 控制台
一种进入 Shell 的方法是让 Linux 系统退出图形界面模式,进入控制台模式,这样一来,显示器上只有一个简单的带着白色文字的“黑屏”,就像图形界面出现之前的样子。这种模式称为 Linux 控制台(Console)。
现代 Linux 系统在启动时会自动创建几个虚拟控制台(Virtual Console),其中一个供图形桌面程序使用,其他的保留原生控制台的样子。虚拟控制台其实就是 Linux 系统内存中运行的虚拟终端(Virtual Terminal)。
从图形界面模式进入控制台模式也很简单,往往按下Ctrl + Alt + Fn(n=1,2,3,4,5...)
快捷键就能够来回切换。
例如,CentOS 在启动时会创建 6 个虚拟控制台,按下快捷键Ctrl + Alt + Fn(n=2,3,4,5,6)
可以从图形界面模式切换到控制台模式,按下Ctrl + Alt + F1
可以从控制台模式再切换回图形界面模式。也就是说,1 号控制台被图形桌面程序占用了。
二:使用终端
进入 Shell 的另外一种方法是使用 Linux 桌面环境中的终端模拟包(Terminal emulation package),也就是我们常说的终端(Terminal),这样在图形桌面中就可以使用 Shell。
以 CentOS 为例,可以在“应用程序”菜单中找到终端,
Shell基本格式¶
进入 Shell 以后,我们就可以输入命令来使用 Linux 的各种功能了,但是在真正使用 Shell 命令之前,我们有必要先学习一下 Shell 命令的基本格式。
进入 Shell 之后第一眼看到的内容类似下面这种形式:
[mozhiyan@localhost ~]$
这叫做命令提示符,看见它就意味着可以输入命令了。命令提示符不是命令的一部分,它只是起到一个提示作用,我们将在《Shell命令提示符》一节中详细分析,本节只分析 Shell 命令的基本格式。
Shell 命令的基本格式如下:
command [选项] [参数]
[]
表示可选的,也就是可有可无。有些命令不写选项和参数也能执行,有些命令在必要的时候可以附带选项和参数。
ls 是常用的一个命令,它属于目录操作命令,用来列出当前目录下的文件和文件夹。ls 可以附带选项,也可以不带,不带选项的写法为:
[mozhiyan@localhost ~]$ cd demo
[mozhiyan@localhost demo]$ ls
abc demo.sh a.out demo.txt
getsum main.sh readme.txt a.sh
module.sh log.txt test.sh main.c
先执行cd demo
命令进入 demo 目录,这是我在自己的主目录下创建的文件夹,用来保存教学使用的各种代码和数据。
接着执行 ls 命令,它列出了 demo 目录下的所有文件,并且进行了格式对齐。
使用选项
ls 命令之后不加选项和参数也能执行,不过只能执行最基本的功能,即显示当前目录下的文件名。那么加入一个选项,会出现什么结果?
[mozhiyan@localhost demo]$ ls -l
总用量 140
-rwxrwxr-x. 1 mozhiyan mozhiyan 8675 4月 2 15:01 a.out
-rwxr-xr-x. 1 mozhiyan mozhiyan 116 4月 3 09:24 a.sh
-rw-rw-r--. 1 mozhiyan mozhiyan 44 4月 2 16:41 check.sh
-rw-r--r--. 1 mozhiyan mozhiyan 399 3月 11 17:12 demo.sh
-rw-rw-r--. 1 mozhiyan mozhiyan 4 4月 8 17:56 demo.txt
-rw-rw-r--. 1 mozhiyan mozhiyan 0 4月 15 17:26 log.txt
-rw-rw-r--. 1 mozhiyan mozhiyan 650 4月 10 11:06 main.c
-rwxrwxr-x. 1 mozhiyan mozhiyan 69 3月 26 10:13 main.sh
-rw-rw-r--. 1 mozhiyan mozhiyan 111 3月 26 09:56 module.sh
-rw-rw-r--. 1 mozhiyan mozhiyan 352 3月 22 17:40 out.log
-rw-rw-r--. 1 mozhiyan mozhiyan 61 4月 16 11:19 output.txt
-rw-r--r--. 1 mozhiyan mozhiyan 5 4月 11 15:16 readme.txt
-rwxr-xr-x. 1 mozhiyan mozhiyan 88 4月 15 17:23 test.sh
如果加一个-l
选项,则可以看到显示的内容明显增多了。-l
是长格式(long list)的意思,也就是显示文件的详细信息。
可以看到,选项的作用是调整命令功能。如果没有选项,那么命令只能执行最基本的功能;而一旦有选项,则能执行更多功能,或者显示更加丰富的数据。
短格式选项和长格式选项
Linux 的选项又分为短格式选项和长格式选项。
- 短格式选项是长格式选项的简写,用一个减号
-
和一个字母表示,例如ls -l
。 - 长格式选项是完整的英文单词,用两个减号
--
和一个单词表示,例如ls --all
。
一般情况下,短格式选项是长格式选项的缩写,也就是一个短格式选项会有对应的长格式选项。当然也有例外,比如 ls 命令的短格式选项-l
就没有对应的长格式选项,所以具体的命令选项还需要通过帮助手册来查询。
使用参数
参数是命令的操作对象,一般情况下,文件、目录、用户和进程等都可以作为参数被命令操作。例如:
[mozhiyan@localhost demo]$ ls -l main.c
-rw-rw-r--. 1 mozhiyan mozhiyan 650 4月 10 11:06 main.c
但是为什么一开始 ls 命令可以省略参数?那是因为有默认参数。命令一般都需要加入参数,用于指定命令操作的对象是谁。如果可以省略参数,则一般都有默认参数。例如 ls:
[mozhiyan@localhost ~]$ cd demo
[mozhiyan@localhost demo]$ ls
abc demo.sh a.out demo.txt
getsum main.sh readme.txt a.sh
module.sh log.txt test.sh main.c
这个 ls 命令后面如果没有指定参数的话,默认参数是当前所在位置,所以会显示当前目录下的文件名。
选项和参数一起使用
Shell 命令可以同时附带选项和参数,例如:
[mozhiyan@localhost ~]$ echo "http://c.biancheng.net/shell/"
http://c.biancheng.net/shell/
[mozhiyan@localhost ~]$ echo -n "http://c.biancheng.net/shell/"
http://c.biancheng.net/shell/[mozhiyan@localhost ~]$
-n
是 echo 命令的选项,"http://c.biancheng.net/shell/"
是 echo 命令的参数,它们被同时用于 echo 命令。
echo 命令用来输出一个字符串,默认输出完成后会换行;给它增加-n
选项,就不会换行了。
选项附带的参数
有些命令的选项后面也可以附带参数,这些参数用来补全选项,或者调整选项的功能细节。
例如,read 命令用来读取用户输入的数据,并把读取到的数据赋值给一个变量,它通常的用法为:
read str
str 为变量名。
如果我们只是想读取固定长度的字符串,那么可以给 read 命令增加-n
选项。比如读取一个字符作为性别的标志,那么可以这样写:
read -n 1 sex
1
是-n
选项的参数,sex
是 read 命令的参数。
-n
选项表示读取固定长度的字符串,那么它后面必然要跟一个数字用来指明长度,否则选项是不完整的。
总结
Shell 命令的选项用于调整命令功能,而命令的参数是这个命令的操作对象。有些选项后面也需要附带参数,以补全命令的功能。
Shell的本质¶
Shell 命令分为两种:
- Shell 自带的命令称为内置命令,它在 Shell 内部可以通过函数来实现,当 Shell 启动后,这些命令所对应的代码(函数体代码)也被加载到内存中,所以使用内置命令是非常快速的。
- 更多的命令是外部的应用程序,一个命令就对应一个应用程序。运行外部命令要开启一个新的进程,所以效率上比内置命令差很多。
用户输入一个命令后,Shell 先检测该命令是不是内置命令,如果是就执行,如果不是就检测有没有对应的外部程序:有的话就转而执行外部程序,执行结束后再回到 Shell;没有的话就报错,告诉用户该命令不存在。
内置命令
内置命令不宜过多,过多的内置命令会导致 Shell 程序本身体积膨胀,运行 Shell 程序后就会占用更多的内存。Shell 是一个常驻内存的程序,占用过多内存会影响其它的程序。
只有那些最常用的命令才有理由成为内置命令,比如 cd、kill、echo 等;你可以转到《Shell内置命令》来了解所有的内置命令,以及如何判断一个命令是否是内置命令。
外部命令
外部命令可能是读者比较疑惑的,一个外部的应用程序究竟是如何变成一个 Shell 命令的呢?
应用程序就是一个文件,只不过这个文件是可以执行的。既然是文件,那么它就有一个名字,并且存放在文件系统中。用户在 Shell 中输入一个外部命令后,只是将可执行文件的名字告诉了 Shell,但是并没有告诉 Shell 去哪里寻找这个文件。
难道 Shell 要遍历整个文件系统,查看每个目录吗?这显然是不能实现的。
为了解决这个问题,Shell 在启动文件中增加了一个叫做 PATH 的环境变量,该变量就保存了 Shell 对外部命令的查找路径,如果在这些路径下找不到同名的文件,Shell 也不会再去其它路径下查找了,它就直接报错。
你不用关心启动文件(我们将在《Shell启动文件》中详解),只需要知道 PATH 变量保存了检索路径即可。
我们使用 echo 命令输出 PATH 变量的值,看看它保存了哪些检索路径:
[mozhiyan@localhost ~]$ echo $PATH
/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/home/mozhiyan/.local/bin:/home/mozhiyan/bin
不同的路径之间以:
分隔。你看,Shell 只会在几个固定的路径中查找外部命令。
总结
Shell 内置命令的本质是一个自带的函数,执行内置命令就是调用这个自带的函数。因为函数代码在 Shell 启动时已经被加载到内存了,所以内置命令的执行速度很快。
Shell 外部命令的本质是一个应用程序,执行外部命令就是启动一个新的应用程序。因为要创建新的进程并加载应用程序的代码,所以外部命令的执行速度很慢。
Shell命令提示符¶
不同的 Linux 发行版使用的提示符格式大同小异,例如在 CentOS 中,默认的提示符类似下面这样:
[mozhiyan@localhost ~]$
各个部分的含义如下:
[]
是提示符的分隔符号,没有特殊含义。mozhiyan
表示当前登录的用户,我现在使用的是 mozhiyan 用户登录。@
是分隔符号,没有特殊含义。localhost
表示当前系统的简写主机名(完整主机名是 localhost.localdomain)。~
代表用户当前所在的目录为主目录(home 目录)。如果用户当前位于主目录下的 bin 目录中,那么这里显示的就是bin
。$
是命令提示符。Linux 用这个符号标识登录的用户权限等级:如果是超级用户(root 用户),提示符就是#
;如果是普通用户,提示符就是$
。 总结起来,Linux Shell 默认的命令提示符的格式为:
[username@host directory]$
或者
[username@host directory]#
什么是主目录?
Linux 系统是纯字符界面,用户登录后,要有一个初始登录的位置,这个初始登录位置就称为用户的主目录(home 目录)。超级用户的主目录为/root/
,普通用户的主目录为/home/用户名/
。
有的资料也称为“家目录”,“家”是 home 的直译,它们都是一个意思。
用户在自己的主目录中拥有完整权限,所以我们也建议操作实验可以放在主目录中进行。
我们使用 cd 命令切换一下用户所在目录,看看有什么效果。
[mozhiyan@localhost ~]$ cd demo
[mozhiyan@localhost demo]$ cd /usr/local
[mozhiyan@localhost local]$
仔细看,如果切换用户所在目录,那么命令提示符中会变成用户当前所在目录的最后一个目录(不显示完整的所在目录 /usr/ local/,只显示最后一个目录 local)。
第二层命令提示符
有些命令不能在一行内输入完成,需要换行,这个时候就会看到第二层命令提示符。第二层命令提示符默认为>
,请看下面的例子:
[mozhiyan@localhost ~]$ echo "Shell教程"
Shell教程
[mozhiyan@localhost ~]$ echo "
> http://
> c.biancheng.net
> "
http://
c.biancheng.net
第一个 echo 命令在一行内输入完成,不会出现第二层提示符。第二个 echo 命令需要多行才能输入完成,提示符>
用来告诉用户命令还没输入完成,请继续输入。
echo 命令用来输出一个字符串。字符串是一组由" "
包围起来的字符序列,echo 将第一个"
作为字符串的开端,将第二个"
作为字符串的结尾。对于第二个 echo 命令,我们将字符串分成多行,echo 遇到第一个"
认为是不完整的字符串,所以会继续等待用户输入,直到遇见第二个"
。
Shell修改提示符¶
hell 通过PS1
和PS2
这两个环境变量来控制提示符的格式,修改PS1
和PS2
的值就能修改命令提示符的格式。
- PS1 控制最外层的命令提示符格式。
- PS2 控制第二层的命令提示符格式。
在修改 PS1 和 PS2 之前,我们先用 echo 命令输出它们的值,看看默认情况下是什么样子的:
[mozhiyan@localhost ~]$ echo $PS1
[\u@\h \W]\$
[mozhiyan@localhost ~]$ echo $PS2
>
Linux 使用以\
为前导的特殊字符来表示命令提示符中包含的要素,这使得 PS1 和 PS2 的格式看起来可能有点奇怪。下表展示了可以在 PS1 和 PS2 中使用的特殊字符。
字符 | 描述 |
---|---|
\a | 铃声字符 |
\d | 格式为“日 月 年”的日期 |
\e | ASCII 转义字符 |
\h | 本地主机名 |
\H | 完全合格的限定域主机名 |
\j | shell 当前管理的作业数 |
\1 | shell 终端设备名的基本名称 |
\n | ASCII 换行字符 |
\r | ASCII 回车 |
\s | shell 的名称 |
\t | 格式为“小时:分钟:秒”的24小时制的当前时间 |
\T | 格式为“小时:分钟:秒”的12小时制的当前时间 |
\@ | 格式为 am/pm 的12小时制的当前时间 |
\u | 当前用户的用户名 |
\v | bash shell 的版本 |
\V | bash shell 的发布级别 |
\w | 当前工作目录 |
\W | 当前工作目录的基本名称 |
! | 该命令的 bash shell 历史数 |
# | 该命令的命令数量 |
$ | 如果是普通用户,则为美元符号$ ;如果超级用户(root 用户),则为井号# 。 |
\nnn | 对应于八进制值 nnn 的字符 |
\ | 斜杠 |
[ | 控制码序列的开头 |
] | 控制码序列的结尾 |
注意,所有的特殊字符均以反斜杠\
开头,目的是与普通字符区分开来。您可以在命令提示符中使用以上任何特殊字符的组合。
【实例】通过修改 PS1 变量的值来修改命令提示符的格式:
[mozhiyan@localhost ~]$ PS1="[\t][\u]\$ "
[12:51:43][mozhiyan]$ PS1="[c.biancheng.net]\$ "
[c.biancheng.net]$
第一次修改后可以显示当前的时间和用户名,第二次修改后显示C语言中文网的域名。为了保留版权,证明该教程出自C语言中文网,后续文章中我经常会使用[c.biancheng.net]$
这种命令提示符,大家不要觉得奇怪。
遗憾的是,通过这种方式修改的命令提示符只在当前的 Shell 会话期间有效,再次启动 Shell 后将重新使用默认的命令提示符。
第一个Shell脚本¶
几乎所有编程语言的教程都是从使用著名的“Hello World”开始的,出于对这种传统的尊重(或者说落入俗套),我们的第一个 Shell 脚本也输出“Hello World”。
打开文本编辑器,新建一个文本文件,并命名为 test.sh。
扩展名
sh
代表 shell,扩展名并不影响脚本执行,见名知意就好,如果你用 php 写 shell 脚本,扩展名就用php
好了。
在 test.sh 中输入代码:
#!/bin/bashecho "Hello World !" #这是一条语句
第 1 行的#!
是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell;后面的/bin/bash
就是指明了解释器的具体位置。
第 2 行的 echo 命令用于向标准输出文件(Standard Output,stdout,一般就是指显示器)输出文本。在.sh
文件中使用命令与在终端直接输入命令的效果是一样的。
第 2 行的#
及其后面的内容是注释。Shell 脚本中所有以#
开头的都是注释(当然以#!
开头的除外)。写脚本的时候,多写注释是非常有必要的,以方便其他人能看懂你的脚本,也方便后期自己维护时看懂自己的脚本——实际上,即便是自己写的脚本,在经过一段时间后也很容易忘记。
下面给出了一段稍微复杂的 Shell 脚本:
#!/bin/bash
# Copyright (c) http://c.biancheng.net/shell/
echo "What is your name?"
read PERSON
echo "Hello, $PERSON"
第 5 行中表示从终端读取用户输入的数据,并赋值给 PERSON 变量。read 命令用来从标准输入文件(Standard Input,stdin,一般就是指键盘)读取用户输入的数据。
第 6 行表示输出变量 PERSON 的内容。注意在变量名前边要加上$
,否则变量名会作为字符串的一部分处理。
如何执行Shell脚本¶
在新进程中运行 Shell 脚本
在新进程中运行 Shell 脚本有多种方法。
1) 将 Shell 脚本作为程序运行
Shell 脚本也是一种解释执行的程序,可以在终端直接调用(需要使用 chmod 命令给 Shell 脚本加上执行权限),如下所示:
[mozhiyan@localhost ~]$ cd demo #切换到 test.sh 所在的目录
[mozhiyan@localhost demo]$ chmod +x ./test.sh #给脚本添加执行权限
[mozhiyan@localhost demo]$ ./test.sh #执行脚本文件
Hello World ! #运行结果
第 2 行中,chmod +x
表示给 test.sh 增加执行权限。
第 3 行中,./
表示当前目录,整条命令的意思是执行当前目录下的 test.sh 脚本。如果不写./
,Linux 会到系统路径(由 PATH 环境变量指定)下查找 test.sh,而系统路径下显然不存在这个脚本,所以会执行失败。
通过这种方式运行脚本,脚本文件第一行的#!/bin/bash
一定要写对,好让系统查找到正确的解释器。
2) 将 Shell 脚本作为参数传递给 Bash 解释器
你也可以直接运行 Bash 解释器,将脚本文件的名字作为参数传递给 Bash,如下所示:
[mozhiyan@localhost ~]$ cd demo #切换到 test.sh 所在的目录
[mozhiyan@localhost demo]$ /bin/bash test.sh #使用Bash的绝对路径
Hello World ! #运行结果
通过这种方式运行脚本,不需要在脚本文件的第一行指定解释器信息,写了也没用。
更加简洁的写法是运行 bash 命令。bash 是一个外部命令,Shell 会在 /bin 目录中找到对应的应用程序,也即 /bin/bash,这点我们已在《Shell命令的本质到底是什么》一节中提到。
[mozhiyan@localhost ~]$ cd demo
[mozhiyan@localhost demo]$ bash test.sh
Hello World !
这两种写法在本质上是一样的:第一种写法给出了绝对路径,会直接运行 Bash 解释器;第二种写法通过 bash 命令找到 Bash 解释器所在的目录,然后再运行,只不过多了一个查找的过程而已。
检测是否开启了新进程
有些读者可能会疑问,你怎么知道开启了新进程?你有什么证据吗?既然如此,那我就来给大家验证一下吧。
Linux 中的每一个进程都有一个唯一的 ID,称为 PID,使用$$
变量就可以获取当前进程的 PID。$$
是 Shell 中的特殊变量,稍后我会在《Shell特殊变量》一节中展开讲解,读者在此不必深究。
首先编写如下的脚本文件,并命名为 check.sh:
#!/bin/bashecho $$ #输出当前进程PID
然后使用以上两种方式来运行 check.sh:
[mozhiyan@localhost demo]$ echo $$
2861 #当前进程的PID
[mozhiyan@localhost demo]$ chmod +x ./check.sh
[mozhiyan@localhost demo]$ ./check.sh
4597 #新进程的PID
[mozhiyan@localhost demo]$ echo $$
2861 #当前进程的PID
[mozhiyan@localhost demo]$ /bin/bash check.sh
4584 #新进程的PID
你看,进程的 PID 都不一样,当然就是两个进程了。
在当前进程中运行 Shell 脚本
这里需要引入一个新的命令——source 命令。source 是 Shell 内置命令的一种,它会读取脚本文件中的代码,并依次执行所有语句。你也可以理解为,source 命令会强制执行脚本文件中的全部命令,而忽略脚本文件的权限。
source 命令的用法为:
source filename
也可以简写为:
. filename
两种写法的效果相同。对于第二种写法,注意点号.
和文件名中间有一个空格。
例如,使用 source 运行上节的 test.sh:
[mozhiyan@localhost ~]$ cd demo #切换到test.sh所在的目录
[mozhiyan@localhost demo]$ source ./test.sh #使用source
Hello World !
[mozhiyan@localhost demo]$ source test.sh #使用source
Hello World !
[mozhiyan@localhost demo]$ . ./test.sh #使用点号
Hello World !
[mozhiyan@localhost demo]$ . test.sh #使用点号
Hello World !
你看,使用 source 命令不用给脚本增加执行权限,并且写不写./
都行,是不是很方便呢?
检测是否在当前 Shell 进程中
我们仍然借助$$
变量来输出进程的 PID,如下所示:
[mozhiyan@localhost ~]$ cd demo
[mozhiyan@localhost demo]$ echo $$
5169 #当前进程PID
[mozhiyan@localhost demo]$ source ./check.sh
5169 #Shell脚本所在进程PID
[mozhiyan@localhost demo]$ echo $$
5169 #当前进程PID
[mozhiyan@localhost demo]$ . ./check.sh
5169 #Shell脚本所在进程PID
你看,进程的 PID 都是一样的,当然是同一个进程了。
总结
作为初学者,你可能看不懂这些运行方式有什么区别,没关系,暂时先留个疑问吧,后续教程中我们会逐一讲解。
如果需要在新进程中运行 Shell 脚本,我一般使用bash test.sh
这种写法;如果在当前进程中运行 Shell 脚本,我一般使用. ./test.sh
这种写法。这是我个人的风格。
最后再给大家演示一个稍微复杂的例子。本例中使用 read 命令从键盘读取用户输入的内容并赋值给 URL 变量,最后在显示器上输出。
#!/bin/bash# Copyright (c) http://c.biancheng.net/shell/echo "What is the url of the shell tutorial?"read URLecho "$URL is very fast!"
运行脚本:
[mozhiyan@localhost demo]$ . ./test.sh
What is the url of the shell tutorial?
http://c.biancheng.net/shell/↙
http://c.biancheng.net/shell/ is very fast!
↙ 表示按下回车键。
Shell的运行方式¶
Shell 是一个应用程序,它的一端连接着 Linux 内核,另一端连接着用户。Shell 是用户和 Linux 系统沟通的桥梁,我们都是通过 Shell 来管理 Linux 系统。
我们可以直接使用 Shell,也可以输入用户名和密码后再使用 Shell;第一种叫做非登录式,第二种叫做登录式。
我们可以在 Shell 中一个个地输入命令并及时查看它们的输出结果,整个过程都在跟 Shell 不停地互动,这叫做交互式。我们也可以运行一个 Shell 脚本文件,让所有命令批量化、一次性地执行,这叫做非交互式。
总起来说,Shell 一共有四种运行方式:
- 交互式的登录 Shell;
- 交互式的非登录 Shell;
- 非交互式的登录 Shell;
- 非交互式的非登录 Shell。
判断 Shell 是否是交互式
判断是否为交互式 Shell 有两种简单的方法。
\1) 查看变量-
的值,如果值中包含了字母i
,则表示交互式(interactive)。
【实例1】在 CentOS GNOME 桌面环境自带的终端下输出-
的值:
[c.biancheng.net]$ echo $-
himBH
包含了i
,为交互式。
【实例2】在 Shell 脚本文件中输出-
的值:
[c.biancheng.net]$ cat test.sh
#!/bin/bash
echo $-
[c.biancheng.net]$ bash ./test.sh
hB
不包含i
,为非交互式。注意,必须在新进程中运行 Shell 脚本。
\2) 查看变量PS1
的值,如果非空,则为交互式,否则为非交互式,因为非交互式会清空该变量。
【实例1】在 CentOS GNOME 桌面环境自带的终端下输出 PS1 的值:
[mozhiyan@localhost]$ echo $PS1
[\u@\h \W]\$
非空,为交互式。
【实例2】在 Shell 脚本文件中输出 PS1 的值:
[c.biancheng.net]$ cat test.sh
#!/bin/bash
echo $PS1
[c.biancheng.net]$ bash ./test.sh
空值,为非交互式。注意,必须在新进程中运行 Shell 脚本。
判断 Shell 是否为登录式
判断 Shell 是否为登录式也非常简单,只需执行shopt login_shell
即可,值为on
表示为登录式,off
为非登录式。
shopt 命令用来查看或设置 Shell 中的行为选项,这些选项可以增强 Shell 的易用性。
【实例1】在 CentOS GNOME 桌面环境自带的终端下查看 login_shell 选项:
[c.biancheng.net]$ shopt login_shell
login_shell off
【实例2】按下Ctrl+Alt+Fn
组合键切换到虚拟终端,输入用户名和密码登录后,再查看 login_shell 选项:
[c.biancheng.net]$ shopt login_shell
login_shell on
【实例3】在 Shell 脚本文件中查看 login_shel 选项:
[c.biancheng.net]$ cat test.sh
#!/bin/bash
shopt login_shell
[c.biancheng.net]$ bash ./test.sh
login_shell off
同时判断交互式、登录式
要同时判断是否为交互式和登录式,可以简单使用如下的命令:
echo $PS1; shopt login_shell
或者
echo $-; shopt login_shell
常见的 Shell 启动方式
\1) 通过 Linux 控制台(不是桌面环境自带的终端)或者 ssh 登录 Shell 时(这才是正常登录方式),为交互式的登录 Shell。
[c.biancheng.net]$ echo $PS1;shopt login_shell
[\u@\h \W]\$
login_shell on
\2) 执行 bash 命令时默认是非登录的,增加--login
选项(简写为-l
)后变成登录式。
[c.biancheng.net]$ cat test.sh
#!/bin/bash
echo $-; shopt login_shell
[c.biancheng.net]$ bash -l ./test.sh
hB
login_shell on
\3) 使用由()
包围的组命令或者命令替换进入子 Shell 时,子 Shell 会继承父 Shell 的交互和登录属性。
[c.biancheng.net]$ bash
[c.biancheng.net]$ (echo $PS1;shopt login_shell)
[\u@\h \W]\$
login_shell off
[c.biancheng.net]$ bash -l
[c.biancheng.net]$ (echo $PS1;shopt login_shell)
[\u@\h \W]\$
login_shell on
\4) ssh 执行远程命令,但不登录时,为非交互非登录式。
[c.biancheng.net]$ ssh localhost 'echo $PS1;shopt login_shell'
login_shell off
\5) 在 Linux 桌面环境下打开终端时,为交互式的非登录 Shell
Shell的配置文件¶
无论是否是交互式,是否是登录式,Bash Shell 在启动时总要配置其运行环境,例如初始化环境变量、设置命令提示符、指定系统命令路径等。这个过程是通过加载一系列配置文件完成的,这些配置文件其实就是 Shell 脚本文件。
与 Bash Shell 有关的配置文件主要有 /etc/profile、/.bash_profile、/.bash_login、/.profile、/.bashrc、/etc/bashrc、/etc/profile.d/*.sh,不同的启动方式会加载不同的配置文件。
~
表示用户主目录。*
是通配符,/etc/profile.d/*.sh 表示 /etc/profile.d/ 目录下所有的脚本文件(以.sh
结尾的文件)。
登录式的 Shell
Bash 官方文档说:如果是登录式的 Shell,首先会读取和执行 /etc/profiles,这是所有用户的全局配置文件,接着会到用户主目录中寻找 /.bash_profile、/.bash_login 或者 ~/.profile,它们都是用户个人的配置文件。
不同的 Linux 发行版附带的个人配置文件也不同,有的可能只有其中一个,有的可能三者都有,笔者使用的是 CentOS 7,该发行版只有 ~/.bash_profile,其它两个都没有。
如果三个文件同时存在的话,到底应该加载哪一个呢?它们的优先级顺序是 ~/.bash_profile > ~/.bash_login > ~/.profile。
如果 ~/.bash_profile 存在,那么一切以该文件为准,并且到此结束,不再加载其它的配置文件。
如果 ~/.bash_profile 不存在,那么尝试加载 /.bash_login。/.bash_login 存在的话就到此结束,不存在的话就加载 ~/.profile。
注意,/etc/profiles 文件还会嵌套加载 /etc/profile.d/*.sh,请看下面的代码:
for i in /etc/profile.d/*.sh ; do
if [ -r "$i" ]; then
if [ "${-#*i}" != "$-" ]; then
. "$i"
else
. "$i" >/dev/null
fi
fi
done
同样,~/.bash_profile 也使用类似的方式加载 ~/.bashrc:
if [ -f ~/.bashrc ]; then
. ~/.bashrc
fi
非登录的 Shell
如果以非登录的方式启动 Shell,那么就不会读取以上所说的配置文件,而是直接读取 ~/.bashrc。
~/.bashrc 文件还会嵌套加载 /etc/bashrc,请看下面的代码:
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
编写Shell配置文件¶
我们可以将自己的一些代码添加到 ~/.bashrc,这样每次启动 Shell 都可以个性化地配置。如果你有代码洁癖,也可以将自己编写的代码放到一个新文件中(假设叫 myconf.sh),只要在 ~/.bashrc 中使用类似. ./myconf.sh
的形式将新文件引入进来就行了
使用 source 命令引入其它代码文件时有一些细节需要注意,我们将在《Shell模块化》一节中展开讨论。
实例1:给 PATH 变量增加新的路径
你曾经是否感到迷惑,Shell 是怎样知道去哪里找到我们输入的命令的?例如,当我们输入 ls 后,Shell 不会查找整个计算机系统,而是在指定的几个目录中检索(最终在 /bin/ 目录中找到了 ls 程序),这些目录就包含在 PATH 变量中。
当用户登录 Shell 时,PATH 变量会在 /etc/profile 文件中设置,然后在 ~/.bash_profile 也会增加几个目录。如果没有登录 Shell,PATH 变量会在 /etc/bashrc 文件中设置。
如果我们想增加自己的路径,可以将该路径放在 ~/.bashrc 文件中,例如:
PATH=PATH:HOME/addon
将主目录下的 addon 目录也设置为系统路径。假如此时在 addon 目录下有一个 getsum 程序,它的作用是计算从 m 累加到 n 的和,那么我们不用 cd 到 addon 目录,直接输入 getsum 命令就能得到结果。
在《Shell命令的本质到底是什么》一节中我已经给出了 getsum 程序及其源代码,有兴趣的读者可以猛击这里下载。下载完成后请配置环境变量,然后输入如下的命令就可以得到结果:
[c.biancheng.net]$ getsum -s 1 -e 100
5050
-s
选项表示起始(start)数字,-e
选项表示终止(end)数字,以上命令用来计算从 1 累加到 100 的和。
实例2:修改命令提示符的格式
在《修改Linux命令提示符》一节中我曾提到,修改 PS1 变量的值就可以修改命令提示符的格式,但是那个时候大家还不了解 Shell 启动文件,所以只能临时性地修改,并不能持久。
现在我们已经知道,在 ~/.bashrc 文件中修改 PS1 变量的值就可以持久化,每个使用 Shell 的用户都会看见新的命令提示符。
将下面的代码添加到 ~/.bashrc 文件中,然后重新启动 Shell,命令提示符就变成了[c.biancheng.net]$
。
PS1="[c.biancheng.net]$ "