MENU

[摘录]《Linux系统命令及Shell脚本实践指南》 - 王军

December 24, 2022 • Read: 34 • 阅读摘录

Linux系统命令及Shell脚本实践指南
王军
415个笔记

◆ 点评

认为好看
此前一些似懂非懂的概念,经常使用却总感觉不得法的工具和系统特性,终于在这本书中系统性地找到了答案。

◆ 第1章 Linux简介

1969年8月左右,他的妻儿出门探亲了一个月,就在这一个月的时间里,Thompson编写了一个操作系统,并成功地将“星际旅行”移植到了DPD-7上,而这个操作系统就是UNIX的原型。

Linux的内核设计分成进程管理、内存管理、进程间通信、虚拟文件系统、网络5部分

Linux采取了很多安全技术措施,包括读写权限控制、带保护的子系统、审计跟踪、核心授权等

多任务是现代化计算机的主要特点,指的是计算机能同时运行多个程序,且程序之间彼此独立,Linux内核负责调度每个进程,使之平等地访问处理器

Grub是一个系统引导工具,通过它可以加载内核,从而引导系统启动。

/boot分区用于放置Linux启动所用到的文件,如kernel和initrd文件。

◆ 第2章 Linux用户管理

Linux系统中的用户分为3类,即普通用户、根用户、系统用户。

通常普通用户的UID大于500,因为在添加普通用户时,系统默认用户ID从500开始编号。

根用户也就是root用户,它的ID是0,也被称为超级用户,root账户拥有对系统的完全控制权:可以修改、删除任何文件,运行任何命令。

root用户也是系统里面最具危险性的用户,root用户甚至可以在系统正常运行时删除所有文件系统,造成无法挽回的灾难

系统用户是指系统运行时必须有的用户,但并不是指真实的使用者。

在RedHat或CentOS下,系统用户的ID范围是1~499

系统用来记录用户名、密码最重要的两个文件就是/etc/passwd和/etc/shadow。

[插图]

[插图]

系统在添加用户时,需要预先为这个用户创建一些默认的“配置文件”,而默认配置的就是/etc/skel目录下的几个隐藏文件。可以说,/etc/skel实际上是创建用户时的“模板”。

创建用户后,该用户实际上还没有登录系统的权限,因为在不设置密码的情况下,在/etc/shadow中该用户记录中以冒号分隔的第二列将显示为两个感叹号“!!”,这说明不允许该用户登录系统。

su是切换用户的意思。在不加参数的情况下,su命令默认表示切换到root用户

su命令后面还可以加上一个“-”参数,就是键盘上的中横线。加上这个参数后,切换成root用户时,不但身份变成了root,而且还能应用root的用户环境。

所谓“用户环境”就是/etc/passwd中定义的用户家目录、使用的Shell,以及关于这个用户的个性化设置等。

使用su命令虽然很方便,但还是有很明显的缺陷,就是切换成其他用户的前提是需要知道对方的密码

但是考虑到这个配置文件的重要性,Linux提供了专门编辑这个文件的方式,就是使用命令visudo来编辑这个文件,它的好处是可以在编辑后保存退出时自动检查语法设置,以防止不小心配置错误而造成无法使用sudo命令。

加入的“john ALL=(ALL) ALL”这一行代表的意思是,john这个用户(第一列)可以从任何地方登录后(第二列的ALL)执行任何人(第三列的ALL)的任何命令(第四列的ALL)。还可以定义某一个组的sudo权限,比如“%john ALL=(ALL) ALL”可以让所有属于john用户组的用户从任何地方登录后执行任何人的任何命令。

只需要知道自己的密码就可以使用sudo执行任何命令,这样方便多了。但是每次都需要输入一遍密码也是比较麻烦的事情,想要实现不需要输入密码就可以执行命令,可以在最后一个ALL前添加“NOPASSWD:”,

严格来说,sudo并不是真的切换了用户,而是使用其他用户的身份和权限执行了命令。

如果任务是周期性执行的,其命令为cron;如果只是在某一个特定的时间执行一次,其命令为at。

可以使用atq命令查看当前使用at命令调度的任务列表,第一列是任务编号;也可以使用atrm删除已经进入任务队列的任务

默认情况下,所有用户都可以使用at命令来调度自己的任务,如果由于特殊的原因需要禁止某些用户使用这个功能,可以将该用户的用户名添加至/etc/at.deny中。

用户可通过crontab来设置自己的计划任务,并使用-e参数来编辑任务

在下面的示例中,前面5个可以用来定义时间,第一个表示分钟,可以使用的值是1~59,每分钟可以使用/1表示;第二个表示小时,可以使用的值是0~23;第三个表示日期,可以使用的值是1~31;第四个表示月份,可以使用的值是1~12;第五个表示星期几,可以使用的值是0~6,0代表星期日;最后是执行的命令。

          • command​​

与at类似,每个用户都可以设置自己的crontab,如果由于特殊的原因需要禁止某些用户使用这个功能,可以将该用户的用户名添加至/etc/cron.deny中。除了root之外,普通用户只可以设置、查看、删除自己的计划任务,root可以使用-u参数查看指定用户的任务。

事实上,系统也有自己的例行任务,而其配置文件是/etc/crontab

◆ 第3章 Linux文件管理

根据FHS的定义,每个目录应该放置的文件如表3-1所示。
表3-1 FHS定义的目录结构

一个点(.)代表的是当前目录,两个点(..)代表的是当前目录的上层目录

查看文件:cat
该命令是concatenate的简写,用户查看文件内容,后面跟上要查看的文件名即可

默认情况下,head将显示该文件前10行的内容

文件格式转换:dos2unix
该命令是DOS to UNIX的简写,也许你从字面上可以大概猜到它的作用,就是可以把DOS格式的文本文件转变成UNIX下的文本文件

进入目录:cd
该命令是change directory的简写,方便用户切换到不同的目录

删除目录:rmdir和rm
该命令是remove directory的简写,用来删除目录。但是需要注意的是,它只能删除空目录,如果目录不为空(存在文件或者子目录),那么该命令将拒绝删除指定的目录

由于root用户在Linux系统中的权限非常高,甚至可以用rm-rf /命令来删除全部的系统文件(这样做的后果是灾难性的),所以使用-rf参数删除目录一定要慎之又慎!

