跟老韩学Linux自动化运维(基础篇)

978-7-115-56232-6
作者: 韩艳威
译者:
编辑: 张涛
分类: 其他

图书目录:

详情

本书全面、系统地介绍 Shell 的各个知识点及其在企业环境中的具体应用。本书主要内容包括 Shell脚本编程、Shell变量与字符串、Shell正则表达式与文本处理、Shell条件测试和循环语句、Shell数组与函数、Linux自动化运维等。 本书适合 Linux 系统管理员阅读,也适合软件开发人员、软件测试人员及数据库管理人员学习,也可以作为大专院校计算机相关专业师生的学习用书以及培训机构的教材。

图书摘要

版权信息

书名:跟老韩学Linux自动化运维(基础篇)

ISBN:978-7-115-56232-6

本书由人民邮电出版社发行数字版。版权所有,侵权必究。

您购买的人民邮电出版社电子书仅供您个人使用,未经授权,不得以任何方式复制和传播本书内容。

我们愿意相信读者具有这样的良知和觉悟,与我们共同保护知识产权。

如果购买者有侵权行为,我们可能对该用户实施包括但不限于关闭该帐号等维权措施,并可能追究法律责任。

编  著 韩艳威

责任编辑 张 涛

人民邮电出版社出版发行  北京市丰台区成寿寺路11号

邮编 100164  电子邮件 315@ptpress.com.cn

网址 http://www.ptpress.com.cn

读者服务热线:(010)81055410

反盗版热线:(010)81055315

读者服务:

微信扫码关注【异步社区】微信公众号,回复“e56232”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


本书全面、系统地介绍 Shell 的各个知识点及其在企业环境中的具体应用。本书主要内容包括 Shell脚本编程、Shell变量与字符串、Shell正则表达式与文本处理、Shell条件测试和循环语句、Shell数组与函数、Linux自动化运维等。

本书适合 Linux 系统管理员阅读,也适合软件开发人员、软件测试人员及数据库管理人员学习,也可以作为大专院校计算机相关专业师生的学习用书以及培训机构的教材。


从早期的单机部署技术,到后来的KVM虚拟化技术、云服务器技术,以及目前比较火热的Docker容器技术和企业级主流的微服务技术,随着运维技术的快速发展,企业对运维人员的技术能力和专业素养的要求越来越高。

运维智能化的基础是运维自动化,而运维自动化的前提是运维服务标准化。如何做到运维服务标准化?最简单的方法就是采用一套适合企业自身的运维管理机制和可执行的有效方法,进而让运维人员从烦琐和重复的体力劳动中解放出来,把更多的精力投入在系统性能优化、架构统筹、安全防控、成本控制、运维开发等工作上。

本书着重讲解Shell编程及相关工具在Linux自动化运维管理中的实际应用,可作为一线Linux运维人员的实战参考书。

您还可以扫码右侧二维码, 关注【异步社区】微信公众号,回复“e56232”直接获取,同时可以获得异步社区15天VIP会员卡,近千本电子书免费畅读。

本书共7章,主要介绍Shell在系统管理中的具体应用。

第1章介绍Shell脚本的基础知识,其中Shell特性和通配符是本章的重点和难点。

第2章从变量基础知识开始讲解,扩展到变量的高级操作。其中变量的替换和引用、环境变量、基本的整数运算是本章的重点。

第3章是全书的重点,尤其是正则表达式的灵活应用,建议读者熟练掌握。

第4章介绍文件查找与处理,其中find指令的使用是本章的重点。

第5章介绍Shell条件测试和循环语句,其中流程控制和判断是本章的重点。

第6章介绍Shell数组与函数的知识。

第7章介绍Linux自动化运维的知识。

读者在学习本书的过程中若遇到问题,可以扫描下方微信二维码,关注笔者微信公众号,通过微信公众号与笔者交流书中内容。

读者也可以发送邮件(3128743705@qq.com)与笔者联系,欢迎各位读者提出宝贵的意见和建议,笔者将不胜感激。

本书编辑联系邮箱为 zhangtao@ptpress.com.cn。

感谢父母给了我生命,让我可以沐浴在阳光下;感谢同事和老师给我鼓励和指导;感谢我的每一位家人和朋友,没有你们的支持和帮助,就没有本书的早日出版;感谢我的一双儿女,是你们让我有了更为强劲的写作动力;感谢我遇到的众多良师益友,有你们的陪伴真好。

注:本书中的一些网址是虚拟的,只是演示使用,不影响读者阅读。若读者学习中有任何问题,可通过前面提供的联系方式,与笔者进行交流。

韩艳威


随着“互联网+”时代的到来,Linux操作系统在服务器领域的市场份额不断增长。由于Linux服务器在互联网企业的大规模部署和应用,因此需要一批专业的技术人员去管理Linux服务器,即Linux运维工程师。

Linux运维工程师的基本工作之一是搭建相关编程语言的运行环境,使程序能够高效、稳定、安全地在服务器上运行。优秀的Linux运维工程师不但需要拥有架设服务器集群的能力,还需要拥有使用不同的编程语言开发常用的自动化运维工具或平台的能力,从而实现高效运维,提升运维团队整体作战实力,为业务提供强有力的支撑,保障业务和服务7 × 24小时不间断运行。

Linux运维工程师日常工作包括但不限于以下内容。

自动部署多版本操作系统,如批量部署CentOS 7.7或CentOS 8.0等,并针对不同版本操作系统的参数进行调试和优化。

部署程序运行环境,如网站后台开发语言采用PHP,搭建Nginx、Apache、MySQL以及PHP运行时所需环境等。

及时修复操作系统漏洞,防止服务器被攻击,这些漏洞包括Linux操作系统本身的漏洞和各个应用软件的漏洞。

根据项目需求批量升级软件,如JDK 1.8在性能方面获得了重大突破,如果现阶段服务器压力较大,可以考虑将JDK 1.7升级到JDK 1.8。

监控服务器运行状态,保障服务持续可用,业务不受宕机影响。服务器宕机后可以实现对业务无感知的集群快速切换,保障业务可持续运营。

分析系统和业务日志,及时发现服务器或网络存在的慢请求增多和网络超时等问题,第一时间通知相关人员修复和解决相关问题。

对服务器资源合理规划和精确管理,节省成本,控制预算。

分析反向代理或负载均衡器的连接数或运行日志,评估服务器性能和用户行为。

对服务器不断加固,如合理设置防火墙策略,部署入侵检测系统,及时发现可能存在的系统漏洞或系统异常行为。

因此,Linux运维工程师需要熟练掌握Shell编程及相关的自动化运维工具。本章从Shell脚本编程入门开始讲解,带领读者踏上Linux自动化运维之路。

熟练掌握Linux指令是Linux系统架构师的必备技能之一。Linux相关从业者,尤其是Linux系统管理员和Linux系统架构师,应熟练掌握Linux指令的常用操作,原因如下。

指令比图形界面更加高效。

指令可以完成图形界面不能完成的任务,如自动批量部署500台服务器。

指令比图形界面更加灵活。

Linux系统初始化环境脚本和Web应用脚本主要是Shell指令和进程判断等的组合体,因此熟练掌握并应用Linux指令是学习Shell编程的必备条件之一,而熟练掌握Shell编程是学习Linux自动化运维的基础和前提,为以后进阶学习基于Python的自动化运维打下坚实的基础。一句话总结:“基础不牢,地动山摇”。

Shell是Linux操作系统指令集的概称,是属于操作系统层面的、基于指令集的人机交互界面的脚本级程序运行环境。

Shell是一种操作系统的应用级脚本。Shell脚本提供了一个人机交互界面,用户通过该界面访问可操作系统内核的服务。

1.初识Shell脚本

计算机只能理解由0和1组成的二进制语言。

早期计算机通过二进制语言来执行指令。二进制语言对人类来说难以理解,读、写都很不友好。后来,操作系统里提供了一种叫作Shell的特殊程序,Shell接收英文格式(大多数情况下是英文)指令,如果指令有效,就会被传递给内核,并执行一系列操作。

Shell是用户和Linux内核沟通的“桥梁”,用户的大部分工作是通过Shell完成的。Shell既是一种指令语言,又是一种脚本设计语言。作为指令语言,它交互式地解释和执行用户输入的指令;作为脚本设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。

Shell脚本不是Linux内核的一部分,但它调用了系统核心的大部分功能来执行脚本、建立文件,并以并行的方式协调各个脚本的运行。因此,对用户来说,Shell脚本是重要的实用脚本,深入了解和熟练掌握Shell脚本的特性及其使用方法,是用好Linux的关键。实际上,Shell可被看作一个提供给用户用来交互的软件,它可以通过标准输入设备(通常是键盘)或者文件读取指令,并且解释执行用户的指令。

用户与Shell交互的过程如图1-1所示。

图1-1 用户与Shell交互的过程

Shell通过系统调用来执行脚本,如创建文件等。Linux中包含各种不同版本的Shell,查看当前操作系统支持哪些Shell类型,代码如下。

[root@laohan_httpd_server ~]# cat /etc/Shells 
/bin/sh
/bin/bash
/sbin/nologin
/bin/bash

查看当前操作系统默认使用的Shell类型,代码如下。

1 [root@laohan_httpd_server ~]# echo $SHELL
2 /bin/bash
3 [root@laohan_httpd_server ~]#

从上述代码第2行中可以看到,当前操作系统默认使用的Shell类型是Bash程序。

2.Shell脚本执行方式

Shell脚本有如下两种执行方式。

(1)交互式(Interactive)。Shell解释器执行用户输入的指令,用户输入一条指令,Shell解释器执行一条。

[root@laohan-Shell-1 ~]# date
2019年 12月 06日星期五 15:27:39 CST
[root@laohan-Shell-1 ~]# whoami
root
[root@laohan-Shell-1 ~]# echo "我的名字是老韩" 
我的名字是老韩

上述代码中,用户输入的指令会逐条执行,结果会输出到当前终端(显示器)。

(2)批处理。用户可事先写一个Shell脚本(Script)文件,脚本文件中有很多条指令,Shell解释器可以一次性把这些指令执行完毕,并将指令运行状态返回给用户。

1 [root@laohan-Shell-1 chapter-1]# cat multi-command.sh 
2 uname -n
3 current_date=$(date +%F-%T)
4 echo $current_date
5 whoami
6 echo
7 echo "我的名字是老韩"
8 echo $?

上述代码第2~8行会依次执行,输出结果如下。

[root@laohan-Shell-1 chapter-1]# bash multi-command.sh 
laohan-Shell-1
2019-12-06-15:29:43
root

我的名字是老韩
0

3.Shell脚本的局限性

Shell只定义了一种非常简单的编程语言,如果脚本复杂度较大,或者要操作的数据结构比较复杂,建议使用Python等其他编程语言解决对应的问题。

建议根据不同的使用场景有选择性地使用不同的编程语言。因为Shell在处理复杂的业务逻辑(如前后端数据交互)方面的能力很弱,此时建议使用Python脚本进行处理,Shell脚本的局限性如下。

Shell脚本中函数只能返回字符串,无法返回数组。

Shell脚本不支持面向对象,无法实现一些优雅的设计模式。

Shell脚本是解释型语言,边解释边执行。如果脚本包含错误(如调用了不存在的函数),只要没调用函数代码库,系统就不会报错。

1.日常运维工作需要

熟练掌握Shell脚本是每个Linux系统管理员的必备技能。

Linux系统管理工作中必不可少的一项是根据日常运维需求开发Shell脚本,熟练编写Shell脚本也是Linux高级系统架构师的必备技能。

2.面试必备技能

在很多企业招聘Linux系统管理员时,编写Shell脚本是必考的项目,有的企业甚至用Shell脚本的编写能力来判断应聘者的Linux系统管理经验是否丰富。

3.系统架构师必备技能

Linux高级系统架构师应对网络、存储、安全、系统运维、编程语言等诸多技术深入研究,还应有较好的专业技能和职业素养,至少能熟练掌握和应用Python、Go、Java、C以及C++中的一门编程语言。可以应对系统架构中的各个实际业务需求,如使用上述编程语言中的一门写出符合实际需求的应用程序。Shell编程是每个系统架构师必须要掌握的Linux系统管理基本技能。

Shell脚本包含一系列的Linux指令,Shell依次执行这些指令。

【实例1-1】Shell脚本的基本结构

Shell脚本的基本结构的代码如下。

[root@laohan_httpd_server chapter-1]# cat -n  01.sh 
    1   #!/bin/bash
    2   
    3   #Author   韩艳威
    4   #Datetime 2018/09/03
    5   #Desc     Shell program basic struct
    6   #Version  1.3
    7   
    8   # One sentence script
    9   echo '<----------1----------->'
   10   echo
   11   echo "Current hostname is $(uname -n) running follow info:"
   12     uptime
   13   echo
   14   echo
   15   
   16   # print |echo string
   17   echo '<----------2----------->'
   18     echo -e "\t\t\t\t*********$0 script run start*******"
   19     echo
   20     echo -e "\033[32;40m\t\t\t\t\tHello\t\tworld!!! \033[0m"
   21     echo
   22     echo -e "\t\t\t\t*********$0 script run stop*******"
   23     echo
   24     echo
   25   
   26   #for loop
   27   echo '<----------3----------->'
   28   for username in /etc/passwd
   29   do
   30     echo -e "\t\t\t\t/etc/passwd file total $(wc -l $username|cut -d' '-f1) line."
   31   done

执行01.sh脚本,执行结果如下。

[root@laohan_httpd_server chapter-1]# bash 01.sh 
<----------1----------->

Current hostname is laohan_httpd_server running follow info:
 23:02:15 up 223 days,  8:02,  1 user,  load average: 0.00, 0.00, 0.00

<----------2----------->
                 *********01.sh script run start*******

                     Hello        world!!! 

                 *********01.sh script run stop*******

<----------3----------->
                /etc/passwd file total 28 line.

01.sh脚本分成了3个独立的脚本片段,其中第8~14行为第1个脚本片段,第16~24行为第2个脚本片段,第26~31行为第3个脚本片段。每个片段都可以从脚本中拆分出来,独立编写,独立运行。该脚本虽然简单,但体现了Shell脚本的一些基本特征。

一般情况下,所有的Shell脚本第1行都以“#!”开头,后面接执行此Shell脚本的解释器所在目录与解释器名,CentOS系列操作系统的默认Shell解释器是Bash,本书中的所有Shell脚本都是用Bash来解释执行的。

除了第1行声明Shell解释器的“#”以外,从第2行开始在脚本出现的“#”均可以视为代码注释符号,最简单的Shell脚本就是一组Shell指令,如第12行的uptime指令。

Shell脚本是一个普通的文本文件。

Shell脚本可以根据Linux系统管理和业务逻辑等的实际需求,以及对应的Shell开发编程规范进行编写和更新。

编写Shell脚本一般有如下两种方法。

1.Shell终端执行指令

第1种方法是在Shell终端输入单条或多条指令。

该方法属于一次性执行指令模式。当系统关闭或退出终端后,执行后的指令及其输出的结果将会从Shell运行环境的终端消失。如果需要再次执行相关指令,用户需要再次输入指令,略显烦琐。因此,此方法仅适用于处理一次性的临时任务。

2.将指令和表达式等Shell脚本写入文本文件

第2种方法是将要执行的指令和表达式组合,写入一个文本文件,在实际企业生产环境中建议采用此模式编写Shell脚本,方便系统管理员对Shell脚本多次调用,且可以对脚本内容不断更新,并将其收录为日常系统运维标准脚本,这是Linux系统自动化运维的标准化操作和规范之一。

【实例1-2】指令模式执行Shell脚本

下面的代码定义变量名为file,变量值为/etc/passwd。

file=/etc/passwd
[ -f $file ] && cp -av $file /tmp/

上述代码判断变量file是否存在,存在则复制其变量值(/etc/passwd)到/tmp/目录中。其中“&&”表示,前面的指令执行结果为真,后面的指令或代码才会执行,完整的指令执行结果如下。

[root@laohan_httpd_server ~]# file=/etc/passwd
[root@laohan_httpd_server ~]# [ -f $file ] && cp -av $file /tmp/
'/etc/passwd' -> '/tmp/passwd'

【实例1-3】Shell脚本执行模式

将【实例1-2】中的指令写入以.sh为扩展名的文本文件中,根据需要赋予可执行权限、检测脚本是否有语法错误,最后使用如下方式执行Shell脚本。

mkdir Shell-scripts

上述代码创建Shell脚本存放目录。

vim 02-cp-file.sh

上述代码编辑02-cp-file.sh脚本。

chmod +x 02-cp-file.sh

上述代码赋予02-cp-file.sh脚本可执行权限。

cat 02-cp-file.sh

上述代码查看02-cp-file.sh脚本的完整内容,具体如下。

[root@laohan_httpd_server ~]# mkdir Shell-scripts
[root@laohan_httpd_server ~]# cd Shell-scripts/
[root@laohan_httpd_serverShell-scripts]# mkdir -pv chapter-1
mkdir: created directory 'chapter-1'
[root@laohan_httpd_serverShell-scripts]# ll
total 0
[root@laohan_httpd_server ~]# vim 02-cp-file.sh
[root@laohan_httpd_server ~]# chmod +x 02-cp-file.sh 
[root@laohan_httpd_server ~]# ls -l 02-cp-file.sh
-rwxr-xr-x 1 root root 64 Jul 22 21:15 cp-file.sh
[root@laohan_httpd_server ~]# cat 02-cp-file.sh
#!/bin/bash
file=/etc/passwd
[ -f $file ] && cp -av $file /tmp/

02-cp-file.sh脚本的执行结果如下。

1 [root@laohan_httpd_server ~]# bash -n 02-cp-file.sh 
2 [root@laohan_httpd_server ~]# ./02-cp-file.sh 
3 `/etc/passwd' -> `/tmp/passwd'

在02-cp-file.sh脚本执行结果的第3行中,可以看到/etc/passwd文件已经被复制到/tmp目录下,读者还可以使用ls -lhrt --full-time /tmp/passwd指令查看被复制文件的时间戳和大小是否正确。

在任何Shell脚本和Linux指令执行完成后,对其执行结果进行确认是一个良好的习惯,也是一个优秀的Linux系统管理员必备的基本技能和专业素养。

Atom是GitHub专门为脚本开发人员推出的一个跨平台文本编辑器。

读者可自行查阅 Atom 官网并下载对应操作系统的版本进行安装。当读者安装好 Atom编辑器之后,打开 Atom 编辑器,会看到图1-2所示的操作界面,本小节将会对 Atom 编辑器的常用操作进行讲解。

图1-2 Atom编辑器操作界面

1.基本术语

先来了解一下接下来要用到的Atom编辑器基本术语。

(1)缓冲区(Buffer)。

缓冲区代表了Atom编辑器中的一个文件的文本内容,它相当于一个真正的文件,但它是被Atom维护在内存中的。如果修改了它,在保存之前,缓冲区的内容不会被写入硬盘。

(2)窗格。

窗格代表Atom编辑器中的一个可见区域。例如在欢迎界面上读者可以看到4个窗格:用来切换文件的标签栏(Tab Bar)、用来显示行号的边框(Gutter)、底部的状态栏(Status Bar)以及文本编辑器。

2.指令面板

当按“Command+Shift+P”快捷键并且当前焦点在一个窗格上的时候,指令面板就会弹出来,这是Atom编辑器在macOS上的默认快捷键。如果在其他的操作系统上使用Atom编辑器,可能会稍有不同。如果某个快捷键无法工作,可以通过指令面板来查找正确的快捷键,如图1-3所示。

除了可以搜索数以千计的指令之外,指令面板上还会显示每条指令对应的快捷键,这意味着可以在使用这些指令的同时学习对应的快捷键,以便之后使用。

3.自定义设置

Atom编辑器有很多自定义设置,我们可以在设置界面修改它们,如图1-4所示。

图1-3 查找快捷键

图1-4 设置界面

在设置界面中,可以修改主题、文本换行(Wrapping)的行为、字体大小、缩进宽度、滚动速度等,也可以安装新的插件和主题等。

4.修改主题

在设置界面中可以修改Atom编辑器的主题,Atom编辑器内创建了4个不同的UI主题(UI Theme),即分为亮色(Light)/暗色(Dark)版本的Atom和One的主题;还创建了8个不同的语法着色主题(Syntax Theme);还可以通过单击左边栏的“Themes”选项来改变当前主题,或安装新的主题,如图1-5所示。

Atom编辑器的UI主题会修改标签栏、目录树等UI元素的颜色,而语法着色主题会修改编辑器中文字的语法高亮方案,用户只需要简单地在下拉列表框中选择另一项,即可修改主题。

5.换行设置

可以通过设置界面指定Atom编辑器处理空白和折行,如图1-6所示。

图1-5 修改主题

图1-6 换行设置

当启用了“Soft Tabs”(制表符)后,Atom编辑器将会在用户按“Tab”键时用空格来替代真正的制表符,“Tab Length”(标签长度)则指定了一个制表符代表多少个空格,或者当制表符被禁用时多少个空格相当于一个制表符。如果开启了“Soft Wrap”(换行)选项,Atom编辑器会将超出屏幕显示范围的一行文本变为两行;如果禁用这个选项,过长的行将超出屏幕显示范围,读者必须要横向移动滚动条才能看到剩余的部分;如果“Soft Wrap At Preferred Line Length”选项被开启,则总是会在80个字符处换行,用户也可以设置一个自定义的长度值来替换默认的80。

6.打开、编辑和保存文件

设置了Atom编辑器后,可以使用它打开、编辑和保存文件,步骤如下所示。

(1)打开文件。

在Atom编辑器中有几种方式可以打开一个文件。可以在菜单栏中单击“File”菜单下的“Open”选项,或者按“Command+O”快捷键,在操作系统的对话框中选择一个文件,如图1-7所示。

图1-7 Atom编辑器打开文件

另一种打开文件的方法是用指令行,在Atom编辑器的菜单栏中有一个名为“Install Shell Commands”的菜单,它会向终端安装一个新的名为“Atom”的指令,可以用一个或多个文件路径作为参数去运行Atom指令。

(2)保存文件。

单击菜单栏中“File”菜单下的“Save”项保存文件,也可单击“Save As”项将文件保存到另一个路径。最后,可以按“Ctrl+Shift+S”快捷键一次保存Atom编辑器中所有打开的文件。

(3)打开目录。

Atom编辑器不仅可以编辑单个文件,大多数情况下还可以编辑由若干个文件组成的项目(Project)。可以单击菜单栏中的“Open”,在弹出的对话框中选择一个目录,或者通过依次单击“File”→“Add Project Folder”或按“Command +Shift+O”快捷键在一个窗口中打开多个目录。

在指令模式下,可以将多个路径作为参数传递给Atom编辑器。例如“Atom ./nginx ./apache”会让Atom编辑器同时打开nginx和apache这两个目录。

当用Atom编辑器打开一个或多个目录时,目录树会自动地出现在窗口左侧,如图1-8所示。

目录树允许我们查看和修改当前项目的目录结构,可以在目录树中打开文件、重命名文件、删除文件、创建文件。

可以通过tree-view:toggle指令来隐藏或重新显示目录树,通过“Ctrl+0”快捷键可以将焦点切换到目录树。当焦点位于目录树上时,可以通过“A”“M”或“Delete”键来创建、移动或删除文件和目录。可以简单地在目录树中右击文件,这样能看到更多选项。还可以在操作系统的文件浏览器中显示文件、复制文件的路径到剪贴板。

7.打开项目中的文件

在Atom编辑器中打开了一个项目(即目录)后,就可以简单地查找并打开来自项目的文件了,按“Command+T”或“Command+P”快捷键的时候,模糊查找(Fuzzy Finder)框就会弹出,它允许通过输入文件名或路径名的一部分,在整个项目中模糊查找相应的文件,如图1-9所示。

图1-8 Atom编辑器之打开目录

图1-9 Atom编辑器之搜索文件

通过“Command+B”快捷键来查找已经打开的文件,而不是所有文件,还可以用“Command+Shift+B”快捷键查找从上次Git提交之后修改过或新增的文件。

首先,编写Shell脚本有一些通用规则。遵守这些规则可以让脚本更为规范,对代码的整洁性和可读性都有很大帮助,且脚本的代码量大小对以后的维护工作也是至关重要的。其次,关于大型Shell脚本代码的调试,如果有几百甚至上千行代码,良好的脚本调试就显得尤为重要。下面将就上述两个方面进行阐述和实例分解。

创建脚本指定存放目录。

编写脚本时,本地开发推荐使用Atom编辑器,服务器开发推荐使用Vim文本编辑器。

根据实际需求,编写脚本。

为核心代码添加注释(说明信息)。

赋予脚本可执行权限。

执行脚本,并将执行结果显示到指定位置。

若脚本无法执行,使用调试模式执行脚本。

【实例1-4】添加注释

Shell编程中,除了“#!/bin/bash”的Bash脚本声明以外,其他以“#”开头的内容均为脚本注释。

#!/bin/bash

上述代码指定脚本解释器。

# Author: hanyanwei
# Datetime 2017-05-15

上述代码以“#”开头的内容为脚本单行注释,Shell解释器会忽略该内容。

1 [root@laohan_httpd_server chapter-1]# cat 03-note.sh 
2 #!/bin/bash
3 # Author: hanyanwei
4 # Datetime 2017-05-15
5 echo 'hello,world!!!'

上述代码中,第3~4行为脚本注释。在执行脚本时,不会执行注释内容,该脚本执行结果如下所示。

[root@laohan_httpd_server chapter-1]# bash 03-note.sh 
hello,world!!!

从脚本执行结果中可以看到,被注释的代码在脚本执行过程中不会被输出。

【实例1-5】脚本调试

04-Shell-debug.sh脚本内容如下所示。

1 [root@laohan_httpd_server chapter-1]# cat 04-Shell-debug.sh 
2 #!/bin/bash
3 # Author: hanyanwei
4 # Datetime 2017-05-15
5 site="http://www.booxin.vip"
6 echo -e "\033[32;40m  我们的网站是:${site}\033[0m"
7 set -x
8 echo 'hello,world!!!'
9 set +x

上述代码第7~9行使用set指令进行调试。执行脚本时也可以使用bash -x调试并执行04-Shell-debug.sh脚本,输出内容如下。

[root@laohan_httpd_server chapter-1]# bash -x  04-Shell-debug.sh 
+ site=http://www.booxin.vip
+ echo -e '\033[32;40m  我们的网站是:http://www.booxin.vip\033[0m'
  我们的网站是:http://www.booxin.vip

上述代码中带有“+”的部分,是脚本执行过程中指令的实际执行过程。

