前言
多数计算机有两种运行模式:内核态和用户态。操作系统运行在内核态中,在这种模式中,操作系统具有对所有硬件的完全访问权,能够执行任何指令。运行在用户态的用户程序只能执行一部分指令。
操作系统要为这些用户程序提供执行核心指令的接口(这些接口又称为系统服务)以及合理分配并且管理用户程序所需的计算机资源(硬件)。
磁盘与文件系统管理
在Linux系统中,每个设备都被当成一个文件来对待,几乎所有的硬件设备文件都在/dev
这个目录,常见的设备与其在Linux当中的文件名如下:
设备 | 设备在Linux内的文件名 |
---|---|
SCSI/SATA/USB硬盘机 | /dev/sd[a-p] |
USB闪存盘 | /dev/sd[a-p] |
VirtI/O界面 | /dev/vd[a-p] (用于虚拟机内) |
软盘机 | /dev/fd[0-7] |
打印机 | /dev/lp[0-2] (25针打印机) /dev/usb/lp[0-15] (USB 接口) |
鼠标 | /dev/input/mouse[0-15] (通用) /dev/mouse (当前鼠标) |
Linux系统安装在硬盘内,常见的硬盘有两种:
- 机械硬盘(HDD):HDD由盘片、机械手臂、磁头与主轴马达所组成,HDD有个很致命的问题,就是需要驱动马达去转动盘片,这会造成很严重的磁盘读取延迟,后来就出现了固态硬盘。
- 固态硬盘(SSD):固态硬盘最大的好处是,它没有马达不需要转动,而是通过内存直接读写的特性,因此除了没数据延迟且快速之外,还很省电! 不过固态硬盘有个很重要的致命伤,就是这些闪存有写入次数的限制。
机械硬盘工作时主轴马达让盘片转动,可伸展的机械手臂让磁头在盘片上进行读写动作,实际数据都是写在具有磁性物质的盘片上。盘片上的最小存储空间称为扇区,一个扇区的大小通常是512b
或4kb
,由同一个同心圆的扇区组成磁道,由多个扇面的相同磁道组成一个柱面。
磁盘分区
多数磁盘划分为一个或多个分区,常用的分区方式如下:
- MBR:MBR分区格式以柱面为分区的最小单位,开机管理程序记录区(446b)和分区表(64b)存放到第一个扇区,一个分区的信息需要由16b的空间记录,所以分区表只能记录四个分区的信息,所以MBR分区格式最多有四个主分区,并且这四个分区加起来不能超过2.2tb,这四个主分区可以有一个延展分区,延展分区像是一个容器,使用延展分区的目的是可以将这个延展分区分成多个逻辑分区,分区信息使用额外的扇区来记录。
- GPT:由于512b和4kb扇区共存的情况,为了兼容性,GPT分区格式将整个磁盘分为多个大小为512b的LBA区块,并且使用前34个LBA存储分区信息,后33个LBA作为分区信息的备份,其中LBA0的前446b还是开机管理程序记录区,而后64b存放了一个标志,表明GPT分区格式,LBA1用于存放分区表本身的位置和大小,备份LBA的位置以及验证信息等,LBA2~LBA33用于存放实际的分区信息,一个LBA可以存放四个分区信息,所以总共可以划分128个分区,并且总分区容量也达到了8zb,也就没有主分区、延展分区和逻辑分区的概念了。
每个分区的文件名就是磁盘文件名加上数字,如sda1
,在MBR分区格式中sda[1-4]
只能由主分区使用,延展分区只能从sda5
开始。
BIOS与UEFI
操作系统会控制所有的硬件并且提供核心功能, 因此计算机能够认识硬盘内的文件系统,并且进一步的读取硬盘内的软件文件与执行该软件来达成各项软件的执行目的。既然操作系统也是软件,那么计算机又是如何认识这个操作系统软件并且执行它的?这就得要牵涉到计算机的开机程序了,目前开机程序主要有BIOS和UEFI两种。
BIOS与MBR搭配
BIOS是一个写入到主板上的一个固件。BIOS是在开机的时候,计算机系统会主动执行的第一个程序,首先BIOS会去分析计算机里面有哪些储存设备,然后会依据使用者的设置去取得能够开机的硬盘,并且到该硬盘里面去读取第一个扇区的MBR位置,然后将计算机的控制前交给MBR中的开机管理程序,这个开机管理程序是操作系统安装在MBR上面的一套软件,它有以下功能:
- 提供菜单:使用者可以选择不同的开机项目。
- 载入核心文件:直接指向可开机的程序区段来开始操作系统。
- 转交其它开机管理程序:将开机管理功能转交给其它开机管理程序负责。
UEFI搭配GPT
由于BIOS不懂GPT,还得要通过 GPT 提供相容模式才能够读写这个磁盘设备而且 BIOS仅为16位的程序,与现阶段新的操作系统相比落后了! 为了解决这个问题,因此就有了UEFI。与 BIOS 相比,UEFI 可以直接取得 GPT 的分区表。当载入操作系统后,一般来说,UEFI 就会停止工作,并将系统交给操作系统,这与早期的BIOS差异不大。比较特别的是,某些特定的环境下, 这些 UEFI 程序是可以部份继续执行的,以协助某些操作系统无法找到特定设备时,该设备还是可以持续运行。UEFI 加入了一个所谓的安全启动机制, 这个机制代表着即将开机的操作系统必须要被 UEFI 所验证,否则就无法顺利开机。同时,为了与 windows 相容,并且提供其他第三方厂商所使用的UEFI 应用程序储存的空间,你必须要格式化一个vfat的文件系统, 大约提供 512MB 到 1G左右的容量,以让其他 UEFI 执行较为方便。
格式化与文件系统
磁盘分区完毕后还需要进行格式化,之后操作系统才能够使用文件系统,传统的磁盘与文件系统之应用中,一个分区就是只能够被格式化成为一个文件系统,但是由于新技术的利用,例如LVM与软件磁盘阵列, 这些技术可以将一个分区格式化为多个文件系统,也能够将多个分区合成一个文件系统。 文件系统需要存储两部分内容:文件内容和文件属性, 文件系统通常会将这两部份的数据分别存放在不同的区域:
- inode:记录文件的属性,一个文件占用一个inode,同时记录此文件的数据所在的 block号码;
- block:实际记录文件的内容,若文件太大时,会占用多个 block 。
- superblock:记录此 filesystem 的整体信息,包括inode/block的总量、使用量、剩余量,以及文件系统的格式与相关信息等;
lsblk
指令查看所有的设备和分区信息:
lsblk [options]
- f #同时列出所有文锦系统名称,UUID等
parted
指令可以列出某磁盘的分区表类型与分区信息:
parted <deviceName> print
MBR分区格式使用 fdisk
指令分区, GPT 分区格式使用 gdisk
分区,这两个命令几乎相同:
gdisk|fdisk <deviceName>
目录和挂载
文件系统提供目录记录文件的位置,而文件数据放置在磁盘分区当中,为了使目录和分区结合,就要使用挂载,所谓挂载就是利用一个目录当作进入点,将磁盘分区的数据放置在该目录下,也就是进入该目录就可以读取该分区的意思。目录本身也是一种文件,当在文件系统下创建一个目录时,文件系统会分配一个inode
和至少一个block
给该目录,其中inode
记录该目录的相关权限与属性,block
记录在这个目录下的文件名和文件名占用的inode
号码。
常用目录
目录 | 说明 |
---|---|
/home | 用户目录 |
/root | root文件目录 |
/etc | 配置文件,一般用户可以查阅,root能修改 |
/tmp | 一般用户或正在执行程序暂时存放文件的地方 |
/var | 存放常态性变动性数据 |
/usr | 软件资源存储库 |
/usr/local | 自行安装软件存放的目录 |
/usr/src | 存放源代码 |
文件
文件是一种抽象机制,它提供了一种在磁盘上保存信息而且方便以后读取的方法。当在目录下创建一个文件时,文件系统会分配一个inode和相对于这个文件大小的block数量给该文件。其中inode记录该文件的相关权限与属性,block记录该文件的实际内容。
文件的详细属性如下:
文件类型
文件类型 | 说明 |
---|---|
普通文件(-) | 普通文件一般分为ASCII文件和二进制文件 |
字符特殊文件(c) | 比如键盘 |
块特殊文件(b) | 比如磁盘 |
数据传输文件(p) | 比如管道 |
目录(d) | / |
套接字文件(s) | / |
链接文件(l) | 软链接 |
文件权限
普通权限
接下来九个字符代编文件的权限,它们三个一组,依次代表文件拥有者、群组和其他人员对此文件的权限。
字符型 | 数字型 | 对文件意义 | 对目录意义 |
---|---|---|---|
r | 4 | 可读取文件内容 | 读取目录结构 |
w | 2 | 可以编辑文件内容(不含删除该文件) | 新增、删除和移动目录下的内容 |
x | 1 | 可以被系统执行 | 是否可以进入此目录 |
- | 0 | 无权限 | 无权限 |
特殊权限
特殊权限仅对二进制程序有效,并且仅在执行程序时起作用。
名称 | 出现位置 | 字符型 | 数字型 | 对文件意义 | 对目录意义 |
---|---|---|---|---|---|
SUID | 文件拥有者的x 的位置 | s | 4 | 执行者对该程序需要具有x 权限执行者将具有该程序拥有者的权限 | / |
SGID | 群组的x 的位置 | s | 2 | 执行者对于该程序需要具备x 权限执行者在执行该程序时可以获得该程序群组的权限 | 使用者必须能i进入和读取此目录 使用者在此目录下的有效群组会变成该目录的群组 若使用者对该目录具有 w 权限,则使用者所创建的新文件的群组与此目录的群组相同 |
SBIT | 其它成员的x 位置 | t | 1 | / | 使用者必须能写入此目录 当使用者在该目录下创建文件和目录时,仅有自己和root才有权力删除该文件 |
默认权限
umask
用于指定创建新文件或目录时的默认权限。它接收四组数字,第一组数字给特殊权限使用,其它三组给普通权限使用,它的计算规则为从最大值减去分值。
常用命令
切换目录
cd <dir>
显示当前目录
pwd [options]
- P #显示真实真实路径,而非链接路径
创建新目录
mkdir [options] <dir>
- m <xyz> #设置文件权限
- p #递归创建目录
创建文件
touch <file>
查看目录信息
ls <dir>
- a #显示所有文件,包括隐藏文件
- l #显示详细信息
查看文件内容
less <file> #查看文本文件
[pagedown] #向上翻页
[pageup] #向下翻页
/string #向下搜索字符string
?string #向上搜索string
q #退出less
od <options> <file> #查看非文本文件
- t <type> #指定输出类型查看文件
- a #默认字符
- c #ASCII
- d #十进制
- o #八进制
- x #十六进制
查看文件或目录类型
file <file>
复制文件或目录
cp <options> <src> <dest>
- p #连同文件的属性一同复制
- r #递归复制
- d #若文件为链接文件则复制的文件也为链接文件,不指定默认复制原始文件
- a #dpr的组合
删除文件或目录
rm <options> <dir/file>
- f #强制删除,不会出现提示信息
- r #递归删除
移动或更名文件和目录
mv <src> <dest>
- f #强制
- i #若destnation已存在,询问是否覆盖
- u #若destnation已存在,且source较新才会更新
修改文件和目录所属群组
chgrp <options> <group> <dir/file>
- R #递归修改
修改文件和目录拥有者
chown <options> <owner> <dir/file>
- R #递归修改
修改文件和目录权限
chmod <options> <xyz> <dir/file>
- R #递归修改
创建链接文件和目录
ln [options] <src> <dest> #创建一个实体链接
- s #创建一个符号链接
- f #如果dest存在,则移除dest再创建
搜寻文件
find <path> <options>
- name <value> #按名字查找
- user <value> #按用户查询
- size <value> #按文件大小查询(+n大于,-n小于,n等于)
压缩解压目录
gzip [options] <file> #压缩fileName
- d #解压缩
tar -czvf <dest.tar.gz> <src> #将src压缩为target.tar.gz
-tzvf <src.tar.gz> #查看source.tar.gz中的文件
-xzvf <src.tar.gz> -C <dir> # 将source.tar.gz解压到dirName路径下
过滤
grep [options] <targetStr> <file>
- n #显示行号
- i #忽略字母大小写
- v #反向选择
vim编辑器
一般指令模式
默认模式,在此模式中可以删除复制粘贴文档内容。
按钮 | 说明 |
---|---|
h,j,k,l | 左,下,上,右移动一个字符 |
ctrl+f | 向下翻一页 |
ctrl+b | 先上翻一页 |
0 | 移动到这一行的最前面 |
$ | 返回这一行的最后面 |
G | 移动到文件的最后一行 |
gg | 移动到文件的第一行 |
n+enter | 光标向下移动n列 |
/word | 向光标下寻找一个名为word的字符串 |
n1,n2s/word1/word2/g | 在n1和n2行之间搜寻word1并替换为word2 |
x,X | delete,backspace |
dd | 删除光标所在行 |
ndd | 删除光标所在以下n行 |
yy | 复制光标所在行 |
nyy | 复制光标所在以下n行 |
p | 在光标先一行粘贴 |
u | 复原前一个动作 |
ctrl+r | 重做上一个动作 |
. | 重复上一个动作 |
编辑模式
在一般指令模式中按下字母i
进入编辑模式,在编辑模式按下Esc
进入一般指令模式,在编辑模式中可以编辑文件内容。
命令行命令模式
在一般指令模式中输入:
进入命令行命令模式,在命令行命令模式按下Esc
进入一般指令模式,在命令行命令模式中提供搜索、存取和退出等操作。
按钮 | 说明 |
---|---|
:w | 写入文件 |
:q | 离开vim |
:wq | 保存后离开 |
shell
shell
是一些命令行工具的统称,bash
是大多数Linux系统默认shell
,当shell被启动时,它初始化自己,然后在屏幕上输出一个提示符并等待用户输入命令行。
<command> [options] <args>
Shell单个命令一般都是一行,用户按下回车键,就开始执行。有些命令比较长,写成多行会有利于阅读和编辑,这时可以在每一行的结尾加上反斜杠,Shell就会将下一行跟当前行放在一起解释。
<command> [optionA] <argA> \
[optionB] <argB>
分号是命令的结束符,使得一行可以放置多个命令,上一个命令执行结束后,再执行第二个命令:
<commandA>;<commandB>
除了分号,Shell还提供两个命令组合符,允许更好地控制多个命令之间的继发关系:
<commandA> && <commandB> #如果comandA运行成功,则运行commandB
<commandA> || <commandB> #如果CommandA命令运行失败,则继续运行CommandB命令。
用户输入一个命令行后,shell
提取其中的第一个字(这里的字指的是被空格或制表符分隔开的一连串字符),假定这个字是将要运行程序的程序名,搜索这个程序,如果找到了这个程序就运行它。命令中还可以包含其它选项和参数,它们作为字符串传给所调用的程序。然后shell
会将自己挂起直到该程序运行完毕,之后再尝试读入下一条命令。重要的是,shell
也只是一个普通用户程序。它仅仅需要从键盘读取数据、向显示器输出数据和运行其他程序的能力。
工作管理
在shell中出现提示字符让你操作的环境就称为前景,至于其它工作就可以让你放入背景去暂停或运行。要注意的是,放入背景的工作想要运行时, 它必须不能够与使用者互动。
背景运行
<cmd> &
背景暂停
ctrl+Z
观察背景工作状态
jobs [options]
- l #除了列出 job number 与指令串之外,同时列出 PID 的号码
- r #仅列出正在背景 run 的工作
- s #仅列出正在背景当中暂停的工作
将背景工作拿到前景来处理
fg %<jobnumber>
让工作在背景下的状态变成运行中
bg %<jobnumber>
杀死背景工作
kill [options] %<jobnumber>
- 1 #重新读取一次参数的配置文件
- 9 #强制杀死工作
- 15 #以正常方式结束工作,默认值
热键
名称 | 说明 |
---|---|
↑↓ | 寻找前或后一个输入的命令 |
tab | 命令补全与文件补全 |
ctrl c | 中止当前正在执行的命令 |
shift pagedown/pageup | 翻页 |
ctrl+u | 从光标位置删除到行首 |
ctrl+k | 从光标位置删除到行尾 |
ctrl+a | 光标移动到指令串的最前边 |
ctrl+e | 光标移动到指令串的最后面 |
ctrl d | 关闭shell |
模式扩展
Shell接收到用户输入的命令以后,会根据空格将用户的输入,拆分成一个个词元。然后,Shell会扩展词元里面的特殊字符,扩展完成后才会调用相应的命令。模式扩展与正则表达式的关系是,模式扩展早于正则表达式出现,可以看作是原始的正则表达式。它的功能没有正则那么强大灵活,但是优点是简单和方便。
文件名扩展
文件名扩展只有文件确实存在的前提下才会扩展。如果文件不存在,就会原样输出。在使用带有扩展的文件名引用文件时,需要把文件名放在单引号或双引号里面。
~ #当前用户的主目录
? #一个任意字符
* #0到多个任意字符,*不会匹配隐藏文件,如果要匹配隐藏文件,需要写成.*。*只匹配当前目录,不会匹配子目录。
[bulabula] #任意一个括号内的字符,如果需要匹配[字符,可以放在方括号内。如果需要匹配连字号-
[!bulabula] #反向选择
[from-to] #任意一个范围内的字符
普通扩展
普通扩展会扩展成所有给定的值,而不管是否有对应的文件存在。
#大括号可以用于多字符的模式,方括号不行
#大括号扩展表示分别扩展成大括号里面的所有值
#各个值之间使用逗号分隔。逗号前后不能有空格。否则扩展会失效。
#逗号前面可以没有值,表示扩展的第一项为空。
#大括号可以嵌套
#大括号也可以与其他模式联用,并且总是先于其他模式进行扩展
{bulabula}
#扩展成一个连续序列,
#如果遇到无法理解的简写,大括号模式就会原样输出,不会扩展
{from..to}
#step表示步长
{from..to..step}
变量扩展
变量扩展可以扩展为变量值。
${variable}
如果变量的值本身也是变量,可以用以下方式扩展到变量的最终值:
${!variableName}
子命令扩展
子命令扩展可以扩展为命令的所有输出。
$(command)
算数扩展
算数扩展可以扩展成整数运算的结果,算术运算只能计算整数,否则会报错。算数扩展内部可以用圆括号改变运算顺序,在算数扩展内部使用变量时可以不用${}
。如果在算数扩展里面使用字符串,Shell会认为那是一个变量名。如果不存在同名变量,Shell就会将其作为空值,因此不会报错。如果一个变量的值为字符串,跟上面的处理逻辑是一样的。
$((expr))//(())这个语法不返回值,命令执行的结果根据算术运算的结果而定。只要算术结果不是0,命令就算执行成功。
算数扩展支持的运算符有:
运算符 | 说明 |
---|---|
= | 赋值运算 |
+ | 加法 |
- | 减法 |
* | 乘法 |
/ | 除法 |
% | 余数 |
** | 指数 |
++ | 自增运算 |
– | 自减运算 |
<< | 位左移运算 |
>> | 位右移运算 |
& | 位的“与”运算 |
| | 位的“或”运算 |
~ | 位的“否”运算 |
^ | 位的异或运算 |
< | 小于 |
> | 大于 |
<= | 小于或相等 |
>= | 大于或相等 |
== | 相等 |
!= | 不相等 |
&& | 逻辑与 |
|| | 逻辑或 |
! | 逻辑否 |
expr1?expr2:expr3 | 三元条件运算符 |
字符类扩展
字符类扩展可以扩展成某一类特定字符之中的一个。
[[:alnum:]] #匹配任意英文字母与数字
[[:alpha:]] #匹配任意英文字母
[[:blank:]] #空格和 Tab 键。
[[:cntrl:]] #ASCII 码 0-31 的不可打印字符。
[[:digit:]] #匹配任意数字 0-9。
[[:graph:]] #A-Z、a-z、0-9 和标点符号。
[[:lower:]] #匹配任意小写字母 a-z。
[[:print:]] #ASCII 码 32-127 的可打印字符。
[[:punct:]] #标点符号(除了 A-Z、a-z、0-9 的可打印字符)。
[[:space:]] #空格、Tab、LF(10)、VT(11)、FF(12)、CR(13)。
[[:upper:]] #匹配任意大写字母 A-Z。
[[:xdigit:]] #16进制字符(A-F、a-f、0-9)。
数据类型
Shell只有一种数据类型,就是字符串。不管用户输入什么数据,Shell都视为字符串。
转义
某些字符在Shell里面有特殊含义,如果想要原样输出这些特殊字符,就必须在它们前面加上反斜杠,使其变成普通字符。反斜杠本身也是特殊字符,如果想要原样输出反斜杠,就需要对它自身转义。反斜杠除了用于转义,还可以表示一些不可打印的字符:
\a,\b,\n,\r,\t
如果想要在命令行使用这些不可打印的字符,可以把它们放在引号里面,然后使用echo命令的-e参数。
单引号
Shell允许字符串放在单引号,单引号用于保留字符的字面含义,各种特殊字符在单引号里面,都会变为普通字符。由于反斜杠在单引号里面变成了普通字符,所以如果单引号之中,还要使用单引号,不能使用转义,需要在外层的单引号前面加上一个美元符号,然后再对里层的单引号转义。
双引号
双引号比单引号宽松,双引号里面不会进行文件扩展,但美元符号和反斜杠依然含有特殊意义。双引号的一个常见的使用场合是文件名包含空格,还有就是双引号保存原始命令的输出格式。
输入输出
当shell
启动或在shell
内启动程序时,它们自动获得了对标准输入(stdin
)、标准输出(stdout
)和标准错误(stderr
)文件进行访问的能力。默认情况下,标准输入是从键盘输入的,标准输出和标准错误是输出到显示器上。也可以对标准输入输出进行重定向:
> <file> #以覆盖的方式将stdout重定向到file
>> <file> #以追加的方式将stdout重定向到file
< <file> #以覆盖的方式将file定向到stdin
<< <file> #以追加的方式将file定向到stdin
常常有把shell
中第一个程序的输出作为下一个程序的输入这种情况。我们可以使用输入输出重定向完成,当然Linux提供了一种更简单的方法来达到相同的结果,那就是管道:
<cmd1> | <cmd2> #将从cmd1得到的输出作为输入传给cmd2
变量
变量就是一组文字和字符,来取代一些设置或数据。不存在的Shel变量一律等于空字符串。
环境变量
环境变量是Shell从配置文件中读取的变量,可以直接使用。它们通常是系统定义好的,也可以由用户从父 Shell 传入子 Shell。以下指令可以查询环境变量:
env
常用的环境变量如下:
#PS1用于设置命令提示字符,常见的特殊字符如下:
#\H :完整的主机名称。
#\h :仅取主机名称在第一个小数点之前的名字。
#\u :目前使用者的帐号名称。
#\w :完整的工作目录名称,由根目录写起的目录名称。但主文件夹会以 ~ 取代。
#\W:工作路径的最后一个目录名
#$ :提示字符,如果是 root 时,提示字符为 # ,否则就是 $。
PS1
HOME #用户的主目录
HOST #当前主机的名称
LANG #字符集以及语言编码
PATH:#由冒号分开的目录列表,当输入可执行程序名后,会搜索这个目录列表
PWD #当前工作目录
UID #当前用户的 ID 编号
USER #当前用户的用户名
HISTFILE #指向.bash_history文件
用户变量
用户变量是用户在当前 Shell 里面自己定义的变量,仅在当前 Shell 可用。不能向子进程传递。
variableName=variableValue #等号两边不能有空格
可以使用以下命令将用户变量转变为环境变量:
export variableName
变量的测试
变量的测试目的是保证变量不为空。
${variableName:-word} #如果变量variableName存在且不为空,则返回它的值,否则返回word
${variableName:=word} #如果变量variableName存在且不为空,则返回它的值,否则将它设为word,并且返回word
${variableName:+word} #如果变量名存在且不为空,则返回word,否则返回空值。
${variableName:?message} #如果变量varname存在且不为空,则返回它的值,否则打印出varname: message,并中断脚本的执行。
declare
declare命令可以声明一些特殊类型的变量,为变量设置一些限制。
declare [options] variableName=variableValue
- a #声明数组变量
- i #声明整数变量
- r #声明为只读变量
- l #声明为小写字母
- u #声明为大写字符
- x #该变量输出为环境变量
- A #创建关联数组
变量的字符串操作
长度和子串
#字符串长度
${#variableName}
#子字符串,如果省略length,则从位置offset开始,一直返回到字符串的结尾。
#如果offset为负值,表示从字符串的末尾开始算起。注意,负数前面必须有一个空格
#这种语法不能直接操作字符串,只能通过变量来读取字符串,并且不会改变原始字符串。
${variableName:offset:length}
搜索和替换
#变量从头开始符合关键字,则符合关键字的最短数据将被删除
variableName=${variableName#keyword}
#变量从头开始符合关键字,则符合关键字的最长数据将被删除
variableName=${variableName##keyword}
#变量从尾开始符合关键字,则符合关键字的最短数据将被删除
variableName=${variableName%keyword}
#变量从尾开始符合关键字,则符合关键字的最长数据将被删除
variableName=${variableName%%keyword}
#将第一个匹配oldChar的字符替换为newChar
variableName=${variableName/oldChar/newChar}
#将所有匹配oldChar的字符替换为newChar
variableName=${variableName//oldChar//newChar}
#添加extendValue
variableName=${variableName}extendValue
大小写转换
# 转为大写
${varname^^}
# 转为小写
${varname,,}
特殊变量
特殊变量是Shell自身提供的变量,用户不能进行修改。
? #为上一个命令的退出码,用来判断上一个命令是否执行成功。返回值是0,表示上一个命令执行成功;如果不是零,表示上一个命令执行失败。
$ #为当前 Shell 的进程 ID。
$_ #上一个命令的最后一个参数
! #为最近一个后台执行的异步命令的进程 ID
0 #为当前Shell的名称或者脚本名
目录栈
目录栈用于方便的在不同目录之间切换。
pushd
pushd可以进入指定目录,并将该目录放入目录栈中。第一次使用pushd命令时,会将当前目录先放入堆栈,然后将所要进入的目录也放入堆栈,位置在前一个记录的上方。
pushd [options] <dir>
- n #仅操作目录栈,不改变目录
- <n> #n为某个整数,表示堆栈中指定位置的记录,这时不会切换目录。
popd
popd会移除目录栈的顶部记录,并进入新的目录栈顶部目录。
popd [options]
- n #仅操作目录栈,不改变目录
- <n> #n为某个整数,表示堆栈中指定位置的记录,这时不会切换目录。
dirs
dirs命令用于直接操作目录栈。
dirs [options]
- c #清空目录栈
- v #带行行号的打印目录栈
Session
刚打开shell就取得一堆有用的变量,这是因为系统有一些环境设置文件的存在,让 shell在启动时直接读取这些配置文件,以规划好shell的操作环境,而这些配置文件又可以分为系统配置文件以及自定义配置文件。根据打开shell的方式不同,它读取的配置文件也不同。
login shell
需要用户输入用户名和密码进行登录的方式称为login shell
。login shell
会进行整个系统环境的初始化,启动的初始化脚本依次如下:
- /etc/profile:所有用户的全局配置脚本
- /etc/profile.d:目录里面所有.sh文件
- ~/.bash_profile:用户的个人配置脚本。如果该脚本存在,则执行完就不再往下执行。
- ~/.bash_login:如果
~/.bash_profile
没找到,则尝试执行这个脚本。如果该脚本存在,则执行完就不再往下执行。
~/.profile:如果~/.bash_profile
和~/.bash_login
都没找到,则尝试读取这个脚本。
non-login shell
non-login shell
是用户进入系统之后创建的shell,此时不需要进行环境初始化,也不需要登录。non-login shell
的初始化脚本依次如下:
- /etc/bash.bashrc:对全体用户有效。
- ~/.bashrc:仅对当前用户有效。
退出
~/.bash_logout
脚本在每次退出 Session 时执行,通常用来做一些清理工作和记录工作,
shell脚本
可以把一系列shell命令放到一个文件中,然后将此文件作为shell的输入来运行。包含shell命令的文件称为shell脚本。
Shebang 行
脚本的第一行通常是指定解释器,即这个脚本必须通过什么解释器执行。这一行以#!
字符开头,字符后面就是脚本解释器的位置。它们之间有没有空格都可以。Shebang 行不是必需的,有 Shebang 行的时候,可以直接执行脚本。但是建议加上这行。没有 Shebang 行的时候,就需要手动将脚本传给解释器。
#!/bin/bash
为了防止脚本解释器位置的变动,可以写成以下形式:
#!/usr/bin/env bash
上面命令使用env命令(这个命令总是在/usr/bin
目录),返回 Bash 可执行文件的位置。
参数
调用脚本的时候,可以在脚本后带参数,在脚本内部,可以使用特殊变量引用这些参数:
0 #脚本文件名
1-9 #对应脚本第一个参数到第九个参数,如果脚本的参数多于9个,那么第10个参数可以用${10}的形式引用,以此类推。
# #参数的总数
@ #全部的参数,参数之间使用空格分隔
* #全部的参数,参数之间使用变量$IFS值的第一个字符分隔,默认为空格
shift
通过shift可以移动参数,每次执行都会移除脚本当前的第一个参数($1),使得后面的参数向前一位。
shift
shift后面可以接数字,代表拿掉最前面的几个参数的意思。
getopts
getopts可以解析带有前置连线词(-)的脚本命令行选项。optstrings是字符串,给出脚本所有的连词线选项。如果选项可以带有参数,那么必须在有参数的选项字符串后面加一个冒号。name是一个变量名,用于保存当前取到的配置项参数。如果用户输入了没有指定的连线词选项,那么name的值为?
。
getopts <optstrings> <name>
只要遇到不带连词线的参数,getopts就会执行失败,多个连词线参数写在一起的形式,getopts也可以正确处理。
OPTIARG
如果某个连接词选项带有参数,那么环境变量OPTIARG
将保存该参数值。
OPTIND
$OPTIND
变量在getopts开始执行前是1,然后每次执行就会加1。等到退出while循环,就意味着连词线参数全部处理完毕。这时,$OPTIND - 1
就是已经处理的连词线参数个数,使用shift命令将这些参数移除,保证后面的代码可以用$1、$2等处理命令的主参数。
- -
如果要确保某个变量不会被当作配置项解释,就要在它前面放上参数终止符--
。
exit
exit命令用于终止当前脚本的执行,并向 Shell 返回一个退出值。0表示正常,1表示发生错误,2表示用法不对,126表示不是可执行脚本,127表示命令没有发现。如果脚本被信号N终止,则退出值为128 + N。
exit <n>
source
source命令可以在当前 Shell 执行脚本,不像直接执行脚本时,会新建一个子 Shell。所以,source命令执行脚本时,不需要export变量。source命令的另一个用途,是在脚本内部加载外部库。
source <sourcePath>
read
read命令将用户的输入存入一个变量,方便后面的代码使用。用户按下回车键,就表示输入结束。
reed [options] <variableNames> #variableNames用于存储用户输入的变量
- p <str> #设置提示符
- t <seconds> #设置等待时间
- a #将用户输出赋值给一个数组
read可以接受用户输入的多个值。如果用户的输入项少于read命令给出的变量数目,那么额外的变量值为空。如果用户的输入项多于定义的变量,那么多余的输入项会包含到最后一个变量中。
REPLY
如果read命令之后没有定义变量名,那么环境变量REPLY会包含所有的输入。
IFS
read命令读取的值,默认是以空格分隔。可以通过自定义环境变量IFS,修改分隔标志。
条件判断
if
if后面可以跟一条或多条命令,命令执行成功就会执行then,失败就跳出if。当有多条指令时只会根据最后一个指令的执行状态判断。
if commands; then
commands
elif commands; then
commands
else
commands
fi
test
test命令用于测试判断表达式的真伪,test命令执行成功;表达式为伪,test命令执行失败。
test <expression>
[ expression ] #括号前后必须有空格
[[ expression ]] #括号前后必须有空格,该形式还可以使用正则表达式
判断表达式
文件判断
- e <file> #该文件名是否存在
- f <file> #该文件名是否存在且为文件
- d <file> #该文件名是否存在且为目录
- b <file> #该文件名是否存在且为一个块设备
- c <file> #该文件名是否存在且为一个字符设备
- S <file> #改文件名是否存在且为一个socket文件
- p <file> #该文件名是否存在且为一个管道文件
- L <file> #改文件名是否存在且为一个链接文件
- r <file> #侦测该文件名是否存在且具有可读的权限
- w <file> #侦测该文件名是否存在且具有可写的权限
- x <file> #侦测该文件名是否存在且具有可执行的权限
- s <file> #侦测该文件名是否存在且为非空白文件
<file1> -nt <file2> #判断 file1 是否比 file2 新
<file1> -ot <file2> #判断 file1 是否比 file2 旧
<file1> -ef <file2> #判断 file1 与 file2 是否为同一文件,主要意义在判定,两个文件是否均指向同一个 inode
字符串判断
字符串判断时,如果要使用变量,变量要放在双引号之中,否则变量替换成字符串以后,test命令可能会报错。
- z <str> #str长度为0则为真
- n <str> #str长度大于0则为真
<str1>==<str2> #str1等于str2则为真
<str1>!=<str2> #str1不等于str2则为真
<str1> '>' <str2> #str1大于str2则为真
<str1> '《' <str2> #str1小于str2则为真
整数判断
<int1> -eq <int2> #两数值相等
<int1> -ne <int2> #两数值不相等
<int1> -gt <int2> #n1 大于 n2
<int1> -lt <int2> #n1 小于 n2
<int1> -ge <int2> #n1 大于等于 n2
<int1> -le <int2> #n1 小于等于 n2
逻辑运算
&&
||
!
case
expression是一个表达式,pattern是表达式的值,可以使用各种扩展。
case commands in
pattern )
commands;;&
pattern )
commands;;&
esac
循环
while
while condition; do
commands
done
untill
until condition ;do
commands
done
for
for variable in list ;do
commands
done
for (( expression1; expression2; expression3 )); do
commands
done
select
select生成一个菜单,内容是列表list的每一项,并且每一项前面还有一个数字编号。Shell提示用户选择一项,输入它的编号。用户输入以后,Shell会将该项的内容存在变量name,该项的编号存入环境变量REPLY
。如果用户没有输入,就按回车键,Shell会重新输出菜单,让用户选择。执行命令体commands。执行结束后,回到第一步,重复这个过程。
select name
in list
do
commands
done
break、continue
break和continue,用来在循环内部跳出循环。
数组
数组是一个包含多个值的变量。成员的编号从0开始,数量没有上限,也没有要求成员被连续索引。
创建数组
可以通过以下方式创建数组:
array[index]=value
array=(valueA valueB) #可以使用通配符
使用数组
读取一个数组成员
${array[index]}
读取所有数组成员
最好放到双引号中读取。
${array[@]}
${array[*]}
数组的长度
${#a[@]}
${#a[*]}
提取数组序号
可以返回数组的成员序号,即哪些位置是有值的。
${!array[@]}
${!array[*]}
提取数组成员
${array[@]:position:length}#省略长度则默认为数组长度
添加成员
array[index]=value
array+=value
关联数组
关联数组使用字符串而不是整数作为数组索引。只能使用declare声明一个关联数组。
函数
函数总是在当前 Shell 执行,如果函数与脚本同名,函数会优先执行。函数的设置一定要在程序的最前面,函数内也有像脚本一样的参数。
fn() {
# codes
}
return
return命令用于从函数返回一个值。函数执行到这条命令,就不再往下执行了,直接返回了。
local
函数体内直接声明的变量,属于全局变量,可以用local命令声明局部变量。
local virableName
mktemp
mktemp命令用于安全的创建文件,该命令创建的额文件只有创建的用户能够读写,并且在脚本退出时会自动删除。
mktemp [options] <file>
- d <dir> #创建一个目录
trap
trap命令用来在脚本中响应系统信号。
trap <command> [signal]
HUP:编号1,脚本与所在的终端脱离联系。
INT:编号2,用户按下 Ctrl + C,意图让脚本终止运行。
QUIT:编号3,用户按下 Ctrl + 斜杠,意图退出脚本。
KILL:编号9,该信号用于杀死进程。
TERM:编号15,这是kill命令发出的默认信号。
EXIT:编号0,这不是系统信号,而是 Bash 脚本特有的信号,不管什么情况,只要退出脚本就会产生。
set
set用于定制子Shell环境。
set [options]
- u #遇到不存在的变量就会报错,并停止执行。
- x #用来在运行结果之前,先输出执行的那一行命令。
+ x #关闭命令输出
- e #只要发生错误就停止运行
- o pipefail #只要一个子命令失败,整个管道命令就失败,脚本就会终止执行。
- E #一旦设置了-e参数,会导致函数内的错误不会被trap命令捕获,-E参数可以纠正这个行为,使得函数也能继承trap命令。
账号与群组管理
Linux是一个多用户多任务的操作系统,任何一个要使用系统资源的用户,都必须向系统管理员申请一个账号,然后以这个账号身份进入系统,用户至少属于一个群组,在创建用户时如果没有创建群组那么会默认创建一个和用户同名的群组并将该用户放入群组内。
账号与配置文件
系统内的账号信息都记录在/etc/passwd
文件中,该文件的每一行都代表着一个账号。它的结构如下:
为了密码的安全性,所有账号的密码加密后都存放在/etc/shadow
文件中,该文件的结构如下:
查询账户信息
id <userName>
添加账号
useradd [options] <userName>
- d <userPath> #指定用户目录
- g <groupName> #指定初始群组
- u <UID> #指定UID
- G <groupName> #指定该账号可以加入的群组
- M #不创建使用者文件夹
- m #必须创建使用者文件夹
- c #用户说明内容
- s #指定shell
- e #设置失效日期
- f #设置密码是否会失效,0为立即失效,-1为永不失效
- r #创建一个系统账号
指定/修改密码
passwd [options] <userName>
- l #使密码失效
- u #使密码恢复
- n #后面接天数,shadow 的第 4 字段,多久不可修改密码天数
- x #后面接天数,shadow 的第 5 字段,多久内必须要更动密码
- w #后面接天数,shadow 的第 6 字段,密码过期前的警告天数
- i #后面接“日期”,shadow 的第 7 字段,密码失效日期
- S #列出账户的相关参数
删除账号
userdel [options] <userName>
- r # 连同主目录一起删除
修改账号信息
usermod [options] <userName>
- d <dirNme> #修改主文件夹
- g <groupName> #修改初始群组
- G <groupName> #添加群组
- l <userName> #修改账号名称
用户切换
su [options] <userName> # 以non-login shell的方式切换到username,不指定suername默认为root
- #以login-shell的方式切换到username
sudo [options] <cmd>
- b #后台执行
- u <userName> #指定用户,默认为root
除了 root 之外的其他帐号,若想要使用 sudo 执行属于 root 的权限指令,则 root 需要先使用 visudo 去修改 /etc/sudoers ,让该帐号能够使用全部或部分的root 指令功能。
查询在线使用者
who
与使用者交流
write <userName> <port> #结束时,请按下 [crtl]-d 来结束输入。
消息会打断userName当前的动作,可以通过以下方式设置是否接收消息:
mesg n|y
但是这个 mesg 的功能对 root 传送来的讯息没有抵挡的能力。
邮件
mail <title> <userName>@<host>
结束时,最后一行输入小数点即可。
群组与配置文件
系统中所有群组信息都存放在/etc/group
文件夹下,该文件的每一行都代表着一个群组。它的结构如下:
一个账号可以归属于多个群组,账号创建的时候指定的群组为初始群组,也就是账号GID指定的群组,初始群组的账号名称不会加入到初始群组配置文件的第四个字段。当使用账户新建一个文件或目录时,文件和目录的初始群组为账号的有效群组。此外群组密码存放在/etc/gshadow
中,它的结构如下:
查看当前用户的所有群组,第一个为有有效群组
groups
切换有效群组
newgrp <groupName> #该指令以切换另外以一个 shell来来提供这个功能,如果想要回到原本的环境中,请输入 exit
创建群组
groupadd [options] <groupName>
- g <GID> #指定GID
删除群组
groupdel <groupName>
修改群组信息
groupmod [options] <groupName>
- n <groupName> #修改群组名称
- - g <GID> #指定GID
群组管理员
gpasswd [options] <groupName> #没有参数,表示给groupName一个密码
- A <userName>#将 groupname 的主控权交由userName管理
- M <userName>#将某些帐号加入这个群组当中
- r #将groupname的密码移除
- a <userName> #将某位使用者加入
- d <userName> #将某位使用者移除
ACL
ACL 是 Access Control List 的缩写,主要的目的是在提供传统的owner,group,others 的read,write,execute 权限之外的细部权限设置。ACL 可以针对单一使用者,单一文件或目录来进行 r,w,x 的权限规范,对于需要特殊权限的使用状况非常有帮助。
setfacl [options] u:<userName>:<rwx>|g:<groupName>:<rwx>|m:<rwx> <file|dir>
- m #设置后续的 acl 参数给文件使用
- x #移除后续的acl参数
- b #移除所有acl参数
- k #移除默认的acl参数
- R #递归设置
- d #设置默认acl参数,只对dir有效
当设置一个用户/群组没有任何权限的 ACL 语法中,在权限的字段不可留白,而是应该加上一个减号。
getfacl <file|dir>
进程管理
进程是对正在运行程序的抽象,程序一般是放置在磁盘中,然后通过使用者的执行来触发。触发后会载入到内存中成为一个个体,那就是进程。 为了操作系统可管理这个程序,因此进程有给予执行者的权限/属性等参数,并包括程序所需要的指令码与数据或文件数据等, 最后再给予一个PID 。
查询进程
ps - aux #查询所有后台进程
- l #显示与当前终端有关的后台进程
动态观察程序的变化
top [options]
- d [num] #整个程序画面更新的秒数。默认是 5 秒
离线后台进程
后台进程的输出会被定向到~/nohup.out
文件中。
nohup <command> & #离线后台运行
进程的创建
在Linux中,新进程都会借由父进程以复制 (fork)的方式产生一个一模一样的子程进程,,子进程与父进程唯一的差别就是 PID 不同! 但是这个子进程还会多一个 PPID 的参数,PPID就是父进程的PID,然后被复制出来的子进程再以 exec 的方式来执行实际要进行的程序。
进程的状态
一个进程有以下状态:
- R:该进程正在运行中
- S:该进程目前正在睡眠状态,但可以被唤醒
- D:不可被唤醒的睡眠状态,通常这支程序可能在等待 I/O 的情况
- T:停止状态,可能是在工作控制或除错状态
- Z:僵尸状态,程序已经终止但却无法被移除至内存外。
信号
通过信号可以实现程序之间的相互控制。
代号 | 名称 | 内容 |
---|---|---|
1 | SIGHUP | 启动被终止的程序,可让该 PID 重新读取自己的配置文件 |
2 | SIGINT | 相当于用键盘输入 [ctrl]-c 来中断一个程序的进行 |
9 | SIGKILL | 代表强制中断一个程序的进行, |
15 | SIGTERM | 以正常的结束程序来终止该程序 |
19 | SIGSTOP | 相当于用键盘输入 [ctrl]-z 来暂停一个程序的进行 |
信号的传递通过 kill 或killall 。
kill [signal] <PID>
- 9 #强制杀死进程
- 15 #以正常方式结束进程,默认值
killall [options] [signal] <cmdName>
- i #交互式
- I #忽略大小写
优先级和系统调度
一个进程的优先执行序(PRI)的计算方式如下:
PRI(new) = PRI(old) + nice
PRI是系统动态决定的,但nice值可以影响它,所以只要调节nice值,就可以调整程序运行的顺序。值得注意的是root 可随意调整自己或他人进程的nice 值,一般使用者仅可调整自己程序的nice 值,并且只能越调越高。
新执行的指令即给予新的 nice 值
nice [options] <cmd>
- n <num> #后面接一个数值,数值的范围 -20 ~ 19。
已存在程序的 nice 重新调整
renice [num] <PID>
端口管理
firewall-cmd --zone=public --add-port=<port>/tcp --permanent #开放port端口
firewall-cmd --zone=public --query-port=<port>/tcp #查看port端口是否开启
firewall-cmd --zone=public --remove-port=<port>/tcp --permanent #关闭port端口
系统服务管理
常驻在内存当中的进程称为系统服务,也就是所谓的守护进程。通常在系统服务的名称之后会加上一个 d。
systemctl [command] <serviceName>
start #立即启动service
stop #立即关闭service
restart #重启service
reload #重新载入service的配置文件
enable #开机自动启动
disable #取消开机自动启动
软件包管理
rpm包管理
查询
rpm -qa|grep processname #查询是否安装了processname
rpm -ql processname #查看processname安装到了哪些位置
卸载
rpm -e processname
rpm -e --nodeps processname #强制删除
安装
rpm -ivh processname
yum包管理
yum可以从指定服务器下载rpm包并安装,可以自动处理依赖关系,并且一次安装所有依赖。
查询
yum list|grep processname
卸载
yum remove process
安装
yum install processname