在Linux下目录也是一种文件,所以如果touch一个目录,这个目录的创建时间也会被更新

第一列是文件类别和权限,这列由10个字符组成,第一个字符表明该文件的类型

目录文件的连接数是该目录中包含其他目录的总个数+2

[插图]

Linux下的文件还有一些隐藏属性,必须使用lsattr来显示

如果要设置文件的隐藏属性,需要使用chattr命令

这里介绍几个常用的隐藏属性,第一种是a属性。拥有这种属性的文件只能在尾部增加数据而不能被删除

还有一种比较常用的属性是i属性。设置了这种属性的文件将无法写入、改名、删除,即便是root用户也不行。这种属性常用于设置在系统或者关键服务中的配置文件,这对提升系统安全性有较大的帮助

[插图]

如果需要修改的不是一个文件而是一个目录,以及该目录下所有的文件、子目录、子目录下所有的文件和目录(即递归设置该目录下所有的文件和目录的权限),则需要使用-R参数,

默认情况下,使用什么用户登录系统,那么该用户新创建的文件和目录的拥有者就是这个用户

改变文件的拥有组:chgrp
该命令用来更改文件的拥有组

文件特殊属性:SUID/SGID/Sticky

这就是奥秘所在—该命令是设置了SUID权限的,这意味着普通用户可以使用root的身份来执行这个命令

但是必须注意的是,SUID权限只能用于二进制文件

SGID就很容易了:如果某个二进制文件的用户组权限被设置了s权限,则该文件的用户组中所有的用户将都能以该文件的用户身份去运行这个命令

Sticky权限只能用于设置在目录上,设置了这种权限的目录,任何用户都可以在该目录中创建或修改文件,但是只有该文件的创建者和root可以删除自己的文件。

可以给出一个结论:对于root用户,文件的默认权限是644,目录的默认权限是755;对于普通用户,文件的默认权限是664,目录的默认权限是775。

在Linux下,定义目录创建的默认权限的值是“umask遮罩777后的权限”,定义文件创建的默认权限是“umask遮罩666后的权限”。
系统在/etc/profile文件中,通过第51行至55行的一段代码设置了不同用户的遮罩值。

[插图]

与find不同,locate命令依赖于一个数据库文件,Linux系统默认每天会检索一下系统中的所有文件,然后将检索到的文件记录到数据库中。

在执行这个命令之前一般需要执行updatedb命令(这不是必须的,因为系统每天会自动检索并更新数据库信息,但是有时候会因为文件发生了变化而系统还没有再次更新而无法找到实际上确实存在的文件。所以有时需要主动运行该命令,以创建最新的文件列表数据库)

which用于从系统的PATH变量所定义的目录中查找可执行文件的绝对路径

使用whereis也能查到其路径,但是和which不同的是,它不但能找出其二进制文件,还能找出相关的man文件

gzip/gunzip是用来压缩和解压缩单个文件的工具

tar不但可以打包文件,还可以将整个目录中的全部文件整合成一个包,整合包的同时还能使用gzip的功能进行压缩

一般来说,整合后的包习惯使用.tar作为其后缀名,使用gzip压缩后的文件则使用.gz作为其后缀名。因为tar有同时整合和压缩的功能,所以可使用.tar.gz作为后缀名,或者简写为.tgz

这里-z的含义是使用gzip压缩,-c是创建压缩文件(create),-v是显示当前被压缩的文件,-f是指使用文件名,也就是这里的boot.tgz文件。

上面的命令会直接将boot.tgz在当前目录中解压成boot目录,-z是解压的意思。如需要指定压缩后的目录存放的位置,需要再使用-C参数

使用bzip2压缩文件时,默认会产生以.bz2扩展名结尾的文件,这里使用-z参数进行压缩,使用-d参数进行解压缩。

该命令一般是不单独使用的,需要和find命令一同使用。当由find按照条件找出需要备份的文件列表后,可通过管道的方式传递给cpio进行备份,生成/tmp/conf.cpio文件,然后再将生成的/tmp/conf.cpio文件中包含的文件列表完全还原回去。
​​#备份:
[root@localhost ~]# find /etc-name *.conf | cpio-cov > /tmp/conf.cpio
#还原:
[root@localhost ~]# cpio--absolute-filenames-icvu < /tmp/conf.cpio​​

◆ 第4章 Linux文件系统

文件系统是操作系统用于明确磁盘或分区上相关文件的方法和数据结构,通俗的说法就是在磁盘上组织文件的方法。

大部分Linux系统都具有类似的通用结构,包括超级块(superblock)、i节点(inode)、数据块(data block)、目录块(directory block)等

超级块包括文件系统的总体信息,是文件系统的核心,所以在磁盘中会有多个超级块,以防止由于磁盘出现坏块导致全部文件系统无法使用

i节点存储所有与文件有关的元数据,也就是文件所有者、权限等属性数据以及指向的数据块,但是不包括文件名和文件内容

数据块是真实存放文件数据的部分,一个数据块默认情况下是4KB

目录块包括文件名和文件在目录中的位置,并包括文件的i节点信息

Linux最早引入的文件系统类型是minix,由于其存在一定的局限性,比如说文件名最长仅支持14个字符,文件最大为64MB等因素,后来被ext2(The Second Extended File System)文件系统所取代

但是ext2文件系统的弱点也是很明显的:它不支持日志功能。这很容易造成在一些情况下丢失数据,这个天然的弱点让ext2文件系统无法用于关键应用中,目前已经很少有企业使用ext2文件系统了。

那么为什么需要日志文件系统呢?因为日志文件系统使用了“两阶段提交”的方式来维护待处理的事务

比方说在写入数据之前,文件系统会先在日志中写入相关记录信息,然后再开始真实地写数据,写完数据后则会将之前写入日志中的内容删除

如果遇到问题需要检查文件系统或对ext3文件系统进行修复时,只需要检查日志即可

为了更好地使用磁盘空间,提高系统空间的可扩展性,此时就需要使用逻辑卷。

逻辑卷就是使用逻辑卷组管理(Logic Volume Manager)创建出来的设备,也是Linux操作系统可以认识的设备