以“+”开头的行都是执行过的指令,行首没有“+”的都是脚本执行结果,并且脚本中的所有变量都使用其值的形式替代。

如果脚本中存在逻辑错误,在上面的调试过程中,就可以清晰地看到脚本执行的过程和出错的提示信息。

使用set指令的x选项启动调试模式时,不一定要将所有的语句都进行调试。如果需要,也可以使用set -x、set +x调试一段或多段可能存在问题的代码。

另外,在执行Shell脚本之前,可以使用bash -n script-name指令对脚本中的语法进行检测,代码如下。

1 [root@laohan-Shell-1 chapter-1]# bash -n debug-Shell.sh 
2 [root@laohan-Shell-1 chapter-1]# echo $?
3 0

上述代码第3行中所示的执行结果为0,表示脚本语法准确无误。

读者也可以使用ShellCheck检测和调试Shell脚本,代码如下。

1   [root@laohan-zabbix-server ~]# ShellCheck a.sh 
2   [root@laohan-zabbix-server ~]# bash a.sh 
3   echo 跟老韩学Python
4   echo 跟老韩学Python
5   echo 跟老韩学Python
6   echo 跟老韩学Python
7   echo 跟老韩学Python
8   echo 跟老韩学Python
9   echo 跟老韩学Python
10  [root@laohan-zabbix-server ~]# cat a.sh 
11  #!/bin/bash
12
13  desc='echo 跟老韩学Python'
14  for ((i=0;i<=6;i++))
15  do
16      echo "${desc}"
17  done

上述代码第1行使用ShellCheck检测a.sh脚本是否有语法错误,没有任何输出信息则表示脚本内容的编写和语法等符合规范,第3~9行为脚本执行结果,第11~17行为脚本内容。

注意:ShellCheck是显示Shell脚本的警告和建议的工具,CentOS系列操作系统可使用如下代码安装相关软件包。

1   [root@laohan-zabbix-server ~]# yum -y install epel-release
2   [root@laohan-zabbix-server ~]# yum list  shellcheck
3   Loaded plugins: fastestmirror, langpacks
4   Repository epel is listed more than once in the configuration
5   Loading mirror speeds from cached hostfile
6    * base: mirrors.aliyun.com
7    * extras: mirrors.aliyun.com
8    * updates: mirrors.aliyun.com
9   Installed Packages
10  ShellCheck.x86_64 0.3.8-1.el7 @epel
11  [root@laohan-zabbix-server ~]# yum -y install ShellCheck
12  [root@laohan-zabbix-server ~]# rpm -ql ShellCheck
13  /usr/bin/shellcheck
14  /usr/share/doc/ShellCheck-0.3.8
15  /usr/share/doc/ShellCheck-0.3.8/README.md
16  /usr/share/licenses/ShellCheck-0.3.8
17  /usr/share/licenses/ShellCheck-0.3.8/LICENSE
18  /usr/share/man/man1/shellcheck.1.gz

上述代码第1行安装EPEL源,第2行查看ShellCheck相关的程序,第11行安装ShellCheck软件包,第13~18行为ShellCheck软件包所有的组件和帮助文档。

Shell编程中单行注释的语法是在每一行注释内容之前加一个“#”,代码如下。

1 echo "#"
2 #跟老韩学
3 #跟老韩学
4 #面朝大海
5 #春暖花开
6 echo "#"

上述代码第2~5行为以“#”开头的注释内容,也可以使用如下方式对代码进行注释说明。

1 echo "#"
2 echo 'My name is handuoduo'  #print string
3 echo "I like Linux so much"  #comment
4 echo "#"

上述代码第2~3行的注释写在Shell语句之后,读者可以根据实际需要对脚本内容自定义位置注释。

【实例1-6】单行注释

04-single-note.sh脚本内容如下。

[root@laohan_httpd_server chapter-1]# cat -n 04-single-note.sh
1   #!/bin/bash
2   
3   #这里是单行注释
4   echo
5   echo "My name is handuoduo."
6   echo

上述代码第3行内容为单行注释。脚本在执行过程中,注释内容不会被执行,脚本执行结果如下所示。

1 [root@laohan_httpd_server chapter-1]# bash 04-single-note.sh
2
3 My name is handuoduo.
4
5 [root@laohan_httpd_server chapter-1]#

上述代码第2~4行为脚本执行结果,其中第2行是echo指令输出的空行,第3行是“My name is handuoduo.”字符串,第4行是空行。

在日常编写Shell脚本时,特别是在调试脚本的时候经常需要注释多行脚本,本小节内容将详细介绍注释多行脚本的常用方法。

【实例1-7】多行注释

1.使用Here Document注释多行Shell脚本

Here Document是Shell中的一种特殊的重定向方式,用于将输入的文件内容重定向到交互式Shell脚本,作用是将如两个delimiter之间的内容传递给command,代码如下。

command << delimiter
Document
delimiter

上述代码中结尾的“delimiter”一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和制表符缩进。

05-multi-note-1.sh脚本内容如下所示。

[root@laohan_httpd_server chapter-1]# cat -n 05-multi-note-1.sh
 1   #以下是多行注释
 2   :<<!
 3   Author:韩艳威
 4   DateTime:2018/09/03
 5   Desc:multi note test
 6   Version:1.1
 7   !
 8   
 9   echo "My name is handuoduo A."
10   echo "My name is handuoduo B."
11   echo "My name is handuoduo C."
12   echo "My name is handuoduo D."
13   echo "My name is handuoduo E."

上述代码中第2~7行均为多行注释内容,第9~13行是脚本主体,该脚本主要功能为输出一系列特定的字符串,脚本执行结果如下所示。

[root@laohan_httpd_server chapter-1]# bash 05-multi-note-1.sh
My name is handuoduo A.
My name is handuoduo B.
My name is handuoduo C.
My name is handuoduo D.
My name is handuoduo E.
[root@laohan_httpd_server chapter-1]#

Shell脚本中添加多行注释的方法如下所示。

1 :<<!
2 #注释内容1   
3 #注释内容2   
4 #注释内容3   
5 #注释内容4   
6 #注释内容5   
7 #注释内容6   
8 !

上述代码第1行结尾的“!”和第8行的“!”也可以是其他的字符串,代码如下。

[root@laohan_httpd_server chapter-1]# cat 06-multi-note-2.sh
#以下是多行注释
:<<note
Author:韩艳威
DateTime:2018/09/03
Desc:multi note test
Version:1.1
note

echo "My name is handuoduo A."
echo "My name is handuoduo B."
echo "My name is handuoduo C."
echo "My name is handuoduo D."
echo "My name is handuoduo E."

06-multi-note-2.sh脚本执行结果如下所示。

[root@laohan_httpd_server chapter-1]# bash 06-multi-note-2.sh
My name is handuoduo A.
My name is handuoduo B.
My name is handuoduo C.
My name is handuoduo D.
My name is handuoduo E.

06-multi-note-2.sh脚本和05-multi-note-1.sh脚本执行结果完全一致。

2.使用空格和单引号注释多行Shell脚本

使用空格和单引号注释多行Shell脚本,语法格式如下所示。

1:'
2注释内容1
3注释内容2
4注释内容3
5注释内容N
6 '

注意,上述代码第1行中,“:”与“'”之间要使用空格分隔,否则会报错,提示没有相应文件或目录。07-multi-note-3.sh脚本内容如下所示。

[root@laohan_httpd_server chapter-1]# cat 07-multi-note-3.sh
#演示多行注释

#注意后面留有空格
: '
Author:韩艳威
DateTime:2018/09/03
Desc:multi note
Version:1.1
'

echo 1
echo 2
echo 3

07-multi-note-3.sh脚本执行结果如下所示。

[root@laohan_httpd_server chapter-1]# bash 07-multi-note-3.sh
1
2
3

上述代码分别使用echo指令输出了1、2、3这3个整数。

3.使用if语句注释多行Shell脚本

使用if语句注释多行Shell脚本,语法格式如下。

if false;then
被注释的内容
被注释的内容
被注释的内容
fi

08-multi-note-4.sh脚本内容如下所示。

[root@laohan_httpd_server chapter-1]# cat 08-multi-note-4.sh
1#演示多行注释
2
3
4 if false;then
5  echo "A."
6  echo "B."
7  echo "C."
8 fi
9
10 echo "D."

上述代码第4~8行均为脚本多行注释,注释内容不会被解释器执行,因此只会执行第10行代码,该脚本执行结果如下所示。

[root@laohan_httpd_server chapter-1]# bash 08-multi-note-4.sh
D.

4.使用冒号和重定向注释多行Shell脚本

使用冒号和重定向注释多行Shell脚本,语法格式如下所示。

:<<任意字符或者数字

注释内容1
注释内容2
注释内容3

任意字符或者数字

09-multi-note-5.sh脚本内容如下所示。

[root@laohan_httpd_server chapter-1]# cat 09-multi-note-5.sh
1 #演示多行注释
2
3
4 :<<65536
5 Author:hanyanwei
6 DateTime:2018/09/03
7 Desc:multi note test
8 Version:1.1
9 65536
10 
11 echo "Welcome"
12 echo "to"
13 echo "taiyuan."

上述代码第4~9行均为脚本注释内容,执行该脚本,结果如下所示。

[root@laohan_httpd_server chapter-1]# bash 09-multi-note-5.sh
1 Welcome
2 to
3 taiyuan.
[root@laohan_httpd_server chapter-1]#

上述代码中第1~3行为脚本实际执行指令的代码的执行结果,其他注释内容均被解释器忽略而不执行。

5.使用“&&”和“{}”注释多行Shell脚本

使用“&&”和“{}”注释多行Shell脚本,语法格式如下所示。

((0)) && {

注释内容1
注释内容2
注释内容3

}

10-multi-note-6.sh脚本内容如下所示。

[root@laohan_httpd_server chapter-1]# cat 10-multi-note-6.sh
1 #演示Shell脚本多行注释
2
3 ((0)) && {
4  Author:韩艳威
5  DateTime:2018/09/03
6  Desc:multi note test
7  Version:1.1
8 }
9
10 echo "ni,hao"

上述代码第3~8行为注释内容,执行该脚本,结果如下所示。

[root@laohan_httpd_server chapter-1]# bash 10-multi-note-6.sh
1 ni,hao
2 [root@laohan_httpd_server chapter-1]#

上述代码第1行为10-multi-note-6.sh脚本文件执行结果,其他注释内容均被解释器忽略而不执行。

注意:注释多行Shell脚本内容的方法,和Linux发行版本有很大的关系,本书所述方法并不兼容所有Linux版本,上述测试结果仅在CentOS系列操作系统的发行版测试通过。

Shell脚本的调试,主要有4种方法:使用trap指令、使用tee指令、使用调试钩子以及使用Shell选项。本节将介绍其中的3种。

trap指令的基本格式如下所示。

trap command sig1 sig2...

功能描述:trap指令收到指定信号(DEBUG、EXIT、ERR)时,执行command。

Shell脚本的3种“伪信号”产生情景如下所示。

DEBUG:脚本中的每一条指令执行之前。

EXIT:从函数中退出,或整个脚本执行完毕。

ERR:当一条指令返回非0状态码时,即指令执行不成功。

被称为“伪信号”是因为这3种信号是由Shell产生的,其他的信号都是由操作系统产生的。

【实例1-8】使用trap指令捕捉DEBUG信号来跟踪变量的取值变化

11-Shell-debug-trap.sh脚本内容如下所示。

[root@laohan_httpd_server chapter-1]# cat 11-Shell-debug-trap.sh
#trap debug Shell script

trap 'echo "Before exec line:$line,num1=$num1 , num2=$num2 , num3=$num3"' DEBUG

declare num1=0
declare num2=2
declare num3=100

while :
do
  if ((num1 >= 10))
  then
    break
  fi
  let "num1=$num1+2"
  let "num2=$num2*2"
  let "num3=$num3-10"
done

上述代码中的“:”指令相当于true,放在while后面,表示无限循环。该脚本执行结果如下。

[root@laohan_httpd_server chapter-1]# bash 11-Shell-debug-trap.sh
Before exec line:,num1= , num2= , num3=
Before exec line:,num1=0 , num2= , num3=
Before exec line:,num1=0 , num2=2 , num3=
Before exec line:,num1=0 , num2=2 , num3=100
Before exec line:,num1=0 , num2=2 , num3=100
Before exec line:,num1=0 , num2=2 , num3=100
Before exec line:,num1=2 , num2=2 , num3=100
Before exec line:,num1=2 , num2=4 , num3=100
Before exec line:,num1=2 , num2=4 , num3=90
Before exec line:,num1=2 , num2=4 , num3=90
Before exec line:,num1=2 , num2=4 , num3=90
Before exec line:,num1=4 , num2=4 , num3=90
Before exec line:,num1=4 , num2=8 , num3=90
Before exec line:,num1=4 , num2=8 , num3=80
Before exec line:,num1=4 , num2=8 , num3=80
Before exec line:,num1=4 , num2=8 , num3=80
Before exec line:,num1=6 , num2=8 , num3=80
Before exec line:,num1=6 , num2=16 , num3=80
Before exec line:,num1=6 , num2=16 , num3=70
Before exec line:,num1=6 , num2=16 , num3=70
Before exec line:,num1=6 , num2=16 , num3=70
Before exec line:,num1=8 , num2=16 , num3=70
Before exec line:,num1=8 , num2=32 , num3=70
Before exec line:,num1=8 , num2=32 , num3=60
Before exec line:,num1=8 , num2=32 , num3=60
Before exec line:,num1=8 , num2=32 , num3=60
Before exec line:,num1=10 , num2=32 , num3=60
Before exec line:,num1=10 , num2=64 , num3=60
Before exec line:,num1=10 , num2=64 , num3=50
Before exec line:,num1=10 , num2=64 , num3=50
Before exec line:,num1=10 , num2=64 , num3=50

根据DEBUG信号产生的条件(脚本中的每一条指令执行之前产生DEBUG信号),每当执行一个语句之前trap指令捕捉到DEBUG信号,进而输出num1、num2、num3的值。

【实例1-9】使用trap指令捕捉EXIT信号跟踪函数结束

12-Shell-debug-trap.sh脚本内容如下所示。

[root@laohan_httpd_server chapter-1]# cat 12-Shell-debug-trap.sh
#!/bin/bash

function year_2018()
{
       echo "This is a test function"
       year=2018
       return 0

}

trap 'echo "Line:$LINENO,year=$year"' EXIT
year_2018

12-Shell-debug-trap.sh脚本执行结果如下所示。

[root@laohan_httpd_server chapter-1]# bash 12-Shell-debug-trap.sh
This is a test function
Line:1,year=2018
[root@laohan_httpd_server chapter-1]#

【实例1-10】使用trap指令捕捉ERR信号

13-Shell-debug-trap-error.sh脚本内容如下。

1 [root@laohan_httpd_server chapter-1]# cat 13-Shell-debug-trap-error.sh
2 #trap debug Shell script
3
4 trap 'echo "Line:$LINENO,year=$year"' ERR
5 function fun2()
6 {
7       echo "This is an err function test..."
8       year=2018/09/03
9       return 1
10 
11 }
12 
13 fun2
14 handuoduo

该脚本执行结果如下。

[root@laohan_httpd_server chapter-1]# bash 13-Shell-debug-trap-error.sh
This is an err function test...
Line:8,year=2018/09/03
13-Shell-debug-trap-error.sh: line 13: handuoduo: command not found
Line:13,year=2018/09/03

该脚本第13行名为“fun2”的函数返回值是1,返回值非0的函数都被视为异常函数,因此在调用fun2()函数时会产生ERR信号,结果输出如下。

Line:8,year=2018/09/03

第14行执行“handuoduo”,由于该代码为错误语句,因此也会产生ERR信号。

使用tee指令显示文件内容的同时,还可以通过管道将显示结果写入某个文件中,【实例1-11】演示tee指令的作用。

【实例1-11】使用tee指令调试Shell脚本

tee -a file指令将标准输出追加到文件末尾,而不会覆盖文件。

本实例以/etc/sysconfig/network-scripts/ifcfg-eth0为源文件进行演示,其内容如下所示。

[root@laohan_httpd_server chapter-1]# cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
HWADDR=00:0C:29:77:B0:BD
TYPE=Ethernet
UUID=bad3529d-c8d8-4459-aedb-7f43ce2ed16f
ONBOOT=yes
IPADDR=192.168.1.110
NETMASK=255.255.255.0
GATEWAY=192.168.1.1
DNS1=114.114.114.114
NM_CONTROLLED=yes
BOOTPROTO=static

使用管道过滤的方式取出IP地址,代码如下。

[root@laohan_httpd_server chapter-1]# cat /etc/sysconfig/network-scripts/ifcfg-eth0 |grep IPADDR
IPADDR=192.168.1.110
[root@laohan_httpd_server chapter-1]# cat /etc/sysconfig/network-scripts/ifcfg-eth0 |grep IPADDR |cut -d'=' -f2
192.168.1.110

使用tee指令调试并执行Shell脚本,执行结果如下所示。

[root@laohan_httpd_server chapter-1]# cat /etc/sysconfig/network-scripts/ifcfg-eth0 |tee /tmp/ip.log|grep IPADDR |tee -a /tmp/ip.log |cut -d'=' -f2
192.168.1.110

查看/tmp/ip.log文件内容。

1[root@laohan_httpd_server chapter-1]# cat /tmp/ip.log 
2 DEVICE=eth0
3 HWADDR=00:0C:29:77:B0:BD
4 TYPE=Ethernet
5 UUID=bad3529d-c8d8-4459-aedb-7f43ce2ed16f
6 ONBOOT=yes
7 IPADDR=192.168.1.110
8 NETMASK=255.255.255.0
9 GATEWAY=192.168.1.1
10 DNS1=114.114.114.114
11 NM_CONTROLLED=yes
12 BOOTPROTO=static
13 IPADDR=192.168.1.110

上述代码第 1~12 行的内容和/etc/sysconfig/network-scripts/ifcfg-eth0 文件内容完全一致,只有第13行多了过滤IP地址的信息。

前面两种方法都通过修改Shell脚本源代码来定位错误,而使用Shell选项可以不修改源代码来定位错误。

脚本调试的核心思想是发现引起脚本错误的原因和脚本源代码中的错误行,调试Shell脚本时常用的方式如下所示。

1.使用echo指令调试

功能:简单的调试方法,可以在任何怀疑出错的地方用echo输出变量。

场合:所有怀疑可能有问题的地方。

【实例1-12】使用echo指令调试Shell脚本

定义file变量,并赋值为/etc/passwd文件,代码如下。

file=/etc/passwd

使用echo指令输出字符串或变量内容,以便决定下一步指令或脚本执行动作,代码如下。

echo $file

输出变量file,代码如下。

[root@laohan_httpd_server ~]# file=/etc/passwd
[root@laohan_httpd_server ~]# echo $file
/etc/passwd

读者也可以使用如下方式,对变量及其内容进行操作。

1 [root@laohan_httpd_server ~]# file=/etc/passwd
2 [root@laohan_httpd_server ~]# [ -z "$file" ] || head $file
3 root:x:0:0:root:/root:/bin/bash
4 bin:x:1:1:bin:/bin:/sbin/nologin
5 daemon:x:2:2:daemon:/sbin:/sbin/nologin
6 adm:x:3:4:adm:/var/adm:/sbin/nologin
7 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
8 sync:x:5:0:sync:/sbin:/bin/sync
9 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
10 halt:x:7:0:halt:/sbin:/sbin/halt
11 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
12 uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin

上述代码第1行定义了file变量,第2行对变量是否为空进行判断,不为空则输出其变量内容。

2.使用-n选项调试Shell脚本

【实例1-13】使用-n选项调试Shell脚本

功能:读取Shell脚本,但不实际执行。

场合:测试Shell脚本中是否存在语法错误。

查看16-bash-n.sh脚本内容,代码如下。

[root@laohan_httpd_server chapter-1]# cat 16-bash-n.sh
#!/bin/bash
# Author: hanyanwei
# Datetime 2017-05-15
echo 'hello,world!!!'

检测16-bash-n.sh脚本是否有语法错误。

1 [root@laohan_httpd_server chapter-1]# bash -n 16-bash-n.sh
2 [root@laohan_httpd_server chapter-1]# echo $?
3 0

上述代码第3行返回值为0,表示上一条指令被成功执行。

3.使用-c选项调试Shell脚本

【实例1-14】使用-c选项调试部分Shell脚本

功能:使Shell解释器从字符串而非文件中读取并执行指令。

场合:需要调试一小段脚本的执行结果时。

[root@laohan_httpd_server ~]# bash -c 'num_1=10;num_2=20;let num_total=num_1+num_2; echo "num_total=$num_total"'
num_total=30

4.使用-v选项调试Shell脚本

【实例1-15】输出脚本执行详细过程

功能:区别于-x选项,-v选项输出指令行的原始内容,而-x选项输出经过替换后指令行的内容。

场合:仅想显示指令行的原始内容。

[root@laohan_httpd_server chapter-1]# bash -v 16-bash-n.sh
#!/bin/bash
# Author: hanyanwei
# Datetime 2017-05-15
echo  'hello,world!!!'
hello,world!!!

5.使用-x选项调试Shell脚本

【实例1-16】追踪Shell脚本执行信息

功能:提供跟踪执行信息,在执行脚本的过程中把实际执行的每条指令显示出来。行首显示“+”,“+”后面显示经过替换之后的指令行内容,有助于分析实际执行的是什么指令。

场合:是调试Shell脚本的强有力工具,是Shell脚本首选的调试手段。

在脚本中用set指令表示启用或禁用参数:set −x表示启用,set +x表示禁用。

[root@laohan_httpd_server chapter-1]# bash -x 16-bash-n.sh
+ echo 'hello,world!!!'
hello,world!!!

如果在脚本文件中加入了set –x指令,那么在set指令之后执行的每一条指令和加载指令行中的任何参数都会显示出来。每一行都会加上“+”,提示它是跟踪输出的标识,在子Shell中执行的Shell跟踪指令会加“++”。

6.使用返回值调试Shell脚本

使用test语句,判断返回值:数字为0表示真,数字为1则表示假。

【实例1-17】终端快速调试Shell脚本

判断数字8大于数字6为真或假,为真则输出数字0,为假则输出非0数字,代码如下。

[ 8 -gt 6 ]

判断/etc/passwd是文件的结果为真或假,为真则输出数字0,为假则输出非0数字,代码如下。

test -f /etc/passwd

执行结果如下。

[root@laohan_httpd_server ~]# [ 8 -gt 6 ]
[root@laohan_httpd_server ~]# echo $?
0
[root@laohan_httpd_server ~]# [ 6 -gt 8 ] 
[root@laohan_httpd_server ~]# echo $?    
1
[root@laohan_httpd_server ~]# test -f /etc/passwd
[root@laohan_httpd_server ~]# echo $?            
0

7.使用set指令调试Shell脚本

set −n和set +n之间的代码当作注释并未执行,脚本内容如下所示。

[root@laohan_httpd_server chapter-1]# cat 17-Shell-tiaoshi.sh
#!/bin/bash
# Author: hanyanwei
# Datetime 2017-05-15
site="http://www.booxin.vip"
echo -e "\033[32;40m  我们的网站是:${site}\033[0m"
set -n
echo 'hello,world!!!'
set +n

上述代码检测17-Shell-tiaoshi.sh脚本语法是否正确,调试并执行脚本,执行结果如下所示。

[root@laohan_httpd_server chapter-1]# bash -n 17-Shell-tiaoshi.sh
[root@laohan_httpd_server chapter-1]# bash -x 17-Shell-tiaoshi.sh
+ site=http://www.booxin.vip
+ echo -e '\033[32;40m  我们的网站是:http://www.booxin.vip\033[0m'
  我们的网站是:http://www.booxin.vip
+ set -n

非调试模式执行17-Shell-tiaoshi.sh脚本,执行结果如下所示。

[root@laohan_httpd_server chapter-1]# bash  17-Shell-tiaoshi.sh
  我们的网站是:<a>http://www.booxin.vip</a>

从执行结果的第2行可以得出结论,该脚本只输出了echo指令的内容,set −n与set +n调试代码中间的指令并未输出任何结果。

8.使用环境变量_DEBUG调试Shell脚本

调试Shell脚本代码如下。

1   [root@laohan_httpd_server ~]# cat debug.sh
2   #!/bin/bash
3 
4   function DEBUG(){
5       [ "$_DEBUG" == "on" ] && $@ || :
6   }
7 
8 
9   for i in {1..8}
10  do
11      DEBUG echo $i
12  done

上述代码第5行可以分解为如下3部分。

第一部分为条件表达式,它检查环境变量_DEBUG的值是否等于on。

[ "$_DEBUG" == "on" ]

第二部分将函数参数作为命令执行,本例的函数参数是echo指令。

$@

第三部分是“空命令”,即什么也不做。

:

通过“&&”和“||”逻辑判断符号连接,形成“cmd0 && cmd1 || cmd2”结构,这表示当cmd0条件为真时,执行cmd1语句,否则执行cmd2语句。

1   [root@laohan_httpd_server ~]# bash debug_2.sh
2   Reading files
3   Fond 老韩 in debug_2.sh file.
4   grep: laohan-c: Is a directory
5   Fond 老韩 in laohan.info file.
6   Fond 老韩 in laohan.info_in file.
7   Fond 老韩 in laohan.info_out file.
8   grep: laohan-shell: Is a directory
9   grep: tech: Is a directory
10  + a=6
11  + b=8
12  + c=14
13  + DEBUG set +x
14  + '[' on == on ']'
15  + set +x
16  6 + 8 = 14

上述代码第1~16行为开启_DEBUG调试执行结果。

1   [root@laohan_httpd_server ~]# bash debug_2.sh 
2   Fond 老韩 in debug_2.sh file.
3   grep: laohan-c: Is a directory
4   Fond 老韩 in laohan.info file.
5   Fond 老韩 in laohan.info_in file.
6   Fond 老韩 in laohan.info_out file.
7   grep: laohan-shell: Is a directory
8   grep: tech: Is a directory
9   6 + 8 = 14

上述代码第1~9行表示关闭_DEBUG调试执行结果。

1   [root@laohan_httpd_server ~]# cat debug_2.sh
2   #DEBUG-2
3   #_DEBUG="on"
4   _DE BUG="off"
5   function DEBUG(){
6       [ "$_DEBUG" == "on" ] && $@
7   }
8 
9   DEBUG echo 'Reading files'
10  for i in * 
11  do
12      grep "老韩" $i >/dev/null
13      [ $? -eq 0 ] && echo "Fond 老韩 in $i file."
14  done
15  DEBUG set -x 
16  a=6
17  b=8
18  c=$(($a+$b))
19  DEBUG set +x
20  echo "$a + $b = $c"

