linux简介
linux的特点
- **免费开源。**Linux是一款完全免费的操作系统,任何人都可以从网络上下载到它的源代码,并可以根据自己的需求进行定制化的开发,而且没有版权限制。
- **模块化程度高。**Linux的内核设计分成进程管理、内存管理、进程间通信、虚拟文件系统、网络5部分,其采用的模块机制使得用户可以根据实际需要,在内核中插入或移走模块,这使得内核可以被高度的剪裁定制,以方便在不同的场景下使用。
- **广泛的硬件支持。**得益于其免费开源的特点,有大批程序员不断地向Linux社区提供代码,使得Linux有着异常丰富的设备驱动资源,对主流硬件的支持极好,而且几乎能运行在所有流行的处理器上。
- **安全稳定。**Linux采取了很多安全技术措施,包括读写权限控制、带保护的子系统、审计跟踪、核心授权等,这为网络环境中的用户提供了安全保障。实际上有很多运行Linux的服务器可以持续运行长达数年而无须重启,依然可以性能良好地提供服务,其安全稳定性已经在各个领域得到了广泛的证实。
- **多用户,多任务。**多用户是指系统资源可以同时被不同的用户使用,每个用户对自己的资源有特定的权限,互不影响。多任务是现代化计算机的主要特点,指的是计算机能同时运行多个程序,且程序之间彼此独立,Linux内核负责调度每个进程,使之平等地访问处理器。由于CPU处理速度极快,从用户的角度来看所有的进程好像在并行运行。
- **良好的可移植性。**Linux中95%以上的代码都是用C语言编写的,由于C语言是一种机器无关的高级语言,是可移植的,因此Linux系统也是可移植的。
linux启动流程
- 首先,计算机会加载BIOS,这是计算机上最接近硬件的软件,各家主板制造商都会开发适合自己主板的BIOS,而BIOS中一项很重要的功能就是对自身的硬件做一次健康检查,只有硬件没有问题,才能运行软件,记住,操作系统也是一种软件
- 机器自检通过后,下面就要引导系统了。这个动作是BIOS设定的,BIOS默认会从硬盘上的第0柱面、第0磁道、第一个扇区中读取被称为MBR的东西,即主引导记录
- 第三步就是顺理成章地运行Grub了。Grub最重要的功能就是根据其配置文件加载kernel镜像,并运行内核加载后的第一个程序/sbin/init,这个程序会根据/etc/inittab来进行初始化的工作
- Linux将根据/etc/inittab中定义的系统初始化配置si::sysinit:/etc/rc.d/rc.sysinit执行/etc/rc.sysinit脚本,该脚本将会设置系统变量、网络配置,并启动swap、设定/proc、加载用户自定义模块、加载内核设置等。
- 根据第三步读到的runlevel值来启动对应的服务,如果值为3,就会运行/etc/rc3.d/下的所有脚本,如果值为5,就会运行/etc/rc5.d/下的所有脚本。
- 将运行/etc/rc.local
- 会生成终端或X Window来等待用户登录
linux帮助命令
使用man page
1 | man ls |
使用info page
可以在命令行中输入info ls来显示ls命令的说明文
1 | info ls |
linux用户管理
linux用户和用户组
uid和gid
Linux系统中的用户分为3类,即普通用户、根用户、系统用户。
- 普通用户: 通常普通用户的UID大于500,因为在添加普通用户时,系统默认用户ID从500开始编号。
- 根用户: 根用户也就是root用户,它的ID是0,也被称为超级用户,root账户拥有对系统的完全控制权:可以修改、删除任何文件,运行任何命令。所以root用户也是系统里面最具危险性的用户,root用户甚至可以在系统正常运行时删除所有文件系统,造成无法挽回的灾难。
- 系统用户:系统用户是指系统运行时必须有的用户,但并不是指真实的使用者。系统用户的ID范围是1~499
1 | #要确认自己的UID,可以使用以下id命令来获得: |
/etc/passwd 和 /etc/shadow
在登录Linux时必须要输入用户名和密码。而系统用来记录用户名、密码最重要的两个文件就是/etc/passwd 和 /etc/shadow(而且默认只有root用户才有读的权限,其他人完全没有读取这个文件的可能)
linux账号管理
新增和删除用户
- 新增用户: useradd
- 修改密码: passwd
- 修改用户: usermod
- 删除用户: userdel
新增和删除用户组
- 新增用户组: groupadd
- 删除用户组: groupdel
检查用户信息
- 查看用户: users、who、w
1 | #使用命令users可以查看当前系统有哪些用户。比如,在当前的系统中运行users命令,就会发现有两个root在当前机器上登录。 |
- 调查用户:finger
1 | #如果在finger后跟上某个用户名,则显示该用户更详细的信息, |
linux切换用户
切换其他用户
假如说我以普通用户john登录到系统中,这时候想使用useradd添加一个用户,怎么办?普通用户是没有添加用户的权限的,只有root用户才能创建用户。当然我们可以使用exit命令退出当前用户,然后使用root用户登录,再使用useradd添加用户。但是也有一种更方便的方式,那就是使用su命令,su是切换用户的意思。在不加参数的情况下,su命令默认表示切换到root用户,之后只要输入root密码就可以切换身份为root了,完成操作后,使用exit命令可以退出root切换到原先的用户。如下所示:
1 | #su命令默认表示切换到root用户,之后只要输入root密码就可以切换身份为root了, |
使用su命令切换用户之后,当前的用户环境并没有发生变化,而使用su-命令切换用户后,用户环境变成root的了
用其他用户的身份执行命令:sudo
sudo并不是真的切换了用户,而是使用其他用户的身份和权限执行了命令。
linux例行任务管理
在Linux中也有处理这两种任务的方法。如果任务是周期性执行的,其命令为cron;如果只是在某一个特定的时间执行一次,其命令为at。
单一时刻执行一次任务:at
默认情况下,所有用户都可以使用at命令来调度自己的任务,如果由于特殊的原因需要禁止某些用户使用这个功能,可以将该用户的用户名添加至/etc/at.deny
周期性执行任务:cron
有一些任务是需要周期性执行的,比如说每天早晨的闹钟会在设定的时间准时响起
- 启动crond进程
1 | service crond start |
- 编辑任务crontab -e
1 | * * * * * command |
- 示例
1 | * * * * * service httpd restart |
- 查看定时任务 crontab -l
如果由于特殊的原因需要禁止某些用户使用这个功能,可以将该用户的用户名添加至/etc/cron.deny中
/etc/crontab管理
我们知道用户可以通过crontab-e命令来编辑定义自己的任务,事实上,系统也有自己的例行任务,而其配置文件是/etc/crontab。
1 | [root@localhost ~]# cat /etc/crontab |
linux文件管理
文件和目录管理
文件的相关操作
Linux遵循一切皆文件的规则,对Linux进行配置时,在很大程度上就是处理文件的过程,所以掌握文件的相关操作是非常有必要的
- 创建文件:touch
- 删除文件:rm
- 移动或重命名文件: mv,后面需要跟两个参数,第一个参数是要被移动的文件,第二个参数是移动到的目录
1 | # 把test.log文件移动到/mnt中 |
- 查看文件:cat
- 查看文件头: head
1 | # 默认情况下,head将显示该文件前10行的内容;也可以使用-n参数指定显示的行数 |
- 查看文件尾: tail
1 | # 要动态地查看文件,使用-f参数就可以做到 |
- 文件格式转换:dos2unix, 当把Windows下的文本文件移动到Linux下时,会由于系统之间文本文件的换行符不同而造成文件在Linux下的读写操作有问题
目录的相关操作
- 创建目录:mkdir
1 | # 可以使用-p参数一次性创建所有目录,这样就不用费力地一个个创建了,命令如下所示 |
- 删除目录:rmdir和rm
1 | # rmdir 用来删除目录。但是需要注意的是,它只能删除空目录,如果目录不为空(存在文件或者子目录),那么该命令将拒绝删除指定的目录 |
- 文件和目录复制:cp
1 | # 复制文件且重命名 |
文件时间戳
通过touch可以创建新文件。如果文件已经存在,那么touch命令仅仅会更新文件的创建时间而不会修改文件内容。请记住,在Linux下目录也是一种文件,所以如果touch一个目录,这个目录的创建时间也会被更新
文件和目录权限
查看文件或者目录权限: ls -al
1 | # -l参数表示要求ls命令列出每个文件的详细信息, |
第一列含义:
改变文件权限:chmod
Linux下的每个文件都定义了文件拥有者(user)、拥有组(group)、其他人(others)的权限,我们使用字母u、g、o来分别代表拥有者、拥有组、其他人,而对应的具体权限则使用rwx的组合来定义,增加权限使用+号,删除权限使用-号,详细权限使用=号。图中用一些例子说明了如何使用chmod来改变文件的权限。
如果要给用户组或其他人添加或删除相关权限,只需要将上面的u相应地更换成g或o即可。但是正如大家看到的,这种方式同一时刻只能给文件拥有者、文件拥有组或是其他所有人设置权限,如果要想同时设置所有人的权限就需要使用数字表示法了,我们定义r=4,w=2,x=1,如果权限是rwx,则数字表示为7,如果权限是r-x,则数字表示为5。假设想设置一个文件的权限是:拥有者的权限是读、写、执行(rwx),拥有组的权限是读、执行(r-x),其他人的权限是只读(r–),那么可以使用命令chmod 754 somefile来设置
1 | # 修改某个目录权限 |
改变文件的拥有者:chown
1 | # 修改文件拥有者 |
改变文件的拥有者:chown
1 | chgrp -R chao somedir |
查看文件类型:file
使用ls-l令可以通过查看第一个字符判断文件类型。字母d代表目录、字母l代表连接文件,字母b代表块文件,字母c代表字符文件,字母s代表socket文件,字符-代表普通文件,字母p代表管道文件
查找文件
一般查找 find
1 | # 从根目录开始寻找名为httpd.conf的文件 |
数据库查找 locate
与find不同,locate命令依赖于一个数据库文件,Linux系统默认每天会检索一下系统中的所有文件,然后将检索到的文件记录到数据库中,所以使用locate命令要比find命令反馈更为迅速。
1 | # 更新数据库 --> 查找文件 |
查找执行文件 which/whereis
which用于从系统的PATH变量所定义的目录中查找可执行文件的绝对路径。比如说想查找node这个命令在系统中的绝对路径
1 | which node |
文件压缩和打包
gzip/gunzip
gzip/gunzip是用来压缩和解压缩单个文件的工具
1 | # 压缩install.log文件 |
tar
**tar不但可以打包文件,还可以将整个目录中的全部文件整合成一个包,**整合包的同时还能使用gzip的功能进行压缩,比如说把整个/boot目录整合并压缩成一个文件
1 | # 压缩命令: 压缩/boot目录成bott.tgz |
文件体积查看
du
1 | # 文件/文件夹名:查看单个文件或文件夹的总大小。 |
linux文件系统
文件系统
大家已经知道Linux使用了树形文件存储结构,在磁盘上存储文件的时候,使用的则是目录加文件的形式。但实际上对于磁盘等各种存储设备来说,无论是什么数据,都只有0和1的概念。而对用户来说,0和1同样毫无意义,那怎么办呢?这就需要一种类似于“翻译”的机制存在于用户和磁盘之间了,在Linux中采用的是文件系统+虚拟文件系统(Virtual File System,VFS)的解决方案。
磁盘分区、创建文件系统、挂载
创建文件系统:fdisk
磁盘挂载:mount
创建了文件系统的分区后,在Linux系统下还需要经过挂载才能使用,挂载设备的命令是mount,
磁盘检查:fsck、badblocks
当磁盘出现逻辑错误时,可以使用fsck来尝试修复。出现此类错误比较典型的情况是当机器突然掉电时可能引发。
硬链接和软连接
硬链接
硬链接(hard link)又称实际链接,是指通过索引节点来进行链接。在Linux文件系统中,所有的文件都会有一个编号,称为inode,多个文件名指向同一索引节点是被允许的,这种链接就是硬链接。硬链接的作用是允许一个文件拥有多个有效路径名,这样用户就可以建立硬链接指向同一文件,删除一个链接并不会影响索引节点本身和其他的链接,只有当最后一个链接被删除时,文件的数据块及目录的链接才会被释放。也就是说,文件真正删除的前提条件是与之相关的所有硬链接均被删除。硬链接有两个限制:
- 不允许给目录创建硬链接;
- 只有在同一文件系统中的文件之间才能创建链接,即不同分区上的两个文件之间不能够建立硬链接。
1 | # 创建目录 |
软连接
软链接(soft link)又称符号链接(symbolic link),是一个包含了另一个文件路径名的文件,可以指向任意文件或目录,也可以跨不同的文件系统。软链接和Windows下的“快捷方式”十分类似,删除软链接并不会删除其所指向的源文件,如果删除了源文件则软链接会出现“断链”。
1 | # 创建目录 |
另外还请注意,在创建软链接的前后分别使用ls -li命令,会发现软链接的inode和源文件的inode不一样,这说明软链接本身就是一个文件。
读者可以尝试删除软链接的源文件,然后可以在终端中看到对应的软链接将会以闪烁的方式标记其已是一个断链。
字符处理
管道
在Linux中也存在着管道,它是一个固定大小的缓冲区,该缓冲区的大小为1页,即4K字节。管道是一种使用非常频繁的通信机制,我们可以用管道符“|”来连接进程,由管道连接起来的进程可以自动运行,如同有一个数据流一样,所以管道表现为输入输出重定向的一种方法,它可以把一个命令的输出内容当作下一个命令的输入内容,两个命令之间只需要使用管道符连接即可。
1 | # 如果想要看一下/etc/init.d目录下文件的详细信息, |
使用grep搜索文本
1 | grep [-ivnc] '需要匹配字符' 文件名 |
案例:
1 | # 查找含有name的行 |
使用sort排序
1 | sort [-ntkr] 文件名 |
案例:
1 | # cat sort.txt |
使用uniq删除重复内容
如果文件(或标准输出)中有多行完全相同的内容,我们很自然希望能删除重复的行,同时还可以统计出完全相同的行出现的总次数,uniq命令就能帮助解决这个问题。
1 | uniq [-ic] |
案例:
1 | # cat uniq.txt | uniq |
使用cut截取文本
顾名思义,cut就是截取的意思,它能处理的对象是“一行”文本,可从中选取出用户所需要的部分。在有特定的分隔符时,可以指定分隔符,然后打印出以分隔符隔开的具体某一列或某几列
1 | cut -f指定行 -d'分隔符' |
案例
1 | # 比如说我们需要打印出系统中的所有用户 |
使用tr做文本转换
tr命令比较简单,其主要作用在于文本转换或删除。这里假设要把文件/etc/passwd中的小写字母转换为大写字母,然后再尝试删除文本中的冒号
1 | cat /etc/passwd | tr '[a-z]' '[A-Z]' |
使用paste做文本合并
paste的作用在于将文件按照行进行合并,中间使用tab隔开。假设有两个文件分别为a.txt、b.txt,下面使用paste命令来合并文件
1 | # cat a.txt |
使用split分割大文件
在Linux下使用split命令来实现文件的分割,支持按照行数分割和按照大小分割这两种模式。要说明的是,二进制文件因为没有“行”的概念,所以二进制文件无法使用行分割,而只能按照文件大小进行分割。
1 | # -l参数:指定每500行为一个文件 |
网络管理
网络接口
ifconfig检查和配置网卡
如果不使用任何参数,输入ifconfig命令时将会输出当前系统中所有处于活动状态的网络接口。
图中的eth0表示的是以太网的第一块网卡。其中eth是Ethernet的前三个字母,代表以太网,0代表是第一块网卡,第二块以太网网卡则是eth1,以此类推。Linkencap是指封装方式为以太网;HWaddr是指网卡的硬件地址(MAC地址);inetaddr是指该网卡当前的IP地址;Broadcast是广播地址(这部分是由系统根据IP和掩码算出来的,一般不需要手工设置);Mask是指掩码;UP说明了该网卡目前处于活动状态;MTU代表最大存储单元,即此网卡一次所能传输的最大分包;RX和TX分别代表接收和发送的包;collision代表发生的冲突数,如果发现值不为0则很可能网络存在故障;txqueuelen代表传输缓冲区长度大小;第二个设备是lo,表示主机的环回地址,这个地址是用于本地通信的。
手动设置etho的ip地址
1 | ifconfig etho 192.168.159.130 netmask 255.255.255.0 |
将ip配置信息写入配置文件
上一小节讲到的ifconfig命令可以直接配置网卡IP,但是这属于一种动态的配置,所配置的信息只是保存在当前运行的内核中。一旦系统重启,这些信息将丢失。为了能在重启后依然生效,可以在相关的配置文件中保存这些信息,这样,系统重启后将从这些配置文件中读取出来。RedHat和CentOS系统的网络配置文件所处的目录为/etc/sysconfig/network-scripts/,eth0的配置文件为ifcfg-eth0,如果有第二块物理网卡,则配置文件为ifcfg-eth1,以此类推
1 | cat ifcfg-eth0 |
路由和网关
Linux主机之间是使用IP进行通信的,假设A主机和B主机同在一个网段内且网卡都处于激活状态,则A具备和B直接通信的能力(通过交换机或简易HUB)。但是如果A主机和B主机处于两个不同的网段,则A必须通过路由器才能和B通信。一般来说,路由器属于IT设备的基础设施,每一个网段都应该有至少一个网关。在Linux中可使用route命令添加默认网关。假设添加的网关是192.168.159.2,添加方式如下
1 | # 添加 |
如果只使用route命令添加网关,一旦系统重启,配置信息就不存在了,必须将这种配置信息写到相关的配置文件中才能永久保存。可以在网卡配置文件中使用GATEWAY变量来定义网关,只需要添加如下部分到ifcfg-eth0中即可,当然别忘了重启网络服务使配置生效
1 | GATEWAY=192.168.159.2 |
另外,在配置文件/etc/sysconfig/network中添加这段配置也能达到同样的效果。
DNS客户端配置
/etc/hosts
们使用hosts文件来记录主机名和IP的对应关系,这样访问对方的主机时,就不需要使用IP了,只需要使用主机名。这个文件在Linux下就是/etc/hosts,这种方式确实“可以工作”,但是当主机数量增长到一定数量级的时候仍然无法适用。为了彻底解决这个问题,人们发明了DNS系统。经过几十年的发展,虽然系统、网络技术都发生了翻天覆地的变化,但是这个文件还是被当作传统保留了下来。hosts文件的作用主要如下:
- 加快域名解析。当访问网站时,系统会首先查看hosts文件中是否有记录,如果记录存在则直接解析出对应的IP,这时则不需要请求DNS服务器
- 方便小型局域网用户使用的内部设备。很多单位的局域网中都存在着不少内部应用系统(比如办公自动化OA、公司论坛等),平时在工作中也都需要访问,但是由于这些局域网太小而不必为此专门设置DNS服务器,那么此时使用hosts文件则能简单地解决这个问题。
/etc/resolv.conf
从技术上来说,DNS就是全互联网上主机名及其IP地址对应关系的数据库。设置主机为DNS客户端的配置文件就是/etc/resolv.conf,其中包含nameserver、search、domain这3个关键字
1 | # search关键字后紧跟的是一个域名。 |
网络测试工具
ping
ping程序的目的在于测试另一台主机是否可达,一般来说,如果ping不到某台主机,就说明对方主机已经出现了问题,但是不排除由于链路中防火墙的因素、ping包被丢弃等原因而造成ping不通的情况
1 | ping 10.0.1.1.145 |
host
host命令是用来查询DNS记录的,如果使用域名作为host的参数,命令返回该域名的IP
1 | # host baidu.com |
网络故障排查
- 硬件故障又主要分为网卡物理损坏、链路故障等原因。
- 软件主要表现为网卡驱动故障,也就是操作系统对网卡驱动的不兼容,这个问题往往需要通过安装对应的网卡设备驱动来解决
1 | # 第一步:确认网卡本身是否能正常工作? |
进程管理
什么是进程
进程表示程序的一次执行过程,它是应用程序的运行实例,是一个动态的过程。或者可以更简单地描述为:进程是操作系统当前运行的程序。当一个进程开始运行时,就是启动了这个过程。
所有的进程都可能存在3种状态:运行态、就绪态、阻塞态。运行态表示程序当前实际占用着CPU等资源;就绪态是指程序除CPU之外的一切运行资源都已经就绪,等待操作系统分配CPU资源,只要分配了CPU资源,即可立即运行;而阻塞态是指程序在运行的过程中由于需要请求外部资源(例如I/O资源、打印机等低速的或同一时刻只能独享的资源)而当前无法继续执行,从而主动放弃当前CPU资源转而等待所请求资源。
而进程同步指的是进程间通过某种通信机制实现信息交互。现代计算机使用信号量机制来实现进程间的互斥和同步,它的基本原理是:两个或者多个进程可以通过简单的信号进行合作,一个进程可以被迫在某一位置停止,直到它接收到一个特定的信号。
进程、线程、程序区别
进程 VS 程序
进程是动态的,而程序是静态的,进程是程序以及数据在计算机上的一次执行,没有静态的程序也就没有动态的执行。程序是可以以某种形式保存在存储介质上的,而进程只能在运行时存在于计算机的内存中。
如果说做一件事情需要经过若干既定的步骤,这些步骤可以被写成清单静态地列在纸上,那么它们就是广义上的“程序”,而只有真正开始将计划的步骤付诸实施的过程才是“进程”
进程 VS 线程
进程是资源分配的最小单位,线程是CPU调度的最小单位。做个简单的比喻:进程=火车,线程=车厢。
- 线程在进程下行进(单纯的车厢无法运行)
- 一个进程可以包含多个线程(一辆火车可以有多个车厢)
- 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
- 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
进程观察: ps、top
- 命令ps输出的只是当前查询状态下进程瞬间的状态信息,如果要想及时动态地查看进程就需要使用top命令了。
- top命令提供了实时的系统状态监控,可以按照CPU使用、内存使用、执行时间等指标对进程进行排序
1 | ps [-Aawu] |
进程的终止: kill、killall
kill这些命令的原理都是向内核发送一个系统操作信号以及某个进程的标识号,使得内核对指定标识号的进程进行相应的操作。 典型用法是使用ps查出进程的PID,然后使用kill将其终止
1 | # 第一步 |
命令kill后可以跟的信号代码一共有64种,使用kill-l就可以看到具体有哪些,如图所示。但是常用的一般只有3个,即HUP(1)、KILL(9)、TERM(15),分别代表重启、强行杀掉、正常结束。
使用kill-1重启进程的时候实际上是不会改变主进程的PID的,也就是说只是发生了原地重启,或者说“软重启”
1 | # 软重启 |
查询进程打开文件:lsof
Linux中一切皆文件,所以在系统中,被打开的文件可以是普通文件、目录、网络文件系统中的文件、字符设备、管道、socket等。那么如何知晓现在系统打开的是哪些文件呢,这时lsof命令就有用武之地了。
查看进程
使用lsof还可以查找使用了某个端口的进程,比如说如果系统中运行了sshd进程(基本上都是默认运行的),则该进程默认会绑定22端口,让我们来确认一下
1 | lsof -i:22 |
查看什么进程使用该文件
1 | lsof /var/log/messages |
恢复被删除的文件, 此文件必须正在被某个进程使用
此文件必须正在被某个进程使用,也就是依然拥有打开文件的句柄能力。
现假设文件/var/log/messages不小心被删除了,首先来确认一下当前是否有进程正在使用这个文件,如果有则可以继续,如果没有就无法使用该方法继续了。本例中看到有个PID为2449的进程正在使用该文件,那么接下来只要找到对应/proc目录下的文件就可以了
1 | # lsof | grep message |
进程优先级调整:nice、renice
vi和vim编辑器
vim编辑器是vi的加强版,在简单的文本操作上与vi几乎完全一致,所以习惯使用vi的人可以完全无缝地切换使用vim编辑器。同时vim还增加了很多新功能,包括代码补全、错误跳转等,可方便编程
vim编辑器使用
- 搜索关键词
1 | /keyword # 搜索keyword关键词 |
- 替换关键词
1 | :s/keyword/kw/g #将本行的keyword替换成kw |
正则表达式
正则基础
- “ .”(一个点)符号
点符号用于匹配除换行符之外的任意一个字符。例如:r.t可以匹配rot、rut
1 | grep 'r..t' /etc/passwd |
- “ *”符号
““符号用于匹配前一个字符 0次 或 任意多次,比如ab,可以匹配a、ab、abb等。“*”号经常和“.”符号加在一起使用
1 | # 查找包含字母r,后面紧跟任意长度的字符,再跟一个字母t的行 |
- “ \{n,m\}”符号
1 | # \{n\} 匹配前面的字符n次 |
- “ ^”符号
1 | # 这个符号用于匹配开头的字符。比如说“^root”匹配的是以字母root开始的行 |
- “ $”符号
1 | # “$”用于匹配尾部,比如说“abc$”代表的是以abc结尾的行。 |
- “ []”符号
这是一对方括号,用于匹配方括号内出现的任一字符
1 | # 匹配abc任意字符 |
- “ \”转义符号
1 | "\\" # 对\符号进行转义 |
- “<”符号和“>”符号
界定单词的左边界和右边界
1 | echo "hello" | grep '\<hello\>' #匹配成功 |
- “ \d”符号
1 | echo 123 | grep [0-9] # 123 |
- “ \b”符号
匹配单词的边界,比如“\bhello\b”可精确匹配“hello”单词
1 | echo 'hello world' | grep '\bhello\b' # hello world |
“ \B”符号
匹配非单词的边界,比如hello\B可以匹配“helloworld”中的“hello”。“ \w”符号
匹配字母、数字和下划线,等价于[A-Za-z0-9]。“ \W”符号
匹配非字母、非数字、非下划线,等价于[^A-Za-z0-9]。“ \s”符号
匹配任何空白字符“ \S”符号
匹配任何非空白字符“ \n”符号
匹配一个换行符(就是另起一新行,光标在新行的开头)。
我们平时编写文件的回车符(即:回车键 )应该确切来说叫做回车式的换行符。“ \r”符号
匹配一个回车符(就是光标回到一旧行的开头,即光标目前所在的行为旧行)。“ \t”符号
匹配一个制表符。扩展正则表达式(需要使用egrep命令)
1 | # “?”符号用于匹配前一个字符0次或1次,所以“ro?t”仅能匹配rot或rt。 |
- 通配符
1 | # * 符号: 这里的“*”就是提到的第一个通配符,代表0个或多个字符。那么之前的*.doc就是指所有以.doc结尾的文件 |
正则表达式示例
grep
grep的英文是Global search Regular Expression and print out the line,即全面搜索正则表达式并打印出匹配行
1 | # 使用“^”匹配行首 |
文本处理工具sed
sed介绍
sed(stream editor)是一种非交互式的流编辑器,通过多种转换修改流经它的文本。但是请注意,默认情况下,sed并不会改变原文件本身,而只是对流经sed命令的文本进行修改,并将修改后的结果打印到标准输出中(也就是屏幕)。所以本节讲的所有的sed操作都只是对“流”的操作,并不会改变原文件。sed处理文本时是以行为单位的,每处理完一行就立即打印出来,然后再处理下一行,直至全文处理结束。sed可做的编辑动作包括删除、查找替换、添加、插入、从其他文件中读入数据等。
使用场景:常规编辑器编辑困难的文本;sed默认不更改源文件,如果想直接修改源文件本身则需要使用“-i”参数
删除
1 | # 删除第一行然后输出到屏幕 |
查找替换
使用s命令可将查找到的匹配文本内容替换为新的文本
1 | # 默认情况下每行只替换第一个line |
字符转换
使用y命令可进行字符转换,其作用为将一系列字符逐个地变换为另外一系列字符
1 | # 将file文件的O替换N, L替换E, D替换成W |
插入文本
使用i或a命令插入文本,其中i代表在匹配行之前插入,而a代表在匹配行之后插入
1 | # 使用i在第二行前插入文本 |
读入文本
使用r命令可从其他文件中读取文本,并插入匹配行之后
1 | # 将/etc/passwd中的内容读出来放到sed.txt空行之后 |
打印
使用p命令可进行打印,这里使用sed命令时一定要加-n参数,表示不打印没关系的行
1 | # 使用p命令, 则只打印实际处理过的行,简化了输出(-n) |
sed脚本
工作往往有一定“标准化”的操作,比如说先去除文件中所有的空行,然后再全部替换某些字符等,这种过程类似于生产线上程式化的流水作业;可以把这些动作静态化地写到某个文件中,然后调用sed命令并使用-f参数指定该文件,这样就可以将这一系列动作“装载”并应用于指定文件中,这无疑加快了工作效率,这种文件就是sed脚本。
1 | # this替换THAT, 然后删除空行 |
文本处理工具awk
介绍
sed其实是以行为单位的文本处理工具,而awk则是基于列的文本处理工具,它的工作方式是按行读取文本并视为一条记录,每条记录以字段分割成若干字段,然后输出各字段的值。
打印指定域
awk使用$1、$2代表不同的域,则可以打印指定域
1 | # 只打印部分 |
指定打印分隔符
默认情况下awk是使用空白字符作为分隔符的,但是也可以通过-F参数指定分隔符,来区分不同的域(有点像之前学过的cut命令)
1 | # 用.作为分隔符,这样每一行$1就是.之前的字符, $2就是.之后的字符 |
截取字符串
可以使用substr()函数对指定域截取字符串。substr(指定域,第一个开始字符的位置,第二个结束的位置)
#其中第二个结束的位置可以为空,这样默认输出到该域的最后一个字符。
1 | cat awk.txt | awk '{print substr($1, 6)}' |
确定字符串的长度
1 | cat awk.txt | awk '{print length}' |
shell编程概述和编程基础
shell介绍
shell是指一种命令行解释器,是为用户和操作系统之间通信提供的一种接口
脚本语言又被称作解释型语言,这种语言经过编写后不需要做任何编译就可以运行。
计算机不能理解高级语言,只能理解机器语言,所以必须把高级语言翻译为机器码。而这种翻译的方式有两类,一类是编译,一类是解释,不同之处在于翻译的时间不同。编译型语言是运行前翻译,一般是使用编译工具将程序源码处理成机器认识的可执行文件(比如说Windows下的exe文件,Linux下的二进制可执行性文件),这种文件一旦产生,以后运行时将不需要再次翻译,所以一般来说,编译型语言的效率较高;而解释型语言是运行时翻译,执行一条语句就立即翻译一条,而且每次执行程序都需要进行解释,相对来说效率较低。但是也不能简单地认为编译型语言就一定比解释型效率高,随着解释器的发展,部分解释器能在运行程序时动态优化代码,因此这种效率差距也在一定程度上不断减小。
shell脚本
编写第一个shell脚本
一个Shell脚本永远是以“#!”开头的,这是一个脚本开始的标记,它是在告诉系统执行这个文件需要使用某个解释器,后面的/bin/bash就是指明了解释器的具体位置
1 |
|
运行脚本
- 第一种就是在该脚本所在的目录中直接bash这个脚本。实际上,如果使用这种方式来运行脚本,该脚本中的第一行“#!/bin/bash”就可以不需要了,因为直接bash一个文件就是指定了使用BashShell来解释脚本内容。
1 | bash test.sh |
- 第二种方式是给该脚本加上可执行权限,然后使用“./”来运行,它代表运行的是当前目录下的HelloWorld.sh脚本,如果采用这种方式而脚本没有可执行权限则会报错。
1 | chmod +x test.sh |
- 如果希望该脚本能成为默认的系统命令,简单地将该脚本复制到任一系统$PATH变量所包含的目录中,同时赋予可执行权限,下次运行的时候只需要直接输入该命令即可
1 | chmod +x text.sh |
shell脚本排错
为了更清晰地看到脚本运行的过程,还可以借助-x参数来观察脚本的运行情况。
1 | bash -x test.sh |
shell内置命令
所谓Shell内建命令,就是由Bash自身提供的命令,而不是文件系统中的某个可执行文件。
通常来说,内建命令会比外部命令执行得更快,执行外部命令时不但会触发磁盘I/O,还需要fork出一个单独的进程来执行,执行完成后再退出。而执行内建命令相当于调用当前Shell进程的一个函数。
如何确定内建命令:type
1 | type cd |
执行程序:“.”(点号)
点号用于执行某个脚本,甚至脚本没有可执行权限也可以运行
1 | ./test.sh |
别名:alias
alias可用于创建命令的别名,若直接输入该命令且不带任何参数,则列出当前用户使用了别名的命令。现在你应该能理解类似ll这样的命令为什么与ls-l的效果是一样的吧
1 | # 这样定义alias只能在当前Shell环境中有效, |
任务前后台切换:bg、fg、jobs
该命令用于将任务放置后台运行,一般会与Ctrl+z、fg、&符号联合使用。典型的使用场景是运行比较耗时的任务。比如打包某个占用较大空间的目录,若在前台执行,在任务完成前将会一直占用当前的终端,而导致无法执行其他任务,此时就应该将这类任务放置后台
1 | tar -zcf user.tgz /user |
声明变量:declare、typeset
1 | # i_num的值为1 |
打印字符:echo
echo用于打印字符,典型用法是使用echo命令并跟上使用双引号括起的内容
1 | echo "hello world" |
跳出循环:break
从一个循环(for、while、until或者select)中退出。break后可以跟一个数字n,代表跳出n层循环,n必须大于1,如果n比当前循环层数还要大,则跳出所有循环。
循环控制:continue
停止当前循环,并执行外层循环(for、while、until或者select)的下一次循环。continue后可以跟上一个数字n,代表跳至外部第n层循环。n必须大于1,如果n比当前循环层数还要大,将跳至最外层的循环
退出Shell:exit
在Shell脚本中使用exit代表退出当前脚本。该命令可以接受的参数是一个状态值n,代表退出的状态,下面的脚本什么都不会做,一旦运行就以状态值为5退出。如果不指定,默认状态值是0。
发送信号给指定PID或进程:kill
Linux操作系统包括3种不同类型的进程,第一种是交互进程,这是由一个Shell启动的进程,既可以在前台运行,也可以在后台运行;第二种是批处理进程,与终端没有联系,是一个进程序列;第三种是监控进程,也称系统守护进程,它们往往在系统启动时启动,并保持在后台运行
kill命令用来终止进程,其工作的原理是向系统的内核发送一个系统操作信号和某个程序的进程标识号,然后系统内核就可以对进程标识号指定的进程进行操作。
声明局部变量:local
该命令用于在脚本中声明局部变量,典型的用法是用于函数体内,其作用域也在声明该变量的函数体中
从标准输入读取一行到变量:read
有时候我们开发的脚本必须具有交互性,也就是在运行过程中依赖人工输入才能继续
1 | #根据输入的箱数计算一共有多少瓶啤酒 |
如果不指定变量,read命令会将读取到的值放入环境变量REPLY中。另外要记住,read是按行读取的,用回车符区分一行,你可以输入任意文字,它们都会保存在变量REPLY中。
1 | read |
定义函数返回值:return
1 | # cat return.sh |
变量
局部变量
所谓局部变量就是指在某个Shell中生效的变量,对其他Shell来说无效,而且会随着当前Shell的消失而消失,局部变量的作用域被限定在声明它们的Shell中,可以使用local内建命令来“显式”的声明局部变量,但仅限于函数内使用。换言之,每个Shell都有自己的变量空间,彼此互不影响。而环境变量不仅仅是对于该Shell生效,对其子Shell也同样生效。
环境变量
环境变量通常又称“全局变量”,以区别于局部变量。在Shell脚本中,变量默认就是全局的,无论在脚本的任何位置声明,但是为了让子Shell继承当前Shell的变量,则可以使用export内建命令将其导出为环境变量。
1 | # VAR是变量的名字,value为值,使用等号相连,注意等号两端没有空格 |
bash中默认包含有几十个预设的环境变量,这里挑选常见的一些予以介绍
1 | # 1. bash shell的全路径 |
变量命名
Shell中的变量必须以字母或者下划线开头,后面可以跟数字、字母和下划线,变量长度没有限制
变量赋值和取值
赋值
1 | #定义变量:变量名=变量值 |
取值
1 | echo $name |
取消变量
1 | name=john |
特殊变量
1. 位置参数
Shell中还有一些预先定义的特殊只读变量,它们的值只有在脚本运行时才能确定。首先是“位置参数”,位置参数的命名简单直接, 比如:脚本本身为$0,第一个参数为$1,第二个参数为$2,第三个为$3,以此类推。当位置参数的个数大于9时,需要用${}括起来标识,比如说第10个位置参数应该记为${10}。另外,$#表示脚本参数的个数总和,$@或$*表示脚本的所有参数
2. 脚本或命令返回值:$?
Linux中规定正常退出的命令和脚本应该以0作为其返回值,任何非0的返回值都表示命令未正确退出或未正常执行。
在自动化脚本中,也可以通过$?变量的值判断之前命令的执行状态,从而采取不同的动作。
1 | # 尝试ping主机ping不通时的返回值 |
数组
Shell中的数组对元素个数没有限制,但只支持一维数组,这一点和很多语言不同。
1. 数组定义
1 | #方法一 |
2. 数组操作
1 | # 1.取某个值 |
变量的作用域
变量的作用域又叫“命名空间”,表示变量(identifier,标识符)的上下文。相同的变量可以在多个命名空间中定义,并且彼此之间互不干涉,所以在一个新的命名空间中可以自定义任何变量,因为所定义的变量都只在各自的命名空间中。
1 | # 在函数体内使用local关键字声明了和全局变量同名的局部变量后, |
转义和引用
Shell中有两类字符,一类是普通字符,在Shell中除了本身的字面意思外没有其他特殊意义,即普通纯文本(literal);另一类即元字符(meta),是Shell的保留字符,在Shell中有着特殊意义。这在很多时候会造成麻烦:比如说想要在程序中用美元符打印商品的价格,但是这个符号一般被解析成提取变量的值。为了消除这些特殊字符的功能,就必须对其进行转义和引用。
转义
要使用“\”来转义“$”字符,让“$”失去其特殊含义,而只作为一个符号出现。
1 | # echo $Dollar 报错 |
引用
引用是指将字符串用某种符号括起来,以防止特殊字符被解析为其他意思。比如说上一小节中的转义符就是一种引用。Shell中一共有4种引用符,分别是双引号、单引号、反引号和转义符。其中双引号又叫“部分引用”或“弱引用”,可以引用除$符、反引号、转义符之外的所有字符;单引号又叫“全引用”或“强引用”,可以引用所有字符;反引号则会将反引号括起的内容解释为系统命令。
部分引用
部分引用是指用双引号括起来的引用。在这种引用方式中,$符、反引号(`)、转义符(\)这3种特殊字符依然会被解析为特殊意义
1 | VAR03=100 |
全引用
全引用是指用单引号括起来的引用。单引号中的任何字符都只当作是普通字符(除了单引号本身,也就是说单引号中间无法再包含单引号,即便用转义符转义单引号也不行)。所有在单引号中的字符都只能代表其作为字符的字面意义。
1 | echo '$VAR03' |
单引号和双引号在很多时候是一样的,只是要记住,在双引号中的$符、反引号、转义符还是会被解析成其特殊含义,而在单引号中所有的字符都只是字面意思
命令替换
命令替换是指将命令的标准输出作为值赋给某个变量。比如,在某个目录中输入ls命令可查看当前目录中所有的文件,但如何将输出存入某个变量中呢?这就需要使用命令替换了,这也是Shell编程中使用非常频繁的功能。
1 | `命令` # DATE_01=`data` |
运算符
算术运算符
Shell只支持整数计算,也就是所有可能产生小数的运算都会舍去小数部分
自增自减
前置自增或前置自减操作会首先修改变量的值,然后再将变量的值传递出去;后置自增或后置自减则会首先将变量的值传递出去,然后再修改变量的值
其他运算符
使用$[]做运算
1 | # $[]和$(())类似,可用于简单的算术运算 |
内建运算命令declare
1 | i=1+1 |
算术扩展
$((算术表达式))
1 | i=2 |
特殊字符
通配符
通配符用于模式匹配,常见的通配符有*、?和用[]括起来的字符序列。其中代表任意长度的字符串。例如:a可以匹配以a开头的任意长度的字符串,但是不包括点号和斜线号。也就是说a*不能匹配abc.txt。问号(?)可用于匹配任一单个字符。方括号[]代表匹配其中的任意一个字符,比如[abc]代表匹配a或者b或者c,[]中可以用-表明起止,比如[a-c]等同于[abc],但是要注意-字符在[]外只是一个普通字符,没有任何特殊作用;*和?在[]中则变成了普通字符,没有通配的功效
引号
引号包括单引号和双引号,单引号又叫称“全引用”或“强引用”;双引号又称“部分引用”或“弱引用”,所有用双引号括起来的字符除了美元符($)、反斜线(\)、反引号(`)依然保留其特殊用途外,其余字符都作为普通字符处理;而所有用单引号括起的部分都作为普通字符处理,但是要注意单引号中间不能再出现单引号,否则会Shell无法判断到底哪里是单引号的起止位置。
注释符
如果出现#后连着!,也就是“#!”不会被理解成注释,因此,其后跟着的部分必须是某个解释器的路径,而且“#!”必须出现在整个脚本的第一行
大括号
大括号{}在Shell中的用法很多,最常见的用法就是引用变量原型,又叫变量扩展。例如变量VAR,可以使用${VAR}引用,这是推荐的引用变量的方法。
杂项
反引号
反引号用于命令替换,和$()的作用相同,表示返回当前命令的执行结果并赋值给变量。
位置参数
1 | $0:脚本名本身。 |
测试和判断
测试
1 | # ls一个存在的文件 |
判断为真则返回0,为假则返回非0值。这种判断行为被称作“测试”
测试结构
测试方式是使用“[”启动一个测试,再写expression,再以“]”结束测试。需要注意的是,左边的括号“[”后有个空格,右括号“]”前面也有个空格,如果任意一边少了空格都会造成Shell报错
1 | [ expression ] |
文件测试
其中file_operator是文件测试符(具体参考下表),FILE是文件、目录(可以是文件或目录的全路径)
1 | # 文件测试写法一 |
1 |
|
字符串测试
Shell中的字符串比较主要有等于、不等于、大于、小于、是否为空等测试
1 | #定义空字符串str1 |
整数比较
整数测试是一种简单的算术运算,作用在于比较两个整数的大小关系,测试成立则返回0,否则返回非0值
1 | [root@localhost ~]# num1=10 |
逻辑测试符和逻辑运算符
逻辑测试用于连接多个测试条件,并返回整个表达式的值。逻辑测试主要有逻辑非、逻辑与、逻辑或3种
1 | #例一:逻辑非的使用 |
如果读者曾经学过其他的编程语言,一定知道“逻辑运算符”也有逻辑非、逻辑与、逻辑或3种判断符号(! && || )
1 | [root@localhost ~]# ! [ -e /var/log/messages ] |
判断
在Shell中,流程控制分为两大类,一类是“循环”,一类是“判断选择”。属于“循环”的有for、while、until,这将会在下一章中介绍,本节介绍“判断选择”,关键字是if、case。
if判断结构
1 | #如果expression测试返回真,则执行command |
例子:
1 |
|
if/else判断结构
1 | if expression; then |
if/elif/else判断结构
1 | if expression; then |
case判断结构
1 | # case判断结构中的var1、var2、var3等这些值只能是常量或正则表达式 |
例子:
1 |
|
循环
for循环
带列表的for循环
1 | fruits="apple orange banana pear" |
不带列表的for循环
1 | for VARIBLE in $@ |
类C的for循环
1 | # 类C的for循环语法结果 |
例子
1 | for((i=0; i<=10; i++)) |
for的无限循环
1 | for((;1;)) |
while循环
while循环的语法
1 | CONTER=5 |
使用while按行读取文件
1 | # cat while04.sh |
while的无限循环
1 | # 我们可以利用while的无限循环实时的监测系统进程, |
until循环
待续…
select循环
select是一种菜单扩展循环方式,其语法和带列表的for循环非常类似,基本结构如下
1 | select MENU in (list) |
例子:
1 | # cat select01.sh |
嵌套循环
1 | # for循环嵌套 |
循环控制
break语句
break用于终止当前整个循环体。一般情况下,break都是和if判断语句一起使用的,当if条件满足时使用break终止循环。
continue语句
continue并不会终止当前的整个循环体,它只是提前结束本次循环,而循环体还将继续执行;而break则会结束整个循环体。
函数
函数基本使用
1 | # cat checkFileExist.sh |
带参数的函数($1, $2, $N…)
1 | # cat power.sh |
函数库
自定义函数库
1 | # cat lib01.sh |
函数库/etc/init.d/functions
很多Linux发行版中都有/etc/init.d目录,这是系统中放置所有开机启动脚本的目录,这些开机脚本在脚本开始运行时都会加载/etc/init.d/functions或/etc/rc.d/init.d/functions函数库(实际上这两个函数库的内容是完全一样的)
1 | # cat callFunctions01.sh |
重定向
I/O重定向是重定向中的一个重要部分,在Shell编程中会有很多机会用到这个功能。简单来说,I/O重定向可以将任何文件、命令、脚本、程序或脚本的输出重定向到另外一个文件、命令、程序或脚本。
io重定向符号和用法
1.标准输出覆盖重定向:>
使用标准输出覆盖重定向符号可以将原本输出到显示器上的内容重定向到一个文件中
1 | # 比如使用ls-l可以列出指定目录中文件的详细信息, |
如果指定的重定向文件不存在,则命令会先创建这个文件,如果文件存在且内容不为空,则原文件内容将被全部清空。所以有时候需要先判断该文件是否存在,以避免不小心破坏了原有文件内容
1 | # 如果某命令的输出既有标准输出,又有标准错误输出, |
2.标准输出追加重定向:>>
该符号用法和>完全一致,不同的只是如果指定的重定向文件存在且内容不为空,重定向并不会清空原文件内容,而是将命令的输出新增到原文件的尾部
1 | ls -l /usr/>>append.txt |
3.标识输出重定向:>&
标识输出重定向的作用是将一个标识的输出重定向到另一个标识的输入。比如想要将标准输出和标准错误同时定向到同一个文件中。
1 | # 执行COMMAND命令,将标准输出的内容重定向到stdout_stderr.txt中, |
很多时候大家并不会在意错误输出,特别是一些系统后台任务可能在每天凌晨运行,这时出现的错误输出可能并不是系统管理员所关心的,所以也没有必要将错误输出保存到任何文件中。这时可以利用系统中的一个特殊设备/dev/null,将所有错误输出重定向到该设备中—系统会将任何输入到该设备的内容全部删除(就像一个宇宙黑洞)
1 | COMMAND > stdout.txt 2> /dev/null |
4.标准输入重定向:<
标准输入重定向可以将原本应由从标准输入设备中读取的内容转由文件内容输入,也就是将文件内容写入标准输入中
1 | #例二:给sort的标准输入重定向 |
5.管道:|
简单地说管道就是将一个命令的输出作为另一个命令的输入,借此方式可通过多个简单命令的共同协作来完成较为复杂的工作
使用exec
exec是Shell的内建命令,执行这个命令时系统不会启动新的Shell,而是用要被执行的命令替换当前的Shell进程。因此假设在一个Shell中执行exec ls,则在列出当前目录后该Shell进程将会主动退出。如果使用ssh进行远程连接,则当前连接也会在执行完这个命令后断开
Here Document
Here Document又称此处文档,用于在命令或脚本中按行输入文本。HereDocument的格式为<<delimiter,其中delimiter是一个用于标注的“分隔符”,该分隔符后所有的输入都被当作是输入的文本,直到出现下一个分隔符为止
1 | sort << END |