LVM是介于硬盘祼设备和文件系统的中间层

首先创建一个或多个物理卷,物理卷按照相同(或不同)的组名称聚集形成一个(或多个)物理卷组,而逻辑卷就是从某个物理卷组中抽象出来的一块磁盘空间。

在对逻辑卷创建文件系统的时候,其全路径是/dev/卷组名/逻辑卷名

硬链接(hard link)又称实际链接,是指通过索引节点来进行链接

在Linux文件系统中,所有的文件都会有一个编号,称为inode,多个文件名指向同一索引节点是被允许的,这种链接就是硬链接

软链接(soft link)又称符号链接(symbolic link),是一个包含了另一个文件路径名的文件,可以指向任意文件或目录,也可以跨不同的文件系统

◆ 第5章 字符处理

在Linux中也存在着管道,它是一个固定大小的缓冲区,该缓冲区的大小为1页,即4K字节。

由于grep区分大小写,所以虽然第二行中含有大写的NAME,但是也不会匹配到。如果希望忽略大小写,可以加上-i参数。

如果想打印出文件中不包含name的行,可以使用grep的反选参数-v。

[root@localhost ~]# sort [-ntkr] 文件名
#-n采取数字排序
#-t指定分隔符
#-k指定第几列
#-r反向排序​​

如果文件(或标准输出)中有多行完全相同的内容,我们很自然希望能删除重复的行,同时还可以统计出完全相同的行出现的总次数,uniq命令就能帮助解决这个问题

uniq [-ic]
#-i忽略大小写
#-c计算重复行数​​

需要说明的是,uniq一般都需要和sort命令一起使用,也就是先将文件使用sort进行排序(这样重复的内容就能显示在连续的几行中),然后再使用uniq删除掉重复的内容(uniq的作用就在于删除连续的完全一致的行)。

顾名思义,cut就是截取的意思,它能处理的对象是“一行”文本,可从中选取出用户所需要的部分。

cut-f指定的列-d'分隔符'​​

cut-c指定列的字符​​

tr命令比较简单,其主要作用在于文本转换或删除

paste的作用在于将文件按照行进行合并,中间使用tab隔开

在Linux下使用split命令来实现文件的分割,支持按照行数分割和按照大小分割这两种模式

◆ 第6章 网络管理

nameserver关键字后面紧跟着一个DNS主机的IP地址,可以设置2~3个nameserver,但是主机在查询域名时会首先查询第一个DNS,当该DNS不可用时才会查询第二个DNS,以此类推

search关键字后紧跟的是一个域名。每个主机严格来说都应该有一个FQDN(全限定域名),所以往往域名就很长,如果这里写成search google.com,那么www就代表www.google. com了,这个关键字后可以跟多个域名。

domain关键字和search类似,不同的是domain后面只能跟一个域名。

host命令是用来查询DNS记录的,如果使用域名作为host的参数,命令返回该域名的IP

在IP包结构中有一个定义数据包生命周期的TTL(Time To Live)字段,该字段用于表明IP数据包的生命值,当IP数据包在网络上传输时,每经过一个路由器该值就减1,当该值减为0时此包就会被路由器丢弃。这种设计可用于避免出现一些由于某种原因始终无法到达目的地的包不断地在互联网上传递(可以形象地称之为“幽灵包”),减少无谓的网络资源耗用。

不过路由器也不是“无声无息”地将TTL值为0的IP包丢弃的,它会同时给发送该IP数据包的主机发送一个ICMP“超时”消息,主机在接收到这个ICMP包后就同时能得到该路由的IP地址。

根据上面两个特点,人们写了一个检测数据包是如何经由路由器的工具—traceroute,我们可以想象一下该工具的工作原理:它先构造出一个TTL值为1的数据包发送给目的主机,这个数据包在经由第一个路由器时,路由器先将TTL值减1变为0,然后将该IP包丢弃,同时给发送一个ICMP消息,这样就得到了经过的第一台路由器的IP地址;然后再构造出一个TTL值为2的数据包,以此类推,就能得到该IP包经历的整条链路的路由器IP。

第一步是要确认网卡本身是否能正常工作?利用ping工具可以确认这点。输入ping 127.0.0.1,然后看是否能正常ping通?这里的127.0.0.1被称为主机的回环接口,是TCP/IP协议栈正常工作的前提。

第二步是要确认网卡是否出现了物理或驱动故障,使用ping本机IP地址的方式,如果能ping通则说明本地设备和驱动都正常。

第三步要确认是否能ping通同网段的其他主机。这一步主要是确认二层网络设备(比如交换机或者HUB)工作是否正常

第四步要确认是否能ping通网关IP。如果数据包能正常到达网关,则说明主机和本地网络都工作正常。

第五步确认是否能ping通公网上的IP,如果可以则说明本地的路由设置正确,否则就要确认路由设备是否做了正确的nat或路由设置。

第六步确认是否能ping通公网上的某个域名,如果能ping通则说明DNS部分设置正确。

◆ 第7章 进程管理

进程包括动态执行的程序和数据两部分

所有的进程都可能存在3种状态:运行态、就绪态、阻塞态

进程之间又存在互斥和同步的关系

现代计算机使用信号量机制来实现进程间的互斥和同步,它的基本原理是:两个或者多个进程可以通过简单的信号进行合作,一个进程可以被迫在某一位置停止,直到它接收到一个特定的信号。

进程是动态的,而程序是静态的,进程是程序以及数据在计算机上的一次执行,没有静态的程序也就没有动态的执行。

第三行是CPU信息,us代表用户空间占用的CPU百分比,sy代表内核空间占用的CPU百分比,ni代表改变过优先级的进程占用的CPU百分比,id代表空闲CPU百分比

wa代表I/O等待百分比,hi代表硬中断占用的CPU百分比,si代表软中断占用的CPU百分比。

如果要显示更多的字段,可以在top显示界面中按字母键f。

另外,默认情况下top显示的进程是按照CPU使用率来进行排序的,如果要另选排序规则怎么办呢?可以按大写字母O键进入排序选择页

比如按字母P键表示按照CPU的使用率排序,按字母M键表示按照Memory的使用率排序,按字母N键表示以PID排序,按字母T键表示按照CPU使用时间排序,按字母K键则表示kill进程,按字母R键表示可以renice一个进程等。