上述代码第1~20行为调试脚本完整执行结果。

9.使用ShellCheck检测Shell脚本

ShellCheck是一款实用的Shell脚本静态检查工具,其在CentOS 7操作系统下的安装过程如下。

1   [root@laohan-zabbix-server ~]# cat /etc/redhat-release 
2   CentOS Linux release 7.6.1810 (Core) 
3   [root@laohan-zabbix-server ~]# yum list shell*
4   Loaded plugins: fastestmirror, langpacks
5   Repository epel is listed more than once in the configuration
6   Loading mirror speeds from cached hostfile
7   Installed Packages
8   ShellCheck.x86_64 0.3.8-1.el7 @epel
9   Available Packages
10  shellinabox.x86_64 2.20-5.el7 epel
11  [root@laohan-zabbix-server ~]# yum install epel-release -y
12  [root@laohan-zabbix-server ~]# yum install ShellCheck -y
13  [root@laohan-zabbix-server ~]# rpm -ql ShellCheck
14  /usr/bin/shellcheck
15  /usr/share/doc/ShellCheck-0.3.8
16  /usr/share/doc/ShellCheck-0.3.8/README.md
17  /usr/share/licenses/ShellCheck-0.3.8
18  /usr/share/licenses/ShellCheck-0.3.8/LICENSE
19  /usr/share/man/man1/shellcheck.1.gz

ShellCheck工具的基本使用代码如下。

1   [root@laohan-zabbix-server ~]# vim test.sh
2   [root@laohan-zabbix-server ~]# shellcheck test.sh
3 
4   In test.sh line 2:
5   for i in {1..6}
6   ^-- SC1073: Couldn't parse this for loop.
7 
8 
9   In test.sh line 3:
10  do
11  ^-- SC1061: Couldn't find 'done' for this 'do'.
12
13
14  In test.sh line 7:
15
16  ^-- SC1062: Expected 'done' matching previously mentioned 'do'.
17  ^-- SC1072: Expected 'done'.. Fix any mentioned problems and try again.
18
19  [root@laohan-zabbix-server ~]# cat test.sh
20  #!/bin/bash
21  for i in {1..6}
22  do
23
24  echo "The \$i is $i."
25
26  [root@laohan-zabbix-server ~]# vim test.sh
27  [root@laohan-zabbix-server ~]# 
28  [root@laohan-zabbix-server ~]# shellcheck test.sh

29 [root@laohan-zabbix-server ~]# cat test.sh 
30 #!/bin/bash
31 for i in {1..6}
32 do
33 
34 echo "The \$i is $i."
35 
36 done
37  [root@laohan-zabbix-server ~]# echo $?
38  0

上述代码使用ShellCheck检测到第36行中少了“done”关键字。

首先,需要知道并理解的是,Shell是一门脚本编程语言。

其次,需要知道Shell的主要用途是帮助Linux系统管理员做日常的系统管理工作。如采用kickstart+PXE自动部署多版本操作系统、监控服务进程存活、监控系统运行情况、统计分析日志、备份MySQL数据库、采用操作系统自带的防火墙(iptables+firewalld)进行安全加固。

对刚开始接触Shell的读者有以下几点建议,仅供大家参考。

掌握Linux常见的基础指令并熟练使用。

熟练部署和优化Linux常见服务,包括但不限于Nginx、Apache、Tomcat等Web服务以及其他服务,如NFS、Redis非关系数据库等。

建议学习Bash编程。

Shell脚本主要是指令和逻辑判断的组合,关键点是如何对指令进行组合以及对指令的常用选项进行灵活驾驭。

建议把man bash的内容读完,然后读一下help指令的Bash内置帮助文档。 - 查看man指令基础信息,代码如下。

1   [root@laohan-zabbix-server ~]# which man
2   /usr/bin/man
3   [root@laohan-zabbix-server ~]# rpm -qf /usr/bin/man
4   man-db-2.6.3-11.el7.x86_64
5   [root@laohan-zabbix-server ~]# rpm -ql man-db | head
6   /etc/cron.daily/man-db.cron
7   /etc/man_db.conf
8   /etc/sysconfig/man-db
9   /usr/bin/apropos
10  /usr/bin/catman
11  /usr/bin/lexgrog
12  /usr/bin/man
13  /usr/bin/mandb
14  /usr/bin/manpath
15  /usr/bin/whatis

上述代码第1行的作用是查看man指令在当前系统中的路径存放位置,第3行的作用是查看man指令归属于哪个rpm软件包,第5行的作用是查看man-db软件包安装了哪些组件,由于篇幅的原因,此处使用head指令获取前10行的内容。

使用man查看ls指令的帮助信息,执行man bash指令后,显示结果如图1-10所示。

图1-10 显示结果

使用help获取bash内置指令的帮助文档,help一般结合type指令使用,代码如下。

1   [root@laohan-zabbix-server ~]# type cd
2   cd is a shell builtin
3   [root@laohan-zabbix-server ~]# help cd
4   cd: cd [-L|[-P [-e]]] [dir]
5        Change the shell working directory.
6        
7        Change the current directory to DIR.  The default DIR is the value of the
8        HOME shell variable.
9        
10       The variable CDPATH defines the search path for the directory containing
11       DIR.  Alternative directory names in CDPATH are separated by a colon (:).
12       A null directory name is the same as the current directory.  If DIR begins
13       with a slash (/), then CDPATH is not used.
14       
15       If the directory is not found, and the shell option `cdable_vars' is set,
16       the word is assumed to be  a variable name.  If that variable has a value,
17       its value is used for DIR.
18       
19       Options:
20            -L  force symbolic links to be followed
21            -P  use the physical directory structure without following symbolic
22            links
23            -e  if the -P option is supplied, and the current working directory
24            cannot be determined successfully, exit with a non-zero status
25       
26       The default is to follow symbolic links, as if `-L' were specified.
27       
28       Exit Status:
29       Returns 0 if the directory is changed, and if $PWD is set successfully when
30       -P is used; non-zero otherwise.

第1行代码的作用是使用type指令确定cd指令是否为内置指令,第2行代码的作用是返回结果中的builtin内容,表示cd指令为bash内置指令,第3行代码的作用是使用help指令查看cd指令的帮助信息,第4~30行代码的作用是输出help cd指令的全部内容。

通过Shell脚本相关指令的组合,可以帮助Linux系统管理员解决日常运维中的常见问题,是日常Linux运维经常用到的技术。

【实例1-18】在当前目录下查找扩展名为.log的文件,目录递归深度为3

find ./ -maxdepth 3 -name "*.log"

上述代码为在当前目录下查找扩展名为.log的文件,目录层级为3级,输出结果如下。

[root@laohan_httpd_server ~]# find ./ -maxdepth 3 -name "*.log" 
./nginx.log
./cut.log
./boxin_2.log
./ntfs-3g_ntfsprogs-2017.3.23/config.log
./boxin_1.log
./cat.log
./100M.log
./ls.log
./boxin_3.log
./cat_2.log
./a/ifstat-1.1/config.log

后台运行某个进程时,可通过如下指令实时查看日志输出信息。

[root@laohan_httpd_server ~]# tailf /data/app/nginx/logs/access.log 
14.215.176.6 - - [11/Sep/2017:21:39:14 +0800] "GET /wp-content/themes/twentyseventeen/assets/js/skip-link-focus-fix.js?ver=1.0 HTTP/1.1" 200 683 "http://www.booxin.vip/" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0"
14.215.176.5 - - [11/Sep/2017:21:39:14 +0800] "GET /wp-content/themes/twentyseventeen/assets/js/navigation.js?ver=1.0 HTTP/1.1" 200 3754 "http://www.booxin.vip/" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0"
14.215.176.6 - - [11/Sep/2017:21:39:14 +0800] "GET /wp-content/themes/twentyseventeen/assets/js/global.js?ver=1.0 HTTP/1.1" 200 7682 "http://www.booxin.vip/" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0"
14.215.176.11 - - [11/Sep/2017:21:39:14 +0800] "GET /wp-includes/js/jquery/jquery.js?ver=1.12.4 HTTP/1.1" 200 97184 "http://www.booxin.vip/" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0"
14.215.176.6 - - [11/Sep/2017:21:39:14 +0800] "GET /wp-content/themes/twentyseventeen/assets/js/jquery.scrollTo.js?ver=2.1.2 HTTP/1.1" 200 5836 "http://www.booxin.vip/" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0"
14.215.176.8 - - [11/Sep/2017:21:39:14 +0800] "GET /wp-includes/js/wp-embed.min.js?ver=4.8.1 HTTP/1.1" 200 1398 "http://www.booxin.vip/" "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:43.0) Gecko/20100101 Firefox/43.0"
123.125.143.12 - - [11/Sep/2017:21:42:02 +0800] "GET /wp-content/themes/twentyseventeen/assets/images/header.jpg HTTP/1.1" 403 166 "-" "Mozilla/5.0 (compatible; pycurl)"
106.2.124.185 - - [11/Sep/2017:21:53:22 +0800] "\x04\x01\x00\x00\x00\x00\x00\x00\x00" 400 170 "-" "-"
106.2.124.185 - - [11/Sep/2017:21:53:22 +0800] "\x05\x03\x00\x01\x02" 400 170 "-" "-"
149.202.175.8 - - [11/Sep/2017:22:31:43 +0800] "GET /oneshotimage1?COUNTER HTTP/1.1" 404 568 "-" "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36"

如下代码按文件大小增序输出当前目录下的文件名及其文件大小(单位为字节)。

[root@laohan_httpd_server ~]# ls -lS | sed '1d' | sort -rn -k5 | awk '{printf "%15s %10s\n", $9,$5}'|head
      io_test1 3480911872
      html.zip  46984328
     nginx.zip   2360843
ntfs-3g_ntfsprogs-2017.3.23.tgz    1259054
         nmon    402146
     boxin.pdf     99312
Shell-programmer_170902_2146.nmon      53529
Shell-programmer_170902_2158.nmon      37160
   Shell.tar.gz     34209
      ping.txt     10319

如上实例所示,Shell脚本的优势如下。

Shell天生就与操作系统有着密不可分的关系。

Shell具有简单、处理第三方应用、贴近操作系统底层、特别适合做系统管理等优点。因此,学好Linux的第一步是掌握Linux指令和Shell这个强大的工具。

读者也可以使用如下代码进行练习,掌握tail指令的使用技巧。

[root@laohan-Shell-1 ~]# touch laohan.log
[root@laohan-Shell-1 ~]# while true ; do echo $(date +%F-%T) >> laohan.log && sleep 3 ;done

开启另外一个终端,使用如下代码,观察执行结果。

[root@laohan-Shell-1 ~]# tailf laohan.log 
2019-12-06-22:36:34
2019-12-06-22:36:37
2019-12-06-22:36:40
2019-12-06-22:36:43
2019-12-06-22:36:46
2019-12-06-22:36:49
2019-12-06-22:36:52
2019-12-06-22:36:55
2019-12-06-22:36:58
2019-12-06-22:37:01
2019-12-06-22:37:04
2019-12-06-22:37:07
2019-12-06-22:37:10
2019-12-06-22:37:13

上述执行结果中,每隔3s会向laohan.log输出系统的当前时间。

1.history指令基本语法

history指令基本语法如下所示。

history [选项] [历史指令保存文件]

2.掌握history指令调用及常用选项

history指令调用及常用选项如下所示。

-c选项清空历史指令。

-w选项把缓存中的历史指令写入历史指令保存文件~/.bash_history。

历史指令默认会保存1000条,可以在环境变量配置文件/etc/profile中进行修改历史指令的调用。

使用向上、向下箭头调用以前的历史指令。

使用“!n”重复执行第n条历史指令。

使用“!!”重复执行上一条指令。

使用“!字串”重复执行最后一条以该字串开头的指令。

3.history记录应用扩展之用户审计

Linux系统管理员需要知道一台服务器上有哪些用户登录过,在服务器上执行了哪些指令,做了哪些事情,因此就需要记录服务器上所有登录用户的历史操作记录,并将其记录到某个文件中方便对服务器做安全审计。记录用户执行的历史指令,代码如下。

1 [root@laohan_shell_c77 ~]# tail -24 /etc/profile
2 # hanyw 2020 set history audit
3 export PS1='[\u@\h \W]\$ '
4 USER_IP=$(who -u am i 2>/dev/null| awk '{print $NF}'|sed -e 's/[()]//g')
5 if [ "$USER_IP" = "" ]
6   then
7   USER_IP=`hostname`
8 fi
9
10 if [ ! -d /var/log/history ]
11  then
12  mkdir /var/log/history
13  chmod 777 /var/log/history
14 fi
15 
16 if [ ! -d /var/log/history/${LOGNAME} ]
17  then
18  mkdir /var/log/history/${LOGNAME}
19  chmod 300 /var/log/history/${LOGNAME}
20 fi
21 
22 export HISTSIZE=100000
23 DT=$(date +"%Y%m%d_%H%M%S")
24 export HISTFILE="/var/log/history/${LOGNAME}/${USER_IP}_history.$DT"
25 export HISTTIMEFORMAT="%F %T `whoami` "

上述代码第2~25行记录当前登录操作系统的用户执行过的指令,执行结果如下所示。

[root@laohan_shell_c77 ~]# tree -L 2 /var/log/history/
/var/log/history/
├── handuoduo
│   ├── 192.168.2.4_history.20191215_215001
│   ├── 192.168.2.4_history.20191215_215136
│   ├── 192.168.2.4_history.20191215_215340
│   └── 192.168.2.4_history.20191215_215706
└── root
   ├── 192.168.2.4\ history.20191215_214446
   ├── 192.168.2.4\ history.20191215_214509
   ├── 192.168.2.4\ history.20191215_214544
   ├── 192.168.2.4_history.20191215_214750
   ├── 192.168.2.4_history.20191215_215330
   ├── 192.168.2.4_history.20191215_215633
   └── 192.168.2.4_history.20191215_215923

2 directories, 11 files

查看某一文件的内容,如下所示。

[root@laohan_shell_c77 ~]# tail /var/log/history/root/192.168.2.4_history.20191215_215923
#1576418336
vim /etc/profile
#1576418363
. /etc/profile
#1576418365
uptime
#1576418368
echo 1 2 3 
#1576418370
ll

上述代码中以“#”开头的为时间戳,可以使用如下指令转换为本地时间。

[root@laohan_shell_c77 ~]# date -d @1576418370
2019年 12月 15日星期日 21:59:30 CST

在Bash中,指令与文件补全是非常方便与常用的功能,我们只要在输入指令或文件时,按“Tab”键就会自动补全。

下列代码中,输入ls后,连续按两次“Tab”键,即可列出当前操作系统中可供使用的所有程序和指令。

[root@laohan_shell_c77 ~]# ls
ls       lsattr   lsblk    lscpu    lsinitrd  lsipc    lslocks   lslogins lsmem lsmod     lsns      lsscsi
[root@laohan_shell_c77 ~]# ls

下列代码中,用户输入ls /opt/mysql/mysql-5.指令后,连续按两次“Tab”键,即可列出当前目录下所有以mysql-5.开头的文件。

[root@laohan_shell_c77 ~]# ls /opt/mysql/mysql-5.
mysql-5.5.log  mysql-5.6.log mysql-5.7.log

1.设置指令别名

设置指令别名(简称别名)语法如下。

alias 别名='原指令|原指令及其选项'

设置ls别名的代码如下。

alias ls='ls -lhrt --full-time'

设置ls指令别名为ls -lhrt --full-time,代码如下。

[root@laohan_httpd_server ~]# alias ls='ls -lhrt --full-time'
[root@laohan_httpd_server ~]# ls
total 28K
-rw-r--r--. 1 root root 3.4K 2018-07-22 20:43:39.104999991 +0800 install.log.syslog
-rw-r--r--. 1 root root 8.7K 2018-07-22 20:44:08.428999988 +0800 install.log
-rw-------. 1 root root 1.1K 2018-07-22 20:44:09.972999988 +0800 anaconda-ks.cfg
-rwxr-xr-x  1 root root   64 2018-07-22 21:15:03.020584717 +0800 cp_file.sh
drwxr-xr-x  3 root root 4.0K 2018-07-22 21:56:49.680551352 +0800 Shell-scripts

2.查询别名

查询当前操作系统设置的别名,代码如下。

alias

上述代码查询当前操作系统设置的别名,执行结果如下。

[root@laohan_httpd_server ~]# alias
alias cp='cp -i'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls -lhrt --full-time'
alias mv='mv -i'
alias rm='rm -i'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

3.指令执行顺序

第1顺位执行时用绝对路径或相对路径执行的指令。

第2顺位执行别名。

第3顺位执行Bash的内部指令。

第4顺位执行按照$PATH环境变量定义的目录查找顺序找到的第1条指令。

4.设置别名永久生效

设置别名永久生效,可以修改/etc/profile文件或当前用户主目录下的.bashrc隐藏文件,代码如下。

[root@laohan-Shell-1 ~]# nl .bashrc 
1   # .bashrc

2   # User specific aliases and functions

3   alias rm='rm -i'
4   alias cp='cp -i'
5   alias mv='mv -i'
6   alias ls="ls -lhrt --full-time"

7   # Source global definitions
8   if [ -f /etc/bashrc ]; then
9       . /etc/bashrc
10  fi

上述代码中第6行设置了ls指令的别名。执行如下指令使别名设置立即生效。

[root@laohan-Shell-1 ~]# . .bashrc 
[root@laohan-Shell-1 ~]# source .bashrc

确认别名设置是否立即生效,代码如下。

[root@laohan-Shell-1 ~]# alias 
alias cp='cp -i'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls -lhrt --full-time'
alias mv='mv -i'
alias rm='rm -i'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

执行ls别名,执行结果如下所示。

[root@laohan-Shell-1 ~]# ls
1总用量 8.0K
2 drwxr-xr-x  2 root root  166 2019-12-06 16:56:00.140261747 +0800 chapter-1
3 -rw-r--r--  1 root root 1.6K 2019-12-06 22:40:32.167135504 +0800 laohan.log
4 -rw-------. 1 root root 1.2K 2019-12-06 22:44:43.097672334 +0800 anaconda-ks.cfg
5 -rw-r--r--  1 root root    0 2019-12-06 22:53:10.138928140 +0800 a.png
6 [root@laohan-Shell-1 ~]# ls -lhrt --full-time
7总用量 8.0K
8 drwxr-xr-x  2 root root  166 2019-12-06 16:56:00.140261747 +0800 chapter-1
9 -rw-r--r--  1 root root 1.6K 2019-12-06 22:40:32.167135504 +0800 laohan.log
10 -rw-------. 1 root root 1.2K 2019-12-06 22:44:43.097672334 +0800 anaconda-ks.cfg
11 -rw-r--r--  1 root root    0 2019-12-06 22:53:10.138928140 +0800 a.png

上述代码第1~5行与第7~11行执行结果完全一致,表明ls别名设置永久生效。

5.删除别名

删除别名语法如下。

unalias 别名

如删除ls别名,代码如下。

unalias ls

查看ls别名信息,代码如下。

alias  |grep ls

删除ls别名,代码如下。

[root@laohan_httpd_server ~]# alias  |grep ls
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls -lhrt --full-time'
[root@laohan_httpd_server ~]# unalias ls
[root@laohan_httpd_server ~]# alias  |grep ls
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'

6.多条指令顺序执行

多条指令执行时,各条指令之间使用“;”作为分隔符,代码如下。

ls -lhrtS --full-time; whoami;id

上述代码查看当前路径下文件的详细信息:文件大小、时间戳。

查看当前终端登录用户身份,代码如下。

whoami

查看当前终端登录用户ID数字标识,代码如下。

id

各指令顺序执行,执行结果如下。

[root@laohan_httpd_server ~]# ls -lhrtS --full-time ; whoami ; id
total 28K
-rwxr-xr-x  1 root root   64 2018-07-22 21:15:03.020584717 +0800 cp_file.sh
-rw-------. 1 root root 1.1K 2018-07-22 20:44:09.972999988 +0800 anaconda-ks.cfg
-rw-r--r--. 1 root root 3.4K 2018-07-22 20:43:39.104999991 +0800 install.log.syslog
drwxr-xr-x  3 root root 4.0K 2018-07-22 21:56:49.680551352 +0800 Shell-scripts
-rw-r--r--. 1 root root 8.7K 2018-07-22 20:44:08.428999988 +0800 install.log
root
uid=0(root) gid=0(root) groups=0(root)

Bash常用快捷键如表1-1所示。

表1-1  Bash常用快捷键

快捷键

说明

Ctrl+C

强制终止当前的指令或前台运行的程序

Ctrl+Y

粘贴“Ctrl+U”或“Ctrl+K”剪切的内容

Ctrl+Z

暂停,并放入后台

Ctrl+S

暂停屏幕输出

Ctrl+Q

恢复屏幕输出

Ctrl-A

相当于“Home”键,用于将光标定位到本行最前面

Ctrl-E

相当于“End”键,将光标移动到本行末尾

Ctrl-B

相当于左箭头键,用于将光标向左移动一格

Ctrl-F

相当于右箭头键,用于将光标向右移动一格

Ctrl-D

相当于“Delete”键,即删除光标所在处的字符

Ctrl-K

删除从光标处开始到结尾处的所有字符

Ctrl-L

清屏,相当于Clear命令

Ctrl-R

进入历史命令查找状态,然后输入几个关键字符,就可以找到使用过的命令

Ctrl-U

删除从光标开始到行首的所有字符

Ctrl-H

删除光标左侧的一个字符

Ctrl-W

删除当前光标左侧的一个单词

Ctrl-P

相当于上箭头键,即显示上一个命令

Ctrl-N

相当于下箭头键,即显示下一个命令

Ctrl-T

颠倒光标所处字符和前一个字符的位置

Ctrl-J

相当于“Enter”键

Alt-.

提取历史命令中的最后一个单词

Alt-BackSpace

删除本行所有的内容

Alt-C

将当前光标处的字符变成大写,同时本光标所在单词的后续字符都变成小写

Alt-L

将光标所在单词及所在单词的后续字符都变成小写

Alt-U

将光标所在单词及之后的所有字符变成大写

1.基础知识

Linux的设计思想是一切皆文件,如C++源文件、视频文件、Shell脚本、可执行文件等都是文件,键盘、显示器、鼠标等硬件设备也都是文件。开发者可以像写文件那样通过网络传输数据,也可以通过/proc/的文件查看进程的资源使用情况。

一个Linux进程可以打开成百上千个文件,为了表示和区分已经打开的文件,Linux会给每个文件分配一个编号(文件描述符),这个编号是一个整数,用于标明每一个被进程打开的文件和socket。第一个打开的文件是0,第二个是1,第三个是2,以此类推。

0、1、2被称为文件描述符,文件描述符如表1-2所示。

表1-2  文件描述符

文件描述符

用途

POSIX名称

stdio流

0

标准输入

STDIN_FILENO

stdin

1

标准输出

STDOUT_FILENO

stdout

2

标准错误

STDERR_FILENO

stderr

2.基本应用

文件描述符帮助应用找到需要的文件,而文件的打开模式等上下文信息存储在文件对象中,这个对象直接与文件描述符关联。

POSIX 已经定义了 STDIN_FILENO、STDOUT_FILENO 以及 STDERR_FILENO 这 3个值,也就是0、1、2。这3个文件描述符是每个进程都有的。这也解释了为什么每个进程都有编号为0、1、2的文件而不会与其他进程冲突。

3.系统级限制

在编写关于文件操作的或者网络通信的软件时,开发者可能会遇到“Too many open files”(打开的文件数量过多)的问题。这主要是因为文件描述符是操作系统的一项宝贵的资源,虽然理论上系统内存有多大容量就可以打开多少个对应的文件描述符,但是在实际实现过程中,内核会做相应的处理,一般最大打开文件数是操作系统内存的10%(以KB来计算),这被称为系统级限制。

查看系统的最大打开文件数,代码如下。

[root@zabbix_server ~]# sysctl -a |grep fs.file-max
sysctl: reading key "net.ipv6.conf.all.stable_secret"
sysctl: reading key "net.ipv6.conf.default.stable_secret"
sysctl: reading key "net.ipv6.conf.eth0.stable_secret"
fs.file-max = 183930
sysctl: reading key "net.ipv6.conf.lo.stable_secret"

与此同时,内核为了不让某一个进程消耗掉所有的文件资源,也会对单个进程最大打开文件数做默认值处理(这被称为用户级限制),默认值一般是1024,可以使用ulimit-n指令查看。在Web服务器中,更改系统默认文件描述符的最大值是优化服务器最常见的方式之一。

注意:每个系统对文件描述符的个数都有限制。Linux操作系统配置ulimit也是为了调大系统的打开文件个数,网络服务器需要同时处理成千上万个请求。

4.文件描述符与打开的文件之间的关系

每一个文件描述符会与一个打开的文件相对应,同时,不同的文件描述符也可能会指向同一个文件。相同的文件可以被不同的进程打开,也可以在同一个进程中被多次打开。系统为每一个进程维护了一个文件描述符表。该表的值都是从0开始的,所以在不同的进程中会有相同的文件描述符。这种情况下相同文件描述符可能指向同一个文件,也可能指向不同的文件,具体情况要具体分析。要理解其具体情况如何,需要查看由内核维护的3个数据结构表。

进程级的文件描述符表。

系统级的文件描述符表。

文件系统的i-node表。

进程级的文件描述符表的每一条目记录了单个文件描述符的相关信息,如下所示。

控制文件描述符操作的一组标志。(目前,此类标志仅定义了一个,即close-on-exec标志。)

对打开文件句柄的引用。

内核对所有打开的文件维护有一个系统级的文件描述符表。有时,也称之为打开文件表(Open File Table),表中各条目称为打开文件句柄(Open File Handle)。一个打开文件句柄存储了与打开文件相关的全部信息,如下所示。

当前文件偏移量(调用read()和write()时更新,或使用lseek()直接修改)。

打开文件时所使用的状态标识(即open()的flags参数)。

文件访问模式(如调用open()时所设置的只读模式、只写模式或读写模式)。

与信号驱动相关的设置。

对该文件i-node对象的引用。

文件类型(例如:常规文件、套接字或FIFO)和访问权限。

一个指针,指向该文件持有的锁列表。

文件的各种属性,包括文件大小以及与不同类型操作相关的时间戳。

1.Squid缓存服务器

代理服务器英文全称是Proxy Server,其功能是代理网络用户获取网络信息。

Squid是一个缓存Internet数据的软件,其接收用户的下载申请,并自动处理所下载的数据。当一个用户想要下载一个主页时,可以向Squid发出一个申请,让Squid代替其进行下载,然后Squid连接所申请网站并请求该主页,接着把该主页传给用户同时保留一个备份,当别的用户申请同样的主页时,Squid把保存的备份立即传给用户,使用户觉得速度相当快。Squid可以代理HTTP、FTP、GOPHER、SSL和WAIS等协议,并且Squid可以自动地进行处理,可以根据自己的需要进行设置,以过滤掉不想要的东西。

文件描述符的限制会对Squid产生极大的性能影响。

当Squid用完所有的文件描述符后,它不能接收用户新的连接。即用完文件描述符导致拒绝服务,直到一部分当前请求完成,相应的文件和socket被关闭,Squid才能接收新请求。当Squid发现文件描述符短缺时,会发出类似“WARNING! Your cache is running out of filedescriptors”的警告。

安装Squid过程中,执行./configure编译选项时,会根据系统中ulimit -n的输出值,来判断其最大可用的文件描述符的值。大多数情况下,1024个文件描述符就足够了,业务繁忙的Squid可能需要设置为4096个,甚至更多,修改方法如下所示。

通常情况下,应当将文件描述符最大值至少设置为ulimit -n的2倍。

第一步:将ulimit -n的数值增大,具体实现如下所示。

直接执行ulimit -HSn 32768,但重启后会失效。

编辑/etc/rc.local,加入一行ulimit -HSn 32768,重启后生效。

编辑/etc/init.d/squid,在该脚本执行start动作前,加入ulimit -HSn 32768。

编辑/etc/security/limits.conf,重启后生效。

cache           soft    nofile          32768
cache           hard    nofile          32768

第二步:将Squid的文件描述符数值增大,具体实现如下所示。

修改/etc/squid/squid.conf,将max_filedesc设置为32768。

编译安装Squid时,在执行configure前,先在指令行执行一次ulimit -HSn 32768。

编译安装Squid时,在执行configure时,加上--with-maxfd=32768参数。

2.查看文件描述符

Linux操作系统下最大文件描述符的限制有两个方面,一个是系统级限制,另一个则是用户级限制。查看Linux文件描述符的指令,如下所示。

查看系统级限制,代码如下。

[root@zabbix_server ~]# sysctl -a |grep -i file-max --color
sysctl: reading key "net.ipv6.conf.all.stable_secret"
sysctl: reading key "net.ipv6.conf.default.stable_secret"
sysctl: reading key "net.ipv6.conf.eth0.stable_secret"
sysctl: reading key "net.ipv6.conf.lo.stable_secret"
fs.file-max = 183930
[root@zabbix_server ~]# cat /proc/sys/fs/file-nr 
2144  0  183930

查看用户级限制,代码如下。

[root@zabbix_server ~]# ulimit -n
100001
[root@zabbix_server ~]#

系统级限制:sysctl指令和proc文件系统中查看到的数值是一样的,这属于系统级限制,它限制所有用户打开文件描述符的总和。

用户级限制:ulimit指令看到的是用户级的最大文件描述符限制,也就是说每一个用户登录后执行的程序占用文件描述符的总数不能超过这个限制。

3.修改文件描述符

在Bash中,可以使用ulimit指令,它提供对Shell和该Shell启动的进程的可用资源控制。这主要包括打开文件描述符数量、用户的最大进程数量、coredump文件的大小等。

修改系统级限制,代码如下。

[root@zabbix_server ~]#  sysctl -wfs.file-max=400000
fs.file-max = 400000
[root@zabbix_server ~]#  echo350000 > /proc/sys/fs/file-max
[root@zabbix_server ~]#  cat /proc/sys/fs/file-max
350000

上述代码是临时修改文件描述符。若要永久修改,则需要把“fs.file-max=400000”选项添加到/etc/sysctl.conf文件中,并执行sysctl -p指令,使配置立即生效。

修改用户级限制,代码如下。

[root@zabbix_server ~]#  ulimit-SHn 10240
[root@zabbix_server ~]#  ulimit  -n
10240
[root@zabbix_server ~]#

以上的修改只对当前会话起作用,是临时性的。如果需要永久修改,代码如下。

[root@zabbix_server ~]#  grep -vE'^$|^#' /etc/security/limits.conf
*             hard nofile                4096

默认配置文件中只有hard选项,soft表示当前系统生效的设置值,hard表示系统中所能设定的最大值,代码如下。

[root@zabbix_server ~]#  grep -vE'^$|^#' /etc/security/limits.conf
*     hard        nofile      10240
*     soft        nofile      10240

注意:设置文件描述符时,soft<=hard soft的限制数量要低于hard限制数量。

4.不同CentOS配置文件描述符异同点分析

(1)CentOS 6配置文件描述符。

在CentOS 6中,资源限制可以在/etc/security/limits.conf文件中配置,也可以在/etc/ security/limits.d/文件中配置。操作系统首先加载limits.conf文件中的配置,然后按照文件名的英文字母顺序加载limits.d目录下的配置文件,最后加载配置覆盖之前的配置,配置实例如下所示。

*     soft   nofile    65535
*     hard   nofile    65535
*     soft   nproc     65535
*     hard   nproc     65535

(2)CentOS 7配置文件描述符。

CentOS 7中,使用systemd替代了之前的SysV,因此/etc/security/limits.conf文件的配置作用域被缩小了。limits.conf只适用于通过PAM认证登录用户的资源限制,它对systemd service的资源限制不生效。

关于登录用户的限制,通过/etc/security/limits.conf和limits.d来配置即可。对于systemd service的资源限制,可以通过全局的配置,放在/etc/systemd/system.conf和/etc/systemd/user.conf文件中。同时,也会加载两个对应的目录中的所有.conf文件(默认不存在),代码如下。

[root@zabbix_server ~]# cat /etc/redhat-release 
CentOS Linux release 7.6.1810 (Core) 
[root@zabbix_server ~]# ll /etc/systemd/system.conf.d/*.conf
ls: cannot access /etc/systemd/system.conf.d/*.conf: No such file or directory
[root@zabbix_server ~]# ll /etc/systemd/user.conf.d/*.conf
ls: cannot access /etc/systemd/user.conf.d/*.conf: No such file or directory

system.conf是系统实例使用的。

user.conf是用户实例使用的。

一般的Service,使用system.conf中的配置即可,systemd.conf.d/*.conf中的配置会覆盖system.conf,代码如下。

DefaultLimitCORE=infinity
DefaultLimitNOFILE=65535
DefaultLimitNPROC=65535
[root@zabbix_server ~]# grep "#DefaultLimitCORE=\|#DefaultLimitNOFILE=\|#DefaultLimitNPROC=" /etc/systemd/system.conf
#DefaultLimitCORE=
#DefaultLimitNOFILE=
#DefaultLimitNPROC=

注意:修改了system.conf后,需要重启系统才会生效。

设置Service,以Nginx为例,编辑/usr/lib/systemd/system/nginx.serv或者/usr/lib/systemd/ system/nginx.service.d/my-limit.conf 文件,配置如下。

[Service]
LimitCORE=infinity
LimitNOFILE=65535
LimitNPROC=65535

然后执行如下指令,才能生效。

systemctl daemon-reload
systemctl restart nginx.service

注意:CentOS 7自带的/etc/security/limits.d/20-nproc.conf文件,内容如下所示。

[root@zabbix_server ~]# nl /etc/security/limits.d/20-nproc.conf
1   # Default limit for number of user's processes to prevent
2   # accidental fork bombs.
3   # See rhbz #432903 for reasoning.

4   *        soft    nproc    4096
5   #root      soft   nproc    unlimited

上述代码第4行设置了非root用户的最大进程数为4096。如果limits.conf设置没生效,可能是因为被limits.d目录中的配置文件覆盖了。

5.文件描述符其他知识

(1)获取系统打开的文件描述符数量,代码如下。

[root@laohan-Shell-1 ~]# cat /proc/sys/fs/file-nr
20800   0  183930

第1列20800为已分配的文件描述符数量。

第2列0为已分配但尚未使用的文件描述符数量。

第3列183930为系统可用的最大文件描述符数量。

已用文件描述符数量=已分配的文件描述符数量−已分配但尚未使用的文件描述符数量。注意,这些数值是系统级的。

(2)获取进程打开的文件描述符数量,代码如下。

[root@laohan-Shell-1 ~]# ll /proc/919/fd/
总用量 0
lrwx------ 1 root root 64 2019-12-07 18:41:07.303274498 +0800 4 -> socket:[19776]
lrwx------ 1 root root 64 2019-12-07 18:41:07.303274498 +0800 3 -> socket:[19767]
lrwx------ 1 root root 64 2019-12-07 18:41:07.303274498 +0800 2 -> socket:[19651]
lrwx------ 1 root root 64 2019-12-07 18:41:07.303274498 +0800 1 -> socket:[19651]
lr-x------ 1 root root 64 2019-12-07 18:41:07.303274498 +0800 0 -> /dev/null

可以看到SSH进程用了5个文件描述符。

(3)更改文件描述符限制——用户或进程级别。

当碰到“Too many open files”错误时,需要增加文件描述符的限制数量,代码如下。

[root@laohan-Shell-1 ~]# ulimit -n
 1024
[root@laohan-Shell-1 ~]# ulimit -n 10240
[root@laohan-Shell-1 ~]# ulimit -n
    10240

注意,使用ulimit指令更改后只是在当前会话生效,当退出当前会话重新登录后又会回到默认值1024。要永久更改可以修改文件 /etc/security/limits.conf,代码如下。

[root@laohan-Shell-1 ~]#vi /etc/security/limits.conf

加入如下指令。

"laohan hard nofile 10240"

laohan:用户名,即允许laohan使用ulimit指令更改文件描述符限制,最大值不超过10240,更改后laohan用户的每一个进程(以abc用户运行的进程)可打开的文件描述符数量为10240。

hard:限制类型,有soft和hard两种,达到soft限制会在系统的日志(一般为/var/log/messages)里面记录一条告警日志,但不影响使用。 -   更改后,退出终端重新登录,用ulimit查看是否生效。如果没生效,可以在laohan用户的.bash_profile文件中加上ulimit -n 10240,这样用户laohan每次登录时都会将文件描述符最大值更改为10240,代码如下。

   [root@laohan-Shell-1 ~]#echo "ulimit -n 10240" >> /home/abc/ .bash_profile
    10240
   [root@laohan-Shell-1 ~]# su - abc
   [abc@localhost ~]$ ulimit -n
    10240

(4)更改文件描述符限制——系统级别。

将整个操作系统可以使用的文件描述符数量更改为102400,代码如下。

     [root@laohan-Shell-1 ~]# echo "102400" > /proc/sys/fs/file-max
     [root@laohan-Shell-1 ~]# cat /proc/sys/fs/file-nr
     2080   0      102400

使用上述修改方式,系统重启后会恢复到默认值。要永久更改可以在sysctl.conf文件中加上“fs.file-max = 102400”,代码如下。

[root@laohan-Shell-1 ~]# echo "fs.file-max = 102400" >> /etc/sysctl.conf

(5)获取打开的文件数量。

Linux中一切皆为文件,使用lsof(list open files)指令即可知道系统或应用打开了哪些文件。

(6)获取整个系统打开的文件数量,代码如下。

[root@laohan-Shell-1 ~]# lsof |wc -l
2292

(7)获取某个用户打开的文件数量,代码如下。

    [root@laohan-Shell-1 ~]# lsof -u laohan |wc -l
       190

(8)获取某个程序打开的文件数量,代码如下。

[root@laohan-Shell-1 ~]# pidof sshd
1189 919
[root@laohan-Shell-1 ~]# lsof -p 919 | wc -l
58

(9)查看进程打开的文件数量和文件描述符,代码如下。

[abc@localhost ~]$ lsof -p 3330
COMMAND  PID USER  FD   TYPE DEVICE SIZE/OFF   NODE NAME
vim    3555  abc  cwd   DIR  253,0     4096 923587 /home/abc
vim    3555  abc  rtd   DIR  253,0     4096      2 /
vim    3555  abc  txt   REG  253,0  1971360 287900 /usr/bin/vim
vim    3555  abc  mem   REG  253,0   155696    846 /lib64/ld-2.12.so
vim    3555  abc  mem   REG  253,0    26104 281208 /usr/lib64/libgpm.so.2.1.0
vim    3555  abc  mem   REG  253,0  1912928    847 /lib64/libc-2.12.so
vim    3555  abc  mem   REG  253,0    22536    852 /lib64/libdl-2.12.so
vim    3555  abc  mem   REG  253,0   145672    859 /lib64/libpthread-2.12.so
vim    3555  abc  mem   REG  253,0   598816    848 /lib64/libm-2.12.so
vim    3555  abc  mem   REG  253,0   124624    857 /lib64/libseLinux.so.1
vim    3555  abc  mem   REG  253,0   113904    856 /lib64/libresolv-2.12.so
vim    3555  abc  mem   REG  253,0  1489600 406575 /usr/lib64/perl5/CORE/libperl.so
vim    3555  abc  mem   REG  253,0   142504     96 /lib64/libncurses.so.5.7
vim    3555  abc  mem   REG  253,0    34336    890 /lib64/libacl.so.1.1.0
vim    3555  abc  mem   REG  253,0    43392    869 /lib64/libcrypt-2.12.so
vim    3555  abc  mem   REG  253,0   387880    868 /lib64/libfreebl3.so
vim    3555  abc  mem   REG  253,0  1753952 271694 /usr/lib64/libpython2.6.so.1.0
vim    3555  abc  mem   REG  253,0    17520    886 /lib64/libutil-2.12.so
vim    3555  abc  mem   REG  253,0   138280    826 /lib64/libtinfo.so.5.7
vim    3555  abc  mem   REG  253,0    20280    887 /lib64/libattr.so.1.1.0
vim    3555  abc  mem   REG  253,0   116136    889 /lib64/libnsl-2.12.so
vim    3555  abc  mem   REG  253,0    61624     42 /lib64/libnss_files-2.12.so
vim    3555  abc  mem   REG  253,0    26050 265158 /usr/lib64/gconv/gconv-modules.cache
vim    3555  abc  mem   REG  253,0   124855 528606 /usr/share/vim/vim72/lang/zh_CN/LC_MESSAGES/vim.mo
vim    3555  abc  mem   REG  253,0   135533 528604 /usr/share/vim/vim72/lang/zh_CN.UTF-8/LC_MESSAGES/vim.mo
vim    3555  abc  mem   REG  253,0 99158752 264902 /usr/lib/locale/locale-archive
vim    3555  abc    0u  CHR  136,0     0t0     3 /dev/pts/0
vim    3555  abc    1u  CHR  136,0     0t0     3 /dev/pts/0
vim    3555  abc    2u  CHR  136,0     0t0     3 /dev/pts/0
vim    3555  abc    4u  REG  253,0    4096 923589 /home/abc/.bash_profile.swp

可看到运行SSHD时,打开了很多个文件,但文件描述符只有后面4个。

(10)进程打开的文件描述符与文件。

“Too many open files”错误不是说打开的文件过多,而是打开的文件描述符数量已达到了限制,可以用man ulimit查看帮助。

[root@laohan-Shell-1 ~]# man ulimit > man.info
[root@laohan-Shell-1 ~]# grep -n  maximum man.info 
937:           -b    The maximum socket buffer size
938:           -c    The maximum size of core files created
939:           -d    The maximum size of a process's data segment
940:           -e    The maximum scheduling priority ("nice")
941:           -f    The maximum size of files written by the Shell and its children
942:           -i    The maximum number of pending signals
943:           -l    The maximum size that may be locked into memory
944:           -m    The maximum resident set size (many systems do not honor this limit)
945:           -n    The maximum number of open file descriptors (most systems do not allow this value to be set)
947:           -q    The maximum number of bytes in POSIX message queues
948:           -r    The maximum real-time scheduling priority
949:           -s    The maximum stack size
950:           -t    The maximum amount of cpu time in seconds
951:           -u    The maximum number of processes available to a single user
952:           -v    The maximum amount of virtual memory available to the Shell and, on some systems, to its children
953:           -x    The maximum number of file locks
954:           -T    The maximum number of threads

上述代码中,第945行表示用ulimit -n xxx更改的是文件描述符而不是文件的最大值。

(11)复盘“Too many open files”错误。

打开Nginx进程,代码如下。

[root@laohan-Shell-1 ~]# systemctl start nginx
[root@laohan-Shell-1 ~]# 
[root@laohan-Shell-1 ~]# netstat -ntpl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address    Foreign Address   State   PID/Program name
tcp      0     0 127.0.0.1:25        0.0.0.0:*         LISTEN  1004/master  
tcp      0     0 0.0.0.0:80          0.0.0.0:*         LISTEN  1575/nginx: master
tcp      0     0 0.0.0.0:22          0.0.0.0:*         LISTEN  919/sshd
tcp6     0     0 ::1:25              :::*              LISTEN  1004/master 
tcp6     0     0 :::80               :::*              LISTEN  1575/nginx: master
tcp6     0     0 :::22               :::*              LISTEN  919/sshd

打开一个进程Nginx,获取Nginx打开的文件和文件描述符数量,代码如下。

[root@laohan-Shell-1 ~]# lsof -p 1575 | wc -l
64

上述代码表示1575的进程号打开的文件数量是64。

[root@laohan-Shell-1 ~]# ls /proc/1575/fd |wc -l
10
[root@laohan-Shell-1 ~]# ls /proc/1575/fd
总用量 0
lrwx------ 1 root root 64 2019-12-07 19:29:31.357792205 +0800 8 -> socket:[24270]
lrwx------ 1 root root 64 2019-12-07 19:29:31.357792205 +0800 7 -> socket:[24264]
lrwx------ 1 root root 64 2019-12-07 19:29:31.357792205 +0800 6 -> socket:[24263]
l-wx------ 1 root root 64 2019-12-07 19:29:31.357792205 +0800 5 -> /var/log/nginx/access.log
l-wx------ 1 root root 64 2019-12-07 19:29:31.357792205 +0800 4 -> /var/log/nginx/error.log
lrwx------ 1 root root 64 2019-12-07 19:29:31.357792205 +0800 3 -> socket:[24269]
l-wx------ 1 root root 64 2019-12-07 19:29:31.357792205 +0800 2 -> /var/log/nginx/error.log
lrwx------ 1 root root 64 2019-12-07 19:29:31.357792205 +0800 1 -> /dev/null
lrwx------ 1 root root 64 2019-12-07 19:29:31.357792205 +0800 0 -> /dev/null

上述代码表示1575的进程号打开的文件描述符数量是10。

终止Nginx进程,代码如下。

[root@laohan-Shell-1 ~]# pkill nginx
[root@laohan-Shell-1 ~]# pkill nginx
[root@laohan-Shell-1 ~]# pkill nginx
[root@laohan-Shell-1 ~]# pkill nginx
[root@laohan-Shell-1 ~]# 
[root@laohan-Shell-1 ~]# 
[root@laohan-Shell-1 ~]# 
[root@laohan-Shell-1 ~]# ps -ef |grep nginx
root      1622  1191  0 19:37 pts/0   00:00:00 grep --color=auto nginx

进程号打开的文件描述符数量更改为3,即小于文件描述符数量4,代码如下。

[root@laohan-Shell-1 ~]# vim .bash_profile 
[root@laohan-Shell-1 ~]# tail -1 .bash_profile
ulimit -n 10240
[root@laohan-Shell-1 ~]# . .bash_profile
[root@laohan-Shell-1 ~]# source .bash_profile
[root@laohan-Shell-1 ~]# ulimit -n
10240
[root@laohan-Shell-1 ~]# ulimit -n 3
[root@laohan-Shell-1 ~]# ulimit -n
3

更改限制,并测试Nginx的运行情况,再次启动Nginx。

[root@laohan-Shell-1 ~]# systemctl start nginx

(pkttyagent:1625): GLib-ERROR **: 11:39:06.041: Creating pipes for GWakeup: Too many open files

上述代码中“Too many open files”表示,打开的文件描述符数量已达到了限制,与打开的文件数量没有关系。执行其他指令,报错代码如下。

[root@laohan-Shell-1 ~]# ss -ntpl
-bash: start_pipeline: pgrp pipe: Too many open files
ss: error while loading shared libraries: libseLinux.so.1: cannot open shared object file: Error 24
[root@laohan-Shell-1 ~]# ps -ef |grep nginx
-bash: pipe error: Too many open files
-bash: start_pipeline: pgrp pipe: Too many open files

执行如下指令,重新设置文件描述符即可。

[root@laohan-Shell-1 ~]# ulimit  -n 1000
[root@laohan-Shell-1 ~]# ps -ef |grep nginx^C
[root@laohan-Shell-1 ~]# netstat -ntpl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address    Foreign Address   State   PID/Program name
tcp       0     0 127.0.0.1:25       0.0.0.0:*         LISTEN  1004/master
tcp       0     0 0.0.0.0:80         0.0.0.0:*         LISTEN  1634/nginx: master
tcp       0     0 0.0.0.0:22         0.0.0.0:*         LISTEN  919/sshd
tcp6      0     0 ::1:25             :::*              LISTEN  1004/master
tcp6      0     0 :::80              :::*              LISTEN  1634/nginx: master
tcp6      0     0 :::22              :::*              LISTEN  919/sshd

从上述代码中可以看到,可以执行netstat和ps指令,Nginx也可以正常启动。

Linux指令默认从标准输入设备获取内容的输入,将结果输出到标准输出设备显示。一般情况下,标准输入设备就是键盘,标准输出设备就是终端,即显示器。

1.标准输入输出

Bash标准输入、输出设备如表1-3所示。

表1-3  Bash标准输入、输出设备

设备

设备文件名

文件描述符

类型

键盘 

/dev/stdin

0

标准输入

显示器

/dev/stdout

1

标准输出

显示器

/dev/stderr

2

标准错误输出

2.输入重定向

Linux指令可以从文件获取输入,语法格式如下。

command < file

原本需要从键盘获取输入的指令会转移到文件读取内容。

注意:输出重定向符号是“>”,输入重定向符号是“<”。

【实例1-19】统计用户数量

计算users文件中的行数,可以使用下面的指令。

[root@laohan_httpd_server ~]# wc -l users 
2 users

输入重定向到指定的users文件,代码如下。

[root@laohan_httpd_server ~]# wc -l <users 
2

3.输出重定向

输出重定向是指内容输出不仅可以输出到显示器,还可以很容易地转移到文件,这被称为输出重定向。输出重定向的语法如下。

command > file

【实例1-20】输出重定向演示

输出重定向代码如下。

[root@laohan_httpd_server ~]# who > users

打开users文件,内容如下。

[root@laohan_httpd_server ~]# cat users 
root    tty1        2018-07-22 20:50
root    pts/0       2018-07-22 20:54 (192.168.1.104)
root    pts/1       2018-07-22 22:09 (192.168.1.104)

输出重定向会覆盖原文件内容,代码如下。

[root@laohan_httpd_server ~]# echo "My name is hanyanwei" > users
[root@laohan_httpd_server ~]# cat users 
My name is hanyanwei

如果不希望原文件内容被覆盖,可以使用追加重定向符号“>>” 追加内容到文件末尾,代码如下。

[root@laohan_httpd_server ~]# echo "My name is handuoduo" >> users
[root@laohan_httpd_server ~]# cat users 
My name is hanyanwei
My name is handuoduo

4.错误输出重定向

错误输出重定向,可以使用2>表示,代码如下。

1 [root@laohan-Shell-1 ~]# ls /laohan/laohan.txt
2 ls: 无法访问/laohan/laohan.txt: 没有那个文件或目录
3 [root@laohan-Shell-1 ~]# /laohan/laohan.txt 2> error.info
4 [root@laohan-Shell-1 ~]# cat error.info
5 -bash: /laohan/laohan.txt: No such file or directory

上述代码第1行表示使用ls指令查看/laohan/laohan.txt文件,但是此文件并不存在,因此向显示器输出错误信息“-bash: /laohan/laohan.txt: No such file or directory”。第3行使用错误输出重定向将错误信息输出到error.info文件。

1.基础知识补充

一般情况下,每个Linux指令运行时都会打开3个文件。

标准输入文件(stdin):stdin的文件描述符为0,UNIX脚本默认从stdin读取数据。

标准输出文件(stdout):stdout的文件描述符为1,UNIX脚本默认向stdout输出数据。

标准错误文件(stderr):stderr的文件描述符为2,UNIX脚本会向stderr流中写入错误信息。

默认情况下,command > file将stdout重定向到file,command < file将stdin重定向到file中。

将stderr重定向到file,语法格式如下。

#command 2 > file

将stderr追加到file末尾,语法格式如下。

#command 2 >> file

数字2表示stderr。

将stdout和stderr合并后重定向到file,语法格式如下。

#command> file 2>&1

或使用如下语法格式。

#command>> file 2>&1

对stdin和stdout都重定向,语法格式如下。

#command< file1 >file2

command指令将stdin重定向到file1,将stdout重定向到file2。

不论stdin或stdout还是stderr都可以输入file,语法格式如下。

#command&>>file

重定向指令列表如表1-4所示。

表1-4  重定向指令列表

指令

说明

command > file

将输出重定向到file

command < file

将输入重定向到file

command >> file

将输出以追加的方式重定向到file

x > file

将文件描述符为x的文件重定向到file

x >> file

将文件描述符为x的文件以追加的方式重定向到file

x >& y

将输出文件y和x合并

x <& y

将输入文件y和x合并

<< eof

将开始标记eof和结束标记eof之间的内容作为输入

2.使用exec绑定重定向

exec指令语法格式如下。

exec 文件描述符[n] <或>文件或文件描述符或设备

输入输出重定向将输入输出绑定文件或设备后,只对当前那条指令是有效的。如果需要在绑定之后对所有指令都支持,则需要使用exec指令。

[root@laohan-Shell-1 ~]# exec 6>&1

上述代码将标准输出与文件描述符6绑定。

1 [root@laohan-Shell-1 ~]# ls /proc/self/fd
2总用量 0
3 lrwx------ 1 root root 64 2019-12-07 20:33:15.565329325 +0800 6 -> /dev/pts/0
4 lr-x------ 1 root root 64 2019-12-07 20:33:15.565329325 +0800 3 -> /proc/1681/fd
5 lrwx------ 1 root root 64 2019-12-07 20:33:15.565329325 +0800 2 -> /dev/pts/0
6 lrwx------ 1 root root 64 2019-12-07 20:33:15.565329325 +0800 1 -> /dev/pts/0
7 lrwx------ 1 root root 64 2019-12-07 20:33:15.565329325 +0800 0 -> /dev/pts

上述代码第3行出现文件描述符6。

[root@laohan-Shell-1 ~]# exec 1>laohan.txt

上述代码将接下来所有指令标准输出绑定到laohan.txt文件(输出到该文件)。

[root@laohan-Shell-1 ~]# ls -lhrt --full-time
[root@laohan-Shell-1 ~]#
[root@laohan-Shell-1 ~]# who am i
[root@laohan-Shell-1 ~]# uptime

执行上述指令,发现什么都不返回。因为标准输出已经将上述指令的输出结果重定向到laohan.txt文件了。

[root@laohan-Shell-1 ~]# exec 1>&6

上述代码恢复标准输出。

1 [root@laohan-Shell-1 ~]# ls -lhrt --full-time
2总用量 112K
3 drwxr-xr-x  2 root root  166 2019-12-06 16:56:00.140261747 +0800 chapter-1
4 -rw-r--r--  1 root root 1.6K 2019-12-06 22:40:32.167135504 +0800 laohan.log
5 -rw-------. 1 root root 1.2K 2019-12-06 22:44:43.097672334 +0800 anaconda-ks.cfg
6 -rw-r--r--  1 root root    0 2019-12-06 22:53:10.138928140 +0800 a.png
7 -rw-r--r--  1 root root  693 2019-12-06 23:26:02.656273105 +0800 nginx-server.load
8 -rw-r--r--  1 root root  106 2019-12-06 23:27:58.699564208 +0800 users
9 -rw-r--r--  1 root root  88K 2019-12-07 19:28:16.846363922 +0800 man.info
10 -rw-r--r--  1 root root   53 2019-12-07 20:12:01.027393450 +0800 error.info
11 -rw-r--r--  1 root root 1020 2019-12-07 20:35:37.932466086 +0800 laohan.txt
12 [root@laohan-Shell-1 ~]# ls /proc/self/fd
13 总用量 0
14 lrwx------ 1 root root 64 2019-12-07 20:36:29.122155703 +0800 6 -> /dev/pts/0
15 lr-x------ 1 root root 64 2019-12-07 20:36:29.122155703 +0800 3 -> /proc/1686/fd
16 lrwx------ 1 root root 64 2019-12-07 20:36:29.122155703 +0800 2 -> /dev/pts/0
17 lrwx------ 1 root root 64 2019-12-07 20:36:29.122155703 +0800 1 -> /dev/pts/0
18 lrwx------ 1 root root 64 2019-12-07 20:36:29.122155703 +0800 0 -> /dev/pts/0

上述代码第1行执行完毕后,输出了第2~11行内容。

1 [root@laohan-Shell-1 ~]# exec 6>&- 
2 [root@laohan-Shell-1 ~]# ls /proc/self/fd
3总用量 0
4 lrwx------ 1 root root 64 2019-12-07 20:36:29.122155703 +0800 6 -> /dev/pts/0
5 lr-x------ 1 root root 64 2019-12-07 20:36:29.122155703 +0800 3 -> /proc/1686/fd
6 lrwx------ 1 root root 64 2019-12-07 20:36:29.122155703 +0800 2 -> /dev/pts/0
7 lrwx------ 1 root root 64 2019-12-07 20:36:29.122155703 +0800 1 -> /dev/pts/0
8 lrwx------ 1 root root 64 2019-12-07 20:36:29.122155703 +0800 0 -> /dev/pts/0

上述代码第1行关闭文件描述符6。查看laohan.txt文件内容,代码如下。

1 [root@laohan-Shell-1 ~]# cat laohan.txt 
2总用量 112K
3 drwxr-xr-x  2 root root  166 2019-12-06 16:56:00.140261747 +0800 chapter-1
4 -rw-r--r--  1 root root 1.6K 2019-12-06 22:40:32.167135504 +0800 laohan.log
5 -rw-------. 1 root root 1.2K 2019-12-06 22:44:43.097672334 +0800 anaconda-ks.cfg
6 -rw-r--r--  1 root root    0 2019-12-06 22:53:10.138928140 +0800 a.png
7 -rw-r--r--  1 root root  693 2019-12-06 23:26:02.656273105 +0800 nginx-server.load
8 -rw-r--r--  1 root root  106 2019-12-06 23:27:58.699564208 +0800 users
9 -rw-r--r--  1 root root  88K 2019-12-07 19:28:16.846363922 +0800 man.info
10 -rw-r--r--  1 root root   53 2019-12-07 20:12:01.027393450 +0800 error.info
11 -rw-r--r--  1 root root   52 2019-12-07 20:34:41.884805917 +0800 laohan.txt
12 root    pts/0       2019-12-07 19:40 (192.168.2.4)
13 20:35:21 up  1:54,  1 user,  load average: 0.00, 0.01, 0.05

上述代码中,第2~11行为ls -lhrt --full -time指令的输出结果,第12行和第13行为uptime指令的输出结果。

3.重定向知识总结

重定向知识总结如表1-5所示。

表1-5  重定向知识总结

指令

说明

command <file

把标准输入重定向到file

command 0<file

把标准输入重定向到file

command >file

把标准输出重定向到file(覆盖)

command 1> filename      

把标准输出重定向到file(覆盖)

command >>file

把标准输出重定向到file(追加)

command 1>>file

把标准输出重定向到file(追加)

command 2>file

把标准错误重定向到file(覆盖)

command 2>>file

把标准输出重定向到file(追加)

command >file 2>&1    

把标准输出和标准错误一起重定向到file(覆盖)

command >>file 2>&1    

把标准输出和标准错误一起重定向到file(追加)

command <file>file2 

把标准输入重定向到file,把标准输出重定向到file2

command 0<file 1>file2

把标准输入重定向到file,把标准输出重定向到file2

4.重定向基本符号及其含义

重定向基本符号及其含义如下。

>:代表重定向的位置,例如echo "laohan" > /home/laohan.txt。

/dev/null:代表空设备文件。

1:表示stdout,系统默认值是1,所以>/dev/null等同于1>/dev/null。

2:表示stderr。

&:表示等同于,2>&1,表示2的输出重定向等同于1。

5.重定向的使用规律

重定向的使用规律如下。

0、1、2需要分别重定向,一个重定向只能改变它们中的一个。

0和1可以省略(当其出现在重定向符号左侧时)。

文件描述符在重定向符号左侧时直接写即可,在右侧时前面加“&”。

文件描述符与重定向符号之间不能有空格。

1>/dev/null表示1重定向到空设备文件,就是不输出任何信息到终端,也就是不显示任何信息。

2>&1表示2重定向等同于1。因为之前1已经重定向到了空设备文件,所以2也重定向到空设备文件。

6.注意事项

注意事项如下。

若为“>”,判断右边文件是否存在。如果存在就先清空文件内容,并写入新内容到该文件。如果不存在就直接创建,无论左边指令执行是否成功,右边文件都会变为空。

若为“>>”,判断右边文件是否存在。如果不存在,就先创建。以添加方式打开文件,会分配一个文件描述符(不特别指定,默认为1或2),然后与左边的1或2绑定。

当指令执行完,绑定的文件描述符也自动失效。0、1、2又会空闲。

一条指令启动,指令的输入、正确输出、错误输出默认分别绑定0、1、2文件描述符。

一条指令在执行前,先会检查输出设备是否正确,如果输出设备错误,将不会进行指令执行。

1.Here Document基础知识

Here Document是在Linux Shell中的一种特殊的重定向方式,基本语法如下。

cmd << delimiter
  Here Document Content
delimiter

它的作用就是将两个delimiter之间的内容(Here Document Content部分)传递给cmd作为输入参数。

如在终端中输入cat << EOF,系统会提示继续输入,输入多行信息再输入EOF,中间输入的信息将会显示在终端上。

[root@laohan-Shell-1 ~]# cat << EOF
> This is laohan's courses
>跟老韩学Shell
>跟老韩学Python
>跟老韩学Nginx
>跟老韩学FastDFS
>跟老韩学HTML、CSS、JavaScript
> EOF
This is laohan's courses
跟老韩学Shell
跟老韩学Python
跟老韩学Nginx
跟老韩学FastDFS
跟老韩学HTML、CSS、JavaScript

注意:>这个符号是终端产生的提示输入信息的标识符。

EOF只是一个标识而已,可以被替换成任意合法字符。

作为结尾的delimiter一定要顶格写,前面不能有任何字符。

作为结尾的delimiter后面也不能有任何的字符(包括空格)。

作为开始的delimiter前后的空格会被省略。

Here Document不仅可以在终端上使用,也可以在Shell文件中使用,例如下面的here.sh脚本。

cat << EOF > output.sh
echo "跟老韩学Python"
echo "跟老韩学Shell"
EOF

使用sh here.sh运行here.sh脚本,会得到output.sh这个新脚本,其内容如下。

echo "跟老韩学Python"
echo "跟老韩学Shell"

2./dev/null文件

如果希望执行某条指令,但又不希望在终端上显示输出结果,那么可以将输出重定向到/dev/null文件中,代码如下。

command > /dev/null

/dev/null是一个特殊的文件,写入它的内容都会被丢弃。如果尝试从该文件读取内容,那么什么也读不到。但是/dev/null文件非常有用,将指令的输出重定向到它,会实现“禁止输出”的效果,实例如下。原本输出到终端上的内容,重定向到/dev/null设备后,终端不再显示任何内容。

[root@laohan_httpd_server ~]# cat users 
My name is hanyanwei
My name is handuoduo
[root@laohan_httpd_server ~]# cat users > /dev/null

如果希望屏蔽stdout和stderr,代码如下。

command > /dev/null 2>&1

实例如下,原本输出到终端上的报错信息,使用“/dev/null 2>&1”指令处理后,终端不再显示任何内容。

[root@laohan_httpd_server ~]# cat users2
cat: users2: No such file or directory
[root@laohan_httpd_server ~]# cat users2 >/dev/null 2>&1

3.管道运算符

管道运算符使用“|”表示,它仅能处理由前一条指令传出的正确输出信息,也就是标准输出的信息,然后传递给下一条指令,作为下一条指令或程序的标准输入。对于标准错误输出信息它没有直接的处理能力。

注意。

管道运算符只处理前一条指令的正确输出,不处理错误输出。

管道运算符右边的指令,必须能够接收标准输入流指令才行。

管道操作基本语法如下。

指令1 | 指令2

指令1的正确输出作为指令2的操作对象。

(1)小写字母转化为大写字母。

使用管道传递内容,将前一条指令的输出转换为后一条指令的输入。

[root@www.blog*.com ~]echo 'i miss you so much' |tr 'a-z' 'A-Z'
I MISS YOU SO MUCH

(2)使用管道传输字符串。

使用管道将字符串传输给后面的passwd指令,进而达到自动修改密码的目的。

[root@www.blog*.com ~]echo 'dasffffs567da1235f()&()Y*YRRW'|passwd --stdin zhangsan
Changing password for user zhangsan.
passwd: all authentication tokens updated successfully.

(3)提取字符串。

使用管道将文本内容传递给cut指令,提取某一列之后,再使用sort指令进行排序,最后使用head指令取前10行的排序结果。

[root@www.blog*.com ~]cat /etc/passwd|cut -d: -f1 |sort|head
abrt
adm
apache
bin
daemon
dbus
ftp
games
gopher
haldaemon

(4)按字母排序。

将内容通过管道传递给sort指令进行字母排序。

[root@www.blog*.com ~]cat /etc/passwd|cut -d: -f3 |sort|head  #字母排序
0
1
10
11
12
13
14
173
2
28

(5)按数字排序。

将内容通过管道传递给sort指令进行数字排序。

[root@www.blog*.com ~]cat /etc/passwd|cut -d: -f3 |sort -n|head #数字排序
0
1
2
3
4
5
6
7
8
10

(6)大小写转换。

将ls指令输出的内容通过管道传递给tr指令进行处理。

[root@www.blog*.com ~]ls /var/ |tr 'a-z' 'A-Z'
ACCOUNT
CACHE
CRASH
CVS
DB
EMPTY
GAMES
LIB
LOCAL
LOCK
LOG
MAIL
NIS
OPT
PRESERVE
RUN
SPOOL
TMP
YP

(7)将数据输出到终端,且保存到文件。

tee指令默认显示文本内容,并将显示的内容写入对应的文件。

[root@www.blog*.com ~]echo "Hello" |tee /tmp/tee.out
Hello
[root@www.blog*.com ~]cat /tmp/tee.out
Hello

(8)显示文件的行数。

wc指令统计行数等信息后通过管道将数据流传递给cut指令处理。

 [root@www.blog*.com ~]wc -l /etc/passwd |cut -d' ' -f1
28

通配符是指可以在指令中使用一个字符串来替代一系列字符或字符串。Bash中有3种通配符,其中“?”和“[]”可以代表单个字符串,“*”可以代表任意一个或多个字符,也可以代表字符串。

通配符常用于路径扩展,或者文件名扩展功能中的模式匹配。

Bash中存在很多种形式的扩展(Expansion),而路径扩展(或者说文件名扩展)只是其中之一,了解这点尤为关键,Bash中常见扩展如下。

Brace Expansion:花括号扩展。

Tilde Expansion:波浪号扩展。

Parameter and Variable Expansion:参数和变量扩展。

Arithmetic Expansion:算术扩展。

Command Substitution:指令置换。

Word Splitting:单词分割。

File Expansion:文件名扩展。

Process Substitution:进程替换。

Bash在扫描指令行参数时会注意操作数(Operand)部分是否有“?”“*”等特殊模式字符。当它发现这些特殊模式字符时,会将它们转换为要匹配的模式。即Bash发现参数部分有这些特殊字符时,会扩展这些字符,生成相应的已存在的文件名或者目录名,最后经过排序后传递给指令。

1.模式匹配

通配符在Bash中的专业名称是模式匹配

Bash特殊模式字符如表1-6所示。

表1-6  Bash特殊模式字符

特殊模式字符

匹配

匹配任何单一字符

*

匹配任何字符和字符串,包括空字符串

[set]

匹配set中的任何字符,[^set]或[!set]表示不匹配set里的字符

?(Linux)

匹配Linux 0次或者1次

*(Linux)

匹配Linux 0次以上(包括0次)

+(Linux)

匹配Linux 1次以上(包括1次)

@(Linux)

匹配Linux 1次

!(Linux)

匹配除Linux之外的模式,反向匹配

首先把表1-6所示的特殊模式字符分为两类:“?”“*”和“[set]”是常见的特殊模式字符,在几乎所有的Shell版本中都支持;而后5项是Bash的扩展特殊模式字符,使用前请确保打开extglob设置。

打开Bash识别正则,代码如下。

shopt -s extglob

关闭Bash识别正则,代码如下。

shopt -u extglob

Bash开启扩展特殊模式字符之后,以下5个模式匹配操作符将被识别。

?(pattern-list):所给模式匹配0次或1次。

*(pattern-list):所给模式匹配0次以上(包括0次)。

+(pattern-list):所给模式匹配1次以上(包括1次)。

@(pattern-list):所给模式仅匹配1次。

!(pattern-list):不匹配所给模式。

列出00~22号的所有目录,代码如下。

ls -al +(0[0-9]|2[0-2])

代码说明如下所示。

Shell的通配符,只是通配语义,不是正则语义。

打开extglob之后,才是正则语义。

语法格式“+”是正则。

特殊模式字符“?”匹配任何单一字符。因此如果目录下有hanyanwei.a、hanyanwei.b与hanyanwei.abc这3个文件,那么表达式hanyanwei.?匹配的结果是hanyanwei.a和hanyanwei.b,但是与hanyanwei.abc不匹配,代码如下。

[root@laohan_httpd_server ~]# touch hanyanwei.{a,b,abc}
[root@laohan_httpd_server ~]# ls -l hanyanwei*
-rw-r--r-- 1 root root 0 Jul 22 23:22 hanyanwei.a
-rw-r--r-- 1 root root 0 Jul 22 23:22 hanyanwei.abc
-rw-r--r-- 1 root root 0 Jul 22 23:22 hanyanwei.b
[root@laohan_httpd_server ~]# ls -lh hanyanwei.?
-rw-r--r-- 1 root root 0 Jul 22 23:22 hanyanwei.a
-rw-r--r-- 1 root root 0 Jul 22 23:22 hanyanwei.b

特殊模式字符“*”是一个功能强大而且广为使用的通配符,它匹配任何字符和字符串(包括空字符串)。表达式hanyanwei.*匹配hanyanwei.{a,b,abc}这3个文件。

Bash中的参数globstar可以控制连续两个“*”的行为,即出现“**”的情况,参数globstar在disable(shopt -u globstar)情况下,“**”和“*”的行为是一样的(即“**”和“*”匹配当前目录下的所有文件名和目录名,“**/”和“*/”匹配当前目录下的所有目录名)。一旦enable(shopt -s globstar),那么“**”就会递归匹配所有的文件和目录,而“**/”仅会递归匹配所有的目录,代码如下。