命令kill后可以跟的信号代码一共有64种,使用kill-l就可以看到具体有哪些

但是常用的一般只有3个,即HUP(1)、KILL(9)、TERM(15),分别代表重启、强行杀掉、正常结束。

使用-9参数强行停止该进程了,其效果是立即杀死进程,而且该信号无法被阻塞或忽略。但是这个命令也有其天然的危险,就是进程可能会直接被系统终止,而没有清理之前申请的内存,这会造成一定程度的“内存泄露”,因此一般情况下不建议使用。

而-15这个参数就比较温和了,它会使进程正常退出,它也是Linux默认的程序中断信号(也就是在不加参数的情况下默认使用的信号)。

lsof(list open files)是一个列出当前系统中所有打开文件的工具。

在系统中,被打开的文件可以是普通文件、目录、网络文件系统中的文件、字符设备、管道、socket等

输出的字段有COMMAND、PID、USER、FD、TYPE、DEVICE、SIZE、NODE、NAME9列,

COMMAND:进程的名称。
▪ PID:进程标识符。
▪ USER:进程所有者。
▪ FD:文件描述符,应用程序通过文件描述符识别该文件。
▪ TYPE:文件类型,如DIR、REG等。
▪ DEVICE:磁盘的名称。
▪ SIZE:文件大小。
▪ NODE:索引节点。
▪ NAME:打开文件的全路径名称。

尝试将其umount卸载,系统提示device is busy,无法卸载。这时候使用lsof命令确认了一下,确实有进程在占用这个目录

使用lsof还可以查找使用了某个端口的进程

lsof-i:22

假设文件/var/log/messages不小心被删除了,首先来确认一下当前是否有进程正在使用这个文件,如果有则可以继续,如果没有就无法使用该方法继续了。本例中看到有个PID为2449的进程正在使用该文件,那么接下来只要找到对应/proc目录下的文件就可以了

在学习top时,我们看到其输出中有NI字段,标记了对应进程的优先级,该字段的取值范围是-20~19,数值越低代表优先级越高,也就能更多地被操作系统调度运行,如果一个进程在启动时并没有设定nice优先级,则默认使用0

普通用户也可以给自己的进程设定nice优先级,但是范围只限于0~19。

实际上,Linux使用了“动态优先级”的调度算法来确定每一个进程的优先级,一个进程的最终优先级=优先级+nice优先级。

nice命令仅限于在启动一个进程的时候同时赋予其nice优先级

对于已经启动的进程,可以用renice命令进行修改

除了使用renice外,还可以使用top提供的功能来修改

◆ 第8章 Linux下的软件安装