[root@laohan_httpd_server ~]# pwd ; ls -lhrt
/root
total 32K
-rw-r--r--. 1 root root 3.4K Jul 22 20:43 install.log.syslog
-rw-r--r--. 1 root root 8.7K Jul 22 20:44 install.log
-rw-------. 1 root root 1.1K Jul 22 20:44 anaconda-ks.cfg
-rwxr-xr-x  1 root root   64 Jul 22 21:15 cp_file.sh
drwxr-xr-x  3 root root 4.0K Jul 22 21:56 Shell-scripts
-rw-r--r--  1 root root   42 Jul 22 22:33 users
-rw-r--r--  1 root root    0 Jul 22 23:22 hanyanwei.b
-rw-r--r--  1 root root    0 Jul 22 23:22 hanyanwei.abc
-rw-r--r--  1 root root    0 Jul 22 23:22 hanyanwei.a
[root@laohan_httpd_server ~]# shopt globstar
globstar         off
[root@laohan_httpd_server ~]# echo * ; echo **
anaconda-ks.cfg cp_file.sh hanyanwei.a hanyanwei.abc hanyanwei.b install.log install.log.syslog Shell-scripts users
anaconda-ks.cfg cp_file.sh hanyanwei.a hanyanwei.abc hanyanwei.b install.log install.log.syslog Shell-scripts users
[root@laohan_httpd_server ~]# 
[root@laohan_httpd_server ~]# echo */ ; echo **/
Shell-scripts/
Shell-scripts/
[root@laohan_httpd_server ~]# shopt -s globstar
[root@laohan_httpd_server ~]# shopt globstar
globstar         on
[root@laohan_httpd_server ~]# echo * ; echo **
anaconda-ks.cfg cp_file.sh hanyanwei.a hanyanwei.abc hanyanwei.b install.log install.log.syslog Shell-scripts users
anaconda-ks.cfg cp_file.sh hanyanwei.a hanyanwei.abc hanyanwei.b install.log install.log.syslog Shell-scripts Shell-scripts/chapter-1 Shell-scripts/chapter-1/cp-file.sh Shell-scripts/chapter-1/hello-world.sh Shell-scripts/chapter-1/hello-world-v2.sh Shell-scripts/chapter-1/here-Document.sh users
[root@laohan_httpd_server ~]# 
[root@laohan_httpd_server ~]# echo */ ; echo **/
Shell-scripts/
Shell-scripts/ Shell-scripts/chapter-1/

特殊模式字符“[ ]”,它与特殊模式字符“?”很相似,但允许匹配得更确切,把所有想要匹配的字符放在“[ ]”内,结果匹配其中的任一字符。可以使用“-”表示范围,也可以使用“!”或者是“^”来表示反向匹配,实例如下。

hanyanwei.[ab]与hanyanwei.[a-z]匹配文件hanyanwei.a和hanyanwei.b,但不匹配文件hanyanwei.abc,代码如下。

[root@laohan_httpd_server ~]# touch  hanyanwei.{a,b}
[root@laohan_httpd_server ~]# ll hanyanwei.[ab]
-rw-r--r-- 1 root root 0 Jul 22 23:49 hanyanwei.a
-rw-r--r-- 1 root root 0 Jul 22 23:49 hanyanwei.b
[root@laohan_httpd_server ~]# ll hanyanwei.[a-z]
-rw-r--r-- 1 root root 0 Jul 22 23:49 hanyanwei.a
-rw-r--r-- 1 root root 0 Jul 22 23:49 hanyanwei.b

[abc]和[a-c]匹配单个字符a、b或c。

[!0-9]或者[^0-9]匹配任何一个非数字字符。

[a-zA-Z0-9_-]匹配任何一个字母、任何一个数字、下画线或者破折号(假设ASCII环境下)。

使用Bash的几个扩展特殊模式字符之前,请确保extglob是打开的(shopt -s extglob)。有了这几个扩展特殊模式字符,就使得模式匹配有了正则表达式的“味道”,自此模式匹配也有了重复、可选的功能。

1 [root@laohan-shell-1 chapter-1]# ls -l  +(abc|def)*.+(jpg|png)
2 -bash: 未预期的符号 '(' 附近有语法错误

若出现上述代码中第2行的报错信息,请执行如下代码,打开特殊模式匹配扩展。

[root@laohan-shell-1 chapter-1]# shopt -s extglob

再次执行第1行即可成功输出对应的结果,如下所示。

[root@laohan-shell-1 chapter-1]# ls -l  +(abc|def)*.+(jpg|png)
-rw-r--r-- 1 root root 0 2019-12-08 12:55:00.911542840 +0800 def.png
-rw-r--r-- 1 root root 0 2019-12-08 12:55:00.911542840 +0800 abc.jpg

2.列出当前目录下以abc或者def开头的.jpg或者.png文件

实现代码如下所示。

1 [root@laohan_httpd_server ~]# touch abc.jpg
2 [root@laohan_httpd_server ~]# touch def.png
3 [root@laohan_httpd_server ~]# ls -l  +(abc|def)*.+(jpg|png)
4 -rw-r--r-- 1 root root 0 Jul 22 23:32 abc.jpg
5 -rw-r--r-- 1 root root 0 Jul 22 23:32 def.png

第1~2行分别创建.jpg和.png文件。

第3行使用ls -l指令匹配以abc或def开头的,中间是任意字符,扩展名为.jpg和.png的文件。

第4~5行为第3行代码的执行结果。

3.找出当前目录下与正则表达式ab(1|2|3)+.jpg匹配的所有文件

实现代码如下所示。

[root@laohan_httpd_server ~]# touch ab{1..3}.jpg
[root@laohan_httpd_server ~]# ls -l ab{1..3}*.jpg
-rw-r--r-- 1 root root 0 Jul 22 23:35 ab1.jpg
-rw-r--r-- 1 root root 0 Jul 22 23:35 ab2.jpg
-rw-r--r-- 1 root root 0 Jul 22 23:35 ab3.jpg

[root@laohan_httpd_server ~]# ls -l  ab+(1|2|3).jpg
-rw-r--r-- 1 root root 0 Jul 22 23:35 ab1.jpg
-rw-r--r-- 1 root root 0 Jul 22 23:35 ab2.jpg
-rw-r--r-- 1 root root 0 Jul 22 23:35 ab3.jpg

执行结果中匹配到的文件诸如ab1.jpg、ab2.jpg、ab3.jpg、ab111.jpg、ab222.jpg、ab333.jpg等。

4.删除当前目录下除了扩展名为.jpg或.png的文件

Linux系统管理员在日常处理文件或数据前,最好使用ls指令结合通配符或者相关表达式把要执行的文件列表或数据列表显示出来,然后使用mv指令移动目标文件到临时备份目录,万一误操作了还有可恢复的余地。同时,这些日常操作也是考核Linux系统管理员经验是否丰富、安全风险意识是否到位的重要标准。具体操作代码如下。

[root@laohan_httpd_server ~]# ls -lhrt
total 32K
-rw-r--r--. 1 root root 3.4K Jul 22 20:43 install.log.syslog
-rw-r--r--. 1 root root 8.7K Jul 22 20:44 install.log
-rw-------. 1 root root 1.1K Jul 22 20:44 anaconda-ks.cfg
-rwxr-xr-x  1 root root   64 Jul 22 21:15 cp_file.sh
drwxr-xr-x  3 root root 4.0K Jul 22 21:56 Shell-scripts
-rw-r--r--  1 root root   42 Jul 22 22:33 users
-rw-r--r--  1 root root    0 Jul 22 23:22 hanyanwei.b
-rw-r--r--  1 root root    0 Jul 22 23:22 hanyanwei.abc
-rw-r--r--  1 root root    0 Jul 22 23:22 hanyanwei.a
-rw-r--r--  1 root root    0 Jul 22 23:32 abc.jpg
-rw-r--r--  1 root root    0 Jul 22 23:32 def.png
-rw-r--r--  1 root root    0 Jul 22 23:35 ab3.jpg
-rw-r--r--  1 root root    0 Jul 22 23:35 ab2.jpg
-rw-r--r--  1 root root    0 Jul 22 23:35 ab1.jpg

使用正则表达式删除文件之前,先使用ls指令结合通配符查看文件信息,确保不会误删除文件,代码如下。

[root@laohan-shell-1 chapter-1]# ls -l !(*.jpg|*.png) 
-rw-r--r-- 1 root root 0 2019-12-08 12:51:09.028339625 +0800 laohan3.log
-rw-r--r-- 1 root root 0 2019-12-08 12:51:09.028339625 +0800 laohan2.log
-rw-r--r-- 1 root root 0 2019-12-08 12:51:09.028339625 +0800 laohan1.log
-rw-r--r-- 1 root root 0 2019-12-08 12:51:26.943176963 +0800 laohan456.log

确认上述表达式书写无误,然后执行如下代码删除对应的文件或目录。

1 [root@laohan_httpd_server ~]# rm -fv !(*.jpg|*.png)
2 removed 'anaconda-ks.cfg'
3 removed 'cp_file.sh'
4 removed 'hanyanwei.a'
5 removed 'hanyanwei.abc'
6 removed 'hanyanwei.b'
7 removed 'install.log'
8 removed 'install.log.syslog'
9 rm: cannot remove `Shell-scripts': Is a directory
10 removed 'users'
11 [root@laohan_httpd_server ~]# ls -lhrt
12 total 4.0K
13 drwxr-xr-x 3 root root 4.0K Jul 22 21:56 Shell-scripts
14 -rw-r--r-- 1 root root    0 Jul 22 23:32 abc.jpg
15 -rw-r--r-- 1 root root    0 Jul 22 23:32 def.png
16 -rw-r--r-- 1 root root    0 Jul 22 23:35 ab3.jpg
17 -rw-r--r-- 1 root root    0 Jul 22 23:35 ab2.jpg
18 -rw-r--r-- 1 root root    0 Jul 22 23:35 ab1.jpg

上述代码中,第1行表示,删除当前目录下除扩展名为.jpg和.png的所有文件。第2~8行表示删除了当前目录下的符合条件的文件。第9行表示为目录,无法删除。第10行表示为普通文件,已经被删除。第11行表示查看当前目录下的所有文件和目录。第13~18行为当前目录下存在的所有文件信息。

5.列出当前目录下不是以abc或者def开头的、扩展名为.jpg或.png的文件

实现代码如下所示。

1 [root@laohan_httpd_server ~]# touch {1..3}.log
2 [root@laohan_httpd_server ~]# touch {1..3}.txt
3 [root@laohan_httpd_server ~]# ls -lhrt
4 total 4.0K
5 drwxr-xr-x 3 root root 4.0K Jul 22 21:56 Shell-scripts
6 -rw-r--r-- 1 root root    0 Jul 22 23:32 abc.jpg
7 -rw-r--r-- 1 root root    0 Jul 22 23:32 def.png
8 -rw-r--r-- 1 root root    0 Jul 22 23:35 ab3.jpg
9 -rw-r--r-- 1 root root    0 Jul 22 23:35 ab2.jpg
10 -rw-r--r-- 1 root root    0 Jul 22 23:35 ab1.jpg
11 -rw-r--r-- 1 root root    0 Jul 22 23:42 3.log
12 -rw-r--r-- 1 root root    0 Jul 22 23:42 2.log
13 -rw-r--r-- 1 root root    0 Jul 22 23:42 1.log
14 -rw-r--r-- 1 root root    0 Jul 22 23:42 3.txt
15 -rw-r--r-- 1 root root    0 Jul 22 23:42 2.txt
16 -rw-r--r-- 1 root root    0 Jul 22 23:42 1.txt
17 [root@laohan_httpd_server ~]# ls -l +(abc|def)*.+(jpg|png)
18 -rw-r--r-- 1 root root 0 Jul 22 23:32 abc.jpg
19 -rw-r--r-- 1 root root 0 Jul 22 23:32 def.png
20 [root@laohan-shell-1 chapter-1]# ls -l !(+(abc|def)*.(jpg|png))
21 -rw-r--r-- 1 root root 0 2019-12-08 13:22:29.629347625 +0800 def.jpg
22 -rw-r--r-- 1 root root 0 2019-12-08 13:22:29.629347625 +0800 abc.jpg
23 -rw-r--r-- 1 root root 0 2019-12-08 13:22:31.384334522 +0800 def.png
24 -rw-r--r-- 1 root root 0 2019-12-08 13:22:31.384334522 +0800 abc.png
25 -rw-r--r-- 1 root root 0 2019-12-08 13:22:41.505258775 +0800 def123.png
26 -rw-r--r-- 1 root root 0 2019-12-08 13:22:41.505258775 +0800 abc123.png
27 -rw-r--r-- 1 root root 0 2019-12-08 13:22:45.802226647 +0800 def123.jpg
28 -rw-r--r-- 1 root root 0 2019-12-08 13:22:45.802226647 +0800 abc123.jpg

上述代码中,第1~2行创建测试文件;第17行使用通配符查询相关文件;第20行使用“!”进行反向匹配文件操作。

1.传输Windows文件到Linux出现的问题及解决方案

使用Windows操作系统的编辑器在本地IDE编写代码并上传到远程Linux服务器,执行代码的时候会出现字符无法被正确识别的问题,代码如下。

1   [root@laohan_Shell_Python ~]# bash laohan_test.sh 
2   我的名字是韩艳威
3   What is your name
4
5   ': 不是有效的标识符3 行:read: `MY_NAME
6   Hello  - hope you're well.

上述代码第5行报错,导致第6行运行结果出错,此情况下需要使用dos2unix指令将.doc文件转换为UNIX格式,代码如下。

1   [root@laohan_Shell_Python ~]# dos2unix  laohan_test.sh
2   dos2unix: converting file laohan_test.sh to Unix format ...
3   [root@laohan_Shell_Python ~]# bash laohan_test.sh 
4    我的名字是韩艳威
5   What is your name
6   hanyanwei
7   Hello hanyanwei - hope you're well.

上述代码第1行使用dos2unix指令转换文件格式,第4~6行为程序执行结果。该指令的具体使用方法如下。

2.基本介绍

dos2unix指令用于将DOS格式的文本文件转换为UNIX格式。

DOS格式的文本文件是以\r\n作为断行标志的,表示成十六进制数就是0D。而UNIX格式的文本文件是以\n作为断行标志的,表示成十六进制数就是0A。在Linux操作系统中,用较低版本的Vi编辑器打开DOS格式的文本文件时行尾会显示^M,而且很多指令都无法很好地处理这种格式的文件。

如果该文件是Shell脚本,那么UNIX格式的文本文件在Windows操作系统下用Notepad++打开时会拼在一起显示,因此产生了两种格式文件相互转换的需求。将UNIX格式文本文件转换为DOS格式时,使用unix2dos指令即可完成。

3.基础语法

dos2unix基础语法如下所示。

dos2unix [-hkqV] [-c convmode] [-o file ...] [-n infile outfile ...]

dos2unix常用选项如表1-7所示。

表1-7  dos2unix常用选项

选项

说明

-k

保持输出文件的日期不变

-q

安静模式,不提示任何警告信息

-V

查看版本

-c

转换模式,模式有ASCII、7bit、ISO、Mac,默认是ASCII

-o

写入源文件

-n

写入新文件

4.dos2unix应用实例

(1)格式化匹配单个文件。

简单的用法就是dos2unix直接跟文件名,可以跟单个文件或多个文件,代码如下。

1   [root@laohan_Shell_Python ~]# dos2unix  laohan_test.sh
2   dos2unix: converting file laohan_test.sh to Unix format ...
3   [root@laohan_Shell_Python ~]# dos2unix  *
4   dos2unix: Skipping chapter-1, not a regular file.
5   dos2unix: Skipping chapter-2, not a regular file.
6   dos2unix: converting file for.sh to Unix format ...
7   dos2unix: converting file laohan_test.sh to Unix format ...
8   dos2unix: converting file len_1.sh to Unix format ...
9   dos2unix: converting file len_2.sh to Unix format ...
10  dos2unix: converting file len_3.sh to Unix format ...
11  dos2unix: converting file rev_bash.sh to Unix format ...
12  dos2unix: converting file rev_file.log to Unix format ...
13  dos2unix: converting file string_instrsub.sh to Unix format ...
14  dos2unix: converting file sub_string.sh to Unix format ...
15  dos2unix: converting file test.sh to Unix format ...
16  dos2unix: converting file v.sh to Unix format ...

上述代码中第1行转换单个文件,第3行使用通配符匹配当前目录下所有的文件,第4~16行为匹配输出结果。

如果一次转换多个文件,把这些文件名直接跟在dos2unix之后。(注:也可加上-o选项,或者不加,二者效果相同。)

dos2unix file1 file2 file3 
dos2unix -o file1 file2 file3

上述代码在进行格式转换时,都会直接在原来的文件上修改,如果想把转换的结果保存在别的文件,而源文件不变,则可以使用-n选项。

如果要保持文件时间戳不变,加上-k选项。所以上面几条指令都是可以加上-k选项来保持文件时间戳的,语法如下。

dos2unix -k file
dos2unix -k file1 file2 file3
dos2unix -k -o file1 file2 file3
dos2unix -k -n oldfile newfile

(2)格式化匹配多文件。

要更改文件格式为.sh,那么借助下面的指令就可以轻松地实现批量替换为UNIX文件格式,代码如下。

1   [root@laohan_Shell_Python ~]# find ./ -name "*.sh" | xargs dos2unix
2   dos2unix: converting file ./len_3.sh to Unix format ...
3   dos2unix: converting file ./chapter-2/check_v_var.sh to Unix format ...
4   dos2unix: converting file ./chapter-2/check_z_var.sh to Unix format ...
5   dos2unix: converting file ./laohan_test.sh to Unix format ...
6   dos2unix: converting file ./v.sh to Unix format ...
7   dos2unix: converting file ./for.sh to Unix format ...
8   dos2unix: converting file ./len_1.sh to Unix format ...
9   dos2unix: converting file ./sub_string.sh to Unix format ...
10  dos2unix: converting file ./rev_bash.sh to Unix format ...
11  dos2unix: converting file ./string_instrsub.sh to Unix format ...
12  dos2unix: converting file ./len_2.sh to Unix format ...
13  dos2unix: converting file ./test.sh to Unix format ...

上述代码第1行使用find指令结合xargs批量转换当前目录下所有以.sh结尾的文件,第2~13行为匹配输出结果。

【实例1-21】统计磁盘容量信息

统计当前系统磁盘分区信息和文件类型,脚本内容如下所示。

[root@laohan_httpd_server chapter-1]# cat disk-total.sh 
#!/bin/bash
clear
df -Th

脚本执行结果如下。

1 [root@laohan_httpd_server chapter-1]# ./disk-total.sh 
2 Filesystem               Type      Size   Used  Avail  Use%  Mounted on
3 devtmpfs                 devtmpfs  475M   0     475M   0%    /dev
4 tmpfs                    tmpfs     487M   0     487M   0%    /dev/shm
5 tmpfs                    tmpfs     487M   7.6M  479M   2%    /run
6 tmpfs                    tmpfs     487M   0     487M   0%    /sys/fs/cgroup
7 /dev/mapper/centos-root  xfs       17G    1.8G  16G    11%   /
8 /dev/sda1                xfs       1014M  136M  879M   14%   /boot
9 tmpfs                    tmpfs     98M    0     98M    0%    /run/user/0

上述代码第1~9行统计了当前操作系统的文件类型和容量信息。实际运维中,一般需要统计和监控根目录的容量大小,脚本修订版如下所示,只统计根目录的磁盘使用空间。

[root@laohan-shell-1 chapter-1]# cat disk-total.sh 
 df -h |grep /$ |awk '{print $5}'

脚本执行结果如下所示。

[root@laohan-shell-1 chapter-1]# bash disk-total.sh 
11%

上述代码统计了磁盘根目录的使用百分比。

【实例1-22】统计磁盘容量信息脚本扩展

对统计磁盘容量信息脚本进行扩展,增加注释和描述信息,并在脚本执行前和脚本执行后输出提示信息。

1 [root@laohan_httpd_server chapter-1]# cat ./disk-total.sh
2 #!/bin/bash
3
4 :<<note
5 Author: hanyanwei
6 Datetime: 2018/07/29
7 Version: 1.2
8 Description: 查看磁盘空间
9 note
10 clear
11 echo
12 echo "--------------------统计磁盘开始----------------------------------"
13 df -Th
14 echo "--------------------统计磁盘结束----------------------------------"

上述代码第4~9行为多行注释,其格式如下。

: <<EOF
语句1
语句2
语句3
语句4
EOF

脚本执行结果如下。

[root@laohan_httpd_server chapter-1]# ./disk-total.sh 

--------------------统计磁盘开始------------------------------------
Filesystem           Type   Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root
                   ext4  8.3G  750M  7.1G  10% /
tmpfs             tmpfs  931M     0  931M   0% /dev/shm
/dev/sda1          ext4  477M   28M  425M   7% /boot
--------------------统计磁盘结束------------------------------------

【实例1-23】复制文件到指定目录

在/tmp目录下创建当天日期的目录,并复制/data/下面的*.log文件到/tmp/当天日期的目录下。

需要对脚本核心指令进行描述和说明,代码如下。

date +%F

输出类似“2018-07-23”格式的日期,代码如下。

find /data/ -maxdepth 1 -type f -name "*.log" |xargs -i  cp -av {} /tmp/$(date +%F)

查找当前目录下以.log为扩展名的文件并复制到/tmp/$(date +%F)目录下,代码如下。

mkdir -pv /data/
touch  /data/{1..9}.log
mkdir -pv /tmp/$(date +%F)
find /data/ -maxdepth 1 -type f -name "*.log" |xargs -i  cp -av {} /tmp/$(date +%F)

【实例1-24】实现一键安装LAMP菜单

用Shell脚本实现一键安装LAMP菜单,要求熟练掌握echo指令的使用,代码如下。

[root@laohan_httpd_server chapter-1]# cat auto-inistall-lamp.sh 
#!/bin/bash
:<<note
Author:HanYanWei
Date:2017-06-11
Version:1.1
Desc auto_install lamp
note
echo  -e "\033[32m------------------------------------------\033[0m"
echo "1)一键安装LAMP"
echo "2)安装MySQL"
echo "3)安装PHP"
echo "4)安装Apache"
echo  -e "\033[32m------------------------------------------\033[0m"

Shell脚本中echo显示内容带颜色显示,需要使用-e选项,格式如下。

echo -e "\033[文字背景颜色;文字颜色m字符串\033[0m"

设置文字的底色和文字背景颜色,格式如下。

echo -e "\033[41;36m something here \033[0m"

其中41代表文字背景颜色,36代表文字颜色。

文字背景颜色和文字颜色之间是半角的“;”。

文字颜色后面有个m。

字符串前后一般没有空格。如果有的话,输出也有空格。

下面是相应的文字和背景颜色,可以自己尝试找出不同颜色搭配。

echo -e "\033[31m 红色字 \033[0m" 
echo -e "\033[34m 黄色字 \033[0m" 
echo -e "\033[41;33m 红底黄字 \033[0m" 
echo -e "\033[41;37m 红底白字 \033[0m"

文字颜色表示范围:30~37。如下。

echo -e "\033[30m 黑色字 \033[0m" 
echo -e "\033[31m 红色字 \033[0m" 
echo -e "\033[32m 绿色字 \033[0m" 
echo -e "\033[33m 黄色字 \033[0m" 
echo -e "\033[34m 蓝色字 \033[0m" 
echo -e "\033[35m 紫色字 \033[0m" 
echo -e "\033[36m 天蓝字 \033[0m" 
echo -e "\033[37m 白色字 \033[0m"

文字背景颜色表示范围:40~47。如下。

echo -e "\033[40;37m 黑底白字 \033[0m" 
echo -e "\033[41;37m 红底白字 \033[0m" 
echo -e "\033[42;37m 绿底白字 \033[0m" 
echo -e "\033[43;37m 黄底白字 \033[0m" 
echo -e "\033[44;37m 蓝底白字 \033[0m" 
echo -e "\033[45;37m 紫底白字 \033[0m" 
echo -e "\033[46;37m 天蓝底白字 \033[0m" 
echo -e "\033[47;30m 白底黑字 \033[0m"

颜色范围控制选项说明如下所示。