在Linux系统中,一般在/usr/local/src/目录里下载源码包(这不是硬规定,而是一个良好的习惯

一般来说建议自行编译安装的软件放置的目录为/usr/local/

源码编译的前提是系统中安装了gcc工具,对于注重安全生产环境的用户而言是不应该安装这个工具的

RPM是RedHat Package Manager的简写,顾名思义是红帽软件包管理器的意思

Linux中一切皆文件,所以说白了,RMP其实是一种集成了文件管理和软件版本控制的工具

RPM分为两类,第一类是二进制安装包(也就是预编译包)

如果将编译好的软件复制到相同软件环境(内核版本一致、软硬件运行环境一致)的服务器中,只要软件在原编译机中能运行,那么在新主机中也同样可以运行

第二类是RPM源码包,当希望自定义编译参数,自行制作二进制安装包的时候使用

使用RPM包管理的方式是通过rpm命令

默认情况下RedHat会因为未注册RHN而无法使用yum

作为一个Linux工程师,不能把技术活做成体力活。

如果读者在网上搜索自建网络yum源的相关文档,可能会发现其中十有八九都有使用createrepo工具重新创建repodata这一步骤

RPM包有两种,一种是二进制安装包,还有一种是源码包,这种包的后缀名一般以.src.rpm结束(有时简称为srpm),标识着这是一个“包含源码的RPM包”。

◆ 第9章 vi和vim编辑器

vi编辑器是Visual Interface的简称,是Linux系统中最基本的文本编辑器

◆ 第10章 正则表达式

sed其实是以行为单位的文本处理工具,而awk则是基于列的文本处理工具

它的工作方式是按行读取文本并视为一条记录,每条记录以字段分割成若干字段,然后输出各字段的值。

◆ 第11章 Shell编程概述

当你使用ssh客户端工具远程连接到系统,或坐在一台服务器前输入密码登录到系统中时,面前呈现的跳动的光标就是一个Shell

在计算机语言中,Shell是指一种命令行解释器,是为用户和操作系统之间通信提供的一种接口

图11-1显示了Shell在操作系统中的位置。

cat /etc/Shells

为了对用户屏蔽这些复杂的技术细节,同时也是为了保护内核不会因用户直接操作而受到损害,有必要在内核之上创建一个层,该层就是一个“壳”,也就是Shell名称的由来。

Bash Shell有两种工作模式,分别是互动模式和脚本模式

个Shell脚本永远是以“#!”开头的,这是一个脚本开始的标记,它是在告诉系统执行这个文件需要使用某个解释器,后面的/bin/bash就是指明了解释器的具体位置。

为了更清晰地看到脚本运行的过程,还可以借助-x参数来观察脚本的运行情况

为了更精细地调试运行Shell,我们可以借助第三方工具bashdb。这是一个类似于GDB的脚本调试软件,小巧而强大,具有设置断点、单步执行、观察变量等功能

所谓Shell内建命令,就是由Bash自身提供的命令,而不是文件系统中的某个可执行文件

通常来说,内建命令会比外部命令执行得更快,执行外部命令时不但会触发磁盘I/O,还需要fork出一个单独的进程来执行,执行完成后再退出。而执行内建命令相当于调用当前Shell进程的一个函数。

不要试图用脑子记住所有的命令,这不可能也不需要。判断一个命令是不是内建命令只需要借助于命令type即可

点号用于执行某个脚本,甚至脚本没有可执行权限也可以运行

alias可用于创建命令的别名,若直接输入该命令且不带任何参数,则列出当前用户使用了别名的命令。

任务前后台切换:bg、fg、jobs
该命令用于将任务放置后台运行,一般会与Ctrl+z、fg、&符号联合使用

declare、typeset
这两个命令都是用来声明变量的,作用完全相同。

Shell又被称为弱类型编程语言

若使用declare命令,可以用-i参数声明整型变量,

使用-r声明变量为只读

使用-a声明变量

使用-F、-f显示脚本中定义的函数和函数体,

echo用于打印字符

该命令会打印出引号中的内容,并在最后默认加上换行符

使用-n参数可以不打印换行符。

默认情况下,echo命令会隐藏-e参数(禁止解释打印反斜杠转义的字符)。比如“\n”代表新的一行,如果尝试使用echo输出新的一行,在不加参数的情况下只会将“\n”当作普通的字符,若要打印转义字符,则需要通过使用-e参数来允许。

从一个循环(for、while、until或者select)中退出。break后可以跟一个数字n,代表跳出n层循环,n必须大于1,如果n比当前循环层数还要大,则跳出所有循环

continue
停止当前循环,并执行外层循环(for、while、until或者select)的下一次循环。continue后可以跟上一个数字n,代表跳至外部第n层循环

将所跟的参数作为Shell的输入,并执行产生的命令:eval

内建命令exec并不启动新的Shell,而是用要被执行的命令替换当前的Shell进程,并且将老进程的环境清理掉,而且exec命令后的其他命令将不再执行

假设在一个Shell里面执行了exec echo ''Hello''命令,在正常地输出一个“Hello”后Shell会退出,因为这个Shell进程已被替换为仅仅执行echo命令的一个进程,执行结束自然也就退出了

exec典型的用法是与find联合使用,用find找出符合匹配的文件,然后交给exec处理,如下所示:
​​#列出系统中所有以.conf结尾的文件
[root@localhost ~]# find /-name ".conf"-exec ls-l {} \;
#删除系统中所有临时文件
find /-name ".tmp"-exec rm-f {} \;​​

在Shell脚本中使用exit代表退出当前脚本

该命令可以接受的参数是一个状态值n,代表退出的状态

如果不指定,默认状态值是0

使用$?可以取出之前命令的退出状态值

要说明的是,即便子Shell确实读取到了父Shell中变量var的值,也只是值的传递,如果在子Shell中尝试改变var的值,改变的只是var在子Shell中的值,父Shell中的该值并不会因此受到影响,你可以认为父Shell和子Shell都各自拥有一个叫var的变量,它们恰巧名字相同而已。

发送信号给指定PID或进程:kill

Linux操作系统包括3种不同类型的进程

第一种是交互进程,这是由一个Shell启动的进程,既可以在前台运行,也可以在后台运行

第二种是批处理进程,与终端没有联系,是一个进程序列

第三种是监控进程,也称系统守护进程,它们往往在系统启动时启动,并保持在后台运行

kill [-s signal |-p ] [-a ] [-- ] pid ...
[root@localhost ~]# kill-l [ signal ]
#-s:指定要发送的信号,信号可以是信号名或是信号数值
#-p:只打印出进程的PID,并不真的发送信号
#-l:指定信号的名称列表
#pid:进程的ID号
#signal:信号​​

let是Shell内建的整数运算命令

pwd命令会打印当前工作目录的绝对路径名

如果使用-P选项,打印出的路径名中不会包含符号连接

如果使用了-L选项,打印出的路径中可以包含符号连接

声明局部变量:local

该命令用于在脚本中声明局部变量,典型的用法是用于函数体内,其作用域也在声明该变量的函数体中。如果试图在函数外使用local声明变量,则会提示错误。

从标准输入读取一行到变量:read

实际上read可以使用-p参数代替

如果不指定变量,read命令会将读取到的值放入环境变量REPLY中

另外要记住,read是按行读取的,用回车符区分一行,你可以输入任意文字,它们都会保存在变量REPLY中。

定义函数返回值:return

典型的用于函数中,常见用法是return n,其中n是一个指定的数字,使函数以指定值退出

如果没有指定n值,则返回状态是函数体中执行的最后一个命令的退出状态。

向左移动位置参数:shift

假设一个脚本在运行时可以接受参数,那么从左到右第一个参数被记作$1,第二个参数为$2,以此类推,第n个参数为$N

所有参数记作$@或$*,参数的总个数记作$#,而脚本本身记作$0。

shift命令可以对脚本的参数做“偏移”操作

使用ulimit可以控制进程对可用资源的访问

默认情况下Linux系统的各个资源都做了软硬限制,其中硬限制的作用是控制软限制(换言之,软限制不能高于硬限制)

使用ulimit-a可以查看当前系统的软限制(使用命令ulimit-a-H可查看系统的硬限制)

使用ulimit直接调整参数,只会在当前运行时生效,一旦系统重启,所有调整过的参数就会变回系统默认值。所以建议将所有的改动放在ulimit的系统配置文件中。

测试表达式:test

该命令用于测试表达式EXPRESSION的值,根据测试结果返回0(测试失败)或1(测试成功)。

◆ 第12章 Bash Shell的安装

Linux是学习Bash Shell的天然环境,但是借助工具,在Windows下同样可以运行bash

最著名的工具是Cygwin,它是模拟类UNIX环境的软件,最初由Cygnus Solution公司开发,目的在于通过重新编译将Linux系统上的软件移植到Windows上。

◆ 第13章 Shell编程基础

可以使用local内建命令来“显式”的声明局部变量,但仅限于函数内使用

每个Shell都有自己的变量空间,彼此互不影响

环境变量通常又称“全局变量”,以区别于局部变量

为了让子Shell继承当前Shell的变量,则可以使用export内建命令将其导出为环境变量

bash中默认包含有几十个预设的环境变量,这里挑选常见的一些予以介绍

变量:BASH
说明:Bash Shell的全路径。

变量:BASH_VERSION
说明:Bash Shell的版本。

变量:CDPATH
说明:用于快速进入某个目录。

变量:EUID
说明:记录当前用户的UID

变量:FUNCNAME
说明:在用户函数体内部,记录当前函数体的函数名。

变量:HISTCMD
说明:记录下一条命令在history命令中的编号

变量:HISTFILE
说明:记录history命令记录文件的位置

变量:HISTFILESIZE
说明:设置HISTFILE文件记录命令的行数。

变量:HISTSIZE

Shell采用了“命令缓冲区”来记录所有已运行过的命令,在缓冲区满或退出Shell时才将缓冲区的记录写到HISTFILE文件中。缓冲区的大小使用HISTSIZE定义。

变量:HOSTNAME
说明:展示主机名。

变量:HOSTTYPE
说明:展示主机的架构,是i386、i686,还是x86_64等。

变量:MACHTYPE
说明:主机类型的GNU标识,这种标识有统一的结构。一般来说是“主机架构-公司-系统-gnu”

变量:LANG
说明:设置当前系统的语言环境,其实就是language的意思。

变量:PWD
说明:记录当前目录。

变量:OLDPWD
说明:记录之前目录,这个值是什么由之前所在的那个目录决定。

变量:PATH

代表命令的搜索路径

变量:PS1
说明:命令提示符,默认值是[\u@\h \W]$,其中\u是用户名、\h是主机名、\W是当前工作目录的basename、$是用户UID的替换字符:如果UID是0则替换成“#”,否则替换成“$”,所以此处具体显示出来就是“[root@localhost ~]#”。该变量可以有很多种组合,可以根据自己的喜好进行定制。

[插图]

[插图]

Shell中的变量必须以字母或者下划线开头,后面可以跟数字、字母和下划线,变量长度没有限制。

定义变量:变量名=变量值

注意点一:变量名和变量值之间用等号紧紧相连,之间没有任何空格

注意点二:当变量中有空格时必须用引号括起,否则会出现错误

变量的取值也很简单,只需要在变量名前加上$符号既可,严谨一点的写法是${}

由于Shell具有“弱变量”的特性,因此即便在没有预先声明变量的时候也是可以引用的,而且没有任何报错或者提醒,这可能会造成脚本中引用不正确的变量,从而导致脚本异常,但是却很难找出原因。

设置变量必须先声明再使用
[root@localhost ~]# shopt-s-o nounset

取消变量指的是将变量从内存中释放,使用的命令是unset,后面跟变量名

函数也是可以被取消的,所以unset后面还可以跟上函数名以取消函数

Shell中还有一些预先定义的特殊只读变量,它们的值只有在脚本运行时才能确定

首先是“位置参数”,位置参数的命名简单直接,比如,脚本本身为$0,第一个参数为$1,第二个参数为$2,第三个为$3,以此类推。当位置参数的个数大于9时,需要用${}括起来标识,比如说第10个位置参数应该记为${10

$#表示脚本参数的个数总和,$@或$*表示脚本的所有参数。

脚本或命令返回值:$?

Shell中的数组对元素个数没有限制,但只支持一维数组,这一点和很多语言不同。

数组的定义方法如下:用declare命令定义数组Array

最简单的操作就是数组取值,其格式为:${数组名[索引]}。

${Array[@]}得到的是以空格隔开的元素值,而${Array[*]}的输出是一整个字符串。

利用“@”或“*”字符,可以将数组扩展成列表,然后使用“#”来获取数组元素的个数,

可以截取某个元素的一部分,对象可以是整个数组或某个元素。

连接数组:将若干个数组进行拼接操作。

替换元素:将数组内某个元素的值替换成其他值。

取消数组或元素:和取消一般变量一样,取消一个数组的方式也使用unset命令。

只读变量又称常量,是通过readonly内建命令创建的变量。

变量的作用域又叫“命名空间”,表示变量(identifier,标识符)的上下文。

在Linux系统中,不同进程ID的Shell默认为一个不同的命名空间

Shell中有两类字符,一类是普通字符,在Shell中除了本身的字面意思外没有其他特殊意义,即普通纯文本(literal);另一类即元字符(meta),是Shell的保留字符,在Shell中有着特殊意义

Shell中的转义符是反斜线“\”,使用转义符的目的是使转义符后面的字符单纯地作为字符出现,而不解释其特殊的含义

[插图]

引用是指将字符串用某种符号括起来,以防止特殊字符被解析为其他意思

比如说上一小节中的转义符就是一种引用

Shell中一共有4种引用符,分别是双引号、单引号、反引号(在键盘上和波浪号位于同一个键)和转义符。

其中双引号又叫“部分引用”或“弱引用”,可以引用除$符、反引号、转义符之外的所有字符

单引号又叫“全引用”或“强引用”,可以引用所有字符

反引号则会将反引号括起的内容解释为系统命令

部分引用是指用双引号括起来的引用。在这种引用方式中,$符、反引号(`)、转义符(\)这3种特殊字符依然会被解析为特殊意义

全引用是指用单引号括起来的引用。单引号中的任何字符都只当作是普通字符(除了单引号本身,也就是说单引号中间无法再包含单引号,即便用转义符转义单引号也不行)

命令替换是指将命令的标准输出作为值赋给某个变量

Shell中有两种方式可以完成命令替换,一种是反引号(`),一种是$()

要注意的是,$()仅在Bash Shell中有效,而反引号可在多种UNIX Shell中使用。

Shell中的运算符主要有比较运算符(用于整数比较)、字符串运算符(用于字符串测试)、文件操作运算符(用于文件测试)、逻辑运算符、算术运算符、位运算符、自增自减运算符等

算术运算符指的是加、减、乘、除、余、幂等常见的算术运算,以及加等、减等、乘等、除等、余等复合算术运算

要特别注意的是,Shell只支持整数计算

常见的位运算有左移运算、右移运算、按位与、按位或、按位非、按位异或等运算。

按位与运算(&),是将两个整数写成二进制的形式,然后同位置相比较,只有当对应的二进制值都为1时,结果才为1。

按位或运算(|),是将两个整数写成二进制的形式,然后同位置相比较,只要对应的位置有1,结果就为1。

按位异或运算(^),是将两个整数写成二进制的形式,然后同位置相比较,只要对应的位置同为0或同为1,结果就为0,否则为1。

按位非(~)的计算方式比较麻烦,这里有个快捷的计算公式:“~a”的值为“-(a+1)”。

自增自减运算主要包括前置自增、前置自减、后置自增、后置自减等

$[]和$(())类似,可用于简单的算术运算

expr命令也可用于整数运算

和其他算术运算方式不同,expr要求操作数和操作符之间使用空格隔开(否则只会打印出字符串),所以特殊的操作符要使用转义符转义(比如*)。
expr支持的算术运算符有加、减、乘、除、余等,

declare是Shell的内建命令,通过它们也能进行整数运算

它和显式使用declare定义变量的差别是很大的。

1里的变量I未经正式定义便赋值“1+1”,对Shell来说,此时的“1+1”只是一个字符串,和“abc”无异,所以打印出来也只是字符串。而例2中,使用declare显式地定义了整数变量J(-i参数指定变量为“整数”),此时再赋值“1+1”,Shell会将后面的字符串解析成算术运算,所以打印出的值是算术表达式的计算值。

算术扩展是Shell提供的整数变量的运算机制,是Shell的内建命令之一

基本语法如下:
​​$((算术表达式))​​

其中的算术表达式由变量和运算符组成,常见的用法是显示输出和变量赋值

Linux下的bc正是这样一款专门用于高精度计算的工具

与其说bc是一个命令或者工具,不如说它是一门语言,bc的man文件对它的描述是:“一款高精度计算语言(An arbitrary precision calculator language)”。

设置显示的小数位数
scale=3

默认情况下bc并不显示小数部分,必须设置要显示的小数位数

希望一次性处理多个计算,只需要创建一个文件,并按行写好需要计算的表达式就可以了

如果想让脚本变得更灵活,也可以使用read命令动态地给变量赋值。

Shell中除了普通字符外,还有很多具有特殊含义和功能的字符,在使用它们时要特别注意其含义和作用。

通配符用于模式匹配,常见的通配符有*、?和用[]括起来的字符序列

Shell使用#作为注释符。

如果出现#后连着!,也就是“#!”不会被理解成注释,因此,其后跟着的部分必须是某个解释器的路径,而且“#!”必须出现在整个脚本的第一行。

.通配符扩展
用于匹配多个排列组合的可能。

比如坐标,横坐标是x1、x2、x3,纵坐标是y1、y2、y3,那么所有可能的坐标就是{x1,x2,x3}{y1,y2,y3}。

还可以用于匹配不同的文件,文件名的特征是只有其中一部分不同。比如file_A、file_B,就可以用file_{A,B}来匹配

控制字符即Ctrl+KEY组合键一起使用,用于修改终端或文本显示。但是控制字符在脚本中不能使用,也就是说控制字符是交互式使用的

[插图]

引号
反引号用于命令替换,和$()的作用相同,表示返回当前命令的执行结果并赋值给变量。

位置参数的含义如下。
$0:脚本名本身。
$1、$2……${10}:脚本的第一个参数、第二个参数……第十个参数。
$#:变量总数。
$*、$@:显示所有参数。
$?:前一个命令的退出的返回值。
$!:最后一个后台进程的ID号。

◆ 第14章 测试和判断

测试的第一种使用方式是直接使用test命令,该命令的格式如下:
​​test expression​​

其中expression是一个表达式,可以是算术比较、字符串比较、文本和文件属性比较等

第二种测试方式是使用“[”启动一个测试,再写expression,再以“]”结束测试。需要注意的是,左边的括号“[”后有个空格,右括号“]”前面也有个空格,如果任意一边少了空格都会造成Shell报错

增加代码的可读性,推荐使用第二种方式,而且这种方式更容易与if、case、while这些条件判断的关键字连用

​​#文件测试方法一
test file_operator FILE
#文件测试方法二
[ file_operator FILE ]​​

[插图]

Shell中的字符串比较主要有等于、不等于、大于、小于、是否为空等测试

[插图]

整数测试是一种简单的算术运算,作用在于比较两个整数的大小关系,测试成立则返回0,否则返回非0值

[插图]

逻辑测试主要有逻辑非、逻辑与、逻辑或3种

[插图]

[插图]

if expression; then
 command
fi​​
如果expression测试返回真,则执行command

if expression; then
 command
else
 command
fi​​

if expression1; then
 command1
else
 if expression2; then
 command2
 else
 command3
 fi
fi​​

case VAR in
var1) command1 ;;
var2) command2 ;;
var3) command3 ;;
...
*) command ;;
esac​​