\33[0m:关闭所有属性。

\33[1m:设置高亮度。

\33[4m:下画线。

\33[5m:闪烁。

\33[7m:反显。

\33[8m:消隐。

\33[30m- \33[37m:设置前景色。

\33[40m- \33[47m:设置背景色。

\33[nA:光标上移n行。

\33[nB:光标下移n行。

\33[nC:光标右移n行。

\33[nD:光标左移n行。

\33[y;xH:设置光标位置。

\33[2J:清屏。

\33[K:清除从光标到行尾的内容。

\33[s:保存光标位置。

\33[u:恢复光标位置。

\33[?25l:隐藏光标。

\33[?25h:显示光标。

项目运行过程中,脚本运行日志和业务自定义日志文件,随着运行时间的增加,日积月累,会逐渐变大。单个100MB的文件增长到20GB的情况在企业级项目中屡见不鲜,但是此类文件又不能轻易删除。因此清空文件内容,保留该文件是个不错的选择,这就需要建立清空文件的机制和制定对应的清空策略。

直接删除文件,若代码中没有相应的异常处理机制,极易引发未知的错误或异常。因此在Linux操作系统中,可以通过指令达到清空文件内容而不删除文件本身的目的。本节将介绍几种方法,用于清空或删除大文件内容。

几种快速清空文件内容的方法如下所示。

: >file

其中的“:”是一个占位符,不产生任何输出。

1 >file
2 echo "" >file
3 echo /dev/null >file
4 echo >file
5 cat /dev/null >file
6 cat/dev/null > test.txt
7 echo "" > test.txt 
8 cp /dev/null file

上述代码中,第1~8行的指令,均可以清空文件内容。其中第5行和第7行,在清空文件内容方面有些许差别,差别如下。

cat/dev/null >test.txt

上述代码中,文件大小被截为0B。

echo "" > test.txt

上述代码中,文件大小被截为1B。

使用重定向是清空文件内容中较简单的方法,通过Shell重定向null到指定文件即可,代码如下。

[root@laohan_httpd_server chapter-1]# df -TH >disk-info.log
[root@laohan_httpd_server chapter-1]# cat disk-info.log
Filesystem         Type   Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root
                   ext4   8.9G  1.4G  7.1G  16% /
tmpfs              tmpfs  977M     0  977M   0% /dev/shm
/dev/sda1          ext4   500M   29M  445M   7% /boot
[root@laohan_httpd_server chapter-1]# >disk-info.log
[root@laohan_httpd_server chapter-1]# cat disk-info.log

还可以使用true指令重定向清空文件内容,代码如下。

free -g >mem-info.log

重定向内存信息到mem-info.log文件。

true > mem-info.log

使用true指令重定向清空mem-info.log文件内容。

1 [root@laohan_httpd_server chapter-1]# free -g
2            total     used      free     shared    buffers    cached
3 Mem:           1        0        1         0         0        0
4 -/+ buffers/cache:      0        1
5 Swap:          0        0        0
6 [root@laohan_httpd_server chapter-1]# free -g > mem-info.log
7 [root@laohan_httpd_server chapter-1]# cat mem-info.log
8            total     used      free     shared    buffers    cached
9 Mem:           1        0        1         0         0        0
10 -/+ buffers/cache:     0        1
11 Swap:         0        0        0
12 [root@laohan_httpd_server chapter-1]# true > mem-info.log
13 [root@laohan_httpd_server chapter-1]# cat mem-info.log

第13行使用cat指令查看mem-info.log文件内容时可以发现,该文件已经是空文件。

可以使用cat、cp、dd指令与/dev/null设备协同工作达到清空文件内容的目的。/dev/null设备是一个特殊的文件,它将清空重定向到它的输出,而它的输入是个空白文件,什么内容也没有。因此,可以使用cat指令查看/dev/null文件的内容,然后重定向输出到指定文件,达到清空文件内容的目的,代码如下。

cat /dev/null  >who-login.log

同理,可以将/dev/null文件的内容复制到指定文件,达到清空文件内容而不删除文件的目的,代码如下。

cp /dev/nullwho-login.log

完整执行结果如下。

1 [root@laohan_httpd_server chapter-1]# who > who-login.log
2 [root@laohan_httpd_server chapter-1]# cat who-login.log
3 root    pts/0       2018-09-06 23:29 (192.168.1.104)
4 [root@laohan_httpd_server chapter-1]# cat /dev/null > who-login.log
5 [root@laohan_httpd_server chapter-1]# cat who-login.log
6 [root@laohan_httpd_server chapter-1]# 
7 [root@laohan_httpd_server chapter-1]# uptime > sys-load.log
8 [root@laohan_httpd_server chapter-1]# cat sys-load.log
9  00:19:58 up 50 min,  1 user,  load average: 0.00, 0.00, 0.00
10 [root@laohan_httpd_server chapter-1]# cp -av /dev/null sys-load.log
11 cp:是否覆盖"sys-load.log"? y
12 已删除"sys-load.log"
13 "/dev/null" -> "sys-load.log"
14 [root@laohan_httpd_server chapter-1]# cat sys-load.log
15 [root@laohan_httpd_server chapter-1]# 
16 [root@laohan_httpd_server chapter-1]# 
17 [root@laohan_httpd_server chapter-1]# last > last-login.log
18 [root@laohan_httpd_server chapter-1]# cat last-login.log
19 root    pts/0       192.168.1.104   Thu Sep  6 23:29  still logged in
20 reboot  system boot  2.6.32-696.el6.x Thu Sep  6 23:29 - 00:20  (00:51)
21 root    pts/1       192.168.1.104   Tue Sep  4 22:00 - down   (00:54)
22 root    pts/0       192.168.1.104   Tue Sep  4 17:46 - down   (05:08)
23 reboot  system boot  2.6.32-696.el6.x Tue Sep  4 16:59 - 22:55  (05:56)
24 root    pts/2       192.168.1.104   Mon Sep  3 16:46 - down   (02:15)
25 root    pts/0       192.168.1.104   Mon Sep  3 16:06 - 18:22  (02:16)
26 root    pts/1       192.168.1.104   Mon Sep  3 05:30 - 17:31  (12:00)
27(中间代码略)
28 root    tty1                      Sat Aug  4 15:17 - 16:19  (01:02)
29 reboot  system boot  2.6.32-696.el6.x Sat Aug  4 15:15 - 16:31  (01:16)
30 root    pts/3       192.168.1.104   Mon Jul 23 01:19 - crash (12+13:56)
31 root    pts/0       192.168.1.104   Mon Jul 23 01:01 - crash (12+14:14)
32 root    pts/0       192.168.1.104   Mon Jul 23 00:58 - 01:00  (00:01)
33 root    pts/2       192.168.1.104   Sun Jul 22 23:18 - crash (12+15:57)
34 root    pts/1       192.168.1.104   Sun Jul 22 22:09 - 01:27  (03:18)
35 root    pts/0       192.168.1.104   Sun Jul 22 20:54 - 00:19  (03:24)
36 root    tty1                      Sun Jul 22 20:50 - crash (12+18:25)
37 reboot  system boot  2.6.32-696.el6.x Sun Jul 22 20:50 - 16:31 (12+19:41)
38 root    tty1                      Sun Jul 22 20:45 - down   (00:04)
39 reboot  system boot  2.6.32-696.el6.x Sun Jul 22 20:45 - 20:49  (00:04)
40
41 wtmp begins Sun Jul 22 20:45:03 2018
42 [root@laohan_httpd_server chapter-1]# dd if=/dev/null of=last-login.log
43 记录了0+0 的读入
44 记录了0+0 的写出
45 0字节(0 B)已复制,0.000103529 秒,0.0 KB/秒
46 [root@laohan_httpd_server chapter-1]# cat last-login.log

上述代码第17行(粗体)使用last指令重定向输出结果到last-login.log文件中,第18行使用cat指令查看last-login.log文件内容,第19~41行为last-login.log文件内容,第42行(粗体)使用dd if=/dev/null指令清空dd if=/dev/null文件内容,第46行查看last-login.log文件时,发现该文件内容已经被清空。

可以使用echo指令将空字符串重定向到指定文件,来清空文件内容,代码如下。

# echo "" >handuoduo-info.log

或者使用如下指令清空文件内容。

# echo >handuoduo-info.log

注意:该方法虽然清空了文件的内容,但是文件会包含一个空字符串,使用cat指令查看时,将看到一个空白行。空字符串不等于null,空字符串只能说明它的内容为空,而null则表示该事物不存在。要想彻底清空文件内容,可以使用echo指令的-n选项,该选项将“告诉”echo指令,不再输出一个空白行。

$  echo -n "" > system.log

完整输出结果如下。

1 [root@laohan_httpd_server chapter-1]# echo "My name is handuoduo" >handuoduo-info.log
2 [root@laohan_httpd_server chapter-1]# cat handuoduo-info.log
3 My name is handuoduo
4 [root@laohan_httpd_server chapter-1]# echo -n "" >handuoduo-info.log 
5 [root@laohan_httpd_server chapter-1]# cat handuoduo-info.log

第1行使用echo指令创建handuoduo-info.log文件,第2行查看handuoduo-info.log文件内容,第4行使用echo-n指令加上输出空字符串的组合选项清空handuoduo-info.log文件内容,第5行使用cat指令查看handuoduo-info.log文件内容。

truncate指令可以将一个文件缩小或者扩大到某个给定的大小,可以利用该指令和-s选项来特别指定文件的大小,输出结果如下。

1 [root@laohan_httpd_server chapter-1]# cp -av /etc/passwd .
2 "/etc/passwd" -> "./passwd"
3 [root@laohan_httpd_server chapter-1]# cat passwd 
4 root:x:0:0:root:/root:/bin/bash
5 bin:x:1:1:bin:/bin:/sbin/nologin
6 daemon:x:2:2:daemon:/sbin:/sbin/nologin
7 adm:x:3:4:adm:/var/adm:/sbin/nologin
8 lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
9 sync:x:5:0:sync:/sbin:/bin/sync
10 shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
11 halt:x:7:0:halt:/sbin:/sbin/halt
12 mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
13 uucp:x:10:14:uucp:/var/spool/uucp:/sbin/nologin
14 operator:x:11:0:operator:/root:/sbin/nologin
15 games:x:12:100:games:/usr/games:/sbin/nologin
16 gopher:x:13:30:gopher:/var/gopher:/sbin/nologin
17 ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
18 nobody:x:99:99:Nobody:/:/sbin/nologin
19 vcsa:x:69:69:virtual console memory owner:/dev:/sbin/nologin
20 saslauth:x:499:76:Saslauthd user:/var/empty/saslauth:/sbin/nologin
21 postfix:x:89:89::/var/spool/postfix:/sbin/nologin
22 sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
23 nginx:x:500:500::/home/nginx:/sbin/nologin
24 hanyanwei:x:501:501::/home/hanyanwei:/bin/bash
25 ntp:x:38:38::/etc/ntp:/sbin/nologin
26 dbus:x:81:81:System message bus:/:/sbin/nologin
27 apache:x:48:48:Apache:/var/www:/sbin/nologin
28 [root@laohan_httpd_server chapter-1]# truncate -s 0 passwd 
29 [root@laohan_httpd_server chapter-1]# cat passwd

上述代码第28行(粗体)使用-s选项设定文件的大小,若要清空文件内容,就设定为0。总体来说,主要是使用重定向来清空文件内容。

批量查找并清空文件内容可以使用一句话脚本,如下所示。

find /data -name *.access.log | xargs -i truncate -s 0 {}

上述代码查找并清空/data/目录下所有以.access.log结尾的文件的内容。

[root@shanghai_web_1_35_117 ~]# df -TH
Filesystem                Type  Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root ext4    51G   48G   14M 100% /
tmpfs                    tmpfs  4.8G  4.1k  4.8G   1% /dev/shm
/dev/vda1                 ext4  508M   35M  448M   8% /boot

上述代码第1行通过df -TH指令查看系统磁盘分区和容量信息,发现根目录已经无可用空间。

1 [root@shanghai_web_1_35_117 ~]# 
2 [root@shanghai_web_1_35_117 ~]# cd /home/handuoduo/runtime/tomcat_8081
3 [root@shanghai_web_1_35_117 tomcat_8081]# ll
4 ...
5 drwxr-xr-x 2 handuoduo handuoduo  4096 11月 23 2016 bin
6 drwxr-xr-x 3 handuoduo handuoduo  4096 5月  30 2016 conf
7 drwxr-xr-x 2 handuoduo handuoduo  4096 5月  30 2016 lib
8 -rwxr-xr-x 1 handuoduo handuoduo 56812 9月  26 2014 LICENSE
9 drwxr-xr-x 2 handuoduo handuoduo  4096 9月   8 00:01 logs
10 -rwxr-xr-x 1 handuoduo handuoduo  1192 9月  26 2014 NOTICE
11 -rwxr-xr-x 1 handuoduo handuoduo  8963 9月  26 2014 RELEASE-NOTES
12 -rwxr-xr-x 1 handuoduo handuoduo 16204 9月  26 2014 RUNNING.txt
13 drwxr-xr-x 2 handuoduo handuoduo 12288 10月 30 2017 temp
14 drwxr-xr-x 2 handuoduo handuoduo  4096 5月  12 2016 webapps
15 drwxr-xr-x 3 handuoduo handuoduo  4096 1月   4 2015 work
16 [root@shanghai_web_1_35_117 tomcat_8081]# du -sh logs/
17 42G     logs/
18 [root@shanghai_web_1_35_117 logs]# ls -lh |grep G
19 ...
20 -rw-r--r-- 1 handuoduo handuoduo  12G 8月  24 23:55 tomcat.log.bak.2018-08-24
21 -rw-r--r-- 1 handuoduo handuoduo  12G 8月  25 23:55 tomcat.log.bak.2018-08-25
22 -rw-r--r-- 1 handuoduo handuoduo  12G 8月  26 23:55 tomcat.log.bak.2018-08-26
23 -rw-r--r-- 1 handuoduo handuoduo 4.9G 8月  27 23:56 tomcat.log.bak.2018-08-27
24 [root@shanghai_web_1_35_117 logs]# pwd
25 /home/handuoduo/runtime/tomcat_8081/logs
26 [root@shanghai_web_1_35_117 logs]# find /home/handuoduo/runtime/tomcat_8081/logs -type f -size +3G 
27 /home/handuoduo/runtime/tomcat_8081/logs/tomcat.log.bak.2018-08-24
28 /home/handuoduo/runtime/tomcat_8081/logs/tomcat.log.bak.2018-08-25
29 /home/handuoduo/runtime/tomcat_8081/logs/tomcat.log.bak.2018-08-26
30 /home/handuoduo/runtime/tomcat_8081/logs/tomcat.log.bak.2018-08-27
31 [root@shanghai_web_1_35_117 logs]# time find 33 /home/handuoduo/runtime/tomcat_8081/logs -type f -size +3G|xargs -i truncate -s 0 {}

32 real    0m1.833s
33 user    0m0.005s
34 sys     0m1.576s
35 [root@shanghai_web_1_35_117 logs]# find /home/handuoduo/runtime/tomcat_8081/logs
36 -type f -size +3G
37 [root@shanghai_web_1_35_117 logs]# df -Th
38 Filesystem                Type   Size  Used Avail Use% Mounted on
39 /dev/mapper/VolGroup-lv_root ext4    47G  4.1G   41G  10% /
40 tmpfs                    tmpfs  4.5G  4.0K  4.5G   1% /dev/shm
41 /dev/vda1                 ext4   485M   33M  427M   8% /boot

上述代码中,第2行进入脚本运行所在目录,查看根目录占满是否由日志文件导致。第16行通过du -sh指令查看日志文件大小,可以确认是日志文件增长导致根目录空间被耗尽。

第18行通过ls -lh |grep G指令过滤GB级别的日志文件。

第26行使用find指令过滤空间占用在3GB以上的文件。

第31行执行批量删除指令,清空大日志文件内容,第37行查看磁盘空间占用是否恢复。

Linux系统清空文件内容注意事项。

1 :>file
2 >file
3 cat /dev/null >file

上述代码中,第1~3行都可以将文件内容清空,且文件大小为0。而下面两种方式,导致文本都有一个“\0”,文件大小为1B。

1 echo "" >file
2 echo >file

上述代码中,第1行和第2行指令清空文件内容。测试代码如下所示。

1[root@laohan_httpd_server chapter-1]# cp -av /etc/passwd .
2 cp:是否覆盖"./passwd"? y
3 "/etc/passwd" -> "./passwd"
4 [root@laohan_httpd_server chapter-1]# echo > passwd  
5 [root@laohan_httpd_server chapter-1]# ls -lhrt passwd 
6 -rw-r--r-- 1 root root 1 9月   8 11:00 passwd
7 [root@laohan_httpd_server chapter-1]# cp -av /etc/passwd .
8 cp:是否覆盖"./passwd"? y
9 "/etc/passwd" -> "./passwd"
10 [root@laohan_httpd_server chapter-1]# echo "" > passwd  
11 [root@laohan_httpd_server chapter-1]# ls -lhrt passwd 
12 -rw-r--r-- 1 root root 1 9月   8 11:00 passwd

上述代码第1行使用cp指令复制/etc/passwd文件到当前目录中。

第4行使用echo指令清空passwd文件内容,第5行使用ls -lhrt指令查看passwd文件,其大小为1B。

第10行使用echo指令输出字符串重定向的方式清空passwd文件内容,第11行使用ls -lhrt指令查看passwd文件,其大小为1B。

如下实例使用重定向的方式清空文件内容并计算耗时。

1 [root@hdfs_5_15 sbin]# ls -lh
2 总用量 28G
3 -rw-rw-r--. 1 tomcat tomcat 28G 9月   8 20:13 mona.log
4 -rwxrwxr-x. 1 tomcat tomcat 11M 4月  11 2017 mona-server
5 -rwxrwxr-x. 1 tomcat tomcat 144 4月  11 2017 mona-start.sh
6 -rwxrwxr-x. 1 tomcat tomcat  76 4月  11 2017 start.sh

上述代码第3行的mona.log文件占用28GB磁盘空间。下面将使用重定向方式清空此文件的内容,代码如下。

1 [root@hdfs_5_15 sbin]# time >mona.log 
2 
3 real    0m2.979s
4 user    0m0.001s
5 sys     0m0.953s
6 You have new mail in /var/spool/mail/root

上述代码中,第1行使用time指令计算清空mona.log文件内容的耗时统计,可以发现清空28GB的文件内容实际耗时不到3s,处理速度还是非常快的。再查看mona.log文件是否清除成功。

1 [root@hdfs_5_15 sbin]# ls -lh         
2 总用量 11M
3 -rw-rw-r--. 1 tomcat tomcat 143K 9月   8 20:13 mona.log
4 -rwxrwxr-x. 1 tomcat tomcat  11M 4月  11 2017 mona-server
5 -rwxrwxr-x. 1 tomcat tomcat  144 4月  11 2017 mona-start.sh
6 -rwxrwxr-x. 1 tomcat tomcat   76 4月  11 2017 start.sh

上述代码中,通过第3行发现mona.log文件并不是空的。这是因为有脚本不断在往此文件写入日志,所以使用ls -lh指令看到的文件大小不为0。在实际Linux服务器环境中,读者可以观察此现象是否存在。

Linux操作系统中的read指令用于从标准输入读取数值。

read内部指令被用于从标准输入读取单行数据,也可以被用于读取键盘输入。当使用重定向的时候,可以读取文件中的一行数据。read指令常用选项如表1-8所示。

表1-8  read指令常用选项

选项

说明

-a

后面跟一个变量,该变量是数组,然后给变量赋值,默认以空格为分隔符

-d

后面跟一个标志符,其实只有其后的第一个字符有用,作为结束的标志

-p

后面跟提示信息,即在输入前输出提示信息

-e

在输入的时候可以实现指令补全功能

-n

后面跟一个数字,定义输入文本的长度

-r

屏蔽“\”,若没有该选项,则“\”作为一个转义字符,否则“\”代表正常字符

-s

安静模式,在输入字符时不在终端上显示,例如登录时输入密码

-t

后面跟秒数,定义输入字符的等待时间

-u

后面跟文件描述符数量,从文件描述符中读入,该文件描述符可以是exec新开启的

【实例1-25】接收用户输入的内容

1   [root@laohan_httpd_server chapter-1]# cat 18-read.sh
2   #!/bin/bash
3
4   #这里默认会换行
5   echo "请输入您的名字: "  
6   #读取键盘输入
7   read name  
8   echo "您的名字是: $name"  
9   exit 0  #退出

上述代码第7行使用name变量接收用户输入的内容,第8行输出字符串和变量值。脚本执行结果如下。

[root@laohan_httpd_server chapter-1]# bash 18-read.sh 
请输入您的名字: 
handuoduo
您的名字是: handuoduo
[root@laohan_httpd_server chapter-1]#

【实例1-26】输出提示信息

使用read 指令中的-p选项,可以在语句中指定提示信息,代码如下。

1    [root@laohan_httpd_server chapter-1]# cat 19-read-p.sh
2   #!/bin/bash
3
4   read -p "请输入您的名字:" name
5   echo "您的名字是: $name" 
6   exit 0

上述代码第4行指定用户输入内容时的提示信息,脚本执行结果如下所示。

[root@laohan_httpd_server chapter-1]# bash 19-read-p.sh
请输入您的名字:handuoduo
您的名字是: handuoduo

【实例1-27】计时器

read指令的-t选项表示等待输入的秒数。当计时结束时,read指令返回非0状态码。

1 [root@laohan_httpd_server chapter-1]# cat 20-read-timeout.sh
2
3 #!/bin/bash
4
5 if read -t 6 -p "您的名字是:" name
6 then
7   echo "您输入的名字是: $name"
8 else
9    echo -e "\n抱歉,您输入已超时了,$0 脚本将自动退出..."
10 fi
11 exit 0

上述代码中,对脚本不传入任何参数,第5行执行等待6s后,执行结果如下所示。

1 [root@laohan_httpd_server chapter-1]# bash 20-read-timeout.sh
2 您的名字是:
3 抱歉,您输入已超时了,20-read-timeout.sh 脚本将自动退出...
4 [root@laohan_httpd_server chapter-1]#

上述脚本的执行结果中,第2行和第3行表示用户未对20-read-timeout.sh传入参数,导致脚本超时6s自动退出。

【实例1-28】匹配字符串

除了输入时间计时,还可以使用-n选项设置read指令计数输入的字符。当输入的字符数目达到预定数目时,自动退出,并将输入的数据赋值给变量,代码如下。

[root@laohan_httpd_server chapter-1]# cat 21-read-n-count.sh
#!/bin/bash

read -n1 -p "Do you want to continue [Y/N]?" Enter
case $Enter in
Y | y)
     echo "Fine ,continue";;
N | n)
     echo "Ok,good bye";;
*)
    echo "Error choice";;
esac
exit 0

21-read-n-count.sh脚本执行结果如下所示。

[root@laohan_httpd_server chapter-1]# bash 21-read-n-count.sh
Do you want to continue [Y/N]?tError choice

该实例使用了-n选项,后接数值1,指示read指令只要接收到1个字符就退出。只要输入1个字符进行回答,read指令立即接收输入并将其传给变量,无须按“Enter”键。如下代码只接收4个字符就退出。

[root@laohan_httpd_server chapter-1]# cat 22-read.sh
#!/bin/bash

read -n4 -p "请随便输入4个字符: " any
echo -e "\n您输入的4个字符是:$any"
exit 0
[root@laohan_httpd_server chapter-1]#

22-read.sh脚本执行结果如下所示。

[root@laohan_httpd_server chapter-1]# bash 22-read.sh
请随便输入4个字符: love
您输入的4个字符是:love

【实例1-29】静默模式

read指令中-s选项能够使用户输入的数据不显示在指令终端上(数据是显示的,只是 read指令的-s选项将文本颜色设置为与背景相同的颜色),要求用户输入密码时常用该选项,代码如下。

[root@laohan_httpd_server chapter-1]# cat 22-read-passwd.sh
#!/bin/bash

read  -s  -p "请输入您的密码:" pass
echo -e "\n您输入的密码是:\033[32;40m$pass\033[0m"
exit 0

执行脚本,输入密码后不显示任何内容,代码如下。

[root@laohan_httpd_server chapter-1]# bash 22-read-passwd.sh
请输入您的密码:
您输入的密码是:handuoduo

【实例1-30】读取文件

每次调用read指令都会读取文件中的一行文本,当文件没有可读的行时,read指令将以非0状态码退出。

使用cat指令读取文件并通过管道将程序运行结果直接传送给包含read指令的while循环。测试文件disk-info.log内容如下。

[root@laohan_httpd_server chapter-1]# df -Th >disk-info.log
[root@laohan_httpd_server chapter-1]# cat disk-info.log
Filesystem         Type   Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root
                   ext4   8.3G  1.3G  6.6G  16% /
tmpfs              tmpfs  931M     0  931M   0% /dev/shm
/dev/sda1          ext4   477M   28M  425M   7% /boot

测试代码如下所示。

[root@laohan_httpd_server chapter-1]# cat 24-read-file.sh
#!/bin/bash


declare -i count=1
cat disk-info.log | while read line
do
    echo "Line $count:$line"
    count=$[ $count + 1 ]
done

echo "$0 exec finished..."
exit 0

24-read-file.sh脚本执行结果如下。

1  [root@laohan_httpd_server chapter-1]# bash 24-read-file.sh
2 Line 1:Filesystem          Type  Size  Used Avail Use% Mounted on
3 Line 2:/dev/mapper/VolGroup-lv_root
4 Line 3:ext4   8.3G  1.3G  6.6G  16% /
5 Line 4:tmpfs              tmpfs  931M     0  931M   0% /dev/shm
6 Line 5:/dev/sda1          ext4   477M   28M  425M   7% /boot
7  24-read-file.sh exec finished...

上述执行结果中可以发现disk-info.log文件内容共有5行(第2~6行)。

sleep指令常用于在Shell脚本中延迟时间。注意:以下用法中可以用小数。sleep指令常用格式如下所示。

$ sleep <n>

其中的n代表数字,可以是延迟n秒、延迟n分钟、延迟n小时、延迟n天,如下所示。

sleep 1表示默认延迟一秒。

sleep 1s表示延迟一秒。

sleep 1m表示延迟一分钟。

sleep 1h表示延迟一小时。

sleep 1d表示延迟一天。

【实例1-31】使用sleep指令延迟执行

1 [root@laohan_httpd_server chapter-1]# date ; sleep 6 ; date
2 2018年 09月 08日星期六 20:48:47 CST
3
4 2018年 09月 08日星期六 20:48:53 CST
5 [root@laohan_httpd_server chapter-1]# 
6 [root@laohan_httpd_server chapter-1]# 
7 [root@laohan_httpd_server chapter-1]# date ; sleep 1m ; date
8 2018年 09月 08日星期六 20:48:57 CST
9 2018年 09月 08日星期六 20:49:57 CST

上述代码第1行sleep 6表示延迟6s,也可以写为sleep 6s,这里的s省略了,第7行sleep 1m表示延迟1分钟。

1.显示时间

date指令可以按照指定格式显示时间,只输入date则以默认格式显示当前时间。代码如下。

[root@laohan_httpd_server chapter-1]# date
2018年 09月 08日星期六 20:53:10 CST

如果需要以指定的格式显示时间,可以使用以“%”开头的字符串指定其格式,详细格式如表1-9所示。

表1-9  date指令详细格式

格式

说明

%n

下一行

%t

跳格

%H

小时(00~23)

%I

小时(01~12)

%k

小时(0~23)

%l

小时(1~12)

%M

分钟(00~59)

%p

显示本地AM或PM

%r

直接显示时间(12小时制,格式为hh:mm:ss [AP]M)

%S

%T

直接显示时间(24小时制)

%X

相当于%H:%M:%S

%Z

显示时区

%a

星期几

%b

月份

%d

%h

同 %b

%j

一年中的第几天

%m

月份

%y

年份的最后两位数字

%Y

完整年份(0000~9999)

上述格式不必全都记住,只需要掌握几个常用的即可。

【实例1-32】date指令常用实例

[root@laohan_httpd_server chapter-1]# date "+现在时间是:%Y-%m-%d %H: %M: %S"
现在时间是:2018-09-08 20: 54: 32

如果要显示的时间不是当前时间,而是经过运算的时间,则可以用-d选项。例如显示3年前的时间,代码如下。

[root@laohan_httpd_server chapter-1]# date "+%Y-%m-%d %H: %M: %S" -d "-3 year"
2015-09-08 20: 55: 20

显示3个月后的时间,代码如下。

[root@laohan_httpd_server chapter-1]# date "+%Y-%m-%d %H: %M: %S" -d "+3 month"
2018-12-08 20: 55: 51

显示10天后的时间,代码如下。

[root@laohan_httpd_server chapter-1]# date "+%Y-%m-%d %H: %M: %S" -d "+10 day"
2018-09-18 20: 56: 18

有时候需要获取当前时间距离1970年1月1日00:00:00的秒数,保存在变量中。

1 [root@laohan_httpd_server chapter-1]# time_1970_now=`date +%s`
2 [root@laohan_httpd_server chapter-1]# echo $time_1970_now
3 1536411423

上述代码第1行将date +%s指令的执行结果赋值给变量time_1970_now,第2~3行输出time_1970_now变量的值。

2.设置时间

使用date指令中的-s选项可以设置系统时间,方式多种多样,代码如下。

[root@laohan_httpd_server chapter-1]# date -s "20121216 12:16:16"
2012年 12月 16日星期日 12:16:16 CST
[root@laohan_httpd_server chapter-1]# date -s "2012-12-16 12:16:16"
2012年 12月 16日星期日 12:16:16 CST
[root@laohan_httpd_server chapter-1]# date -s "2012/12/16 12:16:16"
2012年 12月 16日星期日 12:16:16 CST
[root@laohan_httpd_server chapter-1]# date -s "12:16:16 20121216"
2012年 12月 16日星期日 12:16:16 CST
[root@laohan_httpd_server chapter-1]# date -s "12:16:16 2012/12/16"
2012年 12月 16日星期日 12:16:16 CST

SSH服务登录远程服务器时不能在指令行中指定密码,sshpass指令的出现,解决了这一问题。sshpass指令用于非交互SSH的密码验证,一般用在Shell脚本中,无须再次输入密码。使用sshpass指令中的-p选项指定明文密码,可以直接登录远程服务器,它支持密码从指令行、文件、环境变量中读取。

sshpass指令默认没有安装CentOS,需要手动安装,测试环境服务器基本信息如表1-10所示。

表1-10  测试环境服务器基本信息

操作系统环境

IP

主机名

防火墙及SELinux

CentOS 6.9 x86_64

192.168.1.110

Shell-programmer

均关闭

CentOS 6.9 x86_64

192.168.1.168

Linux-command

均关闭

首先检测两台主机的EPEL源,然后分别安装sshpass软件,代码如下。

[root@laohan_httpd_server chapter-1]# ll /etc/yum.repos.d/ |grep epel
-rw-r--r--  1 root root  957 11月  5 2012 epel.repo
-rw-r--r--  1 root root 1056 11月  5 2012 epel-testing.repo
[root@laohan_httpd_server chapter-1]# yum list sshpass
已加载插件:fastestmirror
Determining fastest mirrors
epel/metalink 
(中间代码略)
Installed:
  sshpass.x86_64 0:1.06-1.el6 

Complete!

经过以上步骤,sshpass安装完成,输入指令sshpass如出现如下提示即表示安装成功。

[root@laohan_httpd_server chapter-1]# sshpass
Usage: sshpass [-f|-d|-p|-e] [-hV] command parameters
   -f file   Take password to use from file
   -d number     Use number as file descriptor for getting password
   -p password   Provide password as argument (security unwise)
   -e            Password is passed as env-var "SSHPASS"
   With no parameters - password will be taken from stdin

   -P prompt     Which string should sshpass search for to detect a password prompt
   -v            Be verbose about what you're doing
   -h            Show help (this screen)
   -V            Print version information
At most one of -f, -d, -p or -e should be used

遇到的问题:第一次从一台服务器使用sshpass登录另一台服务器的时候,有时候执行如下指令无响应。

[root@laohan_httpd_server ~]#  sshpass -p 123.com ssh -p 22 root@192.168.1.168

原因:对于第一次登录,会提示“Are you sure you want to continue connecting (yes/no)”,这时sshpass会不好用。

解决办法:可以在sshpass指令后面加上-o StrictHostKeyChecking=no来解决,代码如下。

[root@laohan_httpd_server ~]#  sshpass -p 123.com ssh -p 22  -o StrictHostKeyChecking=no  root@192.168.1.168 'df -TH'
Filesystem           Type   Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root
                     ext4    19G  982M   17G   6% /
tmpfs                tmpfs  515M     0  515M   0% /dev/shm
/dev/sda1            ext4   500M   29M  445M   7% /boot

如果不想在指令行中添加-o StrictHostKeyChecking=no,可以在sshd配置文件中的/etc/ssh/ ssh_config文件中设置StrictHostKeyChecking no(默认为#StrictHostKeyChecking ask)。

(1)file后跟保存密码的文件名,密码是文件内容的第1行,代码如下。

[root@laohan_httpd_server ~]#  sshpass -f loggin.txt ssh -p 22  -o StrictHostKeyChecking=no  root@192.168.1.168 'df -TH'
Filesystem           Type   Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root
                     ext4    19G  982M   17G   6% /
tmpfs                tmpfs  515M     0  515M   0% /dev/shm
/dev/sda1            ext4   500M   29M  445M   7% /boot

(2)环境变量SSHPASS作为密码,代码如下。

[root@laohan_httpd_server ~]# export SSHPASS=''123.com
[root@laohan_httpd_server ~]# sshpass -e   ssh -p 22  -o StrictHostKeyChecking=no  root@192.168.1.168 'df -TH'
Filesystem           Type   Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root
                     ext4    19G  982M   17G   6% /
tmpfs                tmpfs  515M     0  515M   0% /dev/shm
/dev/sda1            ext4   500M   29M  445M   7% /boot

(3)非交互模式输入密码,使用-p选项指定密码,代码如下。

sshpass -p '123456' ssh user_name@host_ip
sshpass -p '123456' scp root@host_ip:/home/test/t ./tmp/

(4)在多台主机执行指令。

查看远程主机列表信息,代码如下。

[root@laohan_httpd_server chapter-1]# cat hosts.info 
192.168.1.120
192.168.1.121
192.168.1.122
192.168.1.123

查看25-sshpass-command.sh脚本内容,代码如下。

[root@laohan_httpd_server chapter-1]# cat 25-sshpass-command.sh
#!/bin/bash
for i in $(cat hosts.info)
do
    echo $i
    sshpass -p'123.com' ssh root@$i 'ip addr |grep -w inet |grep -v 127 |awk '{print $2}'|cut -d'/' -f1'
done

25-sshpass-command.sh脚本执行结果,代码如下。

[root@laohan_httpd_server chapter-1]# bash 25-sshpass-command.sh
192.168.1.120
192.168.1.121
192.168.1.122
192.168.1.123

(5)实际工作中的小案例。

【实例1-33】批量修改密码

定期修改服务器密码之后,验证是否修改成功,hosts.info文件内容如下所示。

[root@laohan_httpd_server chapter-1]# cat hosts.info 
192.168.1.120
192.168.1.121
192.168.1.122
192.168.1.123

修改25-sshpass-command.sh脚本内容,如下所示。

[root@laohan_httpd_server chapter-1]# cat 25-sshpass-command.sh 
#!/bin/bash
old_pass='123.com'
new_pass='6d@2whs@efB#aZZd'
for host in $(cat hosts.info)
do
/usr/bin/sshpass -p "$old_pass" ssh -p 22  -o StrictHostKeyChecking=no root@$host "echo $new_pass |passwd --stdin root"
done

25-sshpass-command.sh脚本执行结果如下所示。

1   [root@laohan_httpd_server chapter-1]# bash 25-sshpass-command.sh
2   更改用户 root 的密码
3   passwd: 所有的身份验证令牌已经成功更新
4   更改用户 root 的密码
5   passwd: 所有的身份验证令牌已经成功更新
6   更改用户 root 的密码
7   passwd: 所有的身份验证令牌已经成功更新
8   更改用户 root 的密码
9   passwd: 所有的身份验证令牌已经成功更新

从上述代码第2~9行的输出结果可以看到,hosts.info文件中的主机列表修改密码成功。

【实例1-34】sshpass结合scp同步文件

使用sshpass结合scp同步文件,代码如下。

1   [root@kafkazk1 ~]# echo "跟老韩学Shell编程" > laohan_shell.info
2   [root@kafkazk1 ~]# cat laohan_shell.info
3   跟老韩学Shell编程
4   [root@kafkazk1 ~]# sshpass -p 1 scp laohan_shell.info root@kafkazk2:/root/

上述代码第4行使用sshpass结合scp发送laohan_shell.info文件到服务器kafkazk2。

[root@kafkazk2 ~]# ls -lhrt --full-time /root/laohan_shell.info
-rw-r--r-- 1 root root 24 2020-12-26 19:05:46.271864129 +0800 /root/laohan_shell.info
[root@kafkazk2 ~]# cat /root/laohan_shell.info 
跟老韩学Shell编程

上述代码表示登录kafkazk2服务器,确认laohan_shell.info文件是否存在,并使用cat指令查看laohan_shell.info文件内容。

【实例1-35】sshpass结合pssh

在kafkazk1服务器上安装pssh软件,安装过程如下。

1   [root@kafkazk1 ~]# yum list pssh
2   上次元数据过期检查:0:21:52 前,执行于 2020年12月26日 星期六 18时59分00秒
3   可安装的软件包
4   pssh.noarch 2.3.1-29.el8 epel
5   [root@kafkazk1 ~]# yum -y install pssh
6   上次元数据过期检查:0:26:52 前,执行于 2020年12月26日 星期六 18时59分00秒
7   依赖关系解决
8   =====================================================================================================================================
9   软件包  架构  版本  仓库  大小
10  =====================================================================================================================================
11  Installing:
12  pssh  noarch  2.3.1-29.el8  epel  58 kB
13
14  事务概要
15  =====================================================================================================================================
16  安装  1 软件包
17
18  总下载:58 kB
19  安装大小:112 kB
20  下载软件包:
21  pssh-2.3.1-29.el8.noarch.rpm  22 kB/s |  58 kB     00:02
22  -------------------------------------------------------------------------------------------------------------------------------------
23  总计  13 kB/s |  58 kB     00:04
24  运行事务检查
25  事务检查成功
26  运行事务测试
27  事务测试成功
28  运行事务
29    准备中:1/1
30    Installing:pssh-2.3.1-29.el8.noarch  1/1 
31    运行脚本:pssh-2.3.1-29.el8.noarch  1/1 
32    验证:pssh-2.3.1-29.el8.noarch  1/1 
33
34  已安装:
35    pssh-2.3.1-29.el8.noarch
36

上述代码第1行使用yum list指令列出当前软件yum源中是否包含pssh软件。

第2~4行为输出结果。

第5行使用yum指令安装pssh软件,第6~36行为输出结果。

在kafkazk2服务器上安装pssh软件,安装过程如下。

1   [root@kafkazk2 ~]# yum list pssh
2   上次元数据过期检查:0:27:41 前,执行于 2020年12月26日 星期六 18时58分28秒
3   可安装的软件包
4   pssh.noarch 2.3.1-29.el8 epel
5   [root@kafkazk2 ~]# yum -y install pssh
6   上次元数据过期检查:0:27:48 前,执行于 2020年12月26日 星期六 18时58分28秒
7   依赖关系解决
8   =====================================================================================================================================
9   软件包  架构  版本  仓库  大小
10  =====================================================================================================================================
11  Installing:
12   pssh noarch 2.3.1-29.el8 epel 58 kB
13  Upgrading:
14   platform-python-pip noarch 9.0.3-18.el8 base 1.7 MB
15   platform-python-setuptools noarch 39.2.0-6.el8 base 632 kB
16  安装依赖关系
17   python3-pip noarch 9.0.3-18.el8 AppStream 20 kB
18   python36 x86_64 3.6.8-2.module_el8.3.0+562+e162826a AppStream 19 kB
19   python3-setuptools noarch 39.2.0-6.el8 base 163 kB
20  Enabling module streams:
21   python36 3.6 
22
23  事务概要
24  =====================================================================================================================================
25  安装  4 软件包
26  升级  2 软件包
27
28  总下载:2.6 MB
29  下载软件包
30  (1/6):               python3-setuptools-39.2.0-6.el8.noarch.rpm 135 kB/s | 163 kB     00:01
31  (2/6):                       python3-pip-9.0.3-18.el8.noarch.rpm 15 kB/s | 20 kB     00:01
32  (3/6):   python36-3.6.8-2.module_el8.3.0+562+e162826a.x86_64.rpm 15 kB/s | 19 kB     00:01    
33  (4/6):                             pssh-2.3.1-29.el8.noarch.rpm 291 kB/s | 58 kB     00:00    
34  (5/6):       platform-python-setuptools-39.2.0-6.el8.noarch.rpm 1.6 MB/s | 632 kB     00:00    
35  (6/6):              platform-python-pip-9.0.3-18.el8.noarch.rpm 1.5 MB/s | 1.7 MB     00:01    
36  -------------------------------------------------------------------------------------------------------------------------------------
37  总计 688 kB/s | 2.6 MB     00:03     
38  运行事务检查
39  事务检查成功
40  运行事务测试
41  事务测试成功
42  运行事务
43    准备中:1/1 
44    Upgrading:platform-python-setuptools-39.2.0-6.el8.noarch  1/8 
45    Installing:python3-setuptools-39.2.0-6.el8.noarch 2/8 
46    Upgrading:platform-python-pip-9.0.3-18.el8.noarch 3/8 
47    Installing:python36-3.6.8-2.module_el8.3.0+562+e162826a.x86_64 4/8 
48    运行脚本:python36-3.6.8-2.module_el8.3.0+562+e162826a.x86_64 4/8 
49    Installing:python3-pip-9.0.3-18.el8.noarch 5/8 
50    Installing:pssh-2.3.1-29.el8.noarch 6/8 
51    清理:platform-python-pip-9.0.3-13.el8.noarch 7/8 
52    清理:platform-python-setuptools-39.2.0-4.el8.noarch 8/8 
53    运行脚本:platform-python-setuptools-39.2.0-4.el8.noarch 8/8 
54    验证:python3-pip-9.0.3-18.el8.noarch 1/8 
55    验证:python36-3.6.8-2.module_el8.3.0+562+e162826a.x86_64 2/8 
56    验证:python3-setuptools-39.2.0-6.el8.noarch 3/8 
57    验证:pssh-2.3.1-29.el8.noarch 4/8 
58    验证:platform-python-pip-9.0.3-18.el8.noarch 5/8 
59    验证:platform-python-pip-9.0.3-13.el8.noarch 6/8 
60    验证:platform-python-setuptools-39.2.0-6.el8.noarch 7/8 
61    验证:platform-python-setuptools-39.2.0-4.el8.noarch 8/8 
62
63  已升级
64    platform-python-pip-9.0.3-18.el8.noarch platform-python-setuptools-39.2.0-6.el8.noarch
65
66  已安装
67    pssh-2.3.1-29.el8.noarch python3-pip-9.0.3-18.el8.noarch python36-3.6.8-2.module_el8.3.0+562+e162826a.x86_64   
68    python3-setuptools-39.2.0-6.el8.noarch   
69

上述代码第1行使用yum list指令列出当前软件yum源中是否包含pssh软件。

第2~4行为输出结果。

第5行使用yum指令安装pssh软件,第6~69行为输出结果,可以看到此次安装pssh软件的同时附带安装了很多有关Python的软件。

查看pssh软件的组件,代码如下。

1   [root@kafkazk1 ~]# rpm -ql pssh | head -5
2   /usr/bin/pnuke
3   /usr/bin/prsync
4   /usr/bin/pscp.pssh
5   /usr/bin/pslurp
6   /usr/bin/pssh

pssh提供OpenSSH和相关工具的并行版本。包括pssh、pscp、prsync、pnuke和pslurp。该项目包括psshlib,可以在自定义应用程序中使用。pssh的用法可以媲美Ansible的一些简单用法,执行起来速度比Ansible快,它支持文件并行复制、远程命令执行、杀掉远程主机上的进程等。

使用sshpass结合pssh查看服务器的日期和负载,代码如下。

1   [root@kafkazk1 ~]# sshpass -p 1 pssh -A -i -H kafkazk2 "date"
2   Warning: do not enter your password if anyone else has superuser
3   privileges or access to your account.
4   [1] 19:35:48 [SUCCESS] kafkazk2
5   2020年 12月 26日 星期六 19:35:48 CST
6   [root@kafkazk1 ~]# sshpass -p 1 pssh -A -i -H kafkazk2 "uptime"
7   Warning: do not enter your password if anyone else has superuser
8   privileges or access to your account.
9   [1] 19:36:13 [SUCCESS] kafkazk2
10  19:36:13 up 48 min,  1 user,  load average: 0.00, 0.14, 0.18

上述代码第1行查看kafkazk2服务器的系统时间,第2~5行为输出结果,第6行查看kafkazk2服务器的系统负载,第7~10行为输出结果。

【实例1-36】sshpass结合rsync

创建测试文件,代码如下。

[root@kafkazk1 ~]# echo "跟老韩学Shell编程" >> laohan_shell.info
[root@kafkazk1 ~]# cat laohan_shell.info
跟老韩学Shell编程
跟老韩学Shell编程

使用sshpass结合rsync同步文件,可以解决每次远程同步文件需要输入密码的问题,代码如下。

1   [root@kafkazk1 ~]# sshpass -p 1 rsync -avz /root/laohan_shell.info root@ kafkazk2:/root
2   sending incremental file list
3   laohan_shell.info
4 
5   sent 128 bytes  received 41 bytes  338.00 bytes/sec
6   total size is 48  speedup is 0.28
7   [root@kafkazk2 ~]# cat /root/laohan_shell.info 
8   跟老韩学Shell编程
9   跟老韩学Shell编程

上述代码第1行将/root目录下的laohan_shell.info文件传输到kafkazk2服务器的/root目录下,第2~6行为输出结果,第7行登录kafkazk2服务器查看/root/laohan_shell.info文件的内容是否正确,第8行和第9行为输出文件内容,结果和kafkazk1服务器的laohan_shell.info文件的内容显示一致,表示第1行代码执行准确无误。

【实例1-37】定时同步时间

生产环境服务器定时同步时间一直未生效,使用crontab -e指令编辑内容,如下所示。

*/30 * * * *  source  /etc/profile &&  /usr/sbin/ntpdate  ntp1.aliyun.com >>/tmp/$(/bin/date +%F-%T).log

修改后的定时任务,使用crontab -l指令显示内容如下所示。

[root@laohan_httpd_server ~]# crontab  -l
*/30 * * * *  source  /etc/profile &&  /usr/sbin/ntpdate  ntp1.aliyun.com >>/tmp/$(/bin/date +\%F-\%T).log

定时任务执行后,查看/tmp目录下定时任务执行后记录的日志文件,输出结果如下。

[root@laohan_httpd_server chapter-1]# ls -lh /tmp/
总用量 140K
-rw-r--r--  1 root root   85 9月   4 22:00 2018-09-04-22:00:01.log
-rw-r--r--  1 root root   85 9月   4 22:30 2018-09-04-22:30:01.log
-rw-r--r--  1 root root   85 9月   6 23:30 2018-09-06-23:30:05.log
-rw-r--r--  1 root root   85 9月   7 00:00 2018-09-07-00:00:01.log
-rw-r--r--  1 root root   85 9月   7 00:30 2018-09-07-00:30:01.log
-rw-r--r--  1 root root   87 9月   8 08:48 2018-09-07-01:00:01.log
-rw-r--r--  1 root root   85 9月   8 09:00 2018-09-08-09:00:01.log
-rw-r--r--  1 root root   85 9月   8 09:30 2018-09-08-09:30:01.log
-rw-r--r--  1 root root   85 9月   8 10:00 2018-09-08-10:00:01.log
-rw-r--r--  1 root root   85 9月   8 10:30 2018-09-08-10:30:01.log
-rw-r--r--  1 root root   85 9月   8 11:00 2018-09-08-11:00:01.log
-rw-r--r--  1 root root   85 9月   8 11:30 2018-09-08-11:30:01.log
-rw-r--r--  1 root root   86 9月   8 17:23 2018-09-08-12:00:01.log
-rw-r--r--  1 root root   85 9月   8 17:30 2018-09-08-17:30:01.log
-rw-r--r--  1 root root   85 9月   8 18:00 2018-09-08-18:00:01.log
-rw-r--r--  1 root root   85 9月   8 18:30 2018-09-08-18:30:01.log
-rw-r--r--  1 root root   85 9月   8 19:00 2018-09-08-19:00:01.log
-rw-r--r--  1 root root   85 9月   8 19:30 2018-09-08-19:30:01.log
-rw-r--r--  1 root root   85 9月   8 20:00 2018-09-08-20:00:01.log
-rw-r--r--  1 root root   85 9月   8 20:30 2018-09-08-20:30:01.log
-rw-r--r--  1 root root   85 9月   8 21:30 2018-09-08-21:30:01.log
-rw-r--r--  1 root root   86 9月   9 10:07 2018-09-08-22:00:01.log
-rw-r--r--  1 root root   85 9月   9 10:30 2018-09-09-10:30:01.log
-rw-r--r--  1 root root   85 9月   9 11:00 2018-09-09-11:00:01.log

总结:在crontab定时任务中,要对“%”进行转义,否则定时任务中的脚本无法执行。

1.通配符基础知识

使用通配符,可以节省输入的时间,并且可以快速、精确地引用特定的目录。通配符是另一种简写形式,用于引用目录中的内容。

2.通配符与正则表达式

通配符与正则表达式很容易混淆,首先要明白二者是不同的。

通配符用于Linux的Shell指令(如根据文件类型查找文件)中,而正则表达式用于文本内容中的字符串搜索和替换。

通配符是Linux本身就支持的,而正则表达式用于Vim编辑器或awk脚本。文本处理工具正是由于支持正则表达式才变得强大,关于正则表达式的相关内容会在本书的第3章中详解介绍。

3.通配符在Windows操作系统中的应用

在Windows操作系统中指定文件或寻找文件时,使用“”代表任意字符串。例如CentOS.iso表示搜索所有以CentOS开头,中间是任意字符,扩展名为.iso的镜像文件,如图1-11所示。

图1-11 搜索以CentOS开头且扩展名为.iso的文件

4.通配符在Linux操作系统中的应用

Linux操作系统中通配符的基本使用方法如下。

1 [root@laohan_httpd_server ~]# touch laohan-{1..3}.txt
2 [root@laohan_httpd_server ~]# ls -lh  --full-time *.txt
3 -rw-r--r-- 1 root root 0 2019-12-04 22:50:23.000000000 +0800 laohan-1.txt
4 -rw-r--r-- 1 root root 0 2019-12-04 22:50:23.000000000 +0800 laohan-2.txt
5 -rw-r--r-- 1 root root 0 2019-12-04 22:50:23.000000000 +0800 laohan-3.txt
6 [root@laohan_httpd_server ~]# 
7 [root@laohan_httpd_server ~]# ls -lh  --full-time duoduo*.txt
8 ls: cannot access duoduo*.txt: No such file or directory

上述代码中,第2行表示会在当前目录下搜索扩展名为.txt的文件,该指令会被解释成如下指令的合集。

ls -lh  --full-time 1.txt
ls -lh  --full-time 2.txt
ls -lh  --full-time 3.txt
ls -lh  --full-time a.txt
ls -lh  --full-time b.txt
ls -lh  --full-time d.txt
ls -lh  --full-time laohan.txt

上述代码第2行,执行ls指令时,传递给ls指令的有1.txt~3.txt、a.txt~d.txt、laohan.txt等参数。第7行执行后,由于当前目录下面没有对应的文件或目录,直接将“duoduo*.txt”作为ls指令的参数,传给了ls,此时“*”只是一个普通的ls参数而已,已经失去了通配的意义。由于找不到文件,因此会出现“No such file or directory”的提示。

5.Shell常用通配符

Shell常用通配符如表1-11所示。

表1-11  Shell常用通配符

字符

含义

*

匹配0个或多个任意字符,即匹配任何内容

匹配一个任意字符

[]

匹配“[]”中任意一个字符。如[abc]代表一定匹配一个字符,或者是a,或者是b,或者是c

[-]

匹配“[]”中任意一个字符,“-”代表一个范围。例如,[a-z]代表匹配一个小写字母

[^]

逻辑非,表示匹配不是“[]”内的一个字符。例如,[^0-9] 代表匹配一个不是数字的字符

{s1,s2,…}

逻辑或,表示匹配s1或s2

注意:通配符看起来有点像正则表达式,但是它与正则表达式不同,不能混淆。把通配符理解为Shell特殊字符即可。而且涉及的只有“*”“?”“[]”“{}”这几种。

备注:几种常见的特殊符号表示如下。

[[:upper:]]:所有大写字母。

[[:lower:]]:所有小写字母。

[[:alpha:]]:所有字母。

[[:digit:]]:所有数字。

[[:alnum:]]:所有的字母和数字。

[[:space:]]:所有空格。

[[:punct:]]:所有标点符号。

通配符用来匹配符合条件的文件名,通配符是完全匹配。ls、find、cp这些指令不支持正则表达式,所以只能使用Shell自己的通配符来进行匹配。

实例目录中包含如下5个.txt文件,代码如下。

[root@laohan-shell-1 ~]# touch {laohan,duoduo,python,shell,nginx}.txt
[root@laohan-shell-1 ~]# ls -l *.txt
-rw-r--r-- 1 root root    0 2019-12-07 21:12:08.521716909 +0800 shell.txt
-rw-r--r-- 1 root root    0 2019-12-07 21:12:08.521716909 +0800 python.txt
-rw-r--r-- 1 root root    0 2019-12-07 21:12:08.521716909 +0800 nginx.txt
-rw-r--r-- 1 root root 1020 2019-12-07 21:12:08.521716909 +0800 laohan.txt
-rw-r--r-- 1 root root    0 2019-12-07 21:12:08.521716909 +0800 duoduo.txt

查找以“lao”开头的.txt文件,代码如下。

[root@laohan-shell-1 ~]# ls -l laohan*.txt
-rw-r--r-- 1 root root 1020 2019-12-07 21:12:08.521716909 +0800 laohan.txt

查找以“lao”开头且长度为6位的.txt文件,代码如下。

[root@laohan-shell-1 ~]# ls -l lao???.txt
-rw-r--r-- 1 root root 1020 2019-12-07 21:12:08.521716909 +0800 laohan.txt

查找以“duo”开头,即第一位为d,第二位为u,第三位为o的.txt文件。

[root@laohan-shell-1 ~]# ls -l duo[d][u][o].txt
-rw-r--r-- 1 root root 0 2019-12-07 21:12:08.521716909 +0800 duoduo.txt

注意:对于“[]”中连续的字符串可以采用简写的形式,包含首尾字符,中间使用“~”连接,如ls a[bcdefg][hijklm].txt可以简写为ls a[b~g][h~m].txt。

6.综合应用

(1)显示/var目录下所有以“l”开头,以一个小写字母结尾,且中间出现一个任意字符的文件或目录。

ls  -d  /var/l?[[:lower:]]

(2)显示/etc目录下,以任意一个数字开头,且以非数字结尾的文件或目录。

ls  -d  /etc/[0-9]*[^0-9]

(3)显示/etc目录下,以非字母开头,后面跟一个字母,及其他任意长度、任意字符的文件或目录。

ls  -d  /etc/[^a-z][a-z]

(4)复制/etc目录下,所有以“.conf”结尾,且以m、n、r、p开头的文件或目录至/tmp/conf.d/目录下。

cp  -r  /etc/[mnrp]*.conf /tmp/conf.d/

【实例1-38】通配符综合应用

创建文件file1~file6,代码如下所示。

[root@laohan-shell-1 ~]# touch file{1..6}
[root@laohan-shell-1 ~]# ls -l file*
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file6
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file5
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file4
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file3
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file2
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file1

显示以“file”开头,后面跟任意单个字符的文件,代码如下。

[root@laohan-shell-1 ~]# ls -l file?
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file6
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file5
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file4
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file3
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file2
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file1

显示以“file”开头,结尾是1~6的任意数字的文件,代码如下。

[root@laohan-shell-1 ~]# ls -l file[1-6]
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file6
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file5
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file4
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file3
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file2
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file1

显示以“file”开头,排除结尾是1~2的字符的文件,代码如下。

[root@laohan-shell-1 ~]# ls -l file[^1-2]
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file6
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file5
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file4
-rw-r--r-- 1 root root 0 2019-12-07 21:25:04.241834964 +0800 file3

除了通配符由Shell负责预先解释后,将处理结果传给指令行之外,Shell还有一系列自己的其他特殊字符,如Shell元字符(特殊字符Meta)。Shell元字符如表1-12所示。

表1-12  Shell元字符

元字符

含义

IFS

由<space>、<tab>、<enter>三者之一组成

CR

由<enter>产生

=

设定变量

$

进行变量或运算替换

>

重定向stdout

<

重定向stdin

|

指令管道

&

重定向文件描述符,或将指令置于背景执行

( )

用于运算或指令替换

{ }

常用在变量替换的界定范围

;

在前一条指令结束时,忽略其返回值,继续执行下一条指令

&&

在前一条指令结束时,若返回值为true,继续执行下一条指令

||

在前一条指令结束时,若返回值为false,继续执行下一条指令

!

执行history列表中的指令

有时候要让通配符或者元字符变成普通字符,就需要用到转义字符。Shell程序提供的转义字符有3种,如表1-13所示。

表1-13  Shell转义字符

转义字符

含义

' '

硬转义,其内部所有的Shell元字符、通配符都会被关闭

" "

软转义,其内部只允许出现特定的Shell元字符

\

转义,去除其后紧跟的元字符或通配符的特殊意义

【实例1-39】转义字符实例

1 root@laohan-shell-1:~ #ls \*.sh
2 ls: 无法访问*.sh: 没有那个文件或目录
3 root@laohan-shell-1:~ #ls '*.sh'
4 ls: 无法访问*.sh: 没有那个文件或目录
5 root@laohan-shell-1:~ #ls 'test.sh'
6 test.sh
7 root@laohan-shell-1:~ #ls *.sh
8 canshu.sh  laohan.sh  Parameter-location.sh  parameter.sh  Predefined.sh sum.sh  9test.sh

上述代码中,第1行加入了转义字符,“*”已经失去了通配符意义。Shell通配符、元字符和转义字符总结如下。

Shell的通配符,由Shell解释含义,主要应用于文件名和路径扩展。

元字符作用于ls、find、rm等指令上。

通配符和元字符都由特殊字符组成。

转义字符用于屏蔽部分或屏蔽全部的通配符、元字符,转义的目的是输出特殊字符。

【练习1-1】写一个脚本,输出当前操作系统相关信息

包括内核版本、主机名、文件描述符,参考脚本内容如下所示。

[root@laohan_httpd_server chapter-1]# cat host-info.sh 
#!/bin/bash

:<<collect-host-info
Version:1.1
Author:Hanyanwei

1.内核版本
2.主机名
3.文件描述符
collect-host-info


echo -e "\033[41;37m************$(basename $0) 脚本开始执行**************\033[0m"
echo "******* 当前主机  $(ip ro |grep src |awk '{print $NF}')   信息如下所示 ******"
echo "内核版本为:$(uname -r)"
echo "主机名为:$(uname -n)"
echo "文件描述符为:$(ulimit -n)"
echo -e "\033[41;37m************$(basename $0) 脚本执行完毕**************\033[0m"

脚本执行结果如下所示。

[root@laohan_httpd_server chapter-1]# ./host-info.sh 
**************host-info.sh 脚本开始执行*****************
******* 当前主机  192.168.1.110   信息如下所示 ******
内核版本为:2.6.32-696.el6.x86_64
主机名为:Shell-programmer
文件描述符为:1024
**************host-info.sh 脚本执行完毕*****************

【练习1-2】写一个脚本自动安装Nginx软件

nginx-install.sh脚本内容如下所示。

[root@laohan_httpd_server ~]# cat /root/Shell-scripts/chapter-1/nginx-install.sh
#!/bin/bash
:<<nginx
1.环境准备:先安装准备环境
yum -y install wget  gcc gcc-c++ automake pcre pcre-devel zlip zlib-devel openssl openssl-devel 
2.下载Nginx 安装包
3.解压安装包
4.编译Nginx:make
5.生成脚本及配置文件:make
6.安装:make install(创建相关目录)
7.启动
8.通过指令启动和关闭Nginx
nginx

yum -y  install epel-release wget  && yum -y install gcc gcc-c++ automake pcre pcre-devel zlip zlib-devel openssl openssl-devel 
cd /opt && wget -c http://nginx.org/download/nginx-1.14.0.tar.gz && tar xf nginx-1.14.0.tar.gz
cd nginx-1.14.0 &&  ./configure  --prefix=/usr/local/nginx  --sbin-path=/usr/local/nginx/sbin/nginx --conf-path=/usr/local/nginx/conf/nginx.conf --error-log-path=/var/log/nginx/error.log  --http-log-path=/var/log/nginx/access.log  --pid-path=/var/run/nginx/nginx.pid --lock-path=/var/lock/nginx.lock  --user=nginx --group=nginx --with-http_ssl_module --with-http_stub_status_module --with-http_gzip_static_module --http-client-body-temp-path=/var/tmp/nginx/client/ --http-proxy-temp-path=/var/tmp/nginx/proxy/ --http-fastcgi-temp-path=/var/tmp/nginx/fcgi/ --http-uwsgi-temp-path=/var/tmp/nginx/uwsgi --http-scgi-temp-path=/var/tmp/nginx/scgi --with-pcre

make && make install ; echo $?
useradd  -s /sbin/nologin  nginx  && mkdir -pv /var/tmp/nginx/client/
/usr/local/nginx/sbin/nginx -t && /usr/local/nginx/sbin/nginx
netstat -tnpl |grep 80 && ps -ef |grep nginx

执行脚本成功后,浏览器会访问图1-12所示的Nginx默认HTML页面。

图1-12 Nginx默认HTML页面

一般使用3种方法编写Shell脚本,具体如下所示。

使用vim指令编辑Shell脚本。

使用echo指令重定向指令到脚本文件。

使用Linux终端。

【实例1-40】使用echo指令编写Shell脚本

使用echo指令编写Shell脚本,代码如下。

[root@laohan_httpd_server ~]# echo '#!/bin/bash'>/data/sh/hello_world.sh
[root@laohan_httpd_server ~]# echo 'echo "hello,world"'>>/data/sh/hello_world.sh
[root@laohan_httpd_server ~]# /bin/bash /data/sh/hello_world.sh
hello,world
[root@laohan_httpd_server ~]# cat /data/sh/hello_world.sh
#!/bin/bash
echo "hello,world"

【实例1-41】使用cat指令编写多行脚本注意事项

使用cat指令编写Shell脚本及使用cat指令编写脚本时,若脚本中含有特殊字符或空格等内容时,需要使用“\”转义。

[root@laohan_httpd_server ~]# cat >/data/sh/hello_world2.sh<<abc
> #!/bin/bash
> #author hanyanwei
> #Desc cat >> hello world &print hello,world
> echo "hello,world"
> exit 3
> abc
[root@laohan_httpd_server ~]# /bin/bash /data/sh/hello_world2.sh 
hello,world
[root@laohan_httpd_server ~]# cat /data/sh/hello_world2.sh
#!/bin/bash
#author hanyanwei
#Desc cat >> hello world &print hello,world
echo "hello,world"
exit 3

本章主要讲解了Shell脚本的基础概念和一些使用Shell脚本时的注意事项。另外,本章还精选了Shell实例,让读者在掌握了理论之后,可以较快地练习Shell,为下文的学习打下坚实的基础。

Shell脚本比较适合基础系统自动化管理,如自动部署Web应用、批量查看系统负载、批量传输文件到多台目标服务器、大规模自动批量部署操作系统等。

SSH和其他工具的结合则使得自动化管理更加如鱼得水,尤其是结合自动化运维工具Ansible。

读者服务:

微信扫码关注【异步社区】微信公众号,回复“e56232”获取本书配套资源以及异步社区15天VIP会员卡,近千本电子书免费畅读。


相关图书

Flask Web应用开发项目实战 基于Python和统信UOS
Flask Web应用开发项目实战 基于Python和统信UOS
T20天正建筑V8.0实战从入门到精通
T20天正建筑V8.0实战从入门到精通
Marc 非线性有限元分析标准教程
Marc 非线性有限元分析标准教程
大数据安全治理与防范——网址反欺诈实战
大数据安全治理与防范——网址反欺诈实战
Effective Java (第3版 英文版)
Effective Java (第3版 英文版)
电力安全生产典型事故集——变电检修专业
电力安全生产典型事故集——变电检修专业

相关文章

相关课程