◆ 第15章 循环

在现实生活中,如果要某人不断地重复做某一件事情,那么他很快就会感觉到厌倦,并慢慢失去兴趣,随后效率也会渐渐降低,并越来越容易出错。而计算机在这方面就显得非常有“天赋”:它天生就适合做重复的事情,并乐此不疲。

Shell中的循环主要有for、while、until、select几种。

​​for VARIABLE in (list)
do
 command
done​​

比如说上例中1到5可以用{1..5}表示

还可以使用seq命令结合命令替换的方式生成列表

其实列表for循环中in后面的内容可以是任意命令的标准输出

[root@localhost ~]# cat for_list05.sh
#!/bin/bash
for VAR in $(ls)
do
 ls-l $VAR
done​​

不带列表的for循环的结构如下所示:
​​for VARIABLE
do
 command
done​​

使用不带列表的for循环时,需要在运行脚本时通过参数的方式给for循环传递变量值

该语法虽然可以工作,但是可读性较差,所以不建议使用。可利用特殊变量$@改写上述结构

Shell支持类C的for循环

和for循环一样,while循环也是一种运行前测试语句,相比for循环来说,其语法更为简单

while expression
do
 command
done​​

按行读取文件是while一个非常经典的用法,常用于处理格式化数据。

方法一
while ((1))
do
 command
done​​
​​#方法二
while true
do
 command
done​​
​​方法三
while :
do
 command
done​​

我们可以利用while的无限循环实时的监测系统进程,以保证系统中的关键应用一直处于运行状态。

until循环也是运行前测试,但是until采用的是测试假值的方式,当测试结果为假时才继续执行循环体,直到测试为真时才停止循环

until expression
do
 command
done​​

和while的无限循环相反,until的无限循环的条件是判断假成立时退出

​​#方法一
until ((0))
do
 command
done
#方法二
until false
do
 command
done​​

select是一种菜单扩展循环方式,其语法和带列表的for循环非常类似,基本结构如下:

select MENU in (list)
do
 command
done​​

当程序运行到select语句时,会自动将列表中的所有元素生成为可用1、2、3等数选择的列表,并等待用户输入。用户输入并回车后,select可判断输入并执行后续命令。如果用户在等待输入的光标后直接按回车键,select将不会退出而是再次生成列表等待输入。

除了确实必要的情况下,不建议使用多层嵌套(三层以上的嵌套)。

break用于终止当前整个循环体。

continue语句用于结束当前循环转而进入下一次循环

◆ 第16章 函数

函数是Shell脚本中自定义的一系列执行命令,一般来说函数应该设置有返回值(正确返回0,错误返回非0。

使用函数最大的好处是可避免出现大量重复代码,同时增强了脚本的可读性

函数的返回值又叫函数的退出状态,实际上是一种通信方式。

除了在脚本运行时给脚本传入位置参数外,还可以使用内置命令set命令给脚本指定位置参数的值(又叫重置)。一旦使用set设置了传入参数的值,脚本将忽略运行时传入的位置参数(实际上是被set命令重置了位置参数的值)

对某些很常用的功能,必须考虑将其独立出来,集中存放在一些独立的文件中,这些文件就称为“函数库”。

为了和一般函数区分开来,在实践中建议库函数使用下划线开头。

由于Shell是一门面向过程的脚本型语言,而且用户主要是Linux系统管理人员,所以并没有非常活跃的社区

很多Linux发行版中都有/etc/init.d目录,这是系统中放置所有开机启动脚本的目录,这些开机脚本在脚本开始运行时都会加载/etc/init.d/functions或/etc/rc.d/init.d/functions函数库(实际上这两个函数库的内容是完全一样的)

实际上functions函数库中定义了27个函数,表16-1中列举了常见的17个。

[插图]

具有“递归”功能的函数则被称为“递归函数”。递归函数的典型特征为:在函数体中继续调用函数自身。

递归函数一定要有结束递归的条件,当满足该条件时,递归就会终止

典型的递归函数的结构如下所示:
​​function recursion() {
 recursion
 conditionThatEndTheRecursion #停止递归的条件
}​​

数学中有个经典的需要使用递归算法计算的公式是:阶乘。

递归另一个典型的例子是“汉诺塔”游戏

嵌套函数天生的结构就注定了其晦涩的可读性,在不少大公司内部的开发规范中也明确规定了不允许使用递归,所以在实际工作中要尽量避免使用递归。不过实际上你更可能根本没有使用递归的机会—从笔者多年从事Linux系统管理的经验来看,基本上不存在必须使用递归才能解决问题的场景。

◆ 第17章 重定向

计算机最基础的功能是可以提供输入输出操作

常见的输入设备有键盘、鼠标、扫描仪等,对于Linux系统来说,通常以键盘为默认输入设备,又称标准输入设备;计算机常见的输出设备有显示器、蜂鸣器、打印机等,而Linux系统则以显示器为默认的输出设备,又称标准输出设备

所谓“重定向”,就是将原本应该从标准输入设备(键盘)输入的数据,改由其他文件或设备输入;或将原本应该输出到标准输出设备(显示器)的内容,改而输出到其他文件或设备上。

文件标识符是重定向中很重要的一个概念,Linux使用0到9的整数指明了与特定进程相关的数据流

系统在启动一个进程的同时会为该进程打开三个文件:标准输入(stdin)、标准输出(stdout)、标准错误输出(stderr),分别用文件标识符0、1、2来标识

如果要为进程打开其他的输入输出,则需要从整数3开始标识

默认情况下,标准输入为键盘,标准输出和错误输出为显示器。

简单来说,I/O重定向可以将任何文件、命令、脚本、程序或脚本的输出重定向到另外一个文件、命令、程序或脚本。

[插图]

如果指定的重定向文件存在且内容不为空,重定向并不会清空原文件内容,而是将命令的输出新增到原文件的尾部。

标识输出重定向的作用是将一个标识的输出重定向到另一个标识的输入。

这时可以利用系统中的一个特殊设备/dev/null,将所有错误输出重定向到该设备中—系统会将任何输入到该设备的内容全部删除(就像一个宇宙黑洞)

标准输入重定向可以将原本应由从标准输入设备中读取的内容转由文件内容输入,也就是将文件内容写入标准输入中。

简单地说管道就是将一个命令的输出作为另一个命令的输入,借此方式可通过多个简单命令的共同协作来完成较为复杂的工作

exec还可以用于I/O重定向

[插图]

实际上,文件标识符类似于很多编程语言中的“句柄”。在Linux中,文件标识符也是一种“设备”,这个标识符会出现在/dev/fd目录中

进入这个目录,可以看到3是一个到该文件的软链接

主动打开的文件标识符需要主动关闭,否则除了系统重启,该文件标识符会一直被占用

由于在脚本中无法使用组合键,因此要终止输入就需要用到Here Document

◆ 第18章 脚本范例

从字面上就能大概猜出expect的用途,即“期待”系统的输出,且对应地发送输入作为响应。

netcat被誉为网络工具中的“瑞士军刀”,体积虽小但是功能强大。netcat最简单的功能是用于端口扫描

系统在启动时将根据当前的运行级(runlevel X)确定运行在/etc/rc.d/rcX.d目录下的脚本(都是到/etc/init.d目录中的文件软链接)。

和一般的shell脚本不同,init脚本需要满足一定的格式,最基本的要求是,脚本必须接收至少两个参数:start和stop,分别用于启动和停止服务。

LVM的快照(snapshot)功能可以很好地解决这个问题。在对一个LV创建snapshot时,仅会复制其中的元数据,而不会有任何真实数据的复制,所以创建过程几乎是实时的

当原LV有写操作时,数据会写到快照中而不是原LV中(写时复制机制,Copy On Write, CoW),从而保证了原LV中数据的一致性。为了确保数据的一致(这里特指MySQL数据),在对其做快照之前也需要对数据库进行锁定操作,做完快照后再解除锁。

由于快照的过程极为迅速,所以短时间的数据库锁定并不会对前台应用造成影响。

Archives QR Code
QR Code for this page
Tipping QR Code