Linux shell 实战精炼版-更新中

Linux shell 实战精炼版

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

版本标识

V1

编制单位

李斌

编制日期

2022725

 

第一章 认识Linux shell

 

1.1 Linux系统架构

1.1.1 Linux内核

1系统内存管理

 

2软件程序管理

运行级为1时,只启动基本的系统进程以及一个控制台终端进程。我们称之为单用户模式。

单用户模式通常用来在系统有问题时进行紧急的文件系统维护。显然,在这种模式下,仅有一个

人(通常是系统管理员)能登录到系统上操作数据。

 

标准的启动运行级是3。在这个运行级上,大多数应用软件,比如网络支持程序,都会启动。

另一个Linux中常见的运行级是5。在这个运行级上系统会启动图形化的X Window系统,允许用户通过图形化桌面窗口登录系统。

 

Linux系统可以通过调整启动运行级来控制整个系统的功能。通过将运行级从3调整成5,系

统就可以从基于控制台的系统变成更先进的图形化X Window系统。

3硬件设备管理 

在Linux内核中有两种方法用于插入设备驱动代码:

1)编译进内核的设备驱动代码

2)可插入内核的设备驱动模块

Linux系统将硬件设备当成特殊的文件,称为设备文件。设备文件有3种分类:

1)字符型设备文件

2)块设备文件

3)网络设备文件

4文件系统管理

ext :Linux扩展文件系统,最早的Linux文件系统

ext2 :第二扩展文件系统,在ext的基础上提供了更多的功能

ext3 :第三扩展文件系统,支持日志功能

ext4 :第四扩展文件系统,支持高级日志功能

hpfs :OS/2高性能文件系统

jfs :IBM日志文件系统

iso9660 :ISO 9660文件系统(CD-ROM

minix :MINIX文件系统

msdos :微软的FAT16

ncp :Netware文件系统

nfs :网络文件系统

ntfs :支持Microsoft NT文件系统

proc :访问系统信息

ReiserFS :高级Linux文件系统,能提供更好的性能和硬盘恢复功能

smb :支持网络访问的Samba SMB文件系统

sysv :较早期的Unix文件系统

ufs :BSD文件系统

umsdos :建立在msdos上的类Unix文件系统

vfat :Windows 95文件系统(FAT32

XFS :高性能64位日志文件系统

 

1.1.2 GNU工具

1、核实GNU工具

GNU coreutils软件包由三部分构成:

1)用以处理文件的工具

2)用以操作文本的工具

3)用以管理进程的工具

2shell

ash :一种运行在内存受限环境中简单的轻量级shell,但与bash shell完全兼容

korn :一种与Bourne shell兼容的编程shell,但支持如关联数组和浮点运算等一些高级的编程特性

tcsh :一种将C语言中的一些元素引入到shell脚本中的shell

zsh :一种结合了bash、tcsh和korn的特性,同时提供高级编程特性、共享历史文件和主题化提示符的高级

 

1.1.3 Linux桌面环境

1、X window系统
2、KDE桌面
3、GNOME桌面
4、Unity桌面
5、其他桌面

1Fluxbox :一个没有面板的轻型桌面,仅有一个可用来启动程序的弹出式菜单

2Xfce :和KDE很像的一个桌面,但少了很多图像以适应低内存环境

3JWM :Joe的窗口管理器(Joe’s Window Manager),非常适用于低内存低硬盘空间环境的超轻型桌面

4Fvwm :支持如虚拟桌面和面板等高级桌面功能,但能够在低内存环境中运行

5fvwm95 :从fvwm衍生而来,但看起来更像是Windows 95桌面

 

1.2 Linux发行版

 

1、完整的核心Linux发行版

2、特定用途的发行版

3、LiveCD测试发行版

 

1.2.1 Linux桌面环境

 

Slackware :最早的Linux发行版中的一员,在Linux极客中比较流行

Red Hat :主要用于Internet服务器的商业发行版

Fedora :从Red Hat分离出的家用发行版

Gentoo :为高级Linux用户设计的发行版,仅包含Linux源代码

openSUSE :用于商用和家用的发行版

Debian :在Linux专家和商用Linux产品中流行的发行版

 

1.2.2 特定用途的 Linux 发行版

 

CentOS :一款基于Red Hat企业版Linux源代码构建的免费发行版

Ubuntu :一款用于学校和家庭的免费发行版

PCLinuxOS :一款用于家庭和办公的免费发行版

Mint :一款用于家庭娱乐的免费发行版

dyne:bolic :一款用于音频和MIDI应用的免费发行版

Puppy Linux :一款适用于老旧PC的小型免费发行版

 

1.2.3 Linux LiveCD

 

Knoppix :来自德国的一款Linux发行版,也是最早的LiveCD Linux

PCLinuxOS :一款成熟的LiveCD形式的Linux发行版

Ubuntu :为多种语言设计的世界级Linux项目

Slax :基于Slackware Linux的一款LiveCD Linux

Puppy Linux :为老旧PC设计的一款全功能Linux

包括:

能将CD上的Linux系统文件复制到内存中;

能将系统文件复制到硬盘上;

能在U盘上存储系统设置;

能在U盘上存储用户设置

 

第二章 走进shell

2.1 进入命令行

2.1.1 控制台终端

功能键F1生成虚拟控制台1,F2键生成虚拟控制台2,F3键生成虚拟控制台3,F4键生成虚拟控制台4,依次类推。

2.1.2 图形化终端

客户端图形化终端仿真器,桌面环境,网络浏览器 请求图形化服务的应用

显示服务器 :Mir,Wayland Compositor,Xserver 负责管理显示(屏幕)和输入设备(键盘、鼠标、触摸屏)

窗口管理器: Compiz,Metacity,Kwin 为窗口加入边框,提供窗口移动和管理功能

部件库 :Athenal(Xaw),X Intrinsics 为桌面环境中的客户端添加菜单以及外观项

 

2.2 通过Linux控制台终端访问CLl

 

通常必须按下Ctrl+Alt组合键,然后按功能键(F1~F7)进入要使用的虚拟控制台。功能键F1生成虚拟控制台1,F2键生成虚拟控制台2,F3键生成虚拟控制台3,F4键生成虚拟控制台4,依次类推。

将终端的背景色设置成白色、文本设置成黑色,这样可让眼睛轻松些。登录之后,有好几种方法可实现这样的修改。其中一种方法是输入命令setterm -inversescreen on,然后按回车键,如图2-3所示。注意,在途中我们使用选项on启用了inversescreen特性。也可以使用选项off关闭该特性。

 

共有8种颜色可供选择,分别是black、red、green、yellow、blue、magenta、cyan和white(这种颜色在有些发行版中看起来像灰色)。

 

选项

参数

描述

-background

black、red、green、yellow、blue

magenta、cyan或white

将终端的背景色改为指定颜色

-foreground

black、red、green、yellow、blue

magenta、cyan或white

将终端的前景色改为指定颜色

-inversescreen

on或off

交换背景色和前景色

-reset

将终端外观恢复成默认设置并清屏

-store

将终端当前的前景色和背景色设置成-reset选项的值

2.2.1 菜单

Open Terminal

Shift+Ctrl+N

在新的GNOME Terminal窗口中启动一个新的shell会话

Open Tab

Shift+Ctrl+T

在现有的GNOME Terminal窗口的新标签中启动一个新的shell会话

New Profile

定制会话并将其保存为配置文件(profile),以备随后再次使用

Save Contents

将回滚缓冲区(scrollback buffer)中的内容保存到文本文件中

Close Tab

Shift+Ctrl+W

关闭当前标签中的会话

Close Window

Shift+Ctrl+Q

关闭当前的GNOME Terminal会话

Edit菜单

Copy

Shift+Ctrl+C

将所选的文本复制到GNOME的剪贴板中

Paste

 Shift+Ctrl+V

将GNOME剪贴板中的文本粘贴到会话中

Paste Filenames

 

粘贴已复制的文件名和对应的路径

Select All

选中回滚缓冲区中的全部输出

Profiles

添加、删除或修改GNOME Terminal的配置文件

Keyboard Shortcuts

创建快捷键来快速访问GNOME Terminal的各种特性

Profile Preferences

编辑当前会话的配置文件

 

View菜单

Show Menubar

打开/关闭菜单栏

Full Screen

F11

打开/关闭终端窗口全桌面显示模式

Zoom In

Ctrl++

逐步增大窗口显示字号

Normal Size

Ctrl+0

恢复默认字号

Search菜单

Find

Shift+Ctrl+F

打开Find窗口,提供待搜索文本的搜索选项

Find Next

Shift+Ctrl+H

从终端会话的当前位置开始向前搜索指定文本

Find Previous

Shift+Ctrl+G

从终端会话的当前位置开始向后搜索指定文本

 

Terminal菜单

Change Profile

切换到新的配置文件

Set Title

修改标签会话的标题

Set Character Encoding

选择用于发送和显示字符的字符集

Reset

发送终端会话重置控制码

Reset and Clear

发送终端会话重置控制码并清除终端会话显示

Window Size List

列出可用于调整当前终端窗口大小的列表

 

Tabs菜单

Next Tab

Ctrl+PageDown

使下一个标签成为活动标签

Previous Tab

Ctrl+PageUp

使上一个标签成为活动标签

Move Tab Left  

 

Shift+Ctrl+PageUp

将当前标签移动到前一个标签的前面

Move Tab Right Shift+Ctrl+PageDown

将当前标签移动到下一个标签的后面

Detach Tab

删除该标签并使用该标签会话启动一个新的GNOME Terminal窗口

Tab List

列出当前正在运行的标签(选择一个标签,转入对应的会话)

Terminal List

列出当前正在运行的终端(选择一个终端,转入对应的会话。当打开 多个窗口会话的时候才会出现该菜单项)

 

2.2.2 使用 Konsole Terminal 仿真器

File 菜单栏

New Tab

 

Ctrl+Shift+N

 

在现有的Konsole Terminal窗口的新标签中启动一个新的shell会话

New Window

 

Ctrl+Shift+M

 

在新的Konsole Terminal窗口中启动一个新的shell会话

shell

打开采用默认配置文件的shell

Open Browser Here

打开默认的文件浏览器应用

Close Tab

Ctrl+Shift+W

关闭当前标签中的会话

Quit

Ctrl+Shift+Q

退出Konsole Terminal仿真应用

 

Edit菜单

Copy

 

 Ctrl+Shift+C

将选择的文本复制到Konsole的剪贴板中

Paste

 

 Ctrl+Shift+V

将Konsole剪贴板中的文本粘贴到会话中

Rename Tab

Ctrl+Alt+S

修改标签会话的标题

Copy Input To

开始/停止将会话输入复制到所选的其他会话中

Copy Input To

开始/停止将会话输入复制到所选的其他会话中

Clear & Reset

 

清除终端会话中的内容并发送终端会话重置控制码

 

View菜单

Split View

控制显示在Konsole Terminal窗口中的多个标签会话

Detach View

Ctrl+Shift+H

删除一个标签会话并使用该标签中的会话启动一个新的Konsole

Terminal窗口

Show Menu Bar

打开/关闭菜单栏

Full Screen Mode

Ctrl+Shift+F11

打开/关闭终端窗口的全屏模式

Monitor for Silence

 

Ctrl+Shift+I

 

打开/关闭无活动标签(tab silence)的特殊消息

 

Monitor for Activity

 

Ctrl+Shift+A

打开/关闭活动标签(tab activity)的特殊消息

Character Encoding

选择用于发送和显示字符的字符集

Increase Text Size

Ctrl++

逐步增大窗口显示字号

Decrease Text Size

Ctrl+-

逐步减小窗口显示字号

 

Scrollback菜单

Search Output

Ctrl+Shift+F

打开Konsole Terminal窗口底部的Find窗口,提供回滚文本搜索选项

Find Next

F3

在回滚缓冲区历史记录中查找下一个匹配的文本

Find Previous

Shift+F3

在回滚缓冲区历史记录中查找上一个匹配的文本

Save Output

将回滚缓冲区中的内容保存在一个文本文件或HTML文件中

Scrollback Options

打开Scrollback Options窗口来配置回滚缓冲区选项

Clear Scrollback

删除回滚缓冲区中的内容

Clear Scrollback & Reset

Ctrl+Shift+X

删除回滚缓冲区中的内容并重置终端窗口

 

Bookmark菜单

Add Bookmark

Ctrl+Shift+B

在当前目录位置上创建新的书签

Bookmark Tabs as Folder

 

 

为当前所有的终端标签会话创建一个新的书签

New Bookmark Folder

创建新的书签文件夹

Edit Bookmarks

编辑已有的书签

 

Settings菜单

Change Profile

将所选的配置文件应用于当前标签

Edit Current Profile

打开Edit Profile窗口,提供配置文件配置选项

Manage Profiles

打开Manage Profile窗口,提供配置文件管理选项

Configure Shortcuts

创建Konsole Terminal命令快捷键

Configure Notifications

创建定制化的Konsole Terminal方案及会话

 

Help菜单

Konsole Handbook

包含了完整的Konsole手册 

What’s This?

Shift+F1

包含了终端部件的帮助信息

Report Bug

Report Bug

打开Submit Bug Report(提交bug报告)表单

Switch Application

 

打开Switch Application’s Language(切换应用程序语言)表单

Language

About Konsole

显示当前Konsole Terminal的版本

About KDE

 

显示当前KDE桌面环境的版本

 

2.2.3 访问xterm

可以通过向xterm命令加入参数来调用某些配置选项。例如,要想让xterm仿真DEC VT100

终端,可以输入命令xterm -ti vt100,然后按回车键。表2-17给出了一些可以配合xterm终端仿真器使用的参数。

 

xterm命令行参数

-bg color

指定终端背景色

-fb font

指定粗体文本所使用的字体

-fg color

指定文本颜色

-fn font

指定文本字体

-fw font

指定宽文本字体

-lf filename

指定用于屏幕日志的文件名

-ms color

指定文本光标颜色

-name name

指定标题栏中的应用程序名称

-ti terminal

指定要仿真的终端类型

 

xterm +/-命令行参数

ah

启用/禁止文本光标高亮

aw

启用/禁止文本行自动环绕

bc

启用/禁止文本光标闪烁

cm

启用/禁止识别ANSI色彩更改控制码

fullscreen

启用/禁止全屏模式

j

启用/禁止跳跃式滚动

l

启用/禁止将屏幕数据记录进日志文件

mb

启用/禁止边缘响铃

rv

启用/禁止图像反转

t

启用/禁止Tektronix模式

 

第三章 基本的bash shell命令

3.1 启动shell

GNU bash shell能提供对Linux系统的交互式访问。它是作为普通程序运行的,通常是在用户

登录终端时启动。登录时系统启动的shell依赖于用户账户的配置。

 

/etc/passwd文件包含了所有系统用户账户列表以及每个用户的基本配置信息

christine:x:501:501:Christine Bresnahan:/home/christine:/bin/bash

在前面的/etc/passwd样例条目中,用户christine使用/bin/bash作为自己的默认shell程序。这意

味着当christine登录Linux系统后,bash shell会自动启动。

 

每个条目有七个字段,字段之间用冒号分隔。系统使用字段中的数据来赋予用户账户某些特

定特性。其中的大多数条目将在第7章有更加详细的介绍。现在先将注意力放在最后一个字段上,

该字段指定了用户使用的shell程序。

3.2 shell 提示符

默认bash shell提示符是美元符号($),这个符号表明shell在等待用户输入。不同的Linux

行版采用不同格式的提示符。在Ubuntu Linux系统上,shell提示符看起来是这样的:

 

Ubuntu

christine@server01:~$

CentOS

[christine@server01 ~]$

3.3 bash 手册

man命令用来访问存储在Linux系统上的手册页面。在想要查找的工具的名称前面输入man

令,就可以找到那个工具相应的手册条目。

 

Linux手册页惯用的节名

Name

显示命令名和一段简短的描述

Synopsis

命令的语法

Confi guration

命令配置信息

Description

命令的一般性描述

Options

命令选项描述

Exit Status

命令的退出状态指示

Return Value

命令的返回值

Errors

命令的错误消息

Environment

描述所使用的环境变量

Files

命令用到的文件

Versions

命令的版本信息

Conforming To

命名所遵从的标准

Notes

其他有帮助的资料

Bugs

提供提交bug的途径

Example

展示命令的用法

Authors

命令开发人员的信息

Copyright

命令源代码的版权状况

See Also

与该命令类型的其他命令

 

Linux手册页的内容区域

所涵盖的内容

1

可执行程序或shell命令

2

系统调用

3

库调用

4

特殊文件

5

文件格式与约定

6

游戏

7

概览、约定及杂项

8

超级用户和系统管理员命令

9

内核例程

一个命令偶尔会在多个内容区域都有对应的手册页。比如说,有个叫作hostname的命令。手册页中既包括该命令的相关信息,也包括对系统主机名的概述。要想查看所需要的页面,可以输入man section# topic。对手册页中的第1部分而言,就是输入man 1 hostname。对于手册页中的第7部分,就是输入man 7 hostname

可以只看各部分内容的简介:输入man 1 intro阅读第1部分,输入man 2 intro阅读第2部分,输入man 3 intro阅读第3部分,等等。

手册页不是唯一的参考资料。还有另一种叫作info页面的信息。可以输入info info来了解info页面的相关内容。

另外,大多数命令都可以接受-help--help选项。例如你可以输入hostname -help来查看帮助。关于帮助的更多信息,可以输入help help。(看出这里面的门道没?)

3.4 浏览文件系统

3.4.1 Linux 文件系统

3.4.1 Linux 文件系统

通常系统文件会存储在根驱动器中,而用户文件则存储在另一驱动器中

 

 

常见Linux目录名称

/

虚拟目录的根目录。通常不会在这里存储文件

/bin

二进制目录,存放许多用户级的GNU工具

/boot

启动目录,存放启动文件

/dev

设备目录,Linux在这里创建设备节点

/etc

系统配置文件目录

/home

主目录,Linux在这里创建用户目录

/lib

库目录,存放系统和应用程序的库文件

/media

媒体目录,可移动媒体设备的常用挂载点

/mnt

挂载目录,另一个可移动媒体设备的常用挂载点

/opt

可选目录,常用于存放第三方软件包和数据文件

/proc

进程目录,存放现有硬件及当前进程的相关信息

/root

root用户的主目录

/sbin

系统二进制目录,存放许多GNU管理员级工具

/run

运行目录,存放系统运作时的运行时数据

/srv

服务目录,存放本地服务的相关文件

/sys

系统目录,存放系统硬件信息的相关文件

/tmp

临时目录,可以在该目录中创建和删除临时工作文件

/usr

用户二进制目录,大量用户级的GNU工具和数据文件都存储在这里

/var

可变目录,用以存放经常变化的文件,比如日志文件

 

文件系统层次标准注:https://www.pathname.com/fhs/

 

3.4.2 遍历目录

1. 绝对文件路径

用户可在虚拟目录中采用绝对文件路径引用目录名。绝对文件路径定义了在虚拟目录结构中

该目录的确切位置,以虚拟目录的根目录开始,相当于目录的全名

 

绝对文件路径总是以正斜线(/)作为起始,指明虚拟文件系统的根目录。因此,如果要指

向usr目录所包含的bin目录下的用户二进制文件,可以使用如下绝对文件路径:/usr/bin

2. 相对文件路径

相对文件路径允许用户指定一个基于当前位置的目标文件路径。相对文件路径不以代表根目录的正斜线(/)开头,而是以目录名(如果用户准备切换到当前工作目录下的一个目录)或是一个特殊字符开始。假如你位于home目录中,并希望切换到Documents子目录,那你可以使用cd命令加上一个相对文件路径:

单点符(.),表示当前目录

双点符(..),表示当前目录的父目录

3.5 文件和目录列表

3.5.1 基本列表功能

1ls命令最基本的形式会显示当前目录下的文件和目录(按字母排序):ls

 

2)如果没安装彩色终端仿真器,可用带-F参数的ls命令轻松区分文件和目录。ls -F

 

3-F参数在目录名后加了正斜线(/),以方便用户在输出中分辨它们。类似地,它会在可执行文件(若 libin_script文件)的后面加个星号,以便用户找出可在系统上运行的文件。

 

4)要把隐藏文件和普通文件及目录一起显示出来,就得用到-a参数: ls -a

 

(5)-R参数是ls命令可用的另一个参数,叫作递归选项。它列出了当前目录下包含的子目录中的文件。如果目录很多,这个输出就会很长。以下是-R参数输出的简单例子:  ls -F -R

[21:36:28 root@libin3 www]# ls -F -R /www/

/www/:

index.html

 

3.5.2 显示长列表

(1) 在基本的输出列表中,ls命令并未输出太多每个文件的相关信息。要显示附加信息,另一个常用的参数是-l。-l参数会产生长列表格式的输出,包含了目录中每个文件的更多相关信息。ls -l

文件类型,比如目录(d)、文件(-)、字符型文件(c)或块设备(b);

文件的权限(参见第6章);

文件的硬链接总数;

文件属主的用户名;

文件属组的组名;

文件的大小(以字节为单位);

文件的上次修改时间;

文件名或目录名

 

3.5.3 过滤输出列表

(1)ls命令还支持在命令行中定义过滤器。它会用过滤器来决定应该在输出中显示哪些文件

或目录。

 

(2)这个过滤器就是一个进行简单文本匹配的字符串。可以在要用的命令行参数之后添加这个过

滤器: ls -l libin_script

 

(3)当用户指定特定文件的名称作为过滤器时,ls命令只会显示该文件的信息。有时你可能不知

道要找的那个文件的确切名称。ls命令能够识别标准通配符,并在过滤器中用它们进行模式匹配:

问号(?)代表一个字符;

[21:41:02 root@libin3 www]# ls -l /tmp/time.?og

-rw-r--r-- 1 root root 356458 7月  27 21:41 /tmp/time.log

 

星号(*)代表零个或多个字符

[21:42:06 root@libin3 www]# ls -l /tmp/time*

-rw-r--r-- 1 root root 356517 7月  27 21:42 /tmp/time.log

 

问号可用于过滤器字符串中替代任意位置的单个字符

$ ls -l libin_scr?pt

libin_scrapt

libin_script

 

(4) 在过滤器中使用星号和问号被称为文件扩展匹配file globbing),指的是使用通配符进行模式匹配的过程。通配符正式的名称叫作元字符通配符(metacharacter wildcards)。除了星号和问号之外,还有更多的元字符通配符可用于文件扩展匹配。可以使用中括号。

$ ls -l libin_scr[ai]pt

libin_scrapt

libin_script

[21:49:04 root@libin3 scripts]# ls -l ba[ak].sh

-rw-r--r-- 1 root root 54 3月  13 17:32 bak.sh

 

(5)在这个例子中,我们使用了中括号以及在特定位置上可能出现的两种字符:a或i。中括号表示一个字符位置并给出多个可能的选择。可以像上面的例子那样将待选的字符列出来,也可以指定字符范围,例如字母范围[a – i]。

$ ls -l f[a-i]ll

fall

fell

fill

[21:47:00 root@libin3 scripts]# ls -l ba[a-z].sh

-rw-r--r-- 1 root root 54 3月  13 17:32 bak.sh

 

(6)另外,可以使用感叹号(!)将不需要的内容排除在外

$ ls -l f[!a]ll

fell

fill

full

[21:49:14 root@libin3 scripts]# ls -l ba[!az].sh

-rw-r--r-- 1 root root 54 3月  13 17:32 bak.sh

 

 

3.6 处理文件

3.6.1 创建文件

用touch命令

 

要想查看文件的访问时间,需要加入另外一个参数:--time=atime。有了这个参数,就能够显示出已经更改过的文件访问时间。

[21:53:21 root@libin3 scripts]# ls -l --time=atime ip.sh

-rw-r--r-- 1 root root 87 7月  27 21:31 ip.sh

 

3.6.2 复制文件

cp命令

用法:cp source destination

 

如果目标文件已经存在,cp命令可能并不会提醒这一点。最好是加上-i选项

[22:02:24 root@libin3 scripts2]# cp -i ip.sh ../scripts/ip.sh

cp:是否覆盖"../scripts/ip.sh"

 

cp命令的-R参数威力强大。可以用它在一条命令中递归地复制整个目录的内容

3.6.3 制表键自动补全

制表键自动补全允许你在输入文件名或目录名时按一下制表键,让shell帮忙将内容补充完整。

[22:14:42 root@libin3 scripts]# ls *.sh

bak-config.sh  bak.sh  ip.sh

[22:14:58 root@libin3 scripts]# cp ip.sh /libin/scripts2/

22:16:28 root@libin3 scripts]# ls -l /libin/scripts2/*

-rw-r--r-- 1 root root 87 7月  27 22:16 /libin/scripts2/ip.sh

/libin/scripts2/scripts:

总用量 364

-rw-r--r-- 1 root root    306 7月  27 22:08 bak-config.sh

-rw-r--r-- 1 root root     54 7月  27 22:08 bak.sh

-rw-r--r-- 1 root root     87 7月  27 22:08 ip.sh

-rw-r--r-- 1 root root 358110 7月  27 22:09 time.log

 

3.6.4 链接文件

符号链接:符号链接就是一个实实在在的文件,它指向存放在虚拟目录结构中某个地方的另一个文件。这两个通过符号链接在一起的文件,彼此的内容并不相同。

要为一个文件创建符号链接,原始文件必须事先存在。然后可以使用ln命令以及-s选项来创建符号链接。

[22:20:16 root@libin3 scripts]# ls -l time.log

-rw-r--r-- 1 root root 358110 7月  27 22:09 time.log

[22:20:23 root@libin3 scripts]# ln -s time.log libin_time.log

[22:20:49 root@libin3 scripts]# ls -l *time.log

lrwxrwxrwx 1 root root      8 7月  27 22:20 libin_time.log -> time.log

-rw-r--r-- 1 root root 358110 7月  27 22:09 time.log

 

要查看文件或目录的inode编号,可以给ls命令加入-i参数。

[22:22:23 root@libin3 scripts]# ls -i *time.log

26912417 libin_time.log  26912416 time.log

从这个例子中可以看出数据文件的inode编号是26912416 ,而libin_time.log的inode编号则是

26912417。所以说它们是不同的文件。

 

硬链接:硬链接会创建独立的虚拟文件,其中包含了原始文件的信息及位置。但是它们从根本上而言是同一个文件。引用硬链接文件等同于引用了源文件。要创建硬链接,原始文件也必须事先存在,只不过这次使用ln命令时不再需要加入额外的参数了。

[22:24:44 root@libin3 scripts]# ls -l ip.sh

-rw-r--r-- 1 root root 87 7月  27 22:08 ip.sh

[22:27:57 root@libin3 scripts]# ln ip.sh libin_ip.sh

[22:28:09 root@libin3 scripts]# ls -li *ip.sh

26912411 -rw-r--r-- 2 root root 87 7月  27 22:08 ip.sh

26912411 -rw-r--r-- 2 root root 87 7月  27 22:08 libin_ip.sh

 

注:只能对处于同一存储媒体的文件创建硬链接。要想在不同存储媒体的文件之间创建链接,

只能使用符号链接。

 

3.6.5 重命名文件

在Linux中,重命名文件称为移动(moving)。mv命令可以将文件和目录移动到另一个位置

或重新命名。因为mv 只影响文件名,但inode编号和时间戳保持不变。

 

注:和cp命令类似,也可以在mv命令中使用-i参数。这样在命令试图覆盖已有的文件时,你

就会得到提示。太过于简单不做过多解释。

3.6.6 删除文件

在Linux中,删除(deleting)叫作移除(removing)①。bash shell中删除文件的命令是rm。rm命令的基本格式非常简单

 

注:-i命令参数提示你是不是要真的删除该文件。bash shell中没有回收站或垃圾箱,文件

一旦删除,就无法再找回。因此,在使用rm命令时,要养成总是加入-i参数的好习惯。

[22:36:35 root@libin3 scripts]# rm -i libin_ip.sh

rm:是否删除普通文件 "libin_ip.sh"

 

也可以使用通配符删除成组的文件

[22:39:58 root@libin3 scripts]# touch f{a,c}ll

[22:40:16 root@libin3 scripts]# rm -i f{a,c}ll

rm:是否删除普通空文件 "fall"n

rm:是否删除普通空文件 "fcll"n

 

注:rm命令的另外一个特性是,如果要删除很多文件且不受提示符的打扰,可以用-f参数强制

删除。谨慎使用!

1

3.7 处理目录

3.7.1 创建目录

用mkdir命令

-p:同时创建多个目录和子目录

[22:40:35 root@libin3 scripts]# mkdir /libin/new_dir/libin_dir

mkdir: 无法创建目录"/libin/new_dir/libin_dir": 没有那个文件或目录

[22:45:31 root@libin3 scripts]# mkdir -p /libin/new_dir/libin_dir

3.7.2 删除目录

rmdir:只删除空目录。

[22:45:31 root@libin3 scripts]# mkdir -p /libin/new_dir/libin_dir

[22:45:36 root@libin3 scripts]# touch /libin/new_dir/libin_dir/libin.sh

[22:49:08 root@libin3 scripts]# echo date >> /libin/new_dir/libin_dir/libin.sh

[22:49:26 root@libin3 scripts]# rmdir /libin/new_dir/libin_dir/

rmdir: 删除 "/libin/new_dir/libin_dir/" 失败: 目录非空

 

要解决这一问题,得先把目录中的文件删掉,然后才能在空目录上使用rmdir命令

[22:49:33 root@libin3 scripts]# rm -i /libin/new_dir/libin_dir/libin.sh

rm:是否删除普通文件 "/libin/new_dir/libin_dir/libin.sh"y

[22:51:23 root@libin3 scripts]# rmdir /libin/new_dir/libin_dir/

 

也可以在整个非空目录上使用rm命令。使用-r选项使得命令可以向下进入目录,删除其中的文件,然后再删除目录本身。

注:对rm命令而言,-r参数和-R参数的效果是一样的。-R参数同样可以递归地删除目录中的

文件。shell命令很少会就相同的功能采用不同大小写的参数。

 

删除目录及其所有内容的终极大法就是使用带有-r参数和-f参数的rm命令

[22:55:46 root@libin3 scripts]# tree /libin/

/libin/

├── new_dir

│   └── libin_dir

├── scripts

│   ├── bak-config.sh

│   ├── bak.sh

│   └── ip.sh

├── scripts2

│   ├── ip.sh

│   └── scripts

│       ├── bak-config.sh

│       ├── bak.sh

│       ├── fall

│       ├── fcll

│       ├── ip.sh

│       ├── libin_ip.sh

│       ├── libin_time.log -> time.log

│       └── time.log

└── time.log

 

注:务必谨慎使用,请再三检查你所要进行的操作是否符合预期

 

3.8 查看文件内容

3.8.1 查看文件类型

如果打开了一个二进制文件,你会在屏幕上看到各种乱码,甚至会把你的终端仿真器挂起。

 

file命令

[22:55:52 root@libin3 scripts]# file ip.sh

ip.sh: Bourne-Again shell script, ASCII text executable

 

文件是一个text(文本)文件。file命令不仅能确定文件中包含的文本信息,还能确定该文本文件的字符编码,ASCII。

尽管这个文件是ASCII text,但因为它是一个脚本文件,所以可以在系统上执行(运行)

[22:55:52 root@libin3 scripts]# file ip.sh

ip.sh: Bourne-Again shell script, ASCII text executable

 

可以使用file命令作为另一种区分目录的方法:

[23:00:01 root@libin3 scripts]# file ../scripts/

../scripts/: directory

 

file命令甚至能够告诉你它链接到了哪个文件上:

[22:59:53 root@libin3 scripts]# file libin_time.log

libin_time.log: symbolic link to `time.log'

 

二进制可执行程序。file命令能够确定该程序编译时所面向的平台以及需要何种类型的库。如果你有从未知源处获得的二进制文件,这会是个非常有用的特性:

[23:01:23 root@libin3 scripts]# file /bin/ls

/bin/ls: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=c8ada1f7095f6b2bb7ddc848e088c2d615c3743e, stripped

 

3.8.2 查看整个文件

1. cat命令

-n参数会给所有的行加上行号。

-b参数只给有文本的行加上行号。

用-T参数不想让制表符出现

 

2. more命令

more命令会显示文本文件的内容,但会在显示每页数据之后停下来

[23:06:33 root@libin3 scripts]# more /etc/profile

 

通过按空格键或回车键以逐行向前的方式浏览文本文件。浏览完之后,按q键退出。

 

3. less命令

[23:06:33 root@libin3 scripts]# less /etc/profile

 

能够实现在文本文件中前后翻动

3.8.3 查看部分文件

1. tail命令

tail命令会显示文件最后几行的内容(文件的“尾部”)。默认情况下,它会显示文件的末尾10行。

-n:可以向tail命令中加入-n参数来修改所显示的行数。

-f:它允许你在其他进程使用该文件时查看文件的内容。tail命令会保持活动状态,并不断显示添加到文件中的内容

[23:09:13 root@libin3 scripts]# tail -n 2 /etc/passwd

clamscan:x:385:383:Clamav scanner user:/:/sbin/nologin

grafana:x:384:382:grafana user:/usr/share/grafana:/sbin/nologin

2. head命令

head命令,顾名思义,会显示文件开头那些行的内容。默认情况下,它会显示文件前10行的文本:

 

第四章 bash shell命令

 

4.1 监测程序

4.1.1 探查进程

ps命令

[20:23:16 root@libin3 ~]# ps

   PID TTY          TIME CMD

  2960 pts/0    00:00:00 bash

  3193 pts/0    00:00:00 ps

 

显示了程序的进程ID(Process ID,PID)、它们运行在哪个终端(TTY

以及进程已用的CPU时间。

 

U ps命令支持3种不同类型的命令行参数

1Unix风格的参数,前面加单破折线

2)BSD风格的参数,前面不加破折线

3)GNU风格的长参数,前面加双破折线

1. Unix风格的参数

Unix风格的ps命令参数

-A

显示所有进程

-N

显示与指定参数不符的所有进程

-a

显示除控制进程(session leader)和无终端进程外的所有进程

-d

显示除控制进程外的所有进程

-e

显示所有进程

-C cmdlist

显示包含在cmdlist列表中的进程

-G grplist

显示组IDgrplist列表中的进程

-U userlist

显示属主的用户IDuserlist列表中的进程

-g grplist

显示会话或组IDgrplist列表中的进程

-p pidlist

显示PIDpidlist列表中的进程

-s sesslist

显示会话IDsesslist列表中的进程

-t ttylist

显示终端IDttylist列表中的进程

-u userlist

显示有效用户IDuserlist列表中的进程

-F

显示更多额外输出(相对-f参数而言)

-O format

显示默认的输出列以及format列表指定的特定列

-M

显示进程的安全信息

-c

显示进程的额外调度器信息

-f

显示完整格式的输出

-j

显示任务信息

-l

显示长列表

-o format

仅显示由format指定的列

-y

不要显示进程标记(process flag,表明进程状态的标记)

-Z

显示安全标签(security context)信息

-H

用层级格式来显示进程(树状,用来显示父进程)

-n namelist

定义了WCHAN列显示的值

-w

采用宽输出模式,不限宽度显示

-L

显示进程中的线程

-V

显示ps命令的版本号

 

想查看系统上运行的所有进程,可用-ef参数组合(ps命令允许你像这样把参数组合在一起)

[20:37:09 root@libin3 ~]# ps -ef | head -n10

UID         PID   PPID  C STIME TTY          TIME CMD

root          1      0  0 20:21 ?        00:00:03 /usr/lib/systemd/systemd --switched-root --system --deserialize 22

root          2      0  0 20:21 ?        00:00:00 [kthreadd]

root          3      2  0 20:21 ?        00:00:00 [ksoftirqd/0]

root          5      2  0 20:21 ?        00:00:00 [kworker/0:0H]

root          7      2  0 20:21 ?        00:00:00 [migration/0]

root          8      2  0 20:21 ?        00:00:00 [rcu_bh]

root          9      2  0 20:21 ?        00:00:01 [rcu_sched]

root         10      2  0 20:21 ?        00:00:00 [watchdog/0]

root         11      2  0 20:21 ?        00:00:00 [watchdog/1]

UID:启动这些进程的用户。

PID:进程的进程ID

PPID:父进程的进程号(如果该进程是由另一个进程启动的)。

C:进程生命周期中的CPU利用率。

STIME:进程启动时的系统时间。

TTY:进程启动时的终端设备。

TIME:运行进程需要的累计CPU时间。

CMD:启动的程序名称。

 

如果想要获得更多的信息,可采用-l参数,它会产生一个长格式输出

[20:37:16 root@libin3 ~]# ps -l

F S   UID    PID   PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD

4 S     0   2960   2951  0  80   0 - 29178 do_wai pts/0    00:00:00 bash

0 R     0   3750   2960  0  80   0 - 38331 -      pts/0    00:00:00 ps

 

F:内核分配给进程的系统标记。

S:进程的状态(O代表正在运行;S代表在休眠;R代表可运行,正等待运行;Z代表僵

化,进程已结束但父进程已不存在;T代表停止)。

PRI:进程的优先级(越大的数字代表越低的优先级)。

NI:谦让度值用来参与决定优先级。

ADDR:进程的内存地址。

SZ:假如进程被换出,所需交换空间的大致大小。

WCHAN:进程休眠的内核函数的地址。

2. BSD风格的参数

BSD风格的ps命令参数

T

显示跟当前终端关联的所有进程

a

显示跟任意终端关联的所有进程

g

显示所有的进程,包括控制进程

r

仅显示运行中的进程

x

显示所有的进程,甚至包括未分配任何终端的进程

U userlist

显示归userlist列表中某用户ID所有的进程

p pidlist

显示PIDpidlist列表中的进程

t ttylist

显示所关联的终端在ttylist列表中的进程

O format

除了默认输出的列之外,还输出由format指定的列

X

按过去的Linux i386寄存器格式显示

Z

将安全信息添加到输出中

j

显示任务信息

l

采用长模式

o format

仅显示由format指定的列

s

 

采用信号格式显示

 

u

采用基于用户的格式显示

v

采用虚拟内存格式显示

N namelist

定义在WCHAN列中使用的值

O order

定义显示信息列的顺序

S

将数值信息从子进程加到父进程上,比如CPU和内存的使用情况

c

显示真实的命令名称(用以启动进程的程序名称)

e

显示命令使用的环境变量

f

用分层格式来显示进程,表明哪些进程启动了哪些进程

h

不显示头信息

k sort

指定用以将输出排序的列

n

WCHAN信息一起显示出来,用数值来表示用户ID和组ID

w

为较宽屏幕显示宽输出

H

将线程按进程来显示

m

在进程后显示线程

L

列出所有格式指定符

V

显示ps命令的版本号

 

[20:59:37 root@libin3 ~]# ps l

F   UID    PID   PPID PRI  NI    VSZ   RSS WCHAN  STAT TTY        TIME COMMAND

4     0   2960   2951  20   0 116712  3164 do_wai Ss   pts/0      0:00 bash

0     0   4318   2960  20   0 153324  1524 -      R+   pts/0      0:00 ps l

VSZ:进程在内存中的大小,以千字节(KB)为单位。

RSS:进程在未换出时占用的物理内存。

STAT:代表当前进程状态的双字符状态码。

许多系统管理员都喜欢BSD风格的l参数。它能输出更详细的进程状态码(STAT列)。双字

符状态码能比Unix风格输出的单字符状态码更清楚地表示进程的当前状态。

第一个字符采用了和Unix风格S列相同的值,表明进程是在休眠、运行还是等待。第二个参

数进一步说明进程的状态。

<:该进程运行在高优先级上。

N:该进程运行在低优先级上。

L:该进程有页面锁定在内存中。

s:该进程是控制进程。

l:该进程是多线程的。

+:该进程运行在前台。

 

3. GNU长参数

GNU风格的ps命令参数

--deselect

显示所有进程,命令行中列出的进程

--Group grplist

显示组IDgrplist列表中的进程

--group grplist

显示有效组IDgrplist列表中的进程

--pid pidlist

显示PIDpidlist列表中的进程

--ppid pidlist

显示父PIDpidlist列表中的进程

--sid sidlist

显示会话IDsidlist列表中的进程

--tty ttylist

显示终端设备号在ttylist列表中的进程

--user userlist

显示有效用户IDuserlist列表中的进程

--format format

仅显示由format指定的列

--context

显示额外的安全信息

--cols n

将屏幕宽度设置为n

--columns n

将屏幕宽度设置为n

--cumulative

包含已停止的子进程的信息

--forest

用层级结构显示出进程和父进程之间的关系

--headers

在每页输出中都显示列的头

--no-headers

不显示列的头

--lines n

将屏幕高度设为n

--rows n

将屏幕高度设为n

--sort order

指定将输出按哪列排序

--width n

将屏幕宽度设为n

--help

显示帮助信息

--info

显示调试信息

--version

显示ps命令的版本号

 

4.1.2 实时监测进程

top命令

输出的第一部分显示的是系统的概况:第一行显示了当前时间、系统的运行时间、登录的用

户数以及系统的平均负载。

 

平均负载有3个值:最近1分钟的、最近5分钟的和最近15分钟的平均负载。值越大说明系统

的负载越高。由于进程短期的突发性活动,出现最近1分钟的高负载值也很常见,但如果近15分钟内的平均负载都很高,就说明系统可能有问题。

[21:12:07 root@libin3 ~]# top

top - 21:13:39 up 52 min,  2 users,  load average: 0.05, 0.03, 0.05

Tasks: 304 total,   1 running, 303 sleeping,   0 stopped,   0 zombie

%Cpu(s):  0.8 us,  0.3 sy,  0.0 ni, 98.9 id,  0.0 wa,  0.0 hi,  0.1 si,  0.0 st

KiB Mem :  3865308 total,  2058668 free,   951128 used,   855512 buff/cache

KiB Swap:  2097148 total,  2097148 free,        0 used.  2612048 avail Mem

 

   PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                

  1898 root      20   0  348412  34344  17288 S   6.6  0.9   0:11.14 X                                                                      

  2378 root      20   0 3593916 127820  51976 S   4.0  3.3   0:18.80 gnome-shell                                                            

  2951 root      20   0  777436  30560  19148 S   2.3  0.8   0:04.99 gnome-terminal-                                                        

  1394 root      rt   0  194300  96044  70936 S   0.7  2.5   0:17.10 corosync                                                               

     9 root      20   0       0      0      0 S   0.3  0.0   0:02.18 rcu_sched                                                              

   549 root      20   0       0      0      0 S   0.3  0.0   0:03.49 xfsaild/dm-0                                                           

   855 root      20   0  295564   5312   4064 S   0.3  0.1   0:06.76 vmtoolsd                                                               

  1355 root      20   0 1118244  70732  19172 S   0.3  1.8   0:05.66 prometheus                                                             

  2558 root      20   0  779884  16920   9568 S   0.3  0.4   0:00.58 gsd-colo

最后一部分显示了当前运行中的进程的详细列表,有些列跟ps命令的输出类似。

PID:进程的ID

USER:进程属主的名字。

PR:进程的优先级。

NI:进程的谦让度值。

VIRT:进程占用的虚拟内存总量。

RES:进程占用的物理内存总量。

SHR:进程和其他进程共享的内存总量。

S:进程的状态(D代表可中断的休眠状态,R代表在运行状态,S代表休眠状态,T代表

跟踪状态或停止状态,Z代表僵化状态)。

%CPU:进程使用的CPU时间比例。

%MEM:进程使用的内存占可用内存的比例。

TIME+:自进程启动到目前为止的CPU时间总量。

COMMAND:进程所对应的命令行名称,也就是启动的程序名。

 

4.1.3 结束进程

Linux进程信号

1

HUP

挂起

2

INT

中断

3

QUIT

结束运行

9

KILL

无条件终止

11

SEGV

段错误

15

TERM

尽可能终止

17

STOP

无条件停止运行,但不终止

18

TSTP

停止或暂停,但继续在后台运行

19

CONT

在STOP或TSTP之后恢复执行

 

1. kill命令

要发送进程信号,你必须是进程的属主或登录为root用户

 

如果要强制终止,-s参数支持指定其他信号(用信号名或信号值)。

2. killall命令

killall命令非常强大,它支持通过进程名而不是PID来结束进程。killall命令也支持通

配符,这在系统因负载过大而变得很慢时很有用。

4.2 监测磁盘空间

4.2.1 挂载存储媒体

1. mount命令

[21:23:29 root@libin3 ~]# mount

sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)

proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)

devtmpfs on /dev type devtmpfs (rw,nosuid,size=1916364k,nr_inodes=479091,mode=755)

securityfs on /sys/kernel/security type securityfs (rw,nosuid,nodev,noexec,relatime)

tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev)

devpts on /dev/pts type devpts (rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000)

tmpfs on /run type tmpfs (rw,nosuid,nodev,mode=755)

tmpfs on /sys/fs/cgroup type tmpfs (ro,nosuid,nodev,noexec,mode=755)

mount命令提供如下四部分信息:

媒体的设备文件名

媒体挂载到虚拟目录的挂载点

文件系统类型

已挂载媒体的访问状态

 

手动挂载媒体设备的基本命令:type参数指定了磁盘被格式化的文件系统类型。

mount -t type device directory

如果是和Windows PC共用这些存储设备,通常得使用下列文件系统类型。

vfat:Windows长文件系统。

ntfs:Windows NT、XP、Vista以及Windows 7中广泛使用的高级文件系统。

iso9660:标准CD-ROM文件系统。

 

大多数U盘和软盘会被格式化成vfat文件系统。而数据CD则必须使用iso9660文件系统类型。

后面两个参数定义了该存储设备的设备文件的位置以及挂载点在虚拟目录中的位置。比如说,手动将U盘/dev/sdb2挂载到/media/disk

mount -t vfat /dev/sdb1 /media/disk

 

mount命令的参数

-a

挂载/etc/fstab文件中指定的所有文件系统

-f

使mount命令模拟挂载设备,但并不真的挂载

-F

-a参数一起使用时,会同时挂载所有文件系统

-v

详细模式,将会说明挂载设备的每一步

-I

不启用任何/sbin/mount.filesystem下的文件系统帮助文件

-l

ext2ext3XFS文件系统自动添加文件系统标签

-n

挂载设备,但不注册到/etc/mtab已挂载设备文件中

-p num

进行加密挂载时,从文件描述符num中获得密码短语

-s

忽略该文件系统不支持的挂载选项

-r

将设备挂载为只读的

-w

将设备挂载为可读写的(默认参数)

-L label

将设备按指定的label挂载

-U uuid

将设备按指定的uuid挂载

-O

-a参数一起使用,限制命令只作用到特定的一组文件系统上

-o

给文件系统添加特定的选项

 

-o参数允许在挂载文件系统时添加一些以逗号分隔的额外选项。以下为常用的选项

ro:以只读形式挂载。

rw:以读写形式挂载。

user:允许普通用户挂载文件系统。

check=none:挂载文件系统时不进行完整性校验。

loop:挂载一个文件。

2. umount命令

umount命令的格式

umount [directory | device ]

umount命令支持通过设备文件或者是挂载点来指定要卸载的设备。如果有任何程序正在使

用设备上的文件,系统就不会允许你卸载它:

 

[21:37:33 root@libin3 CentOS 7 x86_64]# umount /run/media/root/CentOS\ 7\ x86_64

umount: /run/media/root/CentOS 7 x86_64:目标忙。

        (有些情况下通过 lsof(8) fuser(1) 可以

         找到有关使用该设备的进程的有用信息)

命令行提示符仍然在挂载设备的文件系统目录中,所以umount命令无法卸载该镜像文件。一旦命令提示符移出该镜像文件的文件系统,umount命令就能卸载该镜像文件。

 

 

4.2.2 使用 df 命令

df命令会显示每个有数据的已挂载文件系统。如你在前例中看到的,有些已挂载设备仅限系

统内部使用。命令输出如下

[21:37:50 root@libin3 CentOS 7 x86_64]# df

文件系统               1K-块    已用    可用 已用% 挂载点

devtmpfs             1916364       0 1916364    0% /dev

tmpfs                1932652   54624 1878028    3% /dev/shm

tmpfs                1932652   12692 1919960    1% /run

tmpfs                1932652       0 1932652    0% /sys/fs/cgroup

/dev/mapper/cl-root 10475520 8460276 2015244   81% /

/dev/sda1             201380  174380   27000   87% /boot

tmpfs                 386532      28  386504    1% /run/user/0

/dev/sr0             4414592 4414592       0  100% /run/media/root/CentOS 7 x86_64

命令输出如下:

设备的设备文件位置;

能容纳多少个1024字节大小的块;

已用了多少个1024字节大小的块;

还有多少个1024字节大小的块可用;

已用空间所占的比例;

设备挂载到了哪个挂载点上

 

参数-h:

它会把输出中的磁盘空间按照用户易读的形式显示,通常用M来替代兆字节,用G替代吉字节。

[21:39:40 root@libin3 CentOS 7 x86_64]# df -h

文件系统             容量  已用  可用 已用% 挂载点

devtmpfs             1.9G     0  1.9G    0% /dev

tmpfs                1.9G   54M  1.8G    3% /dev/shm

tmpfs                1.9G   13M  1.9G    1% /run

 

注:有可能系统上有运行的进程已经创建或删除了某个文件,但尚未释放文件。这个值是不会算进闲置空间的。

 

如果在卸载设备时,系统提示设备繁忙,无法卸载设备,通常是有进程还在访问该设备或使用该设备上的文件。这时可用lsof命令获得使用它的进程信息,然后在应用中停止使用该设备或停止该进程。lsof命令的用法很简单:lsof /path/to/device/node,或者lsof /path/to/mount/point

 

4.2.3 使用 du 命令

du命令可以显示某个特定目录(默认情况下是当前目录)的磁盘使用情况。这一方法可用来快速判断系统上某个目录下是不是有超大文件

 

[21:47:18 root@libin3 ~]# du | head -n 10

4 ./.cache/dconf

8 ./.cache/imsettings

0 ./.cache/evolution/addressbook/trash

0 ./.cache/evolution/addressbook

0 ./.cache/evolution/calendar/trash

0 ./.cache/evolution/calendar

0 ./.cache/evolution/mail/trash

0 ./.cache/evolution/mail

0 ./.cache/evolution/memos/trash

0 ./.cache/evolution/memos

每行输出左边的数值是每个文件或目录占用的磁盘块数。注意,这个列表是从目录层级的最底部开始,然后按文件、子目录、目录逐级向上。

 

-c:显示所有已列出文件总的大小。

-h:按用户易读的格式输出大小,即用K替代千字节,用M替代兆字节,用G替代吉字

节。

-s:显示每个输出参数的总计。

系统管理员接下来就是要使用一些文件处理命令操作大批量的数据。这正是下一节的主题。

 

4.3 处理数据文件

4.3.1 排序数据

sort命令:sort命令按照会话指定的默认语言的排序规则对文本文件中的数据行排序

[21:55:55 root@libin3 libin]# cat >>/libin/libin/libin.txt<<EOF

> one

> two

> three

> four

> five

> four

> EOF

[21:56:29 root@libin3 libin]# cat libin.txt

one

two

three

four

five

four

[21:56:32 root@libin3 libin]# sort libin.txt

five

four

four

one

three

two

 

默认情况下,sort命令会把数字当做字符来执行标准的字符排序

[21:57:04 root@libin3 libin]# cat >>/libin/libin/libin2.txt<<EOF

> 1

> 2

> 100

> 30

> 75

> EOF

[21:59:09 root@libin3 libin]# sort libin2.txt

1

100

2

30

75

 

-n:它会告诉sort命令把数字识别成数字而不是字符,并且按值排序。

[21:59:16 root@libin3 libin]# sort -n libin2.txt

1

2

30

75

100

-M:按月排序

 

sort命令参数

单破折线

双破折线

-b

--ignore-leading-blanks

排序时忽略起始的空白

-C

--check=quiet

不排序,如果数据无序也不要报告

-c

--check

不排序,但检查输入数据是不是已排序;未排序的话,报告

-d

--dictionary-order

仅考虑空白和字母,不考虑特殊字符

-f

--ignore-case

默认情况下,会将大写字母排在前面;这个参数会忽略大小写

-g

--general-number-sort

按通用数值来排序(跟-n不同,把值当浮点数来排序,支持科学计数法表示的值)

-i

--ignore-nonprinting

在排序时忽略不可打印字符

-k

--key=POS1[,POS2]

排序从POS1位置开始;如果指定了POS2的话,到POS2位置结束

-M

--month-sort

用三字符月份名按月份排序

-m

--merge

将两个已排序数据文件合并

-n

--numeric-sort

按字符串数值来排序(并不转换为浮点数)

-o

--output=file

将排序结果写出到指定的文件中

-R

--random-sort

按随机生成的散列表的键值排序

--random-source=FILE

指定-R参数用到的随机字节的源文件

-r

--reverse

反序排序(升序变成降序)

-S

--buffer-size=SIZE

指定使用的内存大小

-s

--stable

禁用最后重排序比较

-T

--temporary-directory=DIR

指定一个位置来存储临时工作文件

-t

--field-separator=SEP

指定一个用来区分键位置的字符

-u

--unique

-c参数一起使用时,检查严格排序;不和-c参数一起用时,仅

输出第一例相似的两行

-z

--zero-terminated

NULL字符作为行尾,而不是用换行符

-k:-k参数来指定排序的字段

-t -t参数来指定字段分隔符

例如/etc/passwd文件。

 

[22:12:04 root@libin3 libin]# cat /etc/passwd | sort -t ':' -k 1 -n

abrt:x:173:173::/etc/abrt:/sbin/nologin

adm:x:3:4:adm:/var/adm:/sbin/nologin

apache:x:48:48:Apache:/usr/share/httpd:/sbin/nologin

avahi:x:70:70:Avahi mDNS/DNS-SD Stack:/var/run/avahi-daemon:/sbin/nologin

bin:x:1:1:bin:/bin:/sbin/nologin

chrony:x:995:991::/var/lib/chrony:/sbin/nologin

clamscan:x:385:383:Clamav scanner user:/:/sbin/nologin

clamupdate:x:386:385:Clamav database update user:/var/lib/clamav:/sbin/nologin

colord:x:997:995:User for colord:/var/lib/colord:/sbin/nologin

daemon:x:2:2:daemon:/sbin:/sbin/nologin

dbus:x:81:81:System message bus:/:/sbin/nologin

 

-r-r参数将结果按降序输出

[22:17:02 root@libin3 etc]# du -sh * | sort -nr | head -n10

980K pki

656K services

604K ssh

564K sysconfig

340K dirsrv

332K sane.d

296K lvm

288K xdg

244K vmware-tools

244K dbus-1

 

4.3.2 搜索数据

grep命令

格式:grep [options] pattern [file]

[22:21:42 root@libin3 libin]# grep one libin.txt

one

[22:21:52 root@libin3 libin]# grep t libin.txt

two

three

 

-v参数:进行反向搜索(输出不匹配该模式的行)

[22:22:00 root@libin3 libin]# grep -v t libin.txt

one

four

five

four

 

-n参数:要显示匹配模式的行所在的行号

[22:24:37 root@libin3 libin]# grep -n t libin.txt

2:two

3:three

 

-e参数:要指定多个匹配模式

[22:28:09 root@libin3 libin]# grep -e rhca -e libin /etc/passwd

libin:x:1000:1000:libin:/home/libin:/bin/bash

rhca:x:1001:1001::/home/rhca:/bin/bash

 

在grep搜索中使用正则表达式的简单例子:

[22:30:38 root@libin3 libin]# grep [tf] libin.txt

two

three

four

five

Four

注:[]表明grep应该搜索包含t或者f字符的匹配

 

4.3.3 压缩数据

 

Linux文件压缩工具

文件扩展

bzip2

.bz2

采用Burrows-Wheeler块排序文本压缩算法和霍夫曼编码

compress

.Z

最初的Unix文件压缩工具,已经快没人用了

gzip

.gz

GNU压缩工具,用Lempel-Ziv编码

zip

.zip

Windows上PKZIP工具的Unix实现

 

gzip:用来压缩文件。

gzcat:用来查看压缩过的文本文件的内容。

gunzip:用来解压文件。

 

[22:30:46 root@libin3 libin]# ll

总用量 12

-rw-r--r-- 1 root root 14 7月  28 21:59 libin2.txt

-rw-r--r-- 1 root root 60 7月  28 22:02 libin3.txt

-rw-r--r-- 1 root root 29 7月  28 21:56 libin.txt

[22:37:16 root@libin3 libin]# gzip libin3.txt

[22:37:24 root@libin3 libin]# ls

libin2.txt  libin3.txt.gz  libin.txt

 

4.3.4 归档数据

tar命令

格式:tar function [options] object1 object2 ...

 

tar命令的功能

-A

--concatenate

将一个已有tar归档文件追加到另一个已有tar归档文件

-c

--create

创建一个新的tar归档文件

-d

--diff

检查归档文件和文件系统的不同之处

--delete

从已有tar归档文件中删除

-r

--append

追加文件到已有tar归档文件末尾

-t

--list

列出已有tar归档文件的内容

-u

--update

将比tar归档文件中已有的同名文件新的文件追加到该tar归档文件中

-x

--extract

从已有tar归档文件中提取文件

 

tar命令选项

-C dir

切换到指定目录

-f file

输出结果到文件或设备file

-j

将输出重定向给bzip2命令来压缩内容

-p

保留所有文件权限

-v

在处理文件时显示文件

-z

将输出重定向给gzip命令来压缩内容

 

来创建一个归档文件:

[22:50:12 root@libin3 libin]# tar -cvf libin.tar /libin/libin/ /libin/rhca/

tar: 从成员名中删除开头的“/”

/libin/libin/

/libin/libin/libin.txt

/libin/libin/libin2.txt

/libin/libin/libin3.txt

/libin/rhca/

 

上面的命令创建了名为libin.tar的归档文件,含有 /libin/libin/ 和/libin/rhca/目录内容

 

列出tar文件test.tar的内容(但并不提取文件)

[22:54:51 root@libin3 libin]# tar -tf libin.tar

libin/libin/

libin/libin/libin.txt

libin/libin/libin2.txt

libin/libin/libin3.txt

libin/rhca/

 

从tar文件test.tar中提取内容

[22:58:03 root@libin3 libin]# tar -xvf libin.tar

libin/libin/

libin/libin/libin.txt

libin/libin/libin2.txt

libin/libin/libin3.txt

libin/rhca/

 

第五章 熟悉shell

5.1 shell 的类型

bash shell程序位于/bin目录内。shell程序各自都可以被设置成用户的默认shell。不过由于bash shell的广为流行,很少有人使用其他的shell作为默认shell。

5.2 shell 的父子关系

[root@libin3 cloudman]# ps -f

UID         PID   PPID  C STIME TTY          TIME CMD

root     111104 110949  0 04:13 pts/0    00:00:00 sudo su

root     111105 111104  0 04:13 pts/0    00:00:00 su

root     111106 111105  0 04:13 pts/0    00:00:00 bash

root     111117 111106  0 04:13 pts/0    00:00:00 ps -f

 

[root@libin3 cloudman]# bash

[root@libin3 cloudman]# ps -f

UID         PID   PPID  C STIME TTY          TIME CMD

root     111104 110949  0 04:13 pts/0    00:00:00 sudo su

root     111105 111104  0 04:13 pts/0    00:00:00 su

root     111106 111105  0 04:13 pts/0    00:00:00 bash

root     111119 111106  0 04:13 pts/0    00:00:00 bash

root     111130 111119  0 04:13 pts/0    00:00:00 ps -f

输入命令bash之后,一个子shell就出现了。第二个ps -f是在子shell中执行的。可以从显示

结果中看到有两个bash shell程序在运行。第一个bash shell程序,也就是父shell进程,其原始进程ID是111106 。第二个bash shell程序,即子shell进程,其PID是111119 。注意,子shell的父进程ID(PPID)是111106  ,指明了这个父shell进程就是该子shell的父进程。

 

 

 

 

[root@libin3 cloudman]# bash

[root@libin3 cloudman]# ps --forest

   PID TTY          TIME CMD

111104 pts/0    00:00:00 sudo

111105 pts/0    00:00:00  \_ su

111106 pts/0    00:00:00      \_ bash

111119 pts/0    00:00:00          \_ bash

111135 pts/0    00:00:00              \_ bash

111146 pts/0    00:00:00                  \_ bash

111157 pts/0    00:00:00                      \_ bash

111168 pts/0    00:00:00                          \_ ps

 

bash命令被输入了四次。这实际上创建了四个子shell。ps -forest命令

展示了这些子shell间的嵌套结构

 

bash命令行参数

-c string

从string中读取命令并进行处理

-i

启动一个能够接收用户输入的交互shell

-l

以登录shell的形式启动

-r

启动一个受限shell,用户会被限制在默认目录中

-s

从标准输入中读取命令

 

用exit命令可以退出子shell

[root@libin3 cloudman]# exit

exit

[root@libin3 cloudman]# exit

exit

[root@libin3 cloudman]# exit

exit

[root@libin3 cloudman]# exit

exit

[root@libin3 cloudman]# ps --forest

   PID TTY          TIME CMD

111104 pts/0    00:00:00 sudo

111105 pts/0    00:00:00  \_ su

111106 pts/0    00:00:00      \_ bash

111119 pts/0    00:00:00          \_ bash

111190 pts/0    00:00:00              \_ ps

 

5.2.1 进程列表

可以在一行中指定要依次运行的一系列命令。这可以通过命令列表来实现,只需要在命令

之间加入分号(;)即可。

[root@libin3 cloudman]# pwd ; ls ; cd /etc ; pwd ; cd ;pwd ; ls

/home/cloudman

polkit-0.112-26.el7_9.1.x86_64.rpm

/etc

/root

anaconda-ks.cfg                     iperf-2.0.4-1.el7.rf.x86_64.rpm  libressl-2.8.3         openssh-7.9p1         ssh

 

命令列表要想成为进程列表,这些命令必须包含在括号里

[root@libin3 ~]# (pwd ; ls ; cd /etc ; pwd ; cd ;pwd ; ls)

/root

anaconda-ks.cfg                     iperf-2.0.4-1.el7.rf.x86_64.rpm  libressl-2.8.3         openssh-7.9p1         ssh

iftop-1.0-0.14.pre4.el7.x86_64.rpm  iptable                          libressl-2.8.3.tar.gz  openssh-7.9p1.tar.gz  sshdbak

/etc

/root

anaconda-ks.cfg                     iperf-2.0.4-1.el7.rf.x86_64.rpm  libressl-2.8.3         openssh-7.9p1         ssh

iftop-1.0-0.14.pre4.el7.x86_64.rpm  iptable                          libressl-2.8.3.tar.gz  openssh-7.9p1.tar.gz  sshdbak

 

注:。括号的加入使 命令列表变成了进程列表,生成了一个子shell来执行对应的命令。

 

想知道是否生成了子shell,得借助一个使用了环境变量的命令。

echo $BASH_SUBSHELL

如果该命令返回0,就表明没有子shell。如果返回1或者其他更大的数字,就表明存在子shell

 

[root@libin3 ~]# pwd ; ls ; cd /etc ; pwd ; cd ;pwd ; ls ; echo $BASH_SUBSHELL

/root

anaconda-ks.cfg                     iperf-2.0.4-1.el7.rf.x86_64.rpm  libressl-2.8.3         openssh-7.9p1         ssh

iftop-1.0-0.14.pre4.el7.x86_64.rpm  iptable                          libressl-2.8.3.tar.gz  openssh-7.9p1.tar.gz  sshdbak

/etc

/root

anaconda-ks.cfg                     iperf-2.0.4-1.el7.rf.x86_64.rpm  libressl-2.8.3         openssh-7.9p1         ssh

iftop-1.0-0.14.pre4.el7.x86_64.rpm  iptable                          libressl-2.8.3.tar.gz  openssh-7.9p1.tar.gz  sshdbak

0

 

[root@libin3 ~]# (pwd ; ls ; cd /etc ; pwd ; cd ;pwd ; ls ; echo $BASH_SUBSHELL)

/root

anaconda-ks.cfg                     iperf-2.0.4-1.el7.rf.x86_64.rpm  libressl-2.8.3         openssh-7.9p1         ssh

iftop-1.0-0.14.pre4.el7.x86_64.rpm  iptable                          libressl-2.8.3.tar.gz  openssh-7.9p1.tar.gz  sshdbak

/etc

/root

anaconda-ks.cfg                     iperf-2.0.4-1.el7.rf.x86_64.rpm  libressl-2.8.3         openssh-7.9p1         ssh

iftop-1.0-0.14.pre4.el7.x86_64.rpm  iptable                          libressl-2.8.3.tar.gz  openssh-7.9p1.tar.gz  sshdbak

1

可以在命令列表中嵌套括号来创建子shell的子shell

 

[root@libin3 ~]# ( pwd ; echo $BASH_SUBSHELL )

/root

1

[root@libin3 ~]# ( pwd ; (echo $BASH_SUBSHELL) )

/root

2

 

5.2.2 shell 用法

1. 探索后台模式

命令sleep 10会将会话暂停10秒钟,然后返回shell CLI提示符

[root@libin3 ~]# sleep 10

 

将命令置入后台模式,可以在命令末尾加上字符&。把sleep命令置入后台模式可以让我们利用ps命令来查看。当它被置入后台,在shell CLI提示符返回之前,会出现两条信息。第一条信息是显示在方括号中的后台作业(background job)号(1)。 第二条是后台作业的进程ID(111269)

[root@libin3 ~]# sleep 100&

[1] 111269

[root@libin3 ~]# ps -f

UID         PID   PPID  C STIME TTY          TIME CMD

root     111191 110949  0 04:28 pts/0    00:00:00 sudo su

root     111192 111191  0 04:28 pts/0    00:00:00 su

root     111193 111192  0 04:28 pts/0    00:00:00 bash

root     111269 111193  0 05:04 pts/0    00:00:00 sleep 100

root     111270 111193  0 05:04 pts/0    00:00:00 ps -f

sleep命令会在后台(&)睡眠100秒。

 

jobs命令来显示后台作业信息。jobs命令可以显示出当前运行在后台模式中的所有用户的进程(作业)

[root@libin3 ~]# jobs

[1]+  Done                    sleep 100

jobs命令在方括号中显示出作业号(1)。它还显示了作业的当前状态(done)以及对应的命令(sleep 100&)。

 

2. 将进程列表置入后台

[root@libin3 ~]# (sleep 1 ; echo $BASH_SUBSHELL ; sleep 2)

1

有一个1秒钟的暂停,显示出的数字1表明只有一个子shell,在返回提示符之前又经历了另一个2秒钟的暂停。

 

[root@libin3 ~]# (sleep 1 ; echo $BASH_SUBSHELL ; sleep 2)&

[1] 111283

[root@libin3 ~]# 1

按下回车键

[1]+  Done                    ( sleep 1; echo $BASH_SUBSHELL; sleep 2 )

在CLI中运用子shell的创造性方法之一就是将进程列表置入后台模式。你既可以在子shell中进行繁重的处理工作,同时也不会让子shell的I/O受制于终端

 

例如:创建备份文件是有效利用后台进程列表的一个更实用的例子

[root@libin3 ~]# (tar -cf libin.tar /home/cloudman ; tar -cf libin2.tar /home/ruut)&

 

3. 协程

协程可以同时做两件事。它在后台生成一个子shell,并在这个子shell中执行命令

coproc命令

[root@libin3 ~]# coproc sleep  10

[1] 111303

协程基本上就是将命令置入后台模式,当输入coproc命令及其参数之后,你会发现启用了一个后台作业。屏幕上会显示出后台作业号(1)以及进程ID(111303)。

 

jobs命令能够显示出协程的处理状态

[root@libin3 ~]# jobs

[1]+  Done                    coproc COPROC sleep 10

可以看到在子shell中执行的后台命令是coproc COPROC sleep 10。COPROC是coproc命令给进程起的名字。你可以使用命令的扩展语法自己设置这个名字。

[root@libin3 ~]# coproc libin ( sleep 10; )

[1] 111311

[root@libin3 ~]# jobs

[1]+  Running                 coproc libin ( sleep 10 ) &

通过使用扩展语法,协程的名字被设置成libin,必须确保在第一个花括号({)和命令名之间有一个空格。还必须保证命令以分号(;)结 尾。另外,分号和闭花括号(})之间也得有一个空格。

 

 

将协程与进程列表结合起来产生嵌套的子shell。只需要输入进程列表,然后把命令coproc放在前面就行了。

[root@libin3 ~]# coproc ( sleep 10 ;sleep 2 )

[1] 111331

[root@libin3 ~]# jobs

[1]+  Running                 coproc COPROC ( sleep 10; sleep 2 ) &

[root@libin3 ~]# ps --forest

   PID TTY          TIME CMD

111191 pts/0    00:00:00 sudo

111192 pts/0    00:00:00  \_ su

111193 pts/0    00:00:00      \_ bash

111334 pts/0    00:00:00          \_ ps

[1]+  Done                    coproc COPROC ( sleep 10; sleep 2 )

 

5.3 理解 shell 的内建命令

搞明白shell的内建命令和非内建(外部)命令非常重要。内建命令和非内建命令的操作方式大不相同。

5.3.1 外部命令

ps就是一个外部命令,使用which和type命令找到它

[21:40:04 root@libin3 ~]# which ps

/usr/bin/ps

[21:44:56 root@libin3 ~]# type - a ps

bash: type: -: 未找到

bash: type: a: 未找到

ps /usr/bin/ps

[21:45:19 root@libin3 ~]# ls -l /bin/ps

-rwxr-xr-x. 1 root root 100112 10月  1 2020 /bin/ps

 

当外部命令执行时,会创建出一个子进程。这种操作被称为衍生(forking)。外部命令ps显示出它的父进程以及自己所对应的衍生子进程

[21:45:32 root@libin3 ~]# ps -f

UID         PID   PPID  C STIME TTY          TIME CMD

root       3080   3070  0 21:39 pts/0    00:00:00 bash

root       3365   3080  0 21:47 pts/0    00:00:00 ps -f

 

作为外部命令,ps命令执行时会创建出一个子进程。ps命令的PID是3365,父PID是3080,

作为父进程的bash shell的PID是 3080

 

 

 

 

5.3.2 内建命令

内建命令和外部命令的区别在于前者不需要使用子进程来执行。

[21:47:31 root@libin3 ~]# type cd

cd shell 内嵌

[21:51:20 root@libin3 ~]# type exit

exit shell 内嵌

因为既不需要通过衍生出子进程来执行,也不需要打开程序文件,内建命令的执行速度要更快,效率也更高。

[21:51:24 root@libin3 ~]# type -a echo

echo shell 内嵌

echo /usr/bin/echo

echo /bin/echo

[21:52:20 root@libin3 ~]# which echo

/usr/bin/echo

[21:52:34 root@libin3 ~]# type -a pwd

pwd shell 内嵌

pwd /usr/bin/pwd

pwd /bin/pwd

[21:52:39 root@libin3 ~]# which pwd

/usr/bin/pwd

1. history命令

不带选项的history命令,查看最近用过的命令列表

[21:54:37 root@libin3 ~]# history  | head -n 10

    8  lvs

    9  mkdir /data_01mkdir /data_02mkdir /data_03

   10  ll

   11  mkdir /data_01 && mkdir /data_02 && mkdir /data_03

   12  rm -fr data_01mkdir/

   13  rm -fr data_02mkdir/

   14  rm -fr data_

   15  rm -fr /data_02/

   16  rm -fr /data_02mkdir/

   17  rm -fr /data_01/

 

输入!!或者!上一条命令的第一个字母,然后按回车键就能够唤出刚刚用过的那条命令来使用

[21:55:23 root@libin3 ~]# ps --forest

   PID TTY          TIME CMD

  3080 pts/0    00:00:00 bash

  3659 pts/0    00:00:00  \_ ps

[21:55:29 root@libin3 ~]# !!

ps --forest

   PID TTY          TIME CMD

  3080 pts/0    00:00:00 bash

  3675 pts/0    00:00:00  \_ ps

[21:55:53 root@libin3 ~]# !p

ps --forest

   PID TTY          TIME CMD

  3080 pts/0    00:00:00 bash

  3725 pts/0    00:00:00  \_ ps

注:命令历史记录被保存在隐藏文件.bash_history中,它位于用户的主目录中。这里要注意的是,

bash命令的历史记录是先存放在内存中,当shell退出时才被写入到历史文件中

 

 

 

history -a选项:要实现强制写入

[22:02:08 root@libin3 ~]# history | tail -n 10

 1010  history

 1011  cat ~root/.bash_history

 1012  cat ~root/.bash_history  | head -n 10

 1013  history | head -n 10

 1014  cat ~root/.bash_history  | head -n 10 | grep -n

 1015  grep -n  ~root/.bash_history  | head -n 10

 1016  history -a

 1017  history

 1018  history | head -n 10

 1019  history | tail -n 10

[22:02:13 root@libin3 ~]# cat ~root/.bash_history  | tail -n 10

history  | head -n 10

ps --forset

ps --forest

history

cat ~root/.bash_history

cat ~root/.bash_history  | head -n 10

history | head -n 10

cat ~root/.bash_history  | head -n 10 | grep -n

grep -n  ~root/.bash_history  | head -n 10

history -a

 

注:如果你打开了多个终端会话,仍然可以使用history -a命令在打开的会话中向.bash_history文件中添加记录。但是对于其他打开的终端会话,历史记录并不会自动更新。这是为.bash_history文件只有在打开首个终端会话时才会被读取。

history -n命令更新终端会话的历史记录。

 

历史列表中的编号:可以唤回历史列表中任意一条命令

 

[22:05:13 root@libin3 ~]# history | head -n 10

   25  mkfs.ext4 /dev/RHCA_libin-thin_LV_RHCA

   26  cd /dev/

   27  ls

   28  ll -ls

   29  ll -ld

   30  ls

   31  ll

   32  ll sdb1

   33  cd  sdb1

   34  cat sdb1

[22:05:20 root@libin3 ~]# !26

cd /dev/

 

2. 命令别名

alias命令是另一个shell的内建命令,命令别名允许你为常用的命令(及其参数)创建另一个名称,从而将输入量减少到最低。所使用的Linux发行版很有可能已经为你设置好了一些常用命令的别名。

 

alias -p 查看当前可用的别名

 

[22:05:35 root@libin3 dev]# alias -p

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 --color=auto'

alias mv='mv -i'

alias rm='rm -i'

alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

 

使用alias命令创建属于自己的别名

[22:12:17 root@libin3 dev]# alias li='ls -li'

[22:14:58 root@libin3 dev]# li

总用量 0

   31 crw-rw----   1 root video    10, 175 7月  30 21:36 agpgart

 8111 crw-------   1 root root     10, 235 7月  30 21:36 autofs

10591 drwxr-xr-x   2 root root         260 7月  30 21:36 block

 1486 drwxr-xr-x   2 root root         100 7月  30 21:36 bsg

17514 crw-------   1 root root     10, 234 7月  30 21:36 btrfs-control

   35 drwxr-xr-x   3 root root          60 7月  30 21:36 bus

18583 lrwxrwxrwx   1 root root           3 7月  30 21:36 cdrom -> sr0

  326 drwxr-xr-x   2 root root        3040 7月  30 21:36 char

  674 drwxr-xr-x   2 root root          80 7月  30 21:36 cl

 

因为命令别名属于内部命令,一个别名仅在它所被定义的shell进程中才有效。

[22:15:02 root@libin3 dev]# alias li='ls -li'

[22:16:15 root@libin3 dev]# bash

[22:16:17 root@libin3 dev]# li

bash: li: 未找到命令...

[22:16:19 root@libin3 dev]# exit

exit

6 章

第六章 使用Linux环境变量

Linux环境变量能帮你提升Linux shell体验。很多程序和脚本都通过环境变量来获取系统信息、存储临时数据和配置信息。

6.1 什么是环境变量

bash shell用一个叫作环境变量(environment variable)的特性来存储有关shell会话和工作环境的信息(这也是它们被称作环境变量的原因)。这项特性允许你在内存中存储数据,以便程序或shell中运行的脚本能够轻松访问到它们。

 

在bash shell中,环境变量分为两类:

全局变量

局部变量

6.1.1 全局环境变量

使用envprintenv命令:查看全局变量

[22:16:23 root@libin3 dev]# printenv

XDG_VTNR=1

SSH_AGENT_PID=2354

XDG_SESSION_ID=2

HOSTNAME=libin3.com

IMSETTINGS_INTEGRATE_DESKTOP=yes

VTE_VERSION=5204

TERM=xterm-256color

SHELL=/bin/bash

XDG_MENU_PREFIX=gnome-

HISTSIZE=1000

GNOME_TERMINAL_SCREEN=/org/gnome/Terminal/screen/bd183b02_82fc_45db_a366_0e5325bd92bb

IMSETTINGS_MODULE=none

USER=root

 

printenv命令:显示个别环境变量的值

[22:23:11 root@libin3 dev]# env | grep HOME

HOME=/root

[22:23:25 root@libin3 dev]# printenv HOME

/root

 

echo显示变量的值。在这种情况下引用某个环境变量的时候,必须在变量前面加上一个美元符($)

[22:23:48 root@libin3 dev]# echo $HOME

/root

 

让变量作为命令行参数。

[22:30:15 root@libin3 dev]# ls $HOME

2022-06-12xunjian.sh  elasticsearch-7.4.1-x86_64.rpm      initial-setup-ks.cfg  perf.data  公共  图片  音乐

anaconda-ks.cfg       filebeat-7.7.0-linux-x86_64.tar.gz  linux.html            rpmbuild   模板  文档  桌面

changepwd.ldif        grafana-prometheus                  log                   ty1        视频  下载

[22:30:42 root@libin3 dev]# echo $HOME

/root

[22:30:54 root@libin3 dev]# ls /root

2022-06-12xunjian.sh  elasticsearch-7.4.1-x86_64.rpm      initial-setup-ks.cfg  perf.data  公共  图片  音乐

anaconda-ks.cfg       filebeat-7.7.0-linux-x86_64.tar.gz  linux.html            rpmbuild   模板  文档  桌面

changepwd.ldif        grafana-prometheus                  log                   ty1        视频  下载

 

全局环境变量可用于进程的所有子shell

[22:31:12 root@libin3 dev]# bash

[22:31:57 root@libin3 dev]# ps -f

UID         PID   PPID  C STIME TTY          TIME CMD

root       3080   3070  0 21:39 pts/0    00:00:00 bash

root       5236   3080  0 22:31 pts/0    00:00:00 bash

root       5276   5236  0 22:31 pts/0    00:00:00 ps -f

[22:32:00 root@libin3 dev]# echo $HOME

/root

[22:32:12 root@libin3 dev]# exit

exit

6.1.2 局部环境变量

查看局部环境变量的列表有点复杂。遗憾的是,在Linux系统并没有一个只显示局部环境变量的命令。set命令会显示为某个特定进程设置的所有环境变量,包括局部变量、全局变量以及用户定义变量。

[22:36:06 root@libin3 dev]# set | head -n 10

ABRT_DEBUG_LOG=/dev/null

BASH=/usr/bin/bash

BASHOPTS=checkwinsize:cmdhist:expand_aliases:extglob:extquote:force_fignore:histappend:interactive_comments:progcomp:promptvars:sourcepath

BASH_ALIASES=()

BASH_ARGC=()

BASH_ARGV=()

BASH_CMDS=()

BASH_COMPLETION_COMPAT_DIR=/etc/bash_completion.d

BASH_LINENO=()

BASH_REMATCH=()

所有通过printenv命令能看到的全局环境变量都出现在了set命令的输出中。 但在set命令的输出中还有其他一些环境变量,即局部环境变量和用户定义变量。

 

6.2 设置用户定义变量

6.2.1 设置局部用户定义变量

启动了bash shell(或者执行一个shell脚本),就能创建在这个shell进程内可见的局部变量了。可以通过等号给环境变量赋值,值可以是数值或字符串。

[22:36:12 root@libin3 dev]# echo $my_variable

 

[22:39:01 root@libin3 dev]# my_variable=hello

[22:39:21 root@libin3 dev]# echo $my_variable

hello

 

如果要给变量赋一个含有空格的字符串值,必须用单引号来界定字符串的首和尾。

[22:39:24 root@libin3 dev]# my_variable=hello libin

bash: libin: 未找到命令...

[22:40:48 root@libin3 dev]# my_variable="hello libin"

[22:40:55 root@libin3 dev]# echo $my_variable

hello libin

没有单引号的话,bash shell会以为下一个词是另一个要执行的命令。注意,你定义的局部环

境变量用的是小写字母,而到目前为止你所看到的系统环境变量都是大写字母。

在涉及用户定义的局部变量时坚持使用小写字母,这能够避免重新定义系统环境变量可能带来的问题。

 

变量名、等号和值之间没有空格如果在赋值表达式中加上了空格, bash shell就会把值当成一个单独的命令。

[22:41:05 root@libin3 dev]# my_variable= "hello word"

bash: hello word: 未找到命令...

 

设置了局部环境变量后,就能在shell进程的任何地方使用。但如果生成了另外一个shell,它在子shell中就不可用。当你退出子shell并回到原来的shell时,这个局部环境变量依然可用。

 

[22:48:02 root@libin3 dev]# my_variable="hello libin"

[22:48:27 root@libin3 dev]# bash

[22:48:30 root@libin3 dev]# echo $my_varible

 

[22:48:41 root@libin3 dev]# exit

exit

[22:48:57 root@libin3 dev]# echo $my_variable

hello libin

 

在子进程中设置了一个局部变量,那么一旦你退出了子进程,那个局部环境变量就不可用。

[22:51:12 root@libin3 dev]# echo $my_child_varible

 

[22:51:27 root@libin3 dev]# bash

[22:51:30 root@libin3 dev]# my_child_varible="hello rhce"

[22:52:00 root@libin3 dev]# exit

exit

[22:52:04 root@libin3 dev]# echo $my_child_varible

 

回到父shell时,子shell中设置的局部变量就不存在了。可以通过将局部的用户定义变量变成全局变量来改变这种情况。

6.2.2 设置全局环境变量

 

设定全局环境变量的进程所创建的子进程中,该变量都是可见的。创建全局环境变量的方法是先创建一个局部环境变量,然后再把它导出到全局环境中。这个过程通过export命令来完成,变量名前面不需要加$

 

 

 

 

 

[21:54:22 root@libin3 ~]# my_libin="l am is libin"

[21:54:37 root@libin3 ~]# export my_libin

[21:54:47 root@libin3 ~]# echo $my_libin

l am is libin

[21:54:59 root@libin3 ~]# bash

[21:55:57 root@libin3 ~]# echo $my_libin

l am is libin

[21:56:07 root@libin3 ~]# exit

exit

[21:56:11 root@libin3 ~]# echo $my_libin

l am is libin

 

注:在定义并导出局部环境变量my_libin后,bash命令启动了一个子shell。这个子shell 中能够正确的显示出变量my_libin的值。该变量能够保留住它的值是因为export命令使变成了全局环境变量。

 

 

 

修改子shell中全局环境变量并不会影响到父shell中该变量的值。

 

[21:58:26 root@libin3 ~]# my_libin="i am is libin"

[21:58:30 root@libin3 ~]# export my_libin

[21:58:34 root@libin3 ~]# echo $my_libin

i am is libin

[21:58:42 root@libin3 ~]# bash

[21:58:45 root@libin3 ~]# echo $my_libin

i am is libin

[21:58:52 root@libin3 ~]# my_libin="Null"

[21:59:09 root@libin3 ~]# echo $my_libin

Null

[21:59:23 root@libin3 ~]# exit

exit

[21:59:29 root@libin3 ~]# echo $my_libin

i am is libin

 

注:在定义并导出变量my_libin后,bash命令启动了一个子shell。在这个子shell中能够正

 

确显示出全局环境变量my_libin的值。子shell随后改变了这个变量的值。但是这种改变仅在

 

子shell中有效,并不会被反映到父shell中。

 

 

 

子shell甚至无法使用export命令改变父shell中全局环境变量的值。

 

[21:59:33 root@libin3 ~]# my_libin="i am is libin"

[22:03:17 root@libin3 ~]# export my_libin

[22:03:26 root@libin3 ~]# echo $my_libin

i am is libin

[22:03:34 root@libin3 ~]# bash

[22:03:43 root@libin3 ~]# my_libin="Null"

[22:03:50 root@libin3 ~]# export my_libin

[22:04:07 root@libin3 ~]# echo $my_libin

Null

[22:04:11 root@libin3 ~]# exit

exit

[22:04:15 root@libin3 ~]# echo $my_libin

i am is libin

 

 

 

6.3 删除环境变量

 

unset命令

 

完成这个操作。在unset命令中引用环境变量时,记住不要使用$。

 

 

 

注:如果要用到变量,使用$如果要操作变量,不使用$。这条规则的一个例外就是使用printenv显示某个变量的值。

 

 

 

在子进程中删除了一个全局环境变量,这只对子进程有效。该全局环境变量在父进程中依然可用。

 

[22:04:18 root@libin3 ~]# my_libin="i an is libin"

[22:11:08 root@libin3 ~]# export my_libin

[22:11:18 root@libin3 ~]# echo $my_libin

i an is libin

[22:11:26 root@libin3 ~]# bash

[22:11:29 root@libin3 ~]# unset my_libin

[22:11:43 root@libin3 ~]# echo $my_libin

 

[22:11:51 root@libin3 ~]# exit

exit

[22:11:55 root@libin3 ~]# echo $my_libin

i an is libin

 

 

 

6.4 默认的 shell 环境变量

 

默认情况下,bash shell会用一些特定的环境变量来定义系统环境。这些变量在你的Linux系统上都已经设置好了,只管放心使用。bash shell源自当初的Unix Bourne shell,因此也保留了Unix

 

Bourne shell里定义的那些环境变量。

 

 

 

bash shell支持的Bourne变量

 

CDPATH

 

冒号分隔的目录列表,作为cd命令的搜索路径

 

HOME

当前用户的主目录

IFS

shell用来将文本字符串分割成字段的一系列字符

MAIL

当前用户收件箱的文件名(bash shell会检查这个文件,看看有没有新邮件)

MAILPATH

冒号分隔的当前用户收件箱的文件名列表(bash shell会检查列表中的每个文件,看看有没有新邮件)

OPTARG

getopts命令处理的最后一个选项参数值

OPTIND

getopts命令处理的最后一个选项参数的索引号

PATH

shell查找命令的目录列表,由冒号分隔

PS1

shell命令行界面的主提示符

PS2

shell命令行界面的次提示符

 

 

 

bash shell环境变量

 

BASH

当前shell实例的全路径名

BASH_ALIASES

含有当前已设置别名的关联数组

BASH_ARGC

含有传入子函数或shell脚本的参数总数的数组变量

BASH_ARCV

含有传入子函数或shell脚本的参数的数组变量

BASH_CMDS

关联数组,包含shell执行过的命令的所在位置

BASH_COMMAND

shell正在执行的命令或马上就执行的命令

BASH_ENV

设置了的话,每个bash脚本会在运行前先尝试运行该变量定义的启动文件

BASH_EXECUTION_STRING

使用bash -c选项传递过来的命令

BASH_LINENO

含有当前执行的shell函数的源代码行号的数组变量

BASH_REMATCH

只读数组,在使用正则表达式的比较运算符=~进行肯定匹配(positive match)时,

包含了匹配到的模式和子模式

BASH_SOURCE

含有当前正在执行的shell函数所在源文件名的数组变量

BASH_SUBSHELL

当前子shell环境的嵌套级别(初始值是0)

BASH_VERSINFO

含有当前运行的bash shell的主版本号和次版本号的数组变量

BASH_VERSION

当前运行的bash shell的版本号

BASH_XTRACEFD

若设置成了有效的文件描述符(

0、1、2),则'set -x'调试选项生成的跟踪输出

可被重定向。通常用来将跟踪输出到一个文件中

BASHOPTS

当前启用的bash shell选项的列表

BASHPID

当前bash进程的PID

COLUMNS

当前bash shell实例所用终端的宽度

COMP_CWORD

COMP_WORDS变量的索引值,后者含有当前光标的位置

COMP_LINE

当前命令行

COMP_POINT

当前光标位置相对于当前命令起始的索引

COMP_KEY

用来调用shell函数补全功能的最后一个键

COMP_TYPE

一个整数值,表示所尝试的补全类型,用以完成shell函数补全

COMP_WORDBREAKS

Readline库中用于单词补全的词分隔字符

COMP_WORDS

含有当前命令行所有单词的数组变量

COMPREPLY

含有由shell函数生成的可能填充代码的数组变量

COPROC

占用未命名的协进程的I/O文件描述符的数组变量

DIRSTACK

含有目录栈当前内容的数组变量

EMACS

设置为't'时,表明emacs shell缓冲区正在工作,而行编辑功能被禁止

ENV

如果设置了该环境变量,在bash shell脚本运行之前会先执行已定义的启动文件(仅用于当bash shell以POSIX模式被调用时)

EUID

当前用户的有效用户ID(数字形式)

FCEDIT

供fc命令使用的默认编辑器

FIGNORE

在进行文件名补全时可以忽略后缀名列表,由冒号分隔

FUNCNAME

当前执行的shell函数的名称

FUNCNEST

当设置成非零值时,表示所允许的最大函数嵌套级数(一旦超出,当前命令即被终止)

GLOBIGNORE

冒号分隔的模式列表,定义了在进行文件名扩展时可以忽略的一组文件名

GROUPS

含有当前用户属组列表的数组变量

histchars

控制历史记录扩展,最多可有3个字符

HISTCMD

当前命令在历史记录中的编号

HISTCONTROL

控制哪些命令留在历史记录列表中

HISTFILE

保存shell历史记录列表的文件名(默认是.bash_history)

HISTFILESIZE

最多在历史文件中存多少行

HISTTIMEFORMAT

如果设置了且非空,就用作格式化字符串,以显示bash历史中每条命令的时间戳 

HISTIGNORE

由冒号分隔的模式列表,用来决定历史文件中哪些命令会被忽略

HISTSIZE

最多在历史文件中存多少条命令

HOSTFILE

shell在补全主机名时读取的文件名称

HOSTNAME

当前主机的名称

HOSTTYPE

当前运行bash shell的机器

IGNOREEOF

shell在退出前必须收到连续的EOF字符的数量(如果这个值不存在,默认是1)

INPUTRC

Readline初始化文件名(默认是.inputrc)

LANG

shell的语言环境类别

LC_ALL

定义了一个语言环境类别,能够覆盖LANG变量

LC_COLLATE

设置对字符串排序时用的排序规则

LC_CTYPE

决定如何解释出现在文件名扩展和模式匹配中的字符

LC_MESSAGES

在解释前面带有$的双引号字符串时,该环境变量决定了所采用的语言环境设置

LC_NUMERIC

决定着格式化数字时采用的语言环境设置

LINENO

当前执行的脚本的行号

LINES

定义了终端上可见的行数

MACHTYPE

CPU-公司-系统”(CPU-company-system)格式定义的系统类型

MAPFILE

一个数组变量,当mapfile命令未指定数组变量作为参数时,它存储了mapfile所读入的文本

MAILCHECK

shell查看新邮件的频率(以秒为单位,默认值是60)

OLDPWD

shell之前的工作目录

OPTERR

设置为1时,bash shell会显示getopts命令产生的错误

OSTYPE

定义了shell所在的操作系统

PIPESTATUS

含有前台进程的退出状态列表的数组变量

POSIXLY_CORRECT

设置了的话,bash会以POSIX模式启动

PPID

bash shell父进程的PID

PROMPT_COMMAND

设置了的话,在命令行主提示符显示之前会执行这条命令

PROMPT_DIRTRIM

用来定义当启用了\w或\W提示符字符串转义时显示的尾部目录名的数量。被删除的目录名会用一组英文句点替换

PS3

select命令的提示符

PS4

如果使用了bash的-x选项,在命令行之前显示的提示信息

PWD

当前工作目录

RANDOM

返回一个0~32767的随机数(对其的赋值可作为随机数生成器的种子)

READLINE_LINE

当使用bind –x命令时,存储Readline缓冲区的内容

READLINE_POINT

当使用bind –x命令时,表示Readline缓冲区内容插入点的当前位置

REPLY

read命令的默认变量

SECONDS

自从shell启动到现在的秒数(对其赋值将会重置计数器)

SHELL

bash shell的全路径名

SHELLOPTS

已启用bash shell选项列表,列表项之间以冒号分隔

SHLVL

shell的层级;每次启动一个新bash shell,该值增加1

TIMEFORMAT

指定了shell的时间显示格式

TMOUT

select和read命令在没输入的情况下等待多久(以秒为单位)。默认值为0,表示无限长

TMPDIR

目录名,保存bash shell创建的临时文件

UID

当前用户的真实用户ID(数字形式)

 

6.5 设置 PATH 环境变量

 

PATH环境变量定义了用于进行命令和程序查找的目录

 

 

 

因为我基于centos7操作,所以我的环境变量内容为:PATH中的目录使用冒号分隔。

 

[22:40:02 root@libin3 ~]# echo $PATH

/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin

 

 

 

如果命令或者程序的位置没有包括在PATH变量中,那么如果不使用绝对路径的话,shell是没

 

法找到的。如果shell找不到指定的命令或程序,它会产生一个错误信息:

 

[22:40:14 root@libin3 ~]# libin

bash: libin: 未找到命令...

 

 

 

应用程序放置可执行文件的目录常常不在PATH环境变量所包含的目录中。解决的办法是保证PATH环境变量包含了所有存放应用程序的目录。可以把新的搜索目录添加到现有的PATH环境变量中,不需要从头定义。PATH中各个目录之间是用冒号分隔的。你只需引用原来的PATH值,然后再给这个字符串添加新目录就行了。

 

[22:44:23 root@libin3 ~]# echo $PATH

/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin

[22:46:08 root@libin3 ~]# PATH=$PATH:/home/libin/Scripts

[22:47:26 root@libin3 ~]# echo $PATH

/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin:/home/libin/Scripts

 

注:如果子shell也能找到你的程序的位置,一定要记得把修改后的PATH环境变量导出。

 

6.6 定位系统环境变量

 

怎样让环境变量的作用持久化。启动bash shell有3种方式:

 

1)登录时作为默认登录shell

 

2)作为非登录shell的交互式shell

 

3)作为运行脚本的非交互shell

 

6.6.1 登录 shell

 

登录shell会从5个不同的启动文件里读取命令:

 

1/etc/profile

 

2$HOME/.bash_profile

 

3$HOME/.bashrc

 

4$HOME/.bash_login

 

5$HOME/.profile

 

/etc/profile文件是系统上默认的bash shell的主启动文件。系统上的每个用户登录时都会执行这个启动文件。

 

注:有些Linux发行版使用了可拆卸式认证模块(Pluggable Authentication Modules ,PAM)。在这种情况下,PAM文件会在bash shell启动之前处理,这些文件中可能会包含环境变量。PAM文件包括/etc/environment文件和$HOME/.pam_environment文件。

 

1. /etc/profile文件

 

/etc/profile文件是bash shell默认的的主启动文件。只要你登录了Linux系统,bash就会执行

 

/etc/profile启动文件中的命令。

 

 

 

CentOS发行版的/etc/profile文件

 

[22:56:18 root@libin3 libin]# cat /etc/profile

# /etc/profile

 

# System wide environment and startup programs, for login setup

# Functions and aliases go in /etc/bashrc

 

# It's NOT a good idea to change this file unless you know what you

# are doing. It's much better to create a custom.sh shell script in

# /etc/profile.d/ to make custom changes to your environment, as this

# will prevent the need for merging in future updates.

 

pathmunge () {

    case ":${PATH}:" in

        *:"$1":*)

            ;;

        *)

            if [ "$2" = "after" ] ; then

                PATH=$PATH:$1

            else

                PATH=$1:$PATH

            fi

    esac

}

 

 

if [ -x /usr/bin/id ]; then

    if [ -z "$EUID" ]; then

        # ksh workaround

        EUID=`/usr/bin/id -u`

        UID=`/usr/bin/id -ru`

    fi

    USER="`/usr/bin/id -un`"

    LOGNAME=$USER

    MAIL="/var/spool/mail/$USER"

fi

 

# Path manipulation

if [ "$EUID" = "0" ]; then

    pathmunge /usr/sbin

    pathmunge /usr/local/sbin

else

    pathmunge /usr/local/sbin after

    pathmunge /usr/sbin after

fi

 

HOSTNAME=`/usr/bin/hostname 2>/dev/null`

HISTSIZE=1000

if [ "$HISTCONTROL" = "ignorespace" ] ; then

    export HISTCONTROL=ignoreboth

else

    export HISTCONTROL=ignoredups

fi

 

export PATH USER LOGNAME MAIL HOSTNAME HISTSIZE HISTCONTROL

 

# By default, we want umask to get set. This sets it for login shell

# Current threshold for system reserved uid/gids is 200

# You could check uidgid reservation validity in

# /usr/share/doc/setup-*/uidgid file

if [ $UID -gt 199 ] && [ "`/usr/bin/id -gn`" = "`/usr/bin/id -un`" ]; then

    umask 002

else

    umask 022

fi

 

for i in /etc/profile.d/*.sh /etc/profile.d/sh.local ; do

    if [ -r "$i" ]; then

        if [ "${-#*i}" != "$-" ]; then

            . "$i"

        else

            . "$i" >/dev/null

        fi

    fi

done

 

unset i

unset -f pathmunge

 

 

 

 

大部分应用都会创建两个启动文件:一个供bash shell使用(使用.sh扩展名),一个供c shell使用(使用.csh扩展名)。lang.csh和lang.sh文件会尝试去判定系统上所采用的默认语言字符集,然后设置对应的LANG环境变量。

 

[22:57:10 root@libin3 libin]# ls -l /etc/profile.d/

总用量 84

-rw-r--r--. 1 root root  771 1117 2020 256term.csh

-rw-r--r--. 1 root root  841 1117 2020 256term.sh

-rw-r--r--. 1 root root 1348 10月  2 2020 abrt-console-notification.sh

-rw-r--r--. 1 root root  660 4月   1 2020 bash_completion.sh

-rw-r--r--. 1 root root  196 3月  25 2017 colorgrep.csh

-rw-r--r--. 1 root root  201 3月  25 2017 colorgrep.sh

-rw-r--r--. 1 root root 1741 1116 2020 colorls.csh

-rw-r--r--. 1 root root 1606 1116 2020 colorls.sh

-rw-r--r--. 1 root root   80 4月   1 2020 csh.local

-rw-r--r--. 1 root root  294 8月   5 2021 flatpak.sh

-rw-r--r--. 1 root root 1706 1117 2020 lang.csh

-rw-r--r--. 1 root root 2703 1117 2020 lang.sh

-rw-r--r--. 1 root root  123 7月  31 2015 less.csh

-rw-r--r--. 1 root root  121 7月  31 2015 less.sh

-rw-r--r--. 1 root root 1202 4月   2 2020 PackageKit.sh

-rw-r--r--. 1 root root   81 4月   1 2020 sh.local

-rw-r--r--. 1 root root  105 1216 2020 vim.csh

-rw-r--r--. 1 root root  269 1216 2020 vim.sh

-rw-r--r--. 1 root root 2092 10月  1 2020 vte.sh

-rw-r--r--. 1 root root  164 1月  28 2014 which2.csh

-rw-r--r--. 1 root root  169 1月  28 2014 which2.sh

 

 

 

2. $HOME目录下的启动文件

 

剩下的启动文件都起着同一个作用:提供一个用户专属的启动文件来定义该用户所用到的环境变量。大多数Linux发行版只用这四个启动文件中的一到两个:

 

1$HOME/.bash_profile

 

2$HOME/.bashrc

 

3$HOME/.bash_login

 

4$HOME/.profile

 

 

 

注:这四个文件都以点号开头,这说明它们是隐藏文件(不会在通常的ls命令输出列表中出现)。它们位于用户的HOME目录下,所以每个用户都可以编辑这些文件并添加自己的环境变量,这些环境变量会在每次启动bash shell会话时生效。

 

Linux发行版在环境文件方面存在的差异非常大。本节中所列出的$HOME下的那些文件并非每个用户都有。例如有些用户可能只有一个$HOME/.bash_profile文件。

 

1$HOME/.bash_profile

 

2$HOME/.bash_login

 

3$HOME/.profile

 

注:这个列表中并没有$HOME/.bashrc文件。这是因为该文件通常通过其他文件运行的。

 

 

 

CentOS Linux系统中的.bash_profile文件的内容:

 

[22:57:47 root@libin3 libin]# cat $HOME/.bash_profile

# .bash_profile

 

# Get the aliases and functions

if [ -f ~/.bashrc ]; then

. ~/.bashrc

fi

 

# User specific environment and startup programs

 

PATH=$PATH:$HOME/bin

 

export PATH

 

.bash_profile启动文件会先去检查HOME目录中是不是还有一个叫.bashrc的启动文件。如果有的话,会先执行启动文件里面的命令。

 

6.6.2 交互式 shell 进程

 

你的bash shell不是登录系统时启动的(比如是在命令行提示符下敲入bash时启动),那么你启动的shell叫作交互式shell。交互式shell不会像登录shell一样运行,但它依然提供了命令行提示符来输入命令。

 

如果bash是作为交互式shell启动的,它就不会访问/etc/profile文件,只会检查用户HOME目录

 

中的.bashrc文件。

 

 

 

[23:07:57 root@libin3 libin]# cat .bashrc

# .bashrc

 

# Source global definitions

if [ -f /etc/bashrc ]; then

. /etc/bashrc

fi

 

# Uncomment the following line if you don't like systemctl's auto-paging feature:

# export SYSTEMD_PAGER=

 

# User specific aliases and functions

 

.bashrc文件有两个作用:一是查看/etc目录下通用的bashrc文件,二是为用户提供一个定制自己的命令别名私有脚本函数的地方。

 

6.6.3 非交互式 shell

系统执行shell脚本时用的就是这种shell。不同的地方在于它没有命令行提示符。但是当你在系统上运行脚本时,也许希望能够运行一些特定启动的命令。

 

bash shell提供了BASH_ENV环境变量。当shell启动一个非交互式shell进程时,它会检查这个环境变量来查看要执行的启动文件。如果有指定的文件,shell会执行该文件里的命令,这通常包括shell脚本变量设置。

 

如果变量未设置,printenv命令只会返回CLI提示符:

[09:06:14 root@libin3 ~]# printenv BASH_ENV

[09:07:50 root@libin3 ~]# echo $BASH_ENV

 

子shell可以继承父shell导出过的变量。

举例来说,如果父shell是登录shell,在/etc/profile、/etc/profile.d/*.sh和$HOME/.bashrc文件中

设置并导出了变量,用于执行脚本的子shell就能够继承这些变量。由父shell设置但并未导出的变量都是局部变量。子shell无法继承局部变量。对于那些不启动子shell的脚本,变量已经存在于当前shell中了。所以就算没有设置BASH_ENV,也可以使用当前shell的局部变量和全局变量。

 

6.6.4 环境变量持久化

存储个人用户永久性bash shell变量的地方是$HOME/.bashrc文件。这一点适用于所有类型的shell进程。但如果设置了BASH_ENV变量,那么记住,除非它指向的是$HOME/.bashrc,否则应该将非交互式shell的用户变量放在别的地方。

 

可以把自己的alias设置放在$HOME/.bashrc启动文件中,使其效果永久化。

6.7 数组变量

数组是能够存储多个值的变量。这些值可以单独引用,也可以作为整个数组来引用。

给某个环境变量设置多个值,可以把值放在括号里,值与值之间用空格分隔。

给某个环境变量设置多个值,可以把值放在括号里,值与值之间用空格分隔。

[09:25:50 root@libin3 ~]# rhce=(one two three four five)

[09:26:16 root@libin3 ~]# echo $rhce

one

 

只有数组的第一个值显示出来了。要引用一个单独的数组元素,就必须用代表它在数组中位

置的数值索引值。索引值要用方括号括起来。

[09:26:21 root@libin3 ~]# echo ${rhce[2]}

three

[09:26:38 root@libin3 ~]# echo ${rhce[0]}

one

 

要显示整个数组变量,可用星号作为通配符放在索引值的位置。

[09:53:06 root@libin3 ~]# echo ${rhce[*]}

one two three four five

 

也可以改变某个索引值位置的值

[09:53:39 root@libin3 ~]# rhce[2]=libin

[09:55:47 root@libin3 ~]# echo ${rhce[2]}

libin

 

能用unset命令删除数组中的某个值,但是要小心,这可能会有点复杂

 

[09:56:40 root@libin3 ~]# unset rhce[2]

[09:57:14 root@libin3 ~]# echo ${rhce[*]}

one two four five

[09:57:25 root@libin3 ~]# echo ${rhce[2]}

 

[09:57:36 root@libin3 ~]# echo ${rhce[3]}

four

注:例子用unset命令删除在索引值为2的位置上的值。显示整个数组时,看起来像是索引里面已经没这个索引了。但当专门显示索引值为2的位置上的值时,就能看到这个位置是空的。 最后,可以在unset命令后跟上数组名来删除整个数组。

[09:57:42 root@libin3 ~]# unset rhce

[10:00:07 root@libin3 ~]# echo ${rhce[*]}

 

第七章 Linux文件权限

系统中必须有一套能够保护文件免遭非授权用户浏览或修改的机制。

7.1 Linux 的安全性

Linux安全系统的核心是用户账户。每个能进入Linux系统的用户都会被分配唯一的用户账

户。用户对系统中各种对象的访问权限取决于他们登录系统时用的账户。

 

用户权限是通过创建用户时分配的用户ID(User ID,通常缩写为UID)来跟踪的。UID是数

值,每个用户都有唯一的UID,但在登录系统时用的不是UID,而是登录名。登录名是用户用来登录系统的最长八字符的字符串(字符可以是数字或字母),同时会关联一个对应的密码。

7.1.1 /etc/passwd 文件

Linux系统使用一个专门的文件来将用户的登录名匹配到对应的UID。这个文件就/etc/passwd文件,它包含了一些与用户有关的信息。

[10:04:33 root@libin3 ~]# cat /etc/passwd | head -n 10

root:x:0:0:root:/root:/bin/bash

bin:x:1:1:bin:/bin:/sbin/nologin

daemon:x:2:2:daemon:/sbin:/sbin/nologin

adm:x:3:4:adm:/var/adm:/sbin/nologin

lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

sync:x:5:0:sync:/sbin:/bin/sync

shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown

halt:x:7:0:halt:/sbin:/sbin/halt

mail:x:8:12:mail:/var/spool/mail:/sbin/nologin

operator:x:11:0:operator:/root:/sbin/nologin

注:root用户账户是Linux系统的管理员,固定分配给它的UID是0。Linux 统会为各种各样的功能创建不同的用户账户,而这些账户并不是真的用户。这些账户叫作系统账户,是系统上运行的各种服务进程访问资源用的特殊账户。所有运行在后台的服务都需要用一个系统用户账户登录到Linux系统上。

 

/etc/passwd文件的字段包含了如下信息:

1登录用户名

2用户密码

3用户账户的UID(数字形式)

4户账户的组IDGID)(数字形式)

5用户账户的文本描述(称为备注字段)

6用户HOME目录的位置

7用户的默认shell

 

注:/etc/passwd是一个标准的文本文件。你可以用任何文本编辑器在/etc/password文件里直接手动进行用户管理(比如添加、修改或删除用户账户)。但这样做极其危险。如果/etc/passwd文件出现损坏,系统就无法读取它的内容了,这样会导致用户无法正常登录(即便是root用户)。

7.1.2 /etc/shadow 文件

/etc/shadow文件对Linux系统密码管理提供了更多的控制。只有root用户才能访问/etc/shadow

文件,这让它比起/etc/passwd安全。/etc/shadow文件为系统上的每个用户账户都保存了一条记录

。使用shadow密码系统后,Linux系统可以更好地控制用户密码。它可以控制用户多久更改一

次密码,以及什么时候禁用该用户账户,如果密码未更新的话。

 

 

10:58 root@libin3 ~]# cat /etc/shadow | grep root

root:$6$Rdg.N7/zvPwP6TtR$u/STrppxvXFMIs1cQZse2f.e4PlrC0r/klfJzKi1Frfy9dm/6jgfi5DgNUykdjjMO3MjhADh7Z6Oh3PQFrOZA0::0:99999:7:::

 

在/etc/shadow文件的每条记录中都有9个字段:

1/etc/passwd文件中的登录名字段对应的登录名

2加密后的密码

3自上次修改密码后过去的天数密码(自197011日开始计算)

4多少天后才能更改密码

5多少天后必须更改密码

6密码过期前提前多少天提醒用户更改密码7.1 Linux 的安全性 127

7密码过期后多少天禁用用户账户

8用户账户被禁用的日期(用自197011日到当天的天数表示)

9预留字段给将来使用

7.1.3 添加新用户

Useradd命令:

可以一次性创建新用户账户及设置用户HOME目录结构。useradd命令使用系统的默认值以及命令行参数来设置用户账户。系统默认值被设置在/etc/default/useradd文件中。可以使用加入了-D选项的useradd命令查看所用Linux系统中的这些默认值。

[10:16:31 root@libin3 ~]# useradd -D

GROUP=100                 # 新用户会被添加到GID为100的公共组;

HOME=/home               #新用户的HOME目录将会位于/home/loginname;

INACTIVE=-1                #新用户账户密码在过期后不会被禁用;

EXPIRE=                    #新用户账户未被设置过期日期;

SHELL=/bin/bash             #新用户账户将bash shell作为默认shell;

SKEL=/etc/skel               #系统会将/etc/skel目录下的内容复制到用户的HOME目录下;

CREATE_MAIL_SPOOL=yes     #系统为该用户账户在mail目录下创建一个用于接收邮件的文件。

注:一些Linux发行版会把Linux用户和组工具放在/usr/sbin目录下,这个目录可能不在PATH环境变量里。如果你的Linux系统是这样的话,可以将这个目录添加进PATH环境变量,或者用绝对文件路径名来使用这些工具。

 

 

/etc/skel目录,每个新用户的HOME目录里放置默认的系统文件.

[10:16:34 root@libin3 ~]# ls -al /etc/skel

总用量 28

drwxr-xr-x.   3 root root    78 10月  1 2021 .

drwxr-xr-x. 168 root root 12288 6月  29 01:11 ..

-rw-r--r--.   1 root root    18 4月   1 2020 .bash_logout

-rw-r--r--.   1 root root   193 4月   1 2020 .bash_profile

-rw-r--r--.   1 root root   231 4月   1 2020 .bashrc

drwxr-xr-x.   4 root root    39 9月  30 2021 .mozilla

 

-m选项:

useradd命令不会创建HOME目录,但是-m命令行选项会使其创建HOME目录。

[10:22:33 root@libin3 ~]# useradd -m test

[10:23:24 root@libin3 ~]# ls -al  /home/test/

总用量 12

drwx------  3 test test  78 8月   2 10:23 .

drwxr-xr-x. 8 root root  85 8月   2 10:23 ..

-rw-r--r--  1 test test  18 4月   1 2020 .bash_logout

-rw-r--r--  1 test test 193 4月   1 2020 .bash_profile

-rw-r--r--  1 test test 231 4月   1 2020 .bashrc

drwxr-xr-x  4 test test  39 9月  30 2021 .mozilla

 

useradd命令行参数

-c comment

给新用户添加备注

-d home_dir

为主目录指定一个名字(如果不想用登录名作为主目录名的话)

-e expire_date

YYYY-MM-DD格式指定一个账户过期的日期

-f inactive_days

指定这个账户密码过期后多少天这个账户被禁用;0表示密码一过期就立即禁用,1表示禁用这个功能

-g initial_group

指定用户登录组的GID或组名

-G group ...

指定用户除登录组之外所属的一个或多个附加组

-k

必须和-m一起使用,将/etc/skel目录的内容复制到用户的HOME目录

-m

创建用户的HOME目录

-M

不创建用户的HOME目录(当默认设置里要求创建时才使用这个选项)

-n

创建一个与用户登录名同名的新组

-r

创建系统账户

-p passwd

为用户账户指定默认密码

-s shell

指定默认的登录shell

-u uid

为账户指定唯一的UID

 

useradd更改默认值的参数

-b default_home

更改默认的创建用户HOME目录的位置

-e expiration_date

更改默认的新账户的过期日期

-f inactive

更改默认的新用户从密码过期到账户被禁用的天数

-g group

更改默认的组名称或GID

-s shell

更改默认的登录shell

 

更改默认值:

[10:32:02 root@libin3 ~]# useradd -D -s /bin/test

[10:35:27 root@libin3 ~]# useradd -D

GROUP=100

HOME=/home

INACTIVE=-1

EXPIRE=

SHELL=/bin/test

SKEL=/etc/skel

CREATE_MAIL_SPOOL=yes

useradd命令会将test shell作为所有新建用户的默认登录shell。

7.1.4 删除用户

userdel命令

userdel命令会只删除/etc/passwd文件中的用户信息,而不会删除系统中属于该账户的任何文件。

-r参数userdel会删除用户的HOME目录以及邮件目录。然而,系统上仍可能存有已删除用户的其他文件。这在有些环境中会造成问题。

[10:36:53 root@libin3 ~]# userdel -r test

[10:40:13 root@libin3 ~]# ls -al /home/test

ls: 无法访问/home/test: 没有那个文件或目录

注:在有大量用户的环境中使用-r参数时要特别小心。你永远不知道用户是否在其HOME

录下存放了其他用户或其他程序要使用的重要文件。在删除用户的HOME目录之前一定要检查清楚

7.1.5 修改用户

用户账户修改工具

usermod

修改用户账户的字段,还可以指定主要组以及附加组的所属关系

passwd

修改已有用户的密码

chpasswd

从文件中读取登录名密码对,并更新密码

chage

修改密码的过期日期

chfn

修改用户账户的备注信息

chsh

修改用户账户的默认登录shell

 

1. usermod命令

能用来修改/etc/passwd文件中的大部分字段,只需用与想修改的字段对应的命令行参数就可以了。参数大部分跟useradd命令的参数一样(比如,-c修改备注字段,-e修改过期日期,-g修改默认的登录组)。

 

1l修改用户账户的登录名

2-L锁定账户,使用户无法登录

3-p修改账户的密码

4-U解除锁定,使用户能够登录

 

2. passwdchpasswd

[10:46:36 root@libin3 ~]# useradd -m rhce

[10:46:49 root@libin3 ~]# passwd rhce

更改用户 rhce 的密码 。

新的 密码:

重新输入新的 密码:

passwd:所有的身份验证令牌已经成功更新。

 

-e选项:强制用户下次登录时修改密码

系统上的任何用户都能改自己的密码,但只有root用户才有权限改别人的密码。

 

 

 

如果需要为系统中的大量用户修改密码,chpasswd命令能从标准输入自动读取登录名和密码对(由冒号分割)列表,给密码加密,然后为用户账户设置。你也可以用重定向命令来将含有userid:passwd对的文件重定向给该命令。

 

语法格式 chpasswd [参数]

参数

描述

-e

输入的密码是加密后的密文

-m

当被支持的密码未被加密时,使用MD5加密代替DES加密

-h

显示帮助信息并退出

 

例如:

[10:58:07 root@libin3 ~]# cat rhce.txt

rhce:helloword

[11:00:14 root@libin3 ~]# vim rhce.txt

rhce:libin1234

[11:00:32 root@libin3 ~]# chpasswd < rhce.txt

[11:00:35 root@libin3 ~]# su - rhca

[rhca@libin3 ~]$ su - rhce

密码:输入helloword时就登录不了              

上一次登录:二 8月  2 10:57:59 CST 2022pts/0

最后一次失败的登录:二 8月  2 11:00:50 CST 2022pts/0

最有一次成功登录后有 1 次失败的登录尝试。

[rhce@libin3 ~]$

 

 命令用法:echo 用户名:密码 | chpasswd

 

3. chshchfnchage

chsh、chfn和chage工具专门用来修改特定的账户信息。chsh命令用来快速修改默认的用

户登录shell。使用时必须用shell的全路径名作为参数,不能只用shell名。

[11:08:17 root@libin3 ~]# cat /etc/passwd | grep rhce

rhce:x:1006:1006::/home/rhce:/bin/bash

[11:08:21 root@libin3 ~]# chsh -s /bin/csh rhce

Changing shell for rhce.

Shell changed.

[11:08:38 root@libin3 ~]# cat /etc/passwd | grep rhce

rhce:x:1006:1006::/home/rhce:/bin/csh

 

 

chfn命令提供了在/etc/passwd文件的备注字段中存储信息的标准方法。chfn命令会将用于Unix的finger命令的信息存进备注字段,而不是简单地存入一些随机文本(比如名字或昵称之类的),或是将备注字段留空。finger命令可以非常方便地查看Linux系统上的用户信息。

[11:13:18 root@libin3 ~]# finger rhce

Login: rhce                    Name:

Directory: /home/rhce                Shell: /bin/csh

Last login 8月  2 11:00 (CST) on pts/0

No mail.

No Plan.

注:出于安全性考虑,很多Linux系统管理员会在系统上禁用finger命令,不少Linux发行版

甚至都没有默认安装该命令

 

[11:13:21 root@libin3 ~]# chfn rhce

Changing finger information for rhce.

名称 []: i am is libin

办公 []: technology

办公电话 []: 10086

住宅电话 []: 10086

 

Finger information changed.

[11:15:28 root@libin3 ~]# finger rhce

Login: rhce            Name: i am is libin

Directory: /home/rhce                Shell: /bin/csh

Office: technology, x1-0086 Home Phone: x1-0086

Last login 8月  2 11:00 (CST) on pts/0

No mail.

No Plan.

 

[11:16:29 root@libin3 ~]# cat /etc/passwd | grep rhce

rhce:x:1006:1006:i am is libin,technology,10086,10086:/home/rhce:/bin/csh

所有的指纹信息现在都存在/etc/passwd文件中了

 

chage命令参数

-d

设置上次修改密码到现在的天数

-E

设置密码过期的日期

-I

设置密码过期到锁定账户的天数

-m

设置修改密码之间最少要多少天

-W

设置密码过期前多久开始出现提醒信息

chage命令的日期值可以用下面两种方式:

1YYYY-MM-DD格式的日期

2)代表从1970年1月1日起到该日期天数的数值

7.2 使用 Linux

用户账户在控制单个用户安全性方面很好用,但涉及在共享资源的一组用户时就捉襟见肘

了。为了解决这个问题,Linux系统采用了另外一个安全概念——group)。

7.2.1 /etc/group 文件

[11:16:35 root@libin3 ~]# cat /etc/group | head -n 10

root:x:0:

bin:x:1:

daemon:x:2:

sys:x:3:

adm:x:4:

tty:x:5:

disk:x:6:

lp:x:7:

mem:x:8:

kmem:x:9:

注:系统账户用的组通常会分配低于500的GID值,而用户组的GID则会从500开始分配。万不能通过直接修改/etc/group文件来添加用户到一个组,要用usermod命令。在添加用户到不同的组之前,首先得创建组。

 

/etc/group文件有4个字段:

1组名

2组密码

3GID

4属于该组的用户列表

 

7.2.2 创建新组

groupadd命令可在系统上创建新组

[11:29:15 root@libin3 ~]# tail /etc/group

libin:x:1000:libin

hello1:x:1003:

student:x:1004:

rsync:x:1005:

clamupdate:x:385:

virusgroup:x:384:clamupdate,clamscan

clamscan:x:383:

grafana:x:382:

rhce:x:1006:

groupadd命令没有提供将用户添加到组中的选项,但可以用usermod命令来弥补。

 

usermod命令

-G选项:会把这个新组添加到该用户账户的组列表里

[14:07:06 root@libin3 ~]# usermod -G libin rhcsa

[14:06:54 root@libin3 ~]# cat /etc/group | grep libin

libin:x:1000:libin,rhce

 

libin现有成员libin,rhce。注:如果加了-g选项,指定的组名会替换掉该账户的默认

组。-G选项则将该组添加到用户的属组的列表里,不会影响默认组。

拓展:

UID用户ID0代表root,不能随便改用户ID0

虚拟用户:用户ID1-999rhel7),用户ID1-499rhel6),满足系统服务在运行时,都要有一个用户和用户组,但是不能su - 切换。

普通用户:1000+rhel7500+(rhel6)

7.2.3 修改组

groupmod命令可以修改已有组的GID(加-g选项)或组名(加-n选项)

 

[14:09:26 root@libin3 ~]# groupmod -n libin1 libin

[14:12:34 root@libin3 ~]# tail /etc/group | grep libin1

libin1:x:1000:libin,rhce

 

7.3 理解文件权限

7.3.1 使用文件权限符

ls -l:查看Linux系统上的文件、目录和设备的权限

[14:16:29 root@libin3 libin]# ls -l

总用量 364

drwxr-xr-x 4 root root     84 7月  28 22:58 libin

-rw-r--r-- 1 root root  10240 7月  28 22:50 libin.tar

drwxr-xr-x 3 root root     23 7月  27 22:55 new_dir

drwxr-xr-x 2 root root      6 7月  28 22:50 rhca

drwxr-xr-x 2 root root      6 7月  28 22:57 rhce

drwxr-xr-x 2 root root     54 3月  17 18:27 scripts

drwxr-xr-x 3 root root     34 7月  27 22:09 scripts2

-rw-r--r-- 1 root root 358287 7月  27 22:12 time.log

第一个字段就是描述文件和目录权限的编码。这个字段的第一个字符代表了对象的类型:

1-代表文件

2d代表目录

3l代表链接

4c代表字符型设备

5b代表块设备

6n代表网络设备

之后有3组三字符的编码。每一组定义了3种访问权限:

7r代表对象是可读的

8w代表对象是可写的

9x代表对象是可执行的

10对象的属主

11对象的属组

12系统其他用户

-rw-r--r-- 1 root root 358287 7月  27 22:12 time.log

rw-:文件的属主(设为登录名root

r--:文件的属组(设为组名root

r--:系统上其他人

 

7.3.2 默认文件权限

umask命令:用来设置所创建文件和目录的默认权限

[14:16:32 root@libin3 libin]# umask

0022

[14:23:08 root@libin3 libin]# touch rhcsafile

[14:23:23 root@libin3 libin]# ls -al rhcsafile

-rw-r--r-- 1 root root 0 8月   2 14:23 rhcsafile

umask命令:

第一位代表了一项特别的安全特性,叫作粘着位(sticky bit),后面的3位表示文件或目录对应的umask八进制值。

八进制模式的安全性设置先获取这3个rwx权限的值,然后将其转换成3位二进制值,用一个八进制值来表示。在这个二进制表示中,每个位置代表一个二进制位。因此,如果读权限是唯一置位的权限,权限值就是r--,转换成二进制值就是100,代表的八进制值是4。

 

Linux文件权限码

二进制值

八进制值

---

000

0

没有任何权限

--x

001

1

只有执行权限

-w-

010

2

只有写入权限

-wx

011

3

有写入和执行权限

r--

100

4

只有读取权限

r-x

101

5

有读取和执行权限

rw-

110

6

有读取和写入权限

rwx

111

7

有全部权限

对文件来说,全权限的值是666(所有用户都有读和写的权限)减去umask值022之后,剩下的文件权限就成了644;而对目录来说,则是777(所有用户都有读、写、执行权限)由于目录的默认权限是777,umask作用后生成的目录权限不同于生成的文件权限。umask值026会从777中减去,留下来755作为目录权限设置。

[14:35:28 root@libin3 libin]# touch libin3

[14:36:10 root@libin3 libin]# ll | grep libin3

-rw-r--r-- 1 root root      0 8月   2 14:36 libin3

 

[14:23:31 root@libin3 libin]# mkdir libin2

[14:35:17 root@libin3 libin]# ll | grep libin2

drwxr-xr-x 2 root root      6 8月   2 14:34 libin2

7.4 改变安全性设置

 

7.4.1 改变权限

 

1chmod命令用来改变文件和目录的安全性设置

 

[16:46:54 root@libin3 libin]# chmod 760 time.log

[16:47:30 root@libin3 libin]# ls -l time.log

-rwxrw---- 1 root root 358287 7月  27 22:12 time.log

[16:47:36 root@libin3 libin]#

 

2chmod命令在符号模式下指定权限的格式。

 

格式:[ugoa…][[+-=][rwxXstugo…]

 

1u代表用户

 

2g代表组

 

3o代表其他

 

4a代表上述所有

 

5X:如果对象是目录或者它已有执行权限,赋予执行权限。

 

6s:运行时重新设置UID或GID

 

7t:保留文件或目录。

 

8u:将权限设置为跟属主一样。

 

9g:将权限设置为跟属组一样。

 

10o:将权限设置为跟其他用户一样

 

[16:51:28 root@libin3 libin]# chmod o+r time.log

[16:51:46 root@libin3 libin]# ls -lF time.log

-rwxrw-r-- 1 rhce rhce 358287 7月  27 22:12 time.log*

 

-R选项:让权限的改变递归地作用到文件和子目录

 

7.4.2 改变所属关系

 

chown命令用来改变文件的属主chgrp命令用来改变文件的默认属组。

 

1可用登录名或UID来指定文件的新属主

 

[16:52:00 root@libin3 libin]# chown rhca time.log

[16:55:49 root@libin3 libin]# ls -l time.log

-rwxrw-r-- 1 rhca rhce 358287 7月  27 22:12 time.log

 

 

 

2chown命令也支持同时改变文件的属主和属组

 

[16:55:53 root@libin3 libin]# chown rhca.rhce time.log

[16:56:52 root@libin3 libin]# ls -l time.log

-rwxrw-r-- 1 rhca rhce 358287 7月  27 22:12 time.log

 

 

 

(3)改变一个文件的默认属组

 

[16:56:54 root@libin3 libin]# chown .rhca time.log

[16:58:38 root@libin3 libin]# ls -l time.log

-rwxrw-r-- 1 rhca rhca 358287 7月  27 22:12 time.log

 

注:任何属主都可以改变文件的属组,但前提是属主必须,是原属组和目标属组的成员。

 

 

 

4chgrp命令可以更改文件或目录的默认属组

 

[16:58:43 root@libin3 libin]# chgrp rhce time.log

[17:00:36 root@libin3 libin]# ls -l time.log

-rwxrw-r-- 1 rhca rhce 358287 7月  27 22:12 time.log

 

7.5 共享文件

 

Linux还为每个文件和目录存储了3个额外的信息位。

 

1)设置用户ID(SUID):当文件被用户使用时,程序会以文件属主的权限运行。

 

2)设置组ID(SGID):对文件来说,程序会以文件属组的权限运行;对目录来说,目录中

 

创建的新文件会以目录的默认属组作为默认属组。

 

3)粘着位:进程结束后文件还驻留(粘着)在内存中。

 

 

 

SGID位对文件共享非常重要。启用SGID位后,你可以强制在一个共享目录下创建的新文件都属于该目录的属组,这个组也就成为了每个用户的属组。SGID可通过chmod命令设置。它会加到标准3位八进制值之前(组成4位八进制值),或者在符号模式下用符号s。

 

 

 

 

 

 

 

chmod SUID、SGID和粘着位的八进制值

 

二进制值

八进制值

000

0

所有位都清零

001

1

粘着位置位

010

2

SGID位置位

011

3

SGID位和粘着位都置位

100

4

SUID位置位

101

5

SUID位和粘着位都置位

110

6

SUID位和SGID位都置位

111

7

所有位都置位

 

 

 

 

 

为了让这个环境能正常工作,所有组成员都需把他们的umask值设置成文件对属组成员可写。在前面的例子中,umask改成了002,所以文件对属组是可写的。

 

[17:00:38 root@libin3 libin]# mkdir libindir

[17:12:31 root@libin3 libin]# ls -l

drwxr-xr-x 2 root root      6 8月   2 17:12 libindir

[17:19:23 root@libin3 libin]# tail /etc/group | grep libin1

libin1:x:1000:libin,rhce

[17:25:04 root@libin3 libin]# chgrp libin1 libindir

[17:25:57 root@libin3 libin]# chmod g+s libindir

drwxr-sr-x 2 root libin1      6 8月   2 17:24 libindir

[17:26:11 root@libin3 libin]# chmod g+w libindir

drwxrwsr-x 2 root libin1      6 8月   2 17:24 libindir

[17:26:51 root@libin3 libin]# umask 002

[17:27:42 root@libin3 libin]# cd libindir

[17:27:50 root@libin3 libindir]# touch libinfile

[17:28:02 root@libin3 libindir]# ls -l

总用量 0

-rw-rw-r-- 1 root libin1 0 8月   2 17:28 libinfile

 

 

 

 

 

第八章 管理文件系统

 

8.1 探索 Linux 文件系统

 

Linux支持多种类型的文件系统管理文件和目录。每种文件系统都在存储设备上实现了虚拟目录结构,仅特性略有不同。

 

8.1.1 基本的 Linux 文件系统

 

1. ext文件系统

 

Linux操作系统中引入的最早的文件系统叫作扩展文件系统(extended filesystem,简记为ext)。

 

它为Linux提供了一个基本的类Unix文件系统:使用虚拟目录来操作硬件设备,在物理设备上按定长的块来存储数据。

 

 

 

ext文件系统采用名为索引节点的系统来存放虚拟目录中所存储文件的信息。索引节点系统在每个物理设备中创建一个单独的表(称为索引节点表)来存储这些文件的信息。存储在虚拟目录中的每一个文件在索引节点表中都有一个条目。ext文件系统名称中的extended部分来自其跟踪的每个文件的额外数据,包括:

 

1文件名

 

2文件大小

 

3文件的属主

 

4文件的属组

 

5文件的访问权限

 

6指向存有文件数据的每个硬盘块的指针

 

Linux通过唯一的数值(称作索引节点号)来引用索引节点表中的每个索引节点,这个值是创建文件时由文件系统分配的。文件系统通过索引节点号而不是文件全名及路径来标识文件。

 

2. ext2文件系统

 

ext2的索引节点表为文件添加了创建时间值、修改时间值和最后访问时间值来帮助系统管理员追踪文件的访问情况。ext2文件系统还将允许的最大文件大小增加到了2 TB(在ext2的后期版本中增加到了32 TB),以容纳数据库服务器中常见的大文件。

 

 

 

除了扩展索引节点表外,ext2文件系统还改变了文件在数据块中存储的方式。ext文件系统常见的问题是在文件写入到物理设备时,存储数据用的块很容易分散在整个设备中(称作碎片化

 

fragmentation)。数据块的碎片化会降低文件系统的性能,因为需要更长的时间在存储设备中查找特定文件的所有块。

 

保存文件时,ext2文件系统通过按组分配磁盘块来减轻碎片化。通过将数据块分组,文件系统在读取文件时不需要为了数据块查找整个物理设备。

 

8.1.2 日志文件系统

 

日志文件系统为Linux系统增加了一层安全性。它不再使用之前先将数据直接写入存储设备再更新索引节点表的做法,而是先将文件的更改写入到临时文件(称作日志,journal)中。在数据成功写到存储设备和索引节点表之后,再删除对应的日志条目。

 

 

 

如果系统在数据被写入存储设备之前崩溃或断电了,日志文件系统下次会读取日志文件并处理上次留下的未写入的数据。

 

 

 

文件系统日志方法

 

数据模式

索引节点和文件都会被写入日志;丢失数据风险低,但性能差

有序模式

只有索引节点数据会被写入日志,但只有数据成功写入后才删除;在性能和安全性之间取得了良好的折中

回写模式

只有索引节点数据会被写入日志,但不控制文件数据何时写入;丢失数据风险高,但仍比不用日志好

 

1. ext3文件系统

 

2001年,ext3文件系统被引入Linux内核中,直到最近都是几乎所有Linux发行版默认的文件

 

系统。它采用和ext2文件系统相同的索引节点表结构,但给每个存储设备增加了一个日志文件,

 

以将准备写入存储设备的数据先记入日志。

 

 

 

默认情况下,ext3文件系统用有序模式的日志功能——只将索引节点信息写入日志文件,直到数据块都被成功写入存储设备才删除。可以在创建文件系统时用简单的一个命令行选项ext3文件系统的日志方法改成数据模式或回写模式。

 

 

 

缺点:ext3文件系统无法恢复误删的文件,它没有任何内建的数据压缩功能(虽然有个需单独安装的补丁支持这个功能),ext3文件系统也不支持加密文件。

 

2. ext4文件系统

 

扩展ext3文件系统功能的结果是ext4文件系统,ext4文件系统在2008年受到Linux内核官方支持,现在已是大多数流行的Linux发行版采用的默认文件系统,比如Ubuntu。

 

 

 

除了支持数据压缩和加密,ext4文件系统还支持一个称作区段(extent)的特性。区段在存储设备上按块分配空间,但在索引节点表中只保存起始块的位置。由于无需列出所有用来存储文件中数据的数据块,它可以在索引节点表中节省一些空间。

 

 

 

ext4还引入了块预分配技术(block preallocation)。如果你想在存储设备上给一个你知道要变

 

大的文件预留空间,ext4文件系统可以为文件分配所有需要用到的块,而不仅仅是那些现在已经

 

用到的块。ext4文件系统用0填满预留的数据块,不会将它们分配给其他文件。

 

3. Reiser文件系统

 

2001年,Hans ReiserLinux创建了第一个称为ReiserFS的日志文件系统。ReiserFS文件系统

 

只支持回写日志模式——只把索引节点表数据写到日志文件。ReiserFS文件系统也因此成为Linux 上最快的日志文件系统之一。

 

特性:

 

1可以在线调整已有文件系统的大

 

2作尾部压缩tailpacking)的技术,该技术能将一个文件的数据填进另一个文件的数据块中的空白空间。如果你必须为已有文件系统扩容来容纳更多的数据,在线调整文件系统大小功能非常好用。

 

 

 

4. JFS文件系统

 

JFS(Journaled File System,日志化文件系统)是IBM在1990年为其Unix衍生版AIX开发的。

 

JFS文件系统采用的是有序日志方法,即只在日志中保存索引节点表数据,直到真正的文件数据被写进存储设备时才删除它。这个方法在ReiserFS的速度和数据模式日志方法的完整性之间的采取的一种折中。

 

 

 

JFS文件系统采用基于区段的文件分配,即为每个写入存储设备的文件分配一组块。这样可

 

以减少存储设备上的碎片。

 

5. XFS文件系统

 

XFS日志文件系统是另一种最初用于商业Unix系统而如今走进Linux世界的文件系统。美国硅图公司(SGI)最初在1994年为其商业化的IRIX Unix系统开发了XFS。2002年,它被发布到了适用于Linux环境的版本。

 

XFS文件系统采用回写模式的日志,在提供了高性能的同时也引入了一定的风险,因为实际

 

数据并未存进日志文件。XFS文件系统还允许在线调整文件系统的大小,这点类似于ReiserFS文件系统,除了XFS文件系统只能扩大不能缩小。

 

8.1.3 写时复制文件系统

 

采用了日志式技术,你就必须在安全性和性能之间做出选择。尽管数据模式日志提供了最高的安全性,但是会对性能带来影响,因为索引节点和数据都需要被日志化。如果是回写模式日志,性能倒是可以接受,但安全性就会受到损害。

 

 

 

就文件系统而言,日志式的另一种选择是一种叫作写时复制(copy-on-write,COW)的技术。

 

COW利用快照兼顾了安全性和性能。如果要修改数据,会使用克隆或可写快照。修改过的数据

 

并不会直接覆盖当前数据,而是被放入文件系统中的另一个位置上。即便是数据修改已经完成,

 

之前的旧数据也不会被重写。

 

1. ZFS文件系统

 

COW文件系统ZFS是由Sun公司于2005年研发的,用于OpenSolaris操作系统,从2008年起开始向Linux移植,最终在2012年投入Linux产品的使用。

 

ZFS是一个稳定的文件系统,与Resier4、Btrfs和ext4势均力敌。它最大的弱项就是没有使用GPL许可。自2013年发起的OpenZFS项目有可能改变这种局面。但是,在获得GPL许可之前,ZFS有可能终无法成为Linux默认的文件系统。

 

2. Btrf文件系统

 

Btrfs文件系统是COW的新人,也被称为B树文件系统。它是由Oracle公司于2007年开始研发

 

的。Btrfs在Reiser4的诸多特性的基础上改进了可靠性。它的稳定性、易用性以及能够动态

 

调整已挂载文件系统的大小。OpenSUSE Linux发行版最近将Btrfs作为其默认文件系统。

 

 

 

 

 

8.2 操作文件系统

 

8.2.1 创建分区

 

fdisk工具用来帮助管理安装在系统上的任何存储设备上的分区。它是个交互式程序,允许你输入命令来逐步完成硬盘分区操作。

 

 

 

启动fdisk命令,必须指定要分区的存储设备的设备名,另外还得有超级用户权限。

 

 

 

fdisk命令

 

a

设置活动分区标志

b

编辑BSD Unix系统用的磁盘标签

c

设置DOS兼容标志

d

删除分区

l

显示可用的分区类型

m

显示命令选项

n

添加一个新分区

o

创建DOS分区表

p

显示当前分区表

q

退出,不保存更改

s

为Sun Unix系统创建一个新磁盘标签

t

u

修改分区的系统ID

改变使用的存储单位

v

验证分区表

w

将分区表写入磁盘

x

高级功能

 

注:创建新磁盘分区最麻烦的事情就是找出安装在Linux系统中的物理磁盘。Linux采用了一种标准格式来为硬盘分配设备名称,对于老式的IDE驱动器,Linux使用的是/dev/hdx。其中x表示一个字母,具体是什么要根据驱动器的检测顺序(第一个驱动器是a,第二个驱动器是b,以此类推)。对于较新的SATA驱动器和SCSI驱动器,Linux使用/dev/sdx。其中的x具体是什么也要根据驱动器的检测顺序(和之前一样,第一个驱动器是a,第二个驱动器是b,以此类推)。

 

 

 

[23:09:24 root@libin3 ~]# fdisk /dev/sdb

欢迎使用 fdisk (util-linux 2.23.2)

 

更改将停留在内存中,直到您决定将更改写入磁盘。

使用写入命令前请三思。

 

 

命令(输入 m 获取帮助)p

 

磁盘 /dev/sdb8589 MB, 8589934592 字节,16777216 个扇区

Units = 扇区 of 1 * 512 = 512 bytes

扇区大小(逻辑/物理)512 字节 / 512 字节

I/O 大小(最小/最佳)512 字节 / 512 字节

磁盘标签类型:dos

磁盘标识符:0x8a0b0d9f

 

   设备 Boot      Start         End      Blocks   Id  System

/dev/sdb1            2048     8390655     4194304   8e  Linux LVM

 

命令(输入 m 获取帮助)n

Partition type:

   p   primary (1 primary, 0 extended, 3 free)

   e   extended

Select (default p): p

分区号 (2-4,默认 2)

起始 扇区 (8390656-16777215,默认为 8390656):   

将使用默认值 8390656

Last 扇区, +扇区 or +size{K,M,G} (8390656-16777215,默认为 16777215)+2G

分区 2 已设置为 Linux 类型,大小设为 2 GiB

 

命令(输入 m 获取帮助)p

 

磁盘 /dev/sdb8589 MB, 8589934592 字节,16777216 个扇区

Units = 扇区 of 1 * 512 = 512 bytes

扇区大小(逻辑/物理)512 字节 / 512 字节

I/O 大小(最小/最佳)512 字节 / 512 字节

磁盘标签类型:dos

磁盘标识符:0x8a0b0d9f

 

   设备 Boot      Start         End      Blocks   Id  System

/dev/sdb1            2048     8390655     4194304   8e  Linux LVM

/dev/sdb2         8390656    12584959     2097152   83  Linux

 

命令(输入 m 获取帮助)w

The partition table has been altered!

Calling ioctl() to re-read partition table.

正在同步磁盘。

 

 

 

输出显示这个存储设备有8589 MB8 GB)的空间,有1个分区,分区可以按主分区primary partition)或扩展分区(extended partition)创建。主分区可以被文件系统直接格式化,而扩展分区则只能容纳其他主分区扩展分区出现的原因是每个存储设备上只能有4个分区。可以通过创建多个扩展分区,然后在扩展分区内创建主分区进行扩展

 

8.2.2 创建文件系统

 

创建文件系统的命令行程序

 

mkefs

创建一个ext文件系统

mke2fs

创建一个ext2文件系统

mkfs.ext3

创建一个ext3文件系统

mkfs.ext4

创建一个ext4文件系统

mkreiserfs

创建一个ReiserFS文件系统

jfs_mkfs

创建一个JFS文件系统

mkfs.xfs

创建一个XFS文件系统

mkfs.zfs

创建一个ZFS文件系统

mkfs.btrfs

创建一个Btrfs文件系统

 

 

 

要想知道某个文件系统工具是否可用,可以使用type命令

 

[23:23:03 root@libin3 ~]# type mkfs.ext4

mkfs.ext4 /usr/sbin/mkfs.ext4

[23:26:24 root@libin3 ~]# type mkfs.brtfs

bash: type: mkfs.brtfs: 未找到

 

 

 

 

所有的文件系统命令都允许通过不带选项的简单命令来创建一个默认的文件系统。

 

[23:26:32 root@libin3 ~]# mkfs.ext4 /dev/sdb2

mke2fs 1.42.9 (28-Dec-2013)

文件系统标签=

OS type: Linux

块大小=4096 (log=2)

分块大小=4096 (log=2)

Stride=0 blocks, Stripe width=0 blocks

131072 inodes, 524288 blocks

26214 blocks (5.00%) reserved for the super user

第一个数据块=0

Maximum filesystem blocks=536870912

16 block groups

32768 blocks per group, 32768 fragments per group

8192 inodes per group

Superblock backups stored on blocks:

32768, 98304, 163840, 229376, 294912

 

Allocating group tables: 完成                            

正在写入inode: 完成                            

Creating journal (16384 blocks): 完成

Writing superblocks and filesystem accounting information: 完成

 

注:我这里采用ext4文件系统类型,这是Linux上的日志文件系统。

 

 

 

创建挂载点,进行挂载

 

mount命令的-t选项指明了要挂载的文件系统类型(ext4)。

 

[23:29:26 root@libin3 ~]# mkdir /mnt/rhce

[23:29:59 root@libin3 ~]# ls -dF /mnt/rhce

/mnt/rhce/

[23:32:19 root@libin3 ~]# mount -t ext4 /dev/sdb2 /mnt/rhce

 

 

 

8.2.3 文件系统的检查与修复

 

每个文件系统都有各自可以和文件系统交互的恢复命令。这可能会让局面变得不太舒服,随着Linux环境中可用的文件系统变多,你也不得不去掌握大量对应的命令。好在有个通用的前端程序,可以决定存储设备上的文件系统并根据要恢复的文件系统调用适合的文件系统恢复命令。

 

fsck命令能够检查和修复大部分类型的Linux文件系统:包括ext、ext2、ext3、ext4、ReiserFS、JFS和XFS。

 

格式:fsck options filesystem

 

 

 

fsck命令使用/etc/fstab文件来自动决定正常挂载到系统上的存储设备的文件系统。如果存储

 

设备尚未挂载(比如你刚刚在新的存储设备上创建了个文件系统),你需要用-t命令行选项来指定文件系统类型

 

 

 

fsck的命令行选项

 

-a

如果检测到错误,自动修复文件系统

-A

检查/etc/fstab文件中列出的所有文件系统

-C

给支持进度条功能的文件系统显示一个进度条(只有ext2和ext3)

-N

不进行检查,只显示哪些检查会执行

-r

出现错误时提示

-R

使用-A选项时跳过根文件系统

-s

检查多个文件系统时,依次进行检查

-t

指定要检查的文件系统类型

-T

启动时不显示头部信息

-V

在检查时产生详细输出

-y

检测到错误时自动修复文件系统

 

注:只能在未挂载的文件系统上运行fsck命令。对大多数文件系统来说,你只需卸载文件系统来进行检查,检查完成之后重新挂载就好了。但因为根文件系统含有所有核心的Linux命令和日志文件,所以你无法在处于运行状态的系统上卸载它。

 

[23:43:17 root@libin3 ~]# umount -t ext4 /mnt/rhce

[23:44:59 root@libin3 ~]# fsck -v /dev/sdb2

fsck,来自 util-linux 2.23.2

e2fsck 1.42.9 (28-Dec-2013)

/dev/sdb2: clean, 11/131072 files, 26156/524288 blocks

 

[23:47:30 root@libin3 ~]# fsck -vra /dev/sdb2

fsck,来自 util-linux 2.23.2

/dev/sdb2: clean, 11/131072 files, 26156/524288 blocks

/dev/sdb2: status 0, rss 1400, real 0.004928, user 0.003591, sys 0.000000

 

 

 

 

8.3 逻辑卷管理

 

Linux逻辑卷管理器(logical volume manager,LVM)可以让你在无需重建整个文件系统的情况下,轻松地管理磁盘空间。

 

8.3.1 逻辑卷管理布局

 

逻辑卷管理:

 

1)硬盘称作物理卷(physical volume,PV)

 

2)多个物理卷集中在一起可以形成一个卷组(volume group,VG)。事实上卷组可能是由分布在多个物理硬盘上的多个物理分区组成的。

 

3)整个结构中的最后一层是逻辑卷(logical volume,LV)。Linux系统将逻辑卷视为物理分区。

 

 

 

架构图如下:逻辑卷管理环境

 

8.3.2 Linux 中的 LVM

Linux LVM是由Heinz Mauelshagen开发的,于1998年发布到了Linux社区。它允许你在Linux

上用简单的命令行命令管理一个完整的逻辑卷管理环境。

Linux LVM版本:

1LVM1:最初的LVM包于1998年发布,只能用于Linux内核2.4版本。它仅提供了基本的逻辑卷管理功能。

2LVM2:LVM的更新版本,可用于Linux内核2.6版本。它在标准的LVM1功能外提供了额外的功能。

1. 快照

1LVM1只允许你创建只读快照。一旦创建了快照,就不能再写入东西了。

2)LVM2允许你创建在线逻辑卷的可读写快照。有了可读写的快照,就可以删除原先的逻辑卷,然后将快照作为替代挂载上。这个功能对快速故障转移或涉及修改数据的程序试验(如果失败,需要恢复修改过的数据)非常有用。

1. 条带化

1)可跨多个物理硬盘创建逻辑卷。当Linux LVM将文件写入逻辑卷时,文件中的数据块会被分散到多个硬盘上。每个后继数据块会被写到下一个硬盘上。

2)条带化有助于提高硬盘的性能,因为Linux可以将一个文件的多个数据块同时写入多个硬盘,而无需等待单个硬盘移动读写磁头到多个不同位置。这个改进同样适用于读取顺序访问的文件,因为LVM可同时从多个硬盘读取数据。

 

注:

LVM条带化不同于RAID条带化。LVM条带化不提供用来创建容错环境的校验信息。事实上,LVM条带化会增加文件因硬盘故障而丢失的概率。单个硬盘故障可能会造成多个逻辑卷无法访问。

 

2. 镜像

镜像是一个实时更新的逻辑卷的完整副本。当你创建镜像逻辑卷时,LVM会将原始逻辑卷同步到镜像副本中。根据原始逻辑卷大小,创建镜像需要一定的时间。

 

一旦原始同步完成,LVM会为文件系统的每次写操作执行两次写入这个过程会降低系统的写入性能:(1写入到主逻辑2写入到镜像副本

 

8.3.3 使用 Linux LVM

1、定义物理卷

1fdisk命令,过t命令改变分区类型。

[15:12:32 root@libin3 ~]# fdisk /dev/sdb

欢迎使用 fdisk (util-linux 2.23.2)

 

更改将停留在内存中,直到您决定将更改写入磁盘。

使用写入命令前请三思。

命令(输入 m 获取帮助)p

磁盘 /dev/sdb8589 MB, 8589934592 字节,16777216 个扇区

Units = 扇区 of 1 * 512 = 512 bytes

扇区大小(逻辑/物理)512 字节 / 512 字节

I/O 大小(最小/最佳)512 字节 / 512 字节

磁盘标签类型:dos

磁盘标识符:0x8a0b0d9f

 

   设备 Boot      Start         End      Blocks   Id  System

/dev/sdb1            2048     8390655     4194304   8e  Linux LVM

/dev/sdb2         8390656    12584959     2097152   83  Linux

 

命令(输入 m 获取帮助)t

分区号 (1,2,默认 2)

Hex 代码(输入 L 列出所有代码)8e

已将分区Linux”的类型更改为“Linux LVM

 

命令(输入 m 获取帮助)p

 

磁盘 /dev/sdb8589 MB, 8589934592 字节,16777216 个扇区

Units = 扇区 of 1 * 512 = 512 bytes

扇区大小(逻辑/物理)512 字节 / 512 字节

I/O 大小(最小/最佳)512 字节 / 512 字节

磁盘标签类型:dos

磁盘标识符:0x8a0b0d9f

 

   设备 Boot      Start         End      Blocks   Id  System

/dev/sdb1            2048     8390655     4194304   8e  Linux LVM

/dev/sdb2         8390656    12584959     2097152   8e  Linux LVM

 

命令(输入 m 获取帮助)w

The partition table has been altered!

Calling ioctl() to re-read partition table.

正在同步磁盘。

 

 

2)用分区来创建实际的物理卷。这可以通过pvcreate命令来完成。

[15:18:04 root@libin3 ~]# pvcreate /dev/sdb2

Wiping ext4 signature on /dev/sdb2.

Physical volume "/dev/sdb2" successfully created.

 

3pvdisplay命令来显示已创建的物理卷列表

  "/dev/sdb2" is a new physical volume of "2.00 GiB"

  --- NEW Physical volume ---

  PV Name               /dev/sdb2

  VG Name               

  PV Size               2.00 GiB

  Allocatable           NO

  PE Size               0   

  Total PE              0

  Free PE               0

  Allocated PE          0

  PV UUID               QsnHLm-j0k7-pmYM-9Dt6-8h9n-nvBP-JnfH8O

2. 创建卷组

1使用vgcreate命令,可以将所有的可用物理卷加到一个卷组,也可以结合不同的物理卷创建多个卷组。

[15:21:56 root@libin3 ~]# vgcreate vg_libin /dev/sdb2

Volume group "vg_libin" successfully created

 

(2)用vgdisplay命令,看新创建的卷组的信息

我使用/dev/sdb2分区上创建的物理卷,创建了一个名为vg_libin的卷组

  --- Volume group ---

  VG Name               vg_libin

  System ID             

  Format                lvm2

  Metadata Areas        1

  Metadata Sequence No  1

  VG Access             read/write

  VG Status             resizable

  MAX LV                0

  Cur LV                0

  Open LV               0

  Max PV                0

  Cur PV                1

  Act PV                1

  VG Size               <2.00 GiB

  PE Size               4.00 MiB

  Total PE              511

  Alloc PE / Size       0 / 0   

  Free  PE / Size       511 / <2.00 GiB

  VG UUID               THpup3-jqL5-1Y50-D6NP-vN1S-COUJ-60G6Kb

3. 创建逻辑卷

1)使用lvcreate命令

lvcreate的选项

长选项名

-c

--chunksize

指定快照逻辑卷的单位大小

-C

--contiguous

设置或重置连续分配策略

-i

--stripes

指定条带数

-I

--stripesize

指定每个条带的大小

-l

--extents

指定分配给新逻辑卷的逻辑区段数,或者要用的逻辑区段的百分比

-L

--size

指定分配给新逻辑卷的硬盘大小

 

--minor

指定设备的次设备号

-m

--mirrors

创建逻辑卷镜像

-M

--persistent

让次设备号一直有效

-n

--name

指定新逻辑卷的名称

-p

--permission

为逻辑卷设置读/写权限

-r

--readahead

设置预读扇区数

-R

--regionsize

指定将镜像分成多大的区

-s

snapshot

创建快照逻辑卷

-Z

--zero

将新逻辑卷的前1 KB数据设置为零

 

[15:24:11 root@libin3 ~]# lvcreate -l 100%FREE -n lv_libin vg_libin

 Logical volume "lv_libin" created.

-l选项:定义了要为逻辑卷指定多少可用的卷组空间。注意,你可以按照卷组空闲空间的百分比来指定这个值。我这里为新逻辑卷使用了所有的空闲空间。

-L选项以字节、千字节(KB)、兆字节(MB)或吉字节(GB)为单位来指定实际的大小。

-n选项为逻辑卷指定一个名称(在我这为lv_libin

2)用lvdisplay命令,想查看你创建的逻辑卷的详细情况

  --- Logical volume ---

  LV Path                /dev/vg_libin/lv_libin

  LV Name                lv_libin

  VG Name                vg_libin

  LV UUID                EFTpTE-06B0-77X7-OMZf-5qrt-wRd6-uYu5LA

  LV Write Access        read/write

  LV Creation host, time libin3.com, 2022-08-03 19:32:53 +0800

  LV Status              available

  # open                 0

  LV Size                <2.00 GiB

  Current LE             511

  Segments               1

  Allocation             inherit

  Read ahead sectors     auto

  - currently set to     8192

  Block device           253:2

 

3. 创建文件系统

(1) 格式化文件系统

 

[15:34:37 root@libin3 ~]# mkfs.ext4 /dev/vg_libin/lv_libin

mke2fs 1.42.9 (28-Dec-2013)

文件系统标签=

OS type: Linux

块大小=4096 (log=2)

分块大小=4096 (log=2)

Stride=0 blocks, Stripe width=0 blocks

130816 inodes, 523264 blocks

26163 blocks (5.00%) reserved for the super user

第一个数据块=0

Maximum filesystem blocks=536870912

16 block groups

32768 blocks per group, 32768 fragments per group

8176 inodes per group

Superblock backups stored on blocks:

32768, 98304, 163840, 229376, 294912

 

Allocating group tables: 完成                            

正在写入inode: 完成                            

Creating journal (8192 blocks): 完成

Writing superblocks and filesystem accounting information: 完成

2)挂载

[15:41:19 root@libin3 ~]# mount /dev/vg_libin/lv_libin /mnt/rhce

[15:41:05 root@libin3 ~]# mount | tail -n 1

/dev/mapper/vg_libin-lv_libin on /mnt/rhce type ext4 (rw,relatime,data=ordered)

[15:42:08 root@libin3 rhce]# ls -al

总用量 20

drwxr-xr-x  3 root root  4096 8月   3 15:39 .

drwxr-xr-x. 6 root root    60 8月   2 23:29 ..

drwx------  2 root root 16384 8月   3 15:39 lost+found

注:这里只是临时挂载,重启失效,永久性挂载如下

[15:43:24 root@libin3 rhce]# echo "/dev/vg_libin/lv_libin /mnt/rhce ext4 defaults 0 0">> /etc/fstab

[15:46:51 root@libin3 ~]# df -h

文件系统                       容量  已用  可用 已用% 挂载点

/dev/mapper/vg_libin-lv_libin  2.0G  6.0M  1.9G    1% /mnt/rhce

5. 修改LVM

Linux LVM命令

vgchange

激活和禁用卷组

vgremove

删除卷组

vgextend

将物理卷加到卷组中

vgreduce

从卷组中删除物理卷

lvextend

增加逻辑卷的大小

lvreduce

减小逻辑卷的大小

注:在手动增加或减小逻辑卷的大小时,要特别小心。逻辑卷中的文件系统需要手动修整来处理大小上的改变。大多数文件系统都包含了能够重新格式化文件系统的命令行程序,比如用于ext2ext3ext4文件系统的resize2fs程序。

 

 

第九章 安装软件程序

包管理系统(package management system,PMS)

9.1 包管理基础

软件包存储在服务器上,可以利用本地Linux系统上的PMS工具通过互联网访问。这些服务

器称为仓库repository)。可以用PMS工具来搜索新的软件包,或者是更新系统上已安装软件包。

PMS基础工具是dpkgrpm:

1基于Debian的发行版(如UbuntuLinux Mint)使用的是dpkg命令,这些发行版的PMS工具也是以该命令为基础的。dpkg会直接和Linux系统上的PMS交互,用来安装、管理和删除软件包。

2基于Red Hat的发行版(如FedoraopenSUSEMandriva)使用的是rpm命令,该命令是其PMS 的底层基础。类似于dpkg命令,rmp命令能够列出已安装包、安装新包和删除已有软件。

9.2 基于 Debian 的系统

dpkg命令

 

apt-get

apt-cache

aptitude

9.2.1 aptitude 管理软件包

1)如何显示一个的详情:aptitude show 包名

2)输出一个软件包安装的全部文件:dpkg -L 包名

3查找某个特定文件属于哪个软件包:dpkg --search absolute_file_name

9.2.2 aptitude 安装软件包

1能找到特定的软件包:aptitude search 包名

注:在每个包名字之前都有一个pi。如果看到一个i,说明这个包现在已经安装到了你的系统上了。如果看到一个pv,说明这个包可用,但还没安装。我们在上面的列表中可以看到系统中尚未安装wine,但是在软件仓库中可以找到这个包。

2)在系统上用aptitude从软件仓库中安装软件包:aptitude install 包名

 

9.2.3 aptitude 更新软件

1用软件仓库中的新版本妥善地更新系统上所有的软件包:aptitude safe-upgrade

 

2safe-upgrade的区别在于,它们不会检查包与包之间的依赖关系。如果不是很确定各种包的依赖关系,那还是坚持用safe-upgrade选项

1aptitude full-upgrade

2aptitude dist-upgrade

9.2.4 aptitude 卸载软件

要想只删除软件包而不删除数据和配置文件,可以使用aptituderemove选项。要删除软

件包和相关的数据和配置文件,可用purge选项

 

要看软件包是否已删除,可以再用aptitudesearch选项。如果在软件包名称的前面看到

一个c,意味着软件已删除,但配置文件尚未从系统中清除;如果前面是个p的话,说明配置文件也已删除。

9.2.5 aptitude 仓库

路径:/etc/apt/sources. list

指定仓库源:deb (or deb-src) address distribution_name package_type_list 

 

1debdeb-src的值表明了软件包的类型

2deb值说明这是一个已编译程序源,而deb-src值则说明这是一个源代码的源

3address条目是软件仓库的Web地址

4distribution_name条目是这个特定软件仓库的发行版版本的名称

5package_type_list,表明仓库里面有什么类型的包

 

9.3 基于 Red Hat 的系统

1yum:在Red Hat和FedoraCentos中使用。

2urpm:在Mandriva中使用。

3zypper:在openSUSE中使用。

9.3.1 列出已安装包

1)列出已安装的包:yum list installed

 

zypper和urpm列出已安装软件

前端工具

Mandriva

urpm

rpm -qa > installed_software

rpm -qa > installed_software

openSUSE

 

zypper

zypper search -I > installed_software

 

2)查看包是否已安装

[17:34:23 root@libin3 ~]# yum list xterm

已加载插件:fastestmirror, langpacks, product-id, search-disabled-repos, subscription-manager

Loading mirror speeds from cached hostfile

 * base: mirrors.bfsu.edu.cn

 * epel: mirror.sjtu.edu.cn

 * extras: mirrors.huaweicloud.com

 * updates: mirrors.huaweicloud.com

可安装的软件包

xterm.x86_64                                                      295-3.el7_9.1                                                      updates

 

(3)查看包的详细信息

[17:37:34 root@libin3 ~]# yum info xterm

可安装的软件包

名称    xterm

架构    x86_64

版本    295

发布    3.el7_9.1

大小    456 k

   updates/7/x86_64

简介     Terminal emulator for the X Window System

网址    http://invisible-island.net/xterm

协议     MIT

描述     The xterm program is a terminal emulator for the X Window System. It

         : provides DEC VT102 and Tektronix 4014 compatible terminals for

         : programs that can't use the window system directly.

 

 

 

 

 

 

 

 

zypperurpm查看各种包详细信息

信息类型

前端工具

包信息

urpm

urpmq -i package_name

是否安装

urpm

rpm -q package_name

包信息

zypper

zypper search -s package_name

是否安装

zypper

同样的命令,注意在Status列查找i

 

4)找出系统上的某个特定文件属于哪个软件包

[17:47:16 root@libin3 ~]# yum provides ntp

extras                                                                                                               | 2.9 kB  00:00:00     

network_ha-clustering_Stable                                                                                         | 1.3 kB  00:00:00     

updates                                                                                                              | 2.9 kB  00:00:00     

ntp-4.2.6p5-29.el7.centos.2.x86_64 : The NTP daemon and utilities

   base

9.3.2 yum 安装软件

yum安装软件包:会从仓库中安装软件包、所有它需要的库以及依赖的其他包,要root

yum install 包名

 

如何用zypperurpm安装软件

前端工具

urpm

urpmi package_name

zypper

zypper install package_name

9.3.3 yum 更新软件

1列出所有已安装包的可用更新:yum list updates

2)更新某个特定的软件包:yum update 包名

3)更新所有的软件:yum update

 

 

 

 

如何用zypperurpm更新软件

前端工具

urpm

urpmi --auto-update --update

zypper

zypper update

 

9.3.4 yum 卸载软件

(1)只删除软件包而保留配置文件和数据文件

yum remove 包名

 

2要删除软件和它所有的文件,就用erase选项

yum erase 包名

 

如何用zypperurpm卸载软件

前端工具

urpm

urpme package_name

zypper

zypper remove package_name

 

9.3.5 处理损坏的包依赖关系

1)有时在安装多个软件包时,某个包的软件依赖关系可能会被另一个包的安装覆盖掉。这叫作损坏的包依赖关系(broken dependency)

yum clean all

 

(2)显示了所有包的库依赖关系以及什么软件可以提供这些库依赖关系

 yum deplist ntp

[18:09:01 root@libin3 ~]# yum deplist ntp

软件包:ntp.x86_64 4.2.6p5-29.el7.centos.2

   依赖:/bin/bash

   provider: bash.x86_64 4.2.46-35.el7_9

   依赖:/bin/sh

   provider: bash.x86_64 4.2.46-35.el7_9

   依赖:libc.so.6(GLIBC_2.17)(64bit)

   provider: glibc.x86_64 2.17-326.el7_9

   依赖:libcap.so.2()(64bit)

   provider: libcap.x86_64 2.22-11.el7

   依赖:libcrypto.so.10()(64bit)

   provider: openssl-libs.x86_64 1:1.0.2k-25.el7_9

   依赖:libcrypto.so.10(OPENSSL_1.0.2)(64bit)

   provider: openssl-libs.x86_64 1:1.0.2k-25.el7_9

   依赖:libcrypto.so.10(libcrypto.so.10)(64bit)

   provider: openssl-libs.x86_64 1:1.0.2k-25.el7_9

   依赖:libedit.so.0()(64bit)

   provider: libedit.x86_64 3.0-12.20121213cvs.el7

   依赖:libm.so.6()(64bit)

   provider: glibc.x86_64 2.17-326.el7_9

   依赖:libm.so.6(GLIBC_2.2.5)(64bit)

   provider: glibc.x86_64 2.17-326.el7_9

   依赖:libopts.so.25()(64bit)

   provider: autogen-libopts.x86_64 5.18-5.el7

   依赖:ntpdate = 4.2.6p5-29.el7.centos.2

   provider: ntpdate.x86_64 4.2.6p5-29.el7.centos.2

   依赖:rtld(GNU_HASH)

   provider: glibc.x86_64 2.17-326.el7_9

   provider: glibc.i686 2.17-326.el7_9

   依赖:systemd-units

   provider: systemd.x86_64 219-78.el7_9.5

 

 

(3)允许你忽略依赖关系损坏的那个包,继续去更新其他软件包。这可能救不了损坏的包,但至少可以更新系统上的其他包。

yum update --skip-broken

前端工具

urpm

urpmi –clean

zypper

zypper verify

9.3.6 yum 软件仓库

 

1从哪些仓库中获取软件输入如下命令:yum repolist

2yum的仓库定义文件位置:/etc/yum.repos.d

 

zypperurpm的库

前端工具

显示仓库

urpm

urpmq --list-media

添加仓库

urpm

urpmi.addmedia path_name

显示仓库

zypper

zypper repos

添加仓库

zypper

zypper addrepo path_name

9.4 从源码安装

我这里以软件包sysstat为例:

1)下载编译包:

[17:54:07 root@libin3 ~]# wget  wget http://sebastien.godard.pagesperso-orange.fr/sysstat-12.0.1.tar.gz

[18:04:02 root@libin3 ~]# ll | grep syss

-rw-r--r--  1 root root    710076 8月   7 2018 sysstat-12.0.1.tar.gz

2)解压:

[18:05:12 root@libin3 ~]# tar -zxvf sysstat-12.0.1.tar.gz

[18:05:12 root@libin3 ~]# ll | grep syss

drwxrwxr-x  10 root root      4096 8月   6 2018 sysstat-12.0.1

-rw-r--r--   1 root root    710076 8月   7 2018 sysstat-12.0.1.tar.gz

3)进入新目录,看到README或AAAREADME文件,包含了软件安装所需要的操作

[18:05:57 root@libin3 ~]# cd sysstat-12.0.1/

4)为系统配置sysstat。它会检查你的Linux系统,确保它拥有合适的编译器能够编译源代码,另外还要具备正确的库依赖关系,在configure步骤中会显示一条错误消息说明缺失了什么东西

[18:09:46 root@libin3 sysstat-12.0.1]# ./configure

.

Check programs:

.

checking for gcc... gcc

checking whether the C compiler works... yes

checking for C compiler default output file name... a.out

checking for suffix of executables...

checking whether we are cross compiling... no

checking for suffix of object files... o

checking whether we are using the GNU C compiler... yes

checking whether gcc accepts -g... yes

checking for gcc option to accept ISO C89... none needed

checking how to run the C preprocessor... gcc -E

checking for grep that handles long lines and -e... /usr/bin/grep

checking for egrep... /usr/bin/grep -E

checking for ANSI C header files... yes

checking for sys/types.h... yes

checking for sys/stat.h... yes

checking for stdlib.h... yes

checking for string.h... yes

checking for memory.h... yes

checking for strings.h... yes

checking for inttypes.h... yes

checking for stdint.h... yes

checking for unistd.h... yes

checking minix/config.h usability... no

checking minix/config.h presence... no

checking for minix/config.h... no

checking whether it is safe to define __EXTENSIONS__... yes

checking whether ln -s works... yes

checking for chmod... chmod

checking for chown... chown

checking for ar... ar

checking for install... install

checking for msgfmt... msgfmt

checking for xgettext... xgettext

checking for msgmerge... msgmerge

checking for xz... xz

checking for cp... /usr/bin/cp

checking for chkconfig... /usr/sbin/chkconfig

checking for pkg-config... pkg-config

checking for systemctl... /usr/bin/systemctl

.

Check header files:

.

checking for ANSI C header files... (cached) yes

checking for dirent.h that defines DIR... yes

checking for library containing opendir... none required

checking ctype.h usability... yes

checking ctype.h presence... yes

checking for ctype.h... yes

checking errno.h usability... yes

checking errno.h presence... yes

checking for errno.h... yes

checking libintl.h usability... yes

checking libintl.h presence... yes

checking for libintl.h... yes

checking locale.h usability... yes

checking locale.h presence... yes

checking for locale.h... yes

checking net/if.h usability... yes

checking net/if.h presence... yes

checking for net/if.h... yes

checking regex.h usability... yes

checking regex.h presence... yes

checking for regex.h... yes

checking signal.h usability... yes

checking signal.h presence... yes

checking for signal.h... yes

checking stdio.h usability... yes

checking stdio.h presence... yes

checking for stdio.h... yes

checking sys/file.h usability... yes

checking sys/file.h presence... yes

checking for sys/file.h... yes

checking sys/ioctl.h usability... yes

checking sys/ioctl.h presence... yes

checking for sys/ioctl.h... yes

checking sys/param.h usability... yes

checking sys/param.h presence... yes

checking for sys/param.h... yes

checking for sys/stat.h... (cached) yes

checking sys/sysmacros.h usability... yes

checking sys/sysmacros.h presence... yes

checking for sys/sysmacros.h... yes

.

Check typedefs, structures and compiler characteristics:

.

checking return type of signal handlers... void

checking for size_t... yes

checking for off_t... yes

.

Check library functions:

.

checking for strchr... yes

checking for strcspn... yes

checking for strspn... yes

checking for strstr... yes

checking for sensors support... yes

checking for sensors_get_detected_chips in -lsensors... no

checking for sensors lib... no

.

Check system services:

.

checking for special C compiler options needed for large files... no

checking for _FILE_OFFSET_BITS value needed for large files... no

.

Check configuration:

.

checking run-commands directory... /etc/rc.d

checking sadc directory... /usr/local/lib64/sa

INFO: Directory /usr/local/lib64/sa will be created during installation stage.

checking system activity directory... /var/log/sa

checking sysstat configuration directory... /etc/sysconfig

checking National Language Support... yes

checking number of daily data files to keep... 7

checking number of days after which data files are compressed... 10

checking group for manual pages... man

checking whether attributes should not be set on files being installed... no

checking whether manual pages should be compressed... no

checking whether system activity directory should be cleaned... no

checking whether cron should start sar automatically... no

checking whether documentation should be installed... yes

checking whether object files should be stripped... yes

.

Now create files:

.

configure: creating ./config.status

config.status: creating sa1

config.status: creating sa2

config.status: creating cron/crontab

config.status: creating sysstat.sysconfig

config.status: creating version.h

config.status: creating sysconfig.h

config.status: creating cron/sysstat.cron.daily

config.status: creating cron/sysstat.cron.hourly

config.status: creating cron/sysstat.crond

config.status: creating cron/sysstat.crond.sample.in

config.status: creating sysstat

config.status: creating sysstat.service

config.status: creating cron/sysstat-collect.service

config.status: creating cron/sysstat-collect.timer

config.status: creating cron/sysstat-summary.service

config.status: creating cron/sysstat-summary.timer

config.status: creating man/sa1.8

config.status: creating man/sa2.8

config.status: creating man/sadc.8

config.status: creating man/sadf.1

config.status: creating man/sar.1

config.status: creating man/sysstat.5

config.status: creating man/iostat.1

config.status: creating man/cifsiostat.1

config.status: creating Makefile

 

   Sysstat version: 12.0.1

   Installation prefix: /usr/local

   rc directory: /etc/rc.d

   Init directory: /etc/rc.d/init.d

   Systemd unit dir: /usr/lib/systemd/system

   Configuration directory: /etc/sysconfig

   Man pages directory: ${datarootdir}/man

   Compiler: gcc

   Compiler flags: -g -O2

5)用make命令来构建各种二进制文件,make命令会编译源码,然后链接器会为这个包创建最终的可执行文件

[18:15:21 root@libin3 sysstat-12.0.1]# make

6)编译安装

[18:15:21 root@libin3 sysstat-12.0.1]# make install

 

第十章 使用编辑器(只介绍VIM

10.1 vim 编辑器

10.1.1 检查 vim 软件包

CentOS发行版

[19:51:53 root@libin3 ~]# alias rm

alias rm='rm -i'

[19:51:58 root@libin3 ~]# which rm

alias rm='rm -i'

/usr/bin/rm

[19:52:29 root@libin3 ~]# ls -l /usr/bin/rm

-rwxr-xr-x. 1 root root 62872 1117 2020 /usr/bin/rm

 

readlink –f命令:能够立刻找出链接文件的最后一环

10.1.2 vim 基础

vim编辑器在内存缓冲区中处理数据。只要键入vim命令(或vi,如果这个别名或链接文件

存在的话)和要编辑的文件的名字就可以启动vim编辑器。

vim编辑器有两种操作模式:

1普通模式

1h:左移一个字符。

2j:下移一行(文本中的下一行)。

3k:上移一行(文本中的上一行)。

4l:右移一个字符。

5PageDown(或Ctrl+F):下翻一屏。

6PageUp(或Ctrl+B):上翻一屏。

7G:移到缓冲区的最后一行。

8num G:移动到缓冲区中的第num行。

9q gg:移到缓冲区的第一行。

vim编辑器在普通模式下有个特别的功能叫命令行模式:在普通模式下按下冒号键,输入命令

2插入模式

1q:如果未修改缓冲区数据,退出。

2q!:取消所有对缓冲区数据的修改并退出。

3w filename:将文件保存到另一个文件中。

4wq:将缓冲区数据保存到文件中并退出。

10.1.3 编辑数据

vim编辑命令,普通模式下操作

x

删除当前光标所在位置的字符

dd

删除当前光标所在行

dw

删除当前光标所在位置的单词

dw

删除当前光标所在位置至行尾的内容

d$

删除当前光标所在位置至行尾的内容

J

删除当前光标所在行行尾的换行符(拼接行)

u

撤销前一编辑命令

a

在当前光标后追加数据

A

在当前光标所在行行尾追加数据

r char

char替换当前光标所在位置的单个字符

R text

text覆盖当前光标所在位置的数据,直到按下ESC

2x

删除从光标当前位置开始的两个字符

4dd

删除从光标当前所在行开始的4

10.1.4 复制和粘贴

1dd删除数据时,实际上会将数据保存在单独的一个寄存器中。可以用p命令取回数据。该命令会将文本插入到当前光标所在行之后。

 

2复制命令是y(代表yank)。可以在y后面使用和d命令相同的第二字符(yw表示复制一个单词,y$表示复制到行尾)在复制文本后,把光标移动到你想放置文本的地方,输入p命令。复制的文本就会出现在该位置。

 

(2)可视模式V在你移动光标时高亮显示文本。移动光标来覆盖你想要复制的文本(甚至可以向下移动几行来复制更多行的文本)。在移动光标时,vim会高亮显示复制区域的文本。在覆盖了要复制的文本后,按y键来激活复制命令。移动光标到你要放置的位置,使用p命令来粘贴。

10.1.5 查找和替换

1、查找

1)要输入一个查找字符串,就按下斜线(/)键。查找同一个单词,按下斜线键,然后按回车键。或者使用n键,表示下一个(next)。

2、替换

1)替换命令的格式是:s/old/new/ ,光标要发在所需要替换的单词行在按shift+

2):n,ms/old/new/g:替换行号n和m之间所有old。

3):%s/old/new/g:替换整个文件中的所有old

4):%s/old/new/gc:替换整个文件中的所有old,但在每次出现时提示。

 

第十一章 构建基本脚本

11.1 使用多个命令

如果要两个命令一起运行,可以把它们放在同一行中,彼此间用分号隔开。

[22:12:23 root@libin3 ~]# date ; pwd

20220806日 星期六 22:12:30 CST

/root

 

11.2 创建 shell 脚本文件

在创建shell脚本文件时,必须在文件的第一行指定要使用的shell。其格式为:

#!/bin/bash

在通常的shell脚本中,井号(#)用作注释行。shell并不会处理shell脚本中的注释行。然而,shell脚本文件的第一行是个例外,#后面的惊叹号会告诉shell用哪个shell来运行脚本

[22:17:29 root@libin3 libin]# vim shell1.sh

#!/bin/bash

date

Pwd

写法2#!/bin/bash

写法2#date ; pwd

[22:17:45 root@libin3 libin]# sh shell1.sh

20220806日 星期六 22:18:11 CST

/libin/libin

 

如果不以sh结尾,需要将脚本路径加入环境变量

[22:22:44 root@libin3 libin]# mv shell1.sh shell1

[22:23:02 root@libin3 libin]# shell1

bash: shell1: 未找到命令...

[22:23:24 root@libin3 libin]# PATH=$PATH:/libin/libin/

[22:29:21 root@libin3 libin]# echo $PATH

/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin:/libin/libin/

[22:29:38 root@libin3 libin]# shell1

bash: /libin/libin/shell1: 权限不够

[22:30:24 root@libin3 libin]# chmod u+x shell1

[22:31:02 root@libin3 libin]# ./shell1

20220806日 星期六 22:31:11 CST

/libin/libin

[22:31:11 root@libin3 libin]# ls -l shell1

-rwxr--r-- 1 root root 24 8月   6 22:18 shell1

 

11.3 显示消息

echo命令:

[22:34:05 root@libin3 libin]# echo welcome to study linux

welcome to study linux

 

1)默认情况下,不需要使用引号将要显示的文本字符串划定出来。但有时在字符串中出现引号的话就需要下面方法去解决

[22:50:41 root@libin3 libin]# echo welcome to study linux "libin"

welcome to study linux libin

2)echo命令可用单引号或双引号来划定文本字符串。如果在字符串中用到,需要在文本中使用其中一种引号,而用另外一种来将字符串划定起来。

[22:51:04 root@libin3 libin]# echo 'welcome to study linux "libin"'

welcome to study linux "libin"

[22:53:56 root@libin3 libin]# echo "This is a englishbook to see if you're paying read"

This is a englishbook to see if you're paying read

(3)可以将echo语句添加到shell脚本中任何需要显示额外信息的地方

#!/bin/bash

# This script display the date and pwd's path on

echo The time and path are

date

echo "Let's see pwd's into the system:"

Pwd

 

[23:01:22 root@libin3 libin]# ./shell2

The time and path are

20220806日 星期六 23:01:23 CST

Let's see pwd's into the system:

/libin/libin

 

(4)把文本字符串和命令输出显示在同一行,保证要显示的字符串尾部有一个空格,命令输出将会在紧接着字符串结束的地方出现。

echo -n:

#!/bin/bash

# This script display the date and pwd's path on

echo -n "The time and path are: "

date

echo "Let's see pwd's into the system:"

pwd

 

[23:06:30 root@libin3 libin]# ./shell2

The time and path are: 20220806日 星期六 23:06:32 CST

Let's see pwd's into the system:

/libin/libin

11.4 使用变量

11.4.1 环境变量

(1)set命令:显示一份完整的当前环境变量列表。

[16:53:05 root@libin3 ~]# set | head -n30

ABRT_DEBUG_LOG=/dev/null

BASH=/usr/bin/bash

BASHOPTS=checkwinsize:cmdhist:expand_aliases:extglob:extquote:force_fignore:histappend:interactive_comments:progcomp:promptvars:sourcepath

BASH_ALIASES=()

BASH_ARGC=()

BASH_ARGV=()

BASH_CMDS=()

BASH_COMPLETION_COMPAT_DIR=/etc/bash_completion.d

BASH_LINENO=()

BASH_SOURCE=()

BASH_VERSINFO=([0]="4" [1]="2" [2]="46" [3]="2" [4]="release" [5]="x86_64-redhat-linux-gnu")

BASH_VERSION='4.2.46(2)-release'

COLORTERM=truecolor

COLUMNS=140

DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-i4JEXLzsNm,guid=70b448f53524e33e258f6d1162ef7cf5

DESKTOP_SESSION=gnome-classic

DIRSTACK=()

DISPLAY=:0

EUID=0

GDMSESSION=gnome-classic

GDM_LANG=zh_CN.UTF-8

[...]

 

(2)美元符($)的使用:可以在环境变量名称之前加上美元符($)来使用这些环境变量

[17:00:30 root@libin3 libin]# set | grep "USER"

USER=root

[17:01:46 root@libin3 libin]# set | grep "HOME"

HOME=/root

 

[17:02:19 root@libin3 libin]# vim shell3

#!/bin/bash

#see user information from the system.

#echo "user info the userid: '$USER'"  可以给USER加引号

echo "user info the userid: $USER"

echo UID: $UID

echo HOME: $HOME

 

[17:02:28 root@libin3 libin]# chmod u+x shell3

 

[17:02:38 root@libin3 libin]# ./shell3

user info the userid: root

UID: 0

HOME: /root

注:使用的变量为当前登录用户的变量

[17:06:22 root@libin3 libin]# chown libin. shell3

[17:06:22 root@libin3 libin]# ll | grep shell3

-rwxr--r-- 1 libin libin1 118 8月   7 17:01 shell3

[17:06:27 root@libin3 libin]# su - libin

[libin@libin3 ~]$ set | grep "USER"

USER=libin

[libin@libin3 ~]$ PATH=$PATH:/libin/libin/

[libin@libin3 ~]$ cd /libin/libin

[libin@libin3 libin]$ ./shell3

user info the userid: libin

UID: 1000

HOME: /home/libin

 

3)只要脚本在引号中出现美元符,它就会以为你在引用一个变量,这样会出现问题,脚本会尝试显示变量$1(但并未定义),再显示数字5。要显示美元符,你必须在它前面放置一个反斜线。反斜线允许shell脚本将美元符解读为实际的美元符,而不是变量。

[17:22:14 root@libin3 libin]# vim shell4

#!/bin/bash

#see user information from the system.

echo "the cost of the item is $15"

 

[17:17:02 root@libin3 libin]# ./shell4

the cost of the item is 5

 

[17:22:14 root@libin3 libin]# vim shell4

#!/bin/bash

#see user information from the system.

echo "the cost of the item is \$15"

 

[17:22:10 root@libin3 libin]# ./shell4

the cost of the item is $15

11.4.2 用户变量

除了环境变量,shell脚本允许在脚本中定义和使用自己的变量。用户变量可以是任何由字母、数字或下划线组成的文本字符串,长度不超过20个。用户变量区分大小写,例如Libinlibin是不一样的。使用等号将值赋给用户变量。在变量、等号和值之间不能出现空格

 

[17:27:55 root@libin3 libin]# libin1=10

[17:28:07 root@libin3 libin]# libin2=20

[17:28:26 root@libin3 libin]# libin3=rhcsa

[17:28:34 root@libin3 libin]# libin4="libin is student"

 

 

1shell脚本会自动决定变量值的数据类型。在脚本的整个生命周期里,shell脚本中定义的变量会一直保持着它们的值,但在shell脚本结束时会被删除掉。

[17:35:39 root@libin3 libin]# vim shell5

#!/bin/bash

#libin variables

days=5

guest="rhca"

echo "$guest check in $days days ago"

days=3

guest="rhce"

echo "$guest check in $days days ago"

[17:33:39 root@libin3 libin]# chmod u+x shell5

[17:35:32 root@libin3 libin]# ./shell5

rhca check in 5 days ago

rhce check in 3 days ago

 

2)变量每次被引用时,都会输出当前赋给它的值,引用一个变量值时需要使用美元符,而引用变量来对其进行赋值时则不要使用美元符。

#!/bin/bash

# the is a another variable libin value

libin1=10

libin2=$libin1

echo the libin value is $libin2

 

[17:42:46 root@libin3 libin]# chmod u+x shell6

[17:42:54 root@libin3 libin]# ./shell6

the libin value is 10

 

在赋值语句中使用libin1变量的值时,仍然必须用美元符,如果没有给libin2$美元符,shell会将变量名解释成普通的文本字符串,这样是没有意义的。

11.4.3 命令替换

shell脚本中最有用的特性之一就是可以从命令输出中提取信息,并将其赋给变量。把输出赋给变量之后,就可以随意在脚本中使用了。

1、两种将命令输出给变量:

1反引号字符(`)

用一对反引号给命令

[17:43:36 root@libin3 libin]# libin='date'

2$()格式

[17:54:19 root@libin3 libin]# libin=$(date)

3shell会运行命令替换符号中的命令,并将其输出赋给变量libin,赋值等号和命令替换字符之间没有空格。对于命令的得使用反引号或者$()格式才有效果

[18:01:07 root@libin3 libin]# vim shell7

#!/bin/bash

libin=$(date)

echo "the date and tim is: " $libin

[18:01:56 root@libin3 libin]# chmod u+x shell7

[18:02:08 root@libin3 libin]# ./shell7

the date and tim is:  20220807日 星期日 18:02:11 CST

 

不用变量的写法:

[18:13:15 root@libin3 libin]# vim shell7

#!/bin/bash

echo -n "the date and tim is: "

date

 

[18:01:56 root@libin3 libin]# chmod u+x shell7

[18:14:26 root@libin3 libin]# ./shell7

the date and tim is: 20220807日 星期日 18:14:27 CST

 

(4)通过命令替换获得当前日期并用它来生成唯一文件名,这是做备份的一种好方法,libin变量是被赋予格式化后的date命令的输出。这是提取日期信息来生成日志文件名常用的一种技术。+%y%m%d格式告诉date命令将日期显示为两位数的年月日的组合。

[18:20:55 root@libin3 libin]# vim shell8

#!/bin/bash

#copy  /usr/bin dir list to a log file

libin=$(date +%y%m%d)

ls -al /usr/bin > log.$libin

 

[18:21:33 root@libin3 libin]# chmod u+x shell8

[18:22:02 root@libin3 libin]# ./shell8

[18:23:28 root@libin3 libin]# ll |grep  log

-rw-r--r-- 1 root root 122683 8月   7 18:22 log.220807

注:在命令行提示符下使用路径./运行命令的话,也会创建出子shell;要是运行命令的时候不加入路径,就不会创建子shell。如果你使用的是内建的shell命令,并不会涉及子shell。

11.5 重定向输入和输出

 

重定向可以用于输入,也可以用于输出,可以将文件重定向到命令输入。

 

11.5.1 输出重定向 >>>

 

1最基本的重定向将命令的输出发送到一个文件中。显示器上出现的命令输出会被保存到指定的输出文件中。重定向操作符创建了一个文件redhat.txt6(通过默认的umask设置),并将date命令的输出重定向到该文件中。如果文件存在,则会覆盖旧的文件。

 

 

 

[23:58:30 root@libin3 libin]# date > redhat.txt

[23:58:48 root@libin3 libin]# cat redhat.txt

20220808日 星期一 23:58:48 CST

 

[00:01:44 root@libin3 libin]# who > redhat.txt

[00:01:50 root@libin3 libin]# cat redhat.txt

root     :0           2022-08-08 23:54 (:0)

root     pts/0        2022-08-08 23:55 (:0)

 

2、若不想覆盖文件原内容,想要将命令的输出追加到已有文件中,可以用双大于号(>>)来追加数据。

 

[00:05:00 root@libin3 libin]# date >> redhat.txt

[00:05:06 root@libin3 libin]# cat redhat.txt

root     :0           2022-08-08 23:54 (:0)

root     pts/0        2022-08-08 23:55 (:0)

20220809日 星期二 00:05:06 CST

 

11.5.2 输入重定向<

1、输入重定向和输出重定向正好相反。输入重定向将文件的内容重定向到命令,而非将命令的输出重定向到文件。小于号说明数据正在从输入文件流向命令

 

[00:05:12 root@libin3 libin]# who < redhat.txt

root     :0           2022-08-08 23:54 (:0)

root     pts/0        2022-08-08 23:55 (:0)

 

2wc命令可以对对数据中的文本进行计数。

1)文本的行数 

2)文本的词数

3)文本的字节数 

11.5.3内联输入重定向(<<

 

1)这种方法无需使用文件进行重定向,只需要在命令行中指定用于输入重定向的数据就可以。必须指定一个文本标记来划分输入数据的开始和结尾。任何字符串都可作为文本标记,但在数据的开始和结尾文本标记必须一致。

 

[00:10:04 root@libin3 libin]# date << rhce

> date

> rhce

20220809日 星期二 00:16:57 CST

 

2)在命令行上使用内联输入重定向时,shell会用PS2环境变量中定义的次提示符来提示输入数据。

 

[00:18:41 root@libin3 libin]# wc <<  EOF

> libin rhcsa 1

> libin rhce 1

> libin rhca 1

> EOF

 3  9 40

 

[00:21:51 root@libin3 libin]# cat redhat.txt

root     :0           2022-08-08 23:54 (:0)

root     pts/0        2022-08-08 23:55 (:0)

20220809日 星期二 00:05:06 CST

 

[00:22:05 root@libin3 libin]# cat >> redhat.txt << libin

> libin rhcsa 1

> libin rhce 1

> libin rhca 1

> libin

 

[00:23:11 root@libin3 libin]# cat redhat.txt

root     :0           2022-08-08 23:54 (:0)

root     pts/0        2022-08-08 23:55 (:0)

20220809日 星期二 00:05:06 CST

libin rhcsa 1

libin rhce 1

libin rhca 1

 

11.6 管道|

有时需要将一个命令的输出作为另一个命令的输入

 

[00:30:42 root@libin3 libin]# rpm -qa | sort | grep httpd

httpd-2.4.6-97.el7.centos.2.x86_64

httpd-tools-2.4.6-97.el7.centos.2.x86_64

[00:32:00 root@libin3 libin]# rpm -qa | sort > redhat.txt

 

11.7 执行数学运算

另一个对任何编程语言都很重要的特性是操作数字的能力。在shell脚本中有两种途径来进行数学运算。

expr命令操作符

 

ARG1 | ARG2

如果ARG1既不是null也不是零值,返回ARG1;否则返回ARG2

ARG1 & ARG2

如果没有参数是null或零值,返回ARG1;否则返回0

ARG1 < ARG2

如果ARG1小于ARG2,返回1;否则返回0

ARG1 <= ARG2

如果ARG1小于或等于ARG2,返回1;否则返回0

ARG1 = ARG2

如果ARG1等于ARG2,返回1;否则返回0

ARG1 != ARG2

如果ARG1不等于ARG2,返回1;否则返回0

ARG1 >= ARG2

如果ARG1大于或等于ARG2,返回1;否则返回0

ARG1 > ARG2

如果ARG1大于ARG2,返回1;否则返回0

ARG1 + ARG2

返回ARG1和ARG2的算术运算和

ARG1 - ARG2

返回ARG1和ARG2的算术运算差

ARG1 * ARG2

返回ARG1和ARG2的算术乘积

ARG1 / ARG2

返回ARG1被ARG2除的算术商

ARG1 % ARG2

返回ARG1被ARG2除的算术余数

STRING : REGEXP

如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配

match STRING REGEXP

如果REGEXP匹配到了STRING中的某个模式,返回该模式匹配

substr STRING POS LENGTH

返回起始位置为POS(从1开始计数)、长度为LENGTH个字符的子字符串

index STRING CHARS

返回在STRING中找到CHARS字符串的位置;否则,返回0

length STRING

返回字符串STRING的数值长度

+ TOKEN

将TOKEN解释成字符串,即使是个关键字

(EXPRESSION)

返回EXPRESSION的值

 

11.7.1 expr 命令

1expr命令允许在命令行上处理数学表达式,但是特别笨拙。能识别少量数学与字符串操作

 

[00:40:23 root@libin3 libin]# expr 2 * 5

expr: 语法错误

 

[00:40:26 root@libin3 libin]# expr 2 \* 5

10

 

2exprshell脚本中的使用

[00:48:07 root@libin3 libin]# vim shell9

#!/bin/bash

# a example of user the expr command

libin1=10

libin2=20

libin3=$(expr $libin2 / $libin1)

echo the result is $libin3

 

[00:46:58 root@libin3 libin]# chmod u+x shell9

[00:48:05 root@libin3 libin]# ./shell9

the result is 2

 

11.7.2 使用方括号

在bash中,在将一个数学运算结果赋给某个变量时,可以用美元符和方括号$[ operation ]将数学表达式围起来

 

[21:40:45 root@libin3 ~]# libin=$[1+2]

[21:49:17 root@libin3 ~]# echo $libin

3

[21:49:23 root@libin3 ~]# libin2=$[$libin * 2]

[21:50:16 root@libin3 ~]# echo $libin2

6

[21:50:21 root@libin3 ~]# rhce=$[1 * 2]

[21:53:06 root@libin3 ~]# echo $rhce

2

[21:54:07 root@libin3 ~]# rhca=$[$rhce + 2]

[21:54:15 root@libin3 ~]# echo $rhca

4

 

运用于shell脚本

 

[21:57:13 root@libin3 libin]# vim shell10

#!/bin/bash

libin=10

libin2=20

libin3=30

libin4=$[$libin * $libin2]

echo rhe result is $libin

 

[22:00:40 root@libin3 libin]# chmod u+x shell10

[22:00:53 root@libin3 libin]# ./shell10

rhe result is 200

 

在使用方括号来计算公式时,不用担心shell会误解乘号或其他符号,但在算术运算时会有1个主要限制。下面这种情况运行是有问题的bash shell数学运算符只支持整数运算。

 

[22:04:53 root@libin3 libin]# vim shell11

#!/bin/bash

libin=10

libin2=20

libin3=30

libin4=$[$libin / $libin2]

echo rhe result is $libin4

 

[22:05:13 root@libin3 libin]# chmod u+x shell11

[22:05:26 root@libin3 libin]# ./shell11

rhe result is 0

 

11.7.3 浮点解决方案

1. bc的基本用法

bash计算器能够识别:

(1)数字(整数和浮点数)

(2)变量(简单变量和数组 

(3)注释(以#C语言中的/* */开始的行)

(4)表达式

(5)编程语句(例如if-then语句) 

(6)函数

 

 

 

[22:06:21 root@libin3 libin]# bc

bc 1.06.95

Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.

This is free software with ABSOLUTELY NO WARRANTY.

For details type `warranty'.

12 * 3.3

39.6

1.22 * (1+1)

2.44

quit

 

浮点运算是由内建变量scale控制的。必须将这个值设置为你希望在计算结果中保留的小数位数,否则无法得到期望的结果。scale变量的默认值是0。在scale值被设置前,bash计算器的计算结果不包含小数位。在将其值设置成2后,bash计算器显示的结果包含2位小数。-q命令行选项可以不显示bash计算器冗长的欢迎信息。

 

[22:06:21 root@libin3 libin]# bc

bc 1.06.95

Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006 Free Software Foundation, Inc.

This is free software with ABSOLUTELY NO WARRANTY.

For details type `warranty'.

12 * 3.3

39.6

1.22 * (1+1)

2.44

quit

[22:19:25 root@libin3 libin]# bc -q

3.44 / 5

0

scale=2

3.44 / 5

.68

quit

 

bash计算器还能支持变量,但是maos

 

[22:21:13 root@libin3 libin]# bc -q

libin1=1

libin1 * 2

2

libin2 = libin1 * 3

print libin2

3

libin3 = libin1 / 2

print libin3

0

quit

 

2. 在脚本中使用bc

 

1)基本格式如下:可以用命令替换运行bc命令,并将输出赋给一个变量。

 

 

 

variable=$(echo "options; expression" | 命令)

 

注:第一部分options设置变量,多个变量可以用分号隔开expression参数定义了通过bc执行的数学表达式。

 

[22:36:17 root@libin3 libin]# vim shell12

#!/bin/bash

libin1=$(echo "scale=3; 2.2 / 2" | bc)

echo the resule $libin1

 

[22:34:46 root@libin3 libin]# chmod u+x shell12

[22:34:43 root@libin3 libin]# ./shell12

the resule 1.100

 

 

 

2)不只能用数字作为表达式值,也可以用shell脚本中定义好的变量。

 

[22:44:05 root@libin3 libin]# vim shell13

#!/bin/bash

libin1=10

libin2=5

libin3=$(echo "scale=3; $libin1 / $libin2" | bc)

echo the result this is $libin3

 

[22:44:54 root@libin3 libin]# chmod u+x shell13

[22:44:03 root@libin3 libin]# ./shell13

the result this is 2.000

 

 

 

3)也可以将被赋值的变量用于其他运算

 

[22:57:08 root@libin3 libin]# vim shell14

#!/bin/bash

libin1=10

libin2=5

libin3=$(echo "scale=2; $libin1 / $libin2" | bc)

libin4=$(echo "scale=2; $libin3 * $libin1" | bc)

echo result this is $libin4

 

[22:57:24 root@libin3 libin]# chmod u+x shell14

[22:57:57 root@libin3 libin]# ./shell14

result this is 20.00

 

 

 

(2)在大量的算术运算时,最好还是使用内联输入重定向它允许你直接在命令行中重定向数据,并且可以将输出赋予一个变量

 

[22:58:11 root@libin3 libin]# libin=$(bc << EOF

> libin1

> libin2

> libin3

> EOF

> )

 

(3)将内联输入重定向运用于bash shell。赋值符号要空格。

 

[23:30:46 root@libin3 libin]# vim shell15

#!/bin/bash

libin1=1.1

libin2=2.2

libin3=3.3

libin4=5

 

libin5=$(bc << EOF

scale = 2

rhcsa = ($libin1 + $libin2)

rhce = ($libin3 + $libin4)

rhcsa + rhce

EOF

)

echo the finish result this $libin5

 

[23:32:45 root@libin3 libin]# chmod u+x shell15

[23:32:56 root@libin3 libin]# ./shell15

the finish result this 11.6

 

11.8 退出脚本

11.8.1 查看退出状态码

shell中运行的每个命令都使用退出状态码(exit status)告诉shell它已经运行完毕。退出状态码是一个0~255的整数值,在命令结束运行时由命令传给shell。可以捕获这个值并在脚本中使用。

[22:45:42 root@libin3 libin]# date

20220810日 星期三 22:46:21 CST

[22:46:21 root@libin3 libin]# echo $?

0

注:成功结束命令退出状态是0,如果退出状态是正数值,则说明命令结束时有错误。下面这种退出状码127为一个无效命令。

[22:46:36 root@libin3 libin]# libin

bash: libin: 未找到命令...

[22:48:15 root@libin3 libin]# echo $?

127

 

Linux退出状态码

0

命令成功结束

1

一般性未知错误

2

不适合的shell命令

126

命令不可执行

127

没找到命令

128

无效的退出参数

128+x

Linux信号x相关的严重错误

130

通过Ctrl+C终止的命令

255

正常范围之外的退出状态码

 

退出码为126表示没有执行命令的正确的权限

[22:48:22 root@libin3 libin]# vim libin.c

#!/bin/bash

echo 1

[22:54:19 root@libin3 libin]# ./libin.c

bash: ./libin.c: 权限不够

[22:54:28 root@libin3 libin]# echo $?

126

 

提供了无效参数的常见错误

[22:56:05 root@libin3 libin]# date %t

date: 无效的日期"%t"

[22:56:09 root@libin3 libin]# echo  $?

1

11.8.2 exit 命令

1Shell脚本会以脚本中最后一个命令的退出状态码退出

[22:56:13 root@libin3 libin]# ./shell15

the finish result this 11.6

[22:58:30 root@libin3 libin]# echo $?

0

 

(2)exit命令允许在脚本结束时指定一个退出状态码,查看自己的状态码时,exit命令是允许你在脚本结束时指定一个状态码的。

23:02:54 root@libin3 libin]# vim shell16

#!/bin/bash

# this is result status

libin1=10

libin2=20

libin3=$[$libin1 + $libin2]

echo this result is $libin3

exit 3

 

[23:04:56 root@libin3 libin]# chmod u+x shell16

 

[23:05:09 root@libin3 libin]# ./shell16

this result is 30

 

[23:05:54 root@libin3 libin]# echo $?

3

 

(3)可以在exit命令参数中使用变量,当运行改命令时,会产生退出状态,但是要敲命令 echo $?。

[23:10:55 root@libin3 libin]# vim shell17

#!/bin/bash

# this is ecit status

rhcsa=1

rhce=2

rhca=$[$rhcsa + $rhce]

exit $rhca

 

[23:11:22 root@libin3 libin]# chmod u+x shell17

 

[23:17:43 root@libin3 libin]# ./shell17

[23:17:46 root@libin3 libin]# echo $?

3

 

4)退出状态码被缩短到了0~255之间,shell通过模运算得到这个结果。一个值的模就是被除后的余数。最终的结果是指定的数值除以256后得到的余数。

[23:27:45 root@libin3 libin]# vim shell18

#!/bin/bash

# this is exit status 0~255

libin1=20

libin2=15

libin3=$[$libin1 * $libin2]

echo this is result $libin3

exit $libin3

 

[23:27:56 root@libin3 libin]# ./shell19

this is result 300

 

[23:27:57 root@libin3 libin]# echo $?

44

 

2

[23:42:30 root@libin3 libin]# vim shell18

#!/bin/bash

# this is exit status 0~255

libin1=250

libin2=6

libin3=$[$libin1 + $libin2]

echo this is result $libin3

exit $libin3

 

[23:43:10 root@libin3 libin]# ./shell18

this is result 256

[23:43:11 root@libin3 libin]# echo $?

0

 

 

第十二章 使用结构化命令

许多程序要求对shell脚本中的命令施加一些逻辑流程控制。有一类命令会根据条件使脚本跳过某些命令。这样的命令通常称为结构化命令(structured command)。

 

12.1 使用 if-then 语句

1、第一种if-then 语句

bash shell的if语句会运行if后面的那个命令。如果该命令的退出状态码是0 (该命令成功运行),位于then部分的命令就会被执行。如果该命令的退出状态码是其他值,then部分的命令就不会被执行,bash shell会继续执行脚本中的下一个命令。fi语句用来表示if-then语句到此结束。

 

if-then 语句格式:

 

if 命令

then

命令

fi

 

 

 

1:

 

[22:21:24 root@libin3 libin]# vim shell20

#!/bin/bash

# echo this is if status

if date

then

echo "I'm is libin"

fi

 

[22:22:59 root@libin3 libin]# chmod u+x shell20

 

[22:23:06 root@libin3 libin]# ./shell20

20220811日 星期四 22:23:11 CST

I'm is libin

 

2:

 

[22:26:34 root@libin3 libin]# vim shell20

#!/bin/bash

# echo this is if status

if ping -c 1 www.baidu.com

then

echo ture

fi

 

[22:26:44 root@libin3 libin]# ./shell20

PING www.a.shifen.com (183.232.231.174) 56(84) bytes of data.

64 bytes from 183.232.231.174: icmp_seq=1 ttl=128 time=30.2 ms

--- www.a.shifen.com ping statistics ---

1 packets transmitted, 1 received, 0% packet loss, time 0ms

rtt min/avg/max/mdev = 30.217/30.217/30.217/0.000 ms

ture

 

 

 

3:

 

如果 if语句后放入一个不能工作的命令,则状态码为非0,且bash shell 会跳过then后面的语句。

 

[22:43:53 root@libin3 libin]# vim shell21

/bin/bash

#this is result error

if libin

then

echo "this is error"

fi

if date

then

echo "this is success"

fi

echo "the are rhce"

 

[22:43:42 root@libin3 libin]# ./shell21

./shell21:3: libin: 未找到命令

this is rhcsa

20220811日 星期四 22:43:44 CST

this is success

the are rhce

 

1、第二种if-then 语句

 

在then部分,你可以使用不止一条命令。可以像在脚本中的其他地方一样在这里列出多条命令。bash shell会将这些命令当成一个块,如果if语句行的命令的退出状态值为0,所有的命令都会被执行;如果if语句行的命令的退出状态不为0,所有的命令都会被跳过。

 

格式:

 

if 命令;then

命令

fi

 

1:如果rhce=idid为一个不存在的用户,则不会输出任何东西

 

[22:55:06 root@libin3 libin]# vim shell22

#!/bin/bash

# the multiple command in the then-if

rhce=student

#

if grep $rhce /etc/passwd;then

echo "this is rhcsa"

echo "this is rhce"

echo "this is put in command echo:"

ls -a  /home/$rhce/.b*

fi

 

[22:56:19 root@libin3 libin]# chmod u+x shell22

[23:00:12 root@libin3 libin]# ./shell22

student:x:1004:1004::/home/student:/bin/bash

this is rhcsa

this is rhce

this is put in command echo:

/home/student/.bash_history  /home/student/.bash_logout  /home/student/.bash_profile  /home/student/.bashrc

 

写法2

#!/bin/bash

# the multiple command in the then-if

rhce=student

#

if grep $rhce /etc/passwd;then echo "this is rhcsa";echo "this is rhce";echo "this is put in command echo:";ls -a  /home/$rhce/.b*

fi

 

12.2 if-then-else 语句

 

在if-then语句中,不管命令是否成功执行,你都只有一种选择。如果命令返回一个非零退出状态码,bash shell会继续执行脚本中的下一条命令。if-then-else语句对比于if-then语句的区别就是:if后的命令错误,也会输出else后面的命令“。如果if后接错的交互式命令如data,最后会输出未找到命令。

 

格式:

 

if command

then

commands

else

commands

fi

 

1:

 

[23:12:52 root@libin3 libin]# vim shell23

#!/bin/bash

# the multiple command in the then-if

rhce=nostudent

#

if grep $rhce /etc/passwd

then

echo "this is rhcsa"

echo "this is rhce"

echo "this is put in command echo:"

ls -a  /home/$rhce/.b* ; pwd

else

echo "the user $rhce does not exist on th /etc/passwd."

fi

 

[23:17:27 root@libin3 libin]#  chmod u+x shell23

[23:20:08 root@libin3 libin]# ./shell23

the user nostudent does not exist on th /etc/passwd.

 

 

2:if-then与if-then-else结合写法

[00:02:45 root@libin3 libin]# vim shell23-1

#!/bin/bash

#the are if-then-else ststus

#

libin=data

if $libin

then

echo "this is time"

else

echo "the in /etc/passwd users"

fi

 

if date

then

echo "this is not's exist data"

fi

 

[00:03:32 root@libin3 libin]# chmod u+x shell23-1

[00:02:27 root@libin3 libin]# ./shell23-1

./shell23-1:5: data: 未找到命令

the in /etc/passwd users

20220812日 星期五 00:02:28 CST

this is not's exist data

 

12.3 嵌套 if

1、第一种嵌套if语句

需要检查脚本代码中的多种条件。对此,可以使用嵌套的if-then语句。

1:检查查/etc/passwd文件中是否存在某个用户名以及该用户的目录是否存在,可以使用嵌套的if-then语句。嵌套的if-then语句位于主if-then-else语句的else代码块中。

写法1:我们发现else 后面只输出了if-then 并没有输出嵌套的if-then语句 内容

[00:06:45 root@libin3 libin]# vim shell24

#!/bin/bash

# Test is  if-then-else-fi V2

#

libin=rhce

#

if grep $libin /etc/passwd

then echo "the user $libin exist in system."

else echo "the user $libin does exist in system."

if ls -d /home/$libin/

then echo "howerver,$libin has a dir."

fi

fi

 

[00:07:29 root@libin3 libin]# chmod u+x shell24

 

[00:18:16 root@libin3 libin]# ./shell24

rhce:x:1006:1006:i am is libin,technology,10086,10086:/home/rhce:/bin/csh

the user rhce exist in system.

 

注:我们发现在centos发行版系统里,如果最后两个fi,嵌套if好像没有生效

写法2:我们改变一下第一个fi的位置就可以输出嵌套的if-then语句内容,但是if-then-else if-then 两种语句的结合。

[00:19:23 root@libin3 libin]# vim shell24

#!/bin/bash

# Test is  if-then-else-fi V2

#

libin=rhce

#

if grep $libin /etc/passwd

then echo "the user $libin exist in system."

else echo "the user $libin does exist in system."

fi

if ls -d /home/$libin/

then echo "howerver,$libin has a dir."

fi

 

[00:24:37 root@libin3 libin]# ./shell24

rhce:x:1006:1006:i am is libin,technology,10086,10086:/home/rhce:/bin/csh

the user rhce exist in system.

/home/rhce/

howerver,rhce has a dir.

 

写法3:我们将写法1的变量设置为一个不存在的变量再看变化

[00:28:59 root@libin3 libin]# vim shell24

#!/bin/bash

# Test is  if-then-else-fi V2

#

libin=rhce-1

#

if grep $libin /etc/passwd

then echo "the user $libin exist in system."

else echo "the user $libin does exist in system."

if ls -d /home/$libin/

then echo "howerver,$libin has a dir."

fi

fi

 

[00:28:58 root@libin3 libin]# ./shell24

the user rhce-1 does exist in system.

ls: 无法访问/home/rhce-1/: 没有那个文件或目录

 

写法4:我们将写法2的变量设置为一个不存在的变量再看变化

[00:29:25 root@libin3 libin]# vim shell24

#!/bin/bash

# Test is if-then-else-fi V2

#

libin=rhce-1

#

if grep $libin /etc/passwd

then echo "the user $libin exist in system."

else echo "the user $libin does exist in system."

fi

if ls -d /home/$libin/

then echo "howerver,$libin has a dir."

fi

 

[00:33:18 root@libin3 libin]# ./shell24

the user rhce-1 does exist in system.

ls: 无法访问/home/rhce-1/: 没有那个文件或目录

注:从上面4种写法我们发现很难理清逻辑流程。所以不建议以上这种写法。其实综上表述可能有误,请看下面,这是一个探索的过程。

2、第二种嵌套if语句elif

格式:

If 命令

Then 命令

elif 命令

then more 命令

fi

多种elif语句的if-then-elif嵌套结合

if command1

then

command set 1

elif command2

then

command set 2

elif command3

then

command set 3

elif command4

then

command set 4

fi

 

elif语句行提供了另一个要测试的命令,如果elif后命令的退出状态码是0,则bash会执行第二个then语句部分的命令。

写法1:其实我们发现这种写法还是出现了上述的情况,但是上面是对if语句理解过于肤浅。

[01:08:08 root@libin3 libin]# vim shell25

#!/bin/bash

# this is qian tao ifs- elif

#

libin=student

#

if grep $libin /etc/passwd

then echo "the user $libin exist in the system."

#

elif ls -d /home/$libin

then

echo "the user $libin dones not exist in the system."

echo "the user $libin exist in the system."

#

fi

 

[01:09:03 root@libin3 libin]# ./shell25

student:x:1004:1004::/home/student:/bin/bash

the user student exist in the system.

 

写法2:我们创建一个用户,将该用户在/etc/passwd里面的信息删除,当看到这里的时候我们似乎看出了点特点,这就是嵌套if语句的特点:之前学到如果if语句行的命令的退出状态不为0,所有的命令都会被跳过,但是会输出else和elif后面的语句,这就相当于是否则判断语句;但if语句行如果为0时,则后面的语句也不会再执行(如第一种在else后面的语句及嵌套if和elif语句)这样我们理解就对了。

 

[01:13:09 root@libin3 libin]# useradd helloword

[01:13:09 root@libin3 libin]# grep helloword /etc/passwd

[01:15:31 root@libin3 libin]# vim shell25

#!/bin/bash

# this is qian tao ifs- elif

#

libin=helloword

#

if grep $libin /etc/passwd

then echo "the user $libin exist in the system /etc/passwd."

#

elif ls -d /home/$libin

then

echo "the user $libin dones not exist in the system."

echo "the user $libin exist in the system."

#

fi

 

[01:15:56 root@libin3 libin]# chmod u+x shell25

[01:16:18 root@libin3 libin]# ./shell25

/home/helloword

the user helloword dones not exist in the system.

the user helloword exist in the system.

 

写法3:通过嵌套else语句,进一步让脚本检查拥有目录或者没有拥有目录的不存在用户。

我们为了实验将/home/helloword目录进行删除:我们发现有个局限性,就是嵌套if语句无法同时检测2个退出值都为0的条件。但是可以检测2个退出值都非0的条件;或者检测第一个退出值为非0,第2个退出值可以随意的结果。

1)删除/etc/passwd ,没有删除/home/helloword目录前:

[01:33:50 root@libin3 libin]# vim shell26

#!/bin/bash

#this is ifs- elif and else

libin=helloword

if grep $libin /etc/passwd

then echo "this user $libin is exist in the system"

elif ls -d /home/$libin

then

echo "this user $libin does not exist in the system"

echo "because,$libin has a dir"

else

echo "this user $libin does not exist in the system"

echo "because,$libin not has a dir"

fi

 

[01:52:03 root@libin3 libin]# chmod u+x shell26

[01:52:33 root@libin3 libin]# ./shell26

/home/helloword

this user helloword does not exist in the system

because,helloword has a dir

2)删除目录后:

[02:07:12 root@libin3 libin]# rm -i /home/helloword/

[02:07:21 root@libin3 libin]# ./shell26

ls: 无法访问/home/helloword: 没有那个文件或目录

this user helloword does not exist in the system

because,helloword not has a dir

12.4 test 命令

test命令就会退出并返回退出状态码0。这样if-then语句就与其他编程语言中的if-then语句以类似的方式工作了。如果条件不成立,test命令就会退出并返回非零的退出状态码,这使得if-then语句不会再被执行。

1test 命令的基本使用

test 命令格式:conditiontest命令要测试的一系列参数和值。

 

test condition

 

test命令用在 if-then语句中时的格式:

if test condition

then

commands

fi

 

1:如果不写test命令的condition部分,它会以非0的退出状态码退出,并执行else语句块。

 

[19:50:20 root@libin3 libin]# vim shell27

#!/bin/bash

#test commmand user

#

if test

then

echo "success"

else

echo "failed"

fi

 

[19:53:10 root@libin3 libin]# chmod u+x shell27

[19:54:34 root@libin3 libin]# ./shell27

failed

 

例子2:若加入一个条件时,test命令会测试该条件。可以使用test命令确定变量中是否有内容,这只需要一个简单的条件表达式。变量my_libin中包含内容(rhce)所以当test命令测试时,返回退出状态码为0,使得if then语句块中的语句得以执行。

 

[19:58:25 root@libin3 libin]# vim shell28

#!/bin/bash

# test comand user 2

#

my_libin="rhce"

#

if test $my_libin

then

echo "success"

else

echo "false"

fi

 

[20:14:35 root@libin3 libin]# chmod u+x shell28

[20:14:44 root@libin3 libin]# ./shell28

success

 

例子3:如果变量中没有包含内容,就会出现相反的结果。

 

[20:15:58 root@libin3 libin]# vim shell29

#!/bin/bash

# test command user  3

#

my_libin=""

#

if test $my_libin

then

echo "success"

else

echo "failed"

fi

 

[20:26:14 root@libin3 libin]# chmod u+x shell29

[20:26:22 root@libin3 libin]# ./shell29

failed

 

 

 

2、test 命令的拓展使用

 

无需在if-then语句中写test,基本格式;

 

if [ condition ]

then

commands

fi

 

注:方括号定义了测试条件。注意,第一个方括号之后和第二个方括号之前必须加上一个空格,

否则就会报错。

 

test命令可以判断三类条件:

 

1数值比较

2字符串比较

3文件比较

 

12.4.1 数值比较

 

使用test命令最常见的情形是对两个数值进行比较。以下为测试两个值时可用条件参数。

1、常规对比

test命令的数值比较功能

n1 -eq n2

检查n1是否与n2相等

n1 -ge n2

检查n1是否大于或等于n2

n1 -gt n2

检查n1是否大于n2

n1 -le n2

检查n1是否小于或等于n2

n1 -lt n2

检查n1是否小于n2

n1 -ne n2

检查n1是否不等于n2

 

1数值条件测试可以用在数字和变量上。(可以变量与变量进行对比,也可以变量与数值对比

[20:41:06 root@libin3 libin]# vim shell30

#!/bin/bash

# use v2 test compare

#

libin1=10

libin2=20

libin3=10

#

if [ $libin1 -gt $libin2 ]

then

echo "true"

else

echo "false"

fi

 

if [ $libin1 -eq $libin3 ]

then

echo "success"

else

echo "failed"

fi

 

if [ $libin1 -eq 10 ]

then

echo "true"

else

echo "false"

fi

 

[20:57:24 root@libin3 libin]#

[20:57:24 root@libin3 libin]# chmod u+x shell30

[20:57:32 root@libin3 libin]# ./shell30

false

success

true

 

2、浮点值测试

1:涉及到浮点值时,数值会有限制,bash shell只能处理整数。echo没有问题,但是在基于数字的函数中就不行。

 

[21:03:17 root@libin3 libin]# vim shell31

#!/bin/bash

#test using floating compare

#

libin1=6.66

echo "the test value is $libin1"

#

if [ $libin1 -gt 6 ]

then

echo "true"

fi

 

[22:25:16 root@libin3 libin]# chmod u+x shell31

23:21:37 root@libin3 libin]# ./shell31

the test value is 6.66

./shell31: 7 :[: 6.66: 期待整数表达式

 

12.4.2 字符串比较

条件测试还允许比较字符串值。

 

字符串比较测试

str1 = str2

检查str1是否和str2相同

str1 != str2

检查str1是否和str2不同

str1 < str2

检查str1是否比str2

str1 > str2

检查str1是否比str2

-n str1

检查str1的长度是否非0

-z str1

检查str1的长度是否为0

1. 字符串相等性

 

1:如果不相同就不会输出任何东西

[23:42:04 root@libin3 libin]# vim shell32

#!/bin/bash

# test usring equality

libin=root

if [ $USER = $libin ]

then

echo "welcome $libin"

fi

 

[23:40:36 root@libin3 libin]# ./shell32

welcome root

 

2:字符串不等条件也可以判断两个字符串是否有相同的值。在比较字符串的相等性时,比较测试会将所有的标点和大小写情况都考虑在内

 

[23:46:38 root@libin3 libin]# cp shell32 shell33

[23:47:44 root@libin3 libin]# vim shell33

#!/bin/bash

# test usring equality

libin=rhce

if [ $USER != $libin ]

then

echo "The not is $libin"

else

echo "The is $libin"

fi

 

[23:49:31 root@libin3 libin]# ./shell33

The not is rhce

 

2. 字符串顺序

 

(1)大于号和小于号必须转义,否则shell会把它们当作重定向符号,把字符串值当作文件名;

(2)大于和小于顺序和sort命令所采用的不同。

 

1:脚本中使用大于号,没有报错,但结果错误,脚本把大于号解释成了输出重定向,创建了一个rhce的文件。因为重定向完成了,test命令的返回值就是0,if语句就会认为执行成功。

解决这个问题,就需要正确转义大于号。

 

示例1

[12:19:37 root@libin3 libin]# vim shell34

#!/bin/bash

#character string usring comparing

#

libin1=rhcsa

libin2=rhce

#

if [ $libin1 > $libin2 ]

then

echo "$libin1 is greater than $libin2"

else

echo "$libin1 is less than $libin2"

fi

 

[12:19:37 root@libin3 libin]# chmod u+x shell34

[12:19:04 root@libin3 libin]# ./shell34

rhcsa is greater than rhce

 

[12:19:07 root@libin3 libin]# ls -l rhce

-rw-r--r-- 1 root root 0 8月  15 12:19 rhce

 

示例2:使用了转义,这里我们可能无法理解rhcsa大于rhce

[12:20:34 root@libin3 libin]# cp shell34 shell35

[13:05:22 root@libin3 libin]# vim shell35

#!/bin/bash

#character string usring comparing

#

libin1=rhcsa

libin2=rhce

#

if [ $libin1 \> $libin2 ]

then

echo "$libin1 is greater than $libin2"

else

echo "$libin1 is less than $libin2"

fi

 

[13:08:42 root@libin3 libin]# ./shell35

rhcsa is greater than rhce

 

示例3:把libin1libin2变量定义为数值

13:08:43 root@libin3 libin]# cp shell35 shell36

[13:12:44 root@libin3 libin]# vim shell36

#!/bin/bash

#character string usring comparing

#

libin1=10

libin2=20

#

if [ $libin1 \> $libin2 ]

then

echo "$libin1 is greater than $libin2"

else

echo "$libin1 is less than $libin2"

fi

 

[13:13:07 root@libin3 libin]# ./shell36

10 is less than 20

 

示例4:验证是否大写小于小写字母

[13:17:05 root@libin3 libin]# cp shell36 shell37

[13:18:01 root@libin3 libin]# vim shell37

#!/bin/bash

#character string usring comparing

#

libin1=Rhcsa

libin2=rhce

#

if [ $libin1 \> $libin2 ]

then

echo "$libin1 is greater than $libin2"

else

echo "$libin1 is less than $libin2"

fi

 

[13:18:14 root@libin3 libin]# ./shell37

Rhcsa is less than rhce

注:在比较测试中,大写字母被认为是小于小写字母的。但sort命令恰好相反。当你将同样的字符串放进文件中并用sort命令排序时,小写字母会先出现。这是由各个命令使用的排序技术不同造成的。

比较测试中使用的是标准的ASCII顺序,根据每个字符的ASCII数值来决定排序结果。sort命令使用的是系统的本地化语言设置中定义的排序顺序。对于英语,本地化设置指定了在排序顺序中小写字母出现在大写字母前。

 

3. 字符串大小

-n(是否长度非0-z(是否长度为0可以检查一个变量是否含有数据。

1:注,我这里没有定义libin3,所以它的字符串长度仍然为0,则退出状态码也为0,则就会输出if-then后面的语句,else的语句就跳过了。

[13:22:49 root@libin3 libin]# vim shell38

#!/bin/bash

#testing string length comparing

libin1=rhcsa

libin2=''

#

if [ -n $libin1 ]

then

echo "The $libin1 does not empty"

else

echo "The $libin1 is empty"

fi

#

if [ -z $libin2 ]

then

echo "The $libin2 is empty"

else

echo "The $libin2 does not empty"

fi

#

if [ -z $libin3 ]

then

echo "The $libin3 does not empty"

else

echo "The $libin3 is empty"

fi

 

[13:25:59 root@libin3 libin]# chmod u+x shell38

[13:26:11 root@libin3 libin]# ./shell38

e rhcsa does not empty

The  is empty

The  does not empty

注:空的和未初始化的变量会对shell脚本测试造成灾难性的影响。如果不是很确定一个变量的内容,最好在将其用于数值或字符串比较之前先通过-n-z来测试一下变量是否含有值。

 

12.4.3 文件比较

文件比较测试很有可能是shell编程中最为强大、也是用得最多的比较形式。它允许你测试Linux文件系统上文件和目录的状态。

 

test命令的文件比较功能

-d file

检查file是否存在并是一个目录

-e file

检查file是否存在

-f file

检查file是否存在并是一个文件

-r file

检查file是否存在并可读

-s file

检查file是否存在并非空

-w file

检查file是否存在并可写

-x file

检查file是否存在并可执行

-O file

检查file是否存在并属当前用户所有

-G file

检查file是否存在并且默认组与当前用户相同

file1 -nt file2

检查file1是否比file2

file1 -ot file2

检查file1是否比file2

1. 检查目录-d

-d测试会检查指定的目录是否存在于系统中。

1:这里使用 -d 测试条件检查dir变量中的目录是否存在,存在就会执行if-then语句的命令,若不存在就会输出else的内容。

示例1

[13:39:56 root@libin3 libin]# vim shell39

#!/bin/bash

#file compare testing

#

dir=/home/student

if [ -d $dir ]

then

echo "The $dir dir is exist"

cd $dir

ls

else

echo "The $dir dir is does not exist"

fi

 

[14:10:22 root@libin3 libin]# chmod u+x shell39

[14:10:32 root@libin3 libin]# ./shell39

The /home/student dir is exist

centos-release-7-9.2009.0.el7.centos.x86_64.rpm  openssh-8.8p1-1.s12.rpms.bundle.sp3(1).tar  openssh-8.8p1.tar.gz

2. 检查对象是否存在-e

-e比较允许你的脚本代码在使用文件或目录前先检查它们是否存在。

 

1:第一次检查用-e比较来判断用户是否有$HOME目录。如果有,接下来的-e比较会检查

libin文件是否存在于$HOME目录中。如果不存在,shell脚本就会提示该文件不存在,不需要进行更新。为确保更新操作能够正常进行,我们创建了libin文件,然后重新运行这个shell脚本。这一次在进行条件测试时,$HOME和libin文件都存在,因此当前日期和时间就被追加到了文件中。

 

[14:38:51 root@libin3 libin]# vim shell40

#!/bin/bash

# check other a dir or file exists

#

local=$HOME

file_name="libin"

#

if [ -e $local ]

then #dir does exist

echo "true,the $local dir"

echo "now checking on the file,$file_name."

#

if [ -e $local/$file_name ]

then #file does exist

echo "true,on the filename"

echo "update current date..."

date >> $local/$file_name

#

else #file does not exist

echo "file is not exist"

echo "nothing to update"

fi

else #dir not exist

echo "the $local dir does not exist"

echo "nothing to update"

fi

 

[14:47:18 root@libin3 libin]# chmod u+x shell40

[14:48:18 root@libin3 libin]# ./shell40

true,the /root dir

now checking on the file,libin.

file is not exist

nothing to update

 

[14:54:23 root@libin3 libin]# cd /root/

[14:55:20 root@libin3 ~]# touch libin

[14:55:33 root@libin3 ~]# cd /libin/libin

[14:55:55 root@libin3 libin]# ./shell40

true,the /root dir

now checking on the file,libin.

true,on the filename

update current date...

 

3. 检查文件-f

-e比较可用于文件和目录。要确定指定对象为文件,必须用-f比较。

1:先使用-e比较测试$HOME是否存在。如果存在,继续用-f来测试它是不是一个文件。如果它不是文件,就会输出一条信息。

[00:02:55 root@libin3 libin]# vim shell41

#!/bin/bash

# check other a dir or file exists

#

libin_name=$HOME

echo "the checking: $libin_name"

#

if [ -e $libin_name ]

then #the does exist

echo "The $libin_name does exist."

echo "but is a file?"

#

if [ -f $libin_name ]

then #the is a file

echo "yes,$libin_name is a file."

#

else #libin_name the does not a file

echo "no,$libin_name not does is a file"

fi

#

else #libin_name does not exist

echo "the $libin_name does not exist."

echo "nothing to update"

fi

 

[00:00:41 root@libin3 libin]# chmod u+x shell41

[00:07:36 root@libin3 libin]# ./shell41

the checking: /root

The /root does exist.

but is a file?

no,/root not does is a file

 

2:对变量libin_name进行修改,将目录$HOME替换成$HOME/libin,看会有什么变化对$HOME/libin进行的-f测试所返回的退出状态码为0,then语句得以执行,然后输出消息:

 

[00:26:20 root@libin3 ~]# ll /root/libin

-rw-r--r-- 1 root root 43 8月  15 14:56 /root/libin

 

[00:27:40 root@libin3 libin]# cp shell41 shell42

[00:26:42 root@libin3 ~]# vim shell42

#!/bin/bash

# check other a dir or file exists

#

libin_name=$HOME/libin

echo "the checking: $libin_name"

#

if [ -e $libin_name ]

then #the does exist

echo "The $libin_name does exist."

echo "but is a file?"

#

if [ -f $libin_name ]

then #the is a file

echo "yes,$libin_name is a file."

 

else #libin_name the does not a file

echo "no,$libin_name not does is a file"

fi

 

else #libin_name  does not exist

echo "the $libin_name does not exist."

echo "nothing to update"

fi

 

[00:28:12 root@libin3 libin]# ./shell42

the checking: /root/libin

The /root/libin does exist.

but is a file?

yes,/root/libin is a file.

 

4. 检查是否可读-r

在尝试从文件中读取数据之前,最好先测试一下文件是否可读。可以使用-r比较测试。

示例1

[01:27:08 root@libin3 libin]# ll /etc/rhce

ls: 无法访问/etc/rhce: 没有那个文件或目录

 

[00:58:08 root@libin3 libin]# vim shell43

#!/bin/bash

#test a file reading

#

libin=/etc/rhce

#

if [ -f $libin ]

then  #file exist

#

if [ -r $libin ]

then #yes reading $libin

tail -n1 $libin

#

else

echo "false,the is a not exit $libin "

fi

else

echo "false,the $libin is not reading"

fi

 

[01:02:47 root@libin3 libin]# chmod u+x shell43

[00:59:01 root@libin3 libin]# ./shell43

false,the /etc/rhce is not reading

 

示例2

[01:23:10 root@libin3 libin]# echo "111" > /etc/rhce

[01:25:04 root@libin3 libin]# ./shell43

111

 

 

5. 检查空文件-s

-s比较来检查文件是否为空,尤其是在不想删除非空文件的时候。要留心的是,当-s比较成功时,说明文件中有数据。

1:-f比较测试首先测试文件是否存在。若存在,由-s比较来判断该文件是否为空。空文件会被删除。可以从ls –l的输出中看出sentinel并不是空文件,因此脚本并不会删除它。

 

 

 

[21:35:40 root@libin3 libin]# echo "wlcome to study redhat">> /root/rhce

[21:36:09 root@libin3 libin]# cat /root/rhce

wlcome to study redhat

 

[21:38:19 root@libin3 libin]# vim shell44

#!/bin/bash

# test in a file empty

#

libin_name=$HOME/rhce

#

if [ -f $libin_name ]

then

if [ -s $libin_name ]

then

echo "the $libin_name exist"

echo "not remove file"

else

echo "the $libin_name exists and empty"

echo "delete empty file..."

rm $libin_name

fi

else

echo "file,$libin_name,not exist"

fi

[21:42:06 root@libin3 libin]# chmod u+x shell44

[21:42:12 root@libin3 libin]# ./shell44

the /root/rhce exist

not remove file

 

6.检查是否可写-w

-w比较会判断你对文件是否有可写权限。不单检查libin_name是否存在、是否为文件,还会检查该文件是否有写入权限。

1:变量libin_name被设置成$HOME/rhce,该文件允许用户进行写入,我是root,因此当脚本运行时,-w测试表达式会返回非零退出状态,然后执行then代码块,将时间戳写入文件rhce中。

 

[22:26:32 root@libin3 libin]# vim shell45

#!/bin/bash

#check file is writable

#

libin_name=$HOME/rhce

echo "checking: $libin_name"

echo "yes, $libin_name."

echo "writable?"

#

if [ -w $libin_name ]

then #is writable

echo "writing time to $libin_name"

date +%Y%H%M >> $libin_name

#

else #no writable

echo "no writing time to $libin_name"

fi

 

[22:11:29 root@libin3 libin]# chmod u+x shell45

[22:27:12 root@libin3 libin]# ./shell45

checking: /root/rhce

yes, /root/rhce.

writable?

writing time to /root/rhce

 

[22:28:42 root@libin3 libin]# cat /root/rhce

wlcome to study redhat

20222227

 

7. 检查文件是否可以执行-x

-x比较是判断特定文件是否有执行权限的一个简单方法。虽然可能大多数命令用不到它,但如果你要在shell脚本中运行大量脚本,它就能发挥作用。

1:用-x比较来测试是否有权限执行shell46脚本。如果有权限,它会运行这个脚本。在首次成功运行test16.sh脚本后,更改文件的权限。

 

[22:55:14 root@libin3 libin]# vim shell46

#!/bin/bash

# test file executa

#

if [ -x shell45 ]

then

echo "run the script"

./shell45

else

echo "no run the script"

fi

 

[22:54:21 root@libin3 libin]# chmod u+x shell46

 

[22:56:41 root@libin3 libin]# ./shell46

run the script

checking: /root/rhce

yes, /root/rhce.

writable?

writing time to /root/rhce

 

[22:54:02 root@libin3 libin]# chmod u-x shell45

[22:55:04 root@libin3 libin]# ./shell46

no run the script

 

8. 检查所属关系-O

 

-O比较可以测试出你是否是文件的属主。

 

1:脚本用-O比较来测试运行该脚本的用户是否是/etc/passwd文件的属主。若我用普通用户去执行脚本,就会测试失败。

 

#!/bin/bash

# check file owner

#

if [ -O /etc/passwd ]

then

echo "true,the owner /etc/passwd file"

else

echo "false,are not owner /etc/passwd file"

fi

 

[23:07:26 root@libin3 libin]# chmod u+x shell47

[23:07:31 root@libin3 libin]# ./shell47

true,the owner /etc/passwd file

 

[student@libin3 libin]$ PATH=$PATH:/libin/libin/

[student@libin3 libin]$ echo $PATH

/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/student/.local/bin:/home/student/bin:/libin/libin/

[student@libin3 libin]$ sudo chmod o+x shell47

[student@libin3 libin]$ ./shell47

false,are not owner /etc/passwd file

 

9. 检查默认属组关系-G

 

-G比较会检查文件的默认组,如果它匹配了用户的默认组,则测试成功。由于-G比较只会检查默认组而非用户所属的所有组。

 

1:第一次执行脚本时,$HOME/test文件属于student组,所以就比较失败,因为-G只会比较默认组。

 

[23:26:18 root@libin3 libin]# ll /home/student/test

-rw-rw-r-- 1 student student 11 8月  17 23:26 /home/student/test

 

[23:28:03 root@libin3 libin]# vim shell48

#!/bin/bash

#check file group test

#

if [ -G $HOME/test ]

then

echo "is same group the file"

else

echo "not is group the file"

fi

 

23:30:14 root@libin3 libin]# ./shell48

not is group the file

 

[23:30:18 root@libin3 libin]# su - student

[student@libin3 libin]$ sudo chmod o+x shell48

[student@libin3 ~]$ cd /libin/libin

[student@libin3 libin]$ ./shell48

is same group the file

 

 

 

10. 检查文件日期-nt-ot

 

在编写软件安装脚本时非常有用。有时候,你不会愿意安装一个比系统上已有文件还要旧的文件。

-nt比较会判定一个文件是否比另一个文件新。如果文件较新,那意味着它的文件创建日期更近。-ot比较会判定一个文件是否比另一个文件旧。如果文件较旧,意味着它的创建日期更早。

 

 

 

[23:39:35 root@libin3 libin]# vim shell49

#!/bin/bash

#test file date

if [ shell47 -nt shell46 ]

then

echo "shell47 new,shell46 old"

else

echo "shell46 new,shell47 old"

fi

#

if [ shell45 -ot shell48 ]

then

echo "shell45 old,shell48 new"

else

echo "0"

fi

 

[23:42:06 root@libin3 libin]# chmod u+x shell49

[23:49:13 root@libin3 libin]# ./shell49

shell47 new,shell46 old

shell45 old,shell48 new

 

12.5 复合条件测试

 

if-then语句允许你使用布尔逻辑来组合测试。有两种布尔运算符可用:

1[ condition1 ] && [ condition2 ]

2[ condition1 ] || [ condition2 ]

 

1种布尔运算使用AND布尔运算符来组合两个条件。要让then部分的命令执行,两个条件都必须满足。

第2种布尔运算使用OR布尔运算符来组合两个条件。如果任意条件为TRUE,then部分的命令就会执行。

 

1AND布尔运算符的使用(两个条件都满足的情况下

[21:52:23 root@libin3 libin]# ll $HOME/rhce

---------- 1 root root 68 8月  18 21:31 /root/rhce

[21:40:33 root@libin3 libin]# vim shell50

#!/bin/bash

# test AND compare

#

if [ -d $HOME ] && [ -w $HOME/rhce ]

then

echo "the file exist and write"

else

echo "the file not write"

fi

 

[21:40:41 root@libin3 libin]# chmod u+x shell50

[21:40:49 root@libin3 libin]# ./shell50

the file exist and write

 

22个条件满足1个即可)

示例1:(一个条件满足的情况下)

[21:48:06 root@libin3 libin]# ll $HOME/rhcsa

ls: 无法访问/root/rhcsa: 没有那个文件或目录

[21:42:57 root@libin3 libin]# vim shell51

#!/bin/bash

# if-then || usering

#

if [ -d $HOME/rhcsa ] || [ -w $HOME/rhce ]

then

echo "true"

else

echo "false"

fi

 

[21:46:47 root@libin3 libin]# chmod u+x shell51

[21:46:56 root@libin3 libin]# ./shell51

true

 

示例2:(二个条件都不满足的情况下)

[21:50:46 root@libin3 libin]# ll $HOME/rhca

ls: 无法访问/root/rha: 没有那个文件或目录

[21:50:52 root@libin3 libin]# vim shell51

#!/bin/bash

# if-then || usering

#

if [ -d $HOME/rhcsa ] || [ -w $HOME/rhca ]

then

echo "true"

else

echo "false"

fi

 

[21:51:20 root@libin3 libin]# ./shell51

false

12.6 if-then 的高级特性

1)用于数学表达式的双括号

2)用于高级字符串处理功能的双方括号

12.6.1 使用双括号

双括号命令的格式:(( expression ))

 

expression可以是任意的数学赋值或比较表达式。除了test命令使用的标准数学运算符。

 

双括号命令符号

val++

后增

val--

后减

++val

先增

--val

先减

!

逻辑求反

~

位求反

**

幂运算

<<

左位移

>>

右位移

&

位布尔和

|

位布尔或

&&

逻辑和

||

逻辑或

 

1:(()) 的使用

[22:11:30 root@libin3 libin]# vim shell52

#!/bin/bash

#using (())

#

libin=12

if (( $libin ** 2 > 100 ))

then

 (( libin2 = $libin ** 2 ))

echo "the squaring is $libin is $libin2"

fi

 

[22:12:28 root@libin3 libin]# ./shell52

the squaring is 12 is 144

 

例子2

[22:19:03 root@libin3 libin]# vim shell53

#!/bin/bash

#using (())

#

libin1=10

libin2=20

libin3=30

#

if (( $libin1 > $libin2 )) || (( $libin2 < $libin3 ))

then

echo "true"

else

echo "false"

fi

 

[22:18:50 root@libin3 libin]# chmod +x shell53

[22:19:41 root@libin3 libin]# ./shell53

true

12.6.2 使用双方括号

双方括号命令提供了针对字符串比较的高级特性。它提供了test命令未提供的另一个特性:模式匹配(pattern matching)。

双方括号命令的格式:[[ expression ]]

1:这里使用了双等号(==),双等号将右边的字符串(r*)看作为一个模式,并且应用模式匹配规则。echo $USER 为root ,(r*)看它是否为字母r开头,如果是则退出状态为0。

示例1

[22:34:18 root@libin3 libin]# vim shell54

#!/bin/bash

# using pattern matching

#

if [[ $USER == r* ]]

then

echo "true,echo $USER"

else

echo "false"

fi

[22:34:18 root@libin3 libin]# chmod u+x shell54

[22:33:32 root@libin3 libin]# ./shell54

true,echo root

示例2

[22:40:56 root@libin3 libin]# echo $HOSTNAME

libin3.com

[22:41:03 root@libin3 libin]# vim shell54

#!/bin/bash

# using pattern matching

#

if [[ $HOSTNAME ==  r* ]]

then

echo "true,echo $HOSTNAME"

else

echo "false"

fi

 

[22:41:42 root@libin3 libin]# ./shell54

false

12.7 case 命令

case语句与if-then-else语句的区别。

1:if-then-else语句,比较繁琐

 

[19:37:32 root@libin3 libin]# usermod -G root student

[19:22:33 root@libin3 libin]# vim shell55

[19:37:32 root@libin3 libin]# cat shell55

#!/bin/bash

# look for a value

#

if [ $USER = root ]

then echo "the user is root"

elif [ $USER = student ]

then echo "the user is student"

elif [ $USER = rhce ]

then echo "the user is student"

elif [ $USER =  helloword ]

then echo "the user is helloword"

else

echo "sorry,no a user exist"

fi

 

[19:26:24 root@libin3 libin]# chmod u+x shell55

[19:26:31 root@libin3 libin]# ./shell55

the user is root

[student@libin3 libin]$ sudo chmod g+x shell55

[student@libin3 libin]$ ./shell55

the user is student

 

case命令,就不需要写elif语句检查同一个变量值。case命令会采用下面的列表格式来检查单个变量的多个值。

 

格式:

 

case variable in

pattern1 | pattern2) commands1;;

pattern3) commands2;;

*) default commands;;

esac

 

case命令会将指定的变量与不同模式进行比较。如果变量和模式是匹配的,那么shell会执行为该模式指定的命令。通过 | 操作符在一行中分隔出多各模式。 * 号会捕获所有与已知模式不匹配的值,就相当于else

 

2:将例1的语句转换成case语句

 

示例1

[19:48:36 root@libin3 libin]# vim shell56

#!/bin/bash

#using the case command

#

case $USER in

root | student)

echo "the is $USER";;

rhce)

echo "the is rhce";;

student)

echo "the is student";;

helloword)

echo "the is helloword";;

*)

echo "no is user";;

esac

 

[19:52:16 root@libin3 libin]# chmod +x shell56

[19:53:11 root@libin3 libin]# ./shell56

the is root

 

示例2

[19:56:00 root@libin3 libin]# su - student

[student@libin3 libin]$ sudo chmod g+x /libin/libin/shell56

[student@libin3 libin]$ ./shell56

the is student

 

3:这里我们可以得出case语句判断出了libin3.com 为主机名

 

[21:11:43 root@libin3 libin]# echo $HOSTNAME

libin3.com

[21:11:45 root@libin3 libin]# vim shell57

#!/bin/bash

# using the case command

#

case $HOSTNAME in

libin2.com | libin4.com )

echo "the is $HOSTNAME";;

libin5.com | libin3.com )

echo "the hostname is libin5.com or $HOSTNAME";;

libin6.com)

echo "the hostname is libin6.com";;

*)

echo "no HOSTNAME";;

esac

 

[21:04:47 root@libin3 libin]# chmod u+x shell57

[21:05:02 root@libin3 libin]# ./shell57

the hostname is libin5.com or libin3.com

 

 

 

第十三章 更多的结构化命令

 

13.1 for命令

重复执行一系列命令。通常需要重复一组命令直至达到某个特定条件,比如处理某个目录下的所有文件、系统上的所有用户或是某个文本文件中的所有行。

for命令,允许你创建一个遍历一系列值的循环。每次迭代都使用其中一个值来执行已定义好的一组命令。

 

 

 

基本格式:

 

for var in list

do

commands

done

 

list参数中需要提供迭代中要用到的一系列值。在每次迭代中,变量var会包含列表中的当前值。第一次迭代会使用列表中的第一个值,第二次迭代使用第二个值,以此类推,直到列表中的所有值都过一遍。在do和done语句之间输入的命令可以是一条或多条标准的bash shell命令。在这些命令中,$var变量包含着这次迭代对应的当前列表项中的值。

 

for语句可以与do与语句放在同一行,但必须用分号将其同列表中的值分开:for var in list;do。

13.1.1 读取列表中的值

for命令最基本的用法就是遍历for命令自身所定义的一系列值。

 

1

 

示例1每次for命令遍历值列表,它都会将列表中的下个值赋给$libin变量。$libin变量可以像for命令语句中的其他脚本变量一样使用。在最后一次迭代后,$test变量的值会在shell脚本的剩余部分一直保持有效。会一直保持最后一次迭代的值(除非你修改了它)。

 

[22:48:03 root@libin3 libin]# vim shell59

#!/bin/bash

# using for command

#

for libin in libin1 libin2 libin3 libin4

do

echo "the libin state is $libin"

done

 

[22:49:25 root@libin3 libin]# chmod u+x shell59

[22:49:40 root@libin3 libin]# ./shell59

the libin state is libin1

the libin state is libin2

the libin state is libin3

the libin state is libin4

 

示例2我们也可以将值列表进行定义到一个文件,这样当我们有成千上万的值的时候,写法就比较便捷,如下

[22:58:57 root@libin3 libin]# vim libin6

libin1

libin2

libin3

libin4

 

[23:05:54 root@libin3 libin]# vim shell60

#!/bin/bash

# using for command

#

for libin in `cat libin6`

do

echo "the libin state is $libin"

done

 

[23:06:13 root@libin3 libin]# chmod u+x shell60

[23:06:53 root@libin3 libin]# ./shell60

the libin state is libin1

the libin state is libin2

the libin state is libin3

the libin state is libin4

 

2$libin变量保持了其值,也允许我们修改它的值,并在for命令循环之外跟其他变量一样使用。

 

[23:06:56 root@libin3 libin]# cp shell60 shell61

[23:15:14 root@libin3 libin]# vim shell61

#!/bin/bash

# using for command

#

for libin in `cat libin6`

do

echo "the libin state is $libin"

done

libin=rhca

echo "now value visiting is $libin"

 

[23:17:53 root@libin3 libin]# ./shell61

the libin state is libin1

the libin state is libin2

the libin state is libin3

the libin state is libin4

now value visiting is rhca

 

 

 

13.1.2 读取列表中的复杂值

1、对于有单引号的复杂值

1:对于遇到难处理的数据,下面这种输出,肯定不是我们需要的,我们看到列表值中的单引号并尝试使用它们来定义一个单独的数据值

 

[23:19:27 root@libin3 libin]# vim shell62

[23:35:55 root@libin3 libin]# chmod u+x shel62

#!/bin/bash

# other example for cammand using

for libin in I see's this'll is word

do

echo "word:$libin"

done

 

[23:36:26 root@libin3 libin]# chmod u+x shell62

[23:36:30 root@libin3 libin]# ./shell62

word:I

word:sees thisll

word:is

word:word

 

 

 

解决办法:

1)使用转义字符(反斜线)来将单引号转义;

2)使用双引号来定义用到单引号的值。

2:我们这里全都采用了双引号,但其实转义符或者双引号都是可以的

 

[23:37:26 root@libin3 libin]# cp shell62 shell63

[23:43:16 root@libin3 libin]# vim shell63

#!/bin/bash

# other example for cammand using

for libin in I "see's" "this'll" is word

do

echo "word:$libin"

done

 

[23:43:46 root@libin3 libin]# ./shell63

word:I

word:see's

word:this'll

word:is

word:word

 

2、对于有多个词的值

for循环假定每个值都是用空格分割的。如果有包含空格的数据值,你就陷入麻烦了。

1:这并不是我们需要的结果,这时我们可能就需要用双引号来将这些值圈起来

 

示例1

[00:07:08 root@libin3 libin]# vim shell64

#!/bin/bash

#other example of how use the for command

for libin in libin1 rhcsa libin2 rhce libin3 rhca

do

echo "the libin id $libin"

done

 

[00:09:16 root@libin3 libin]# chmod +x shell64

[00:09:25 root@libin3 libin]# ./shell64

the libin id libin1

the libin id rhcsa

the libin id libin2

the libin id rhce

the libin id libin3

the libin id rhca

 

示例2:这才是我们需要的值,在某个值两边使用双引号时,shell 并不会将双引号当成值的一部分。

 

[00:09:29 root@libin3 libin]# vim shell64

#!/bin/bash

#other example of how use the for command

for libin in "libin1 rhcsa" "libin2 rhce" "libin3 rhca"

do

echo "the libin id $libin"

done

 

[00:13:33 root@libin3 libin]# ./shell64

the libin id libin1 rhcsa

the libin id libin2 rhce

the libin id libin3 rhca

 

13.1.3 从变量读取列表

通常shell脚本遇到的情况是,你将一系列值都集中存储在了一个变量中,然后需要遍历变量中的整个列表。也可以通过for命令完成这个任务。

 

注:libin=$libin" connection " 是能接上上一条的变量定义的值,如果写成libin=connection 之前的变量定义的值就不能被继承。其实还可以写成libin="$libin connection"上条变量定义的值与 这次值之间要有空格,如果二者之间没有双引号就会把connection 认定为命令。

 

示例1

[00:27:05 root@libin3 libin]# vim shell65

#!/bin/bash

# using a value to list

libin="libin1 libin2 libin3 libin4"

libin=$libin" connection "

for rhce in $libin

do

echo "the have you visited $rhce?"

done

 

[00:26:52 root@libin3 libin]# chmod +x shell65

[00:27:29 root@libin3 libin]# ./shell65

the have you visited libin1?

the have you visited libin2?

the have you visited libin3?

the have you visited libin4?

the have you visited connection?

 

示例2

[00:47:51 root@libin3 libin]# vim shell66

#!/bin/bash

#useing the for $

rhce="libin1 libin2 libin3"

rhce="$rhce libin4"

for i in $rhce

do

echo "$i?"

done

 

[00:47:59 root@libin3 libin]# chmod +x shell66

[00:49:46 root@libin3 libin]# ./shell66

libin1?

libin2?

libin3?

libin4?

 

13.1.4 从命令读取值

 

生成列表中所需值的另外一个途径就是使用命令的输出。可以用命令替换来执行任何能产生输出的命令,然后在for命令中使用该命令的输出。

1:可以使用两种写法去定义从命令读取值。将文件名赋给变量,文件名中没有加入路径。这要求文件和脚本位于同一个目录中。如果不是的话,你需要使用全路径名(不管是绝对路径还是相对路径)来引用文件位置。

 

[01:04:53 root@libin3 libin]# cat libin6

rhcsa

rhce

rhca

hcia

hcip

hcie

 

示例1

[00:53:58 root@libin3 libin]# vim  shell67

#!/bin/bash

# reading value a file

file="libin6"

for libin in $(cat $file)

do

echo "see the $libin"

done

 

[00:56:28 root@libin3 libin]# chmod +x shell67

[00:57:26 root@libin3 libin]# ./shell67

see the rhcsa

see the rhce

see the rhca

see the hcia

see the hcip

see the hcie

 

示例2

[01:03:49 root@libin3 libin]# vim  shell67

#!/bin/bash

# reading value a file

file="libin6"

for libin in `cat $file`

do

echo "see the $libin"

done

 

[01:04:36 root@libin3 libin]# ./shell67

see the rhcsa

see the rhce

see the rhca

see the hcia

see the hcip

see the hcie

 

13.1.5 更改字段分隔符

 

特殊的环境变量IFS内部字段分隔符(internal field separator),定义了bash shell用作字段分隔符的一系列字符,默认情况下,bash shell 会将下列当作字段分隔符号。

(1)空格

(2)制表符

(3)换行符

 

如果列表中一个新数据字段的开始。在处理可能含有空格的数据(比如文件名)时,这会非常麻烦,要解决这个问题,就可以在shell脚本中更改IFS环境变量的值来限制被bash shell当作字段分隔符的字符。

 

其他用法:

1)假定你要遍历一个文件中用冒号分隔的值(比如在/etc/passwd文件中)。你要做的就是将IFS的值设为冒号为:

IFS=:

2如果要指定多个IFS字符,只要将它们在赋值行串起来就行,例如赋值会将换行符、冒号、分号和双引号作为字段分隔符

IFS=$'\n':;''

例子1:使其只能识别换行符IFS=$'\n'

[14:36:12 root@libin3 libin]# cat libin6

rhcsa

rhce

rhca

hcia libin1

hcip libin2

hcie libin3

 

[14:33:32 root@libin3 libin]# vim shell68

#!/bin/bash

# reading value from a file

#

file="libin6"

IFS=$'\n'

for libin in $( cat $file )

do

echo "see the $libin"

done

 

[14:35:54 root@libin3 libin]# chmod +x shell68

[14:36:06 root@libin3 libin]# ./shell68

see the rhcsa

see the rhce

see the rhca

see the hcia libin1

see the hcip libin2

see the hcie libin3

 

如果我们不加入分隔符的结果会是:

[14:38:11 root@libin3 libin]# ./shell68

see the rhcsa

see the rhce

see the rhca

see the hcia

see the libin1

see the hcip

see the libin2

see the hcie

see the libin3

13.1.6 用通配符读取目录

可以用for命令来自动遍历目录中的文件,进行此操作时,必须在文件名或路径名中使用通配符。它会强制shell使用文件扩展匹配。文件扩展匹配是生成匹配指定通配符的文件名或路径名的过程。

 

例子1:for命令会遍历/libin/libin/*输出的结果,该代码用test 命令12.4章)的用法测试每个条目(使用方括号方法),用来查看它是目录(-d参数)还是文件(-f参数)(12.4.3章),并且要记住将变量用双引号圈起来。"$libin"

[15:05:04 root@libin3 libin]# vim shell69

#!/bin/bash

# Senior for wildcard using 1

for libin in /libin/libin/*

do

if [ -d "$libin" ]

then

echo "$libin this is dir"

elif [ -f "$libin" ]

then

echo "exist,$libin this is file"

fi

done

 

[15:11:33 root@libin3 libin]# chmod +x shell69

[15:11:43 root@libin3 libin]# ./shell69

[15:12:33 root@libin3 libin]# ./shell69

/libin/libin/libin this is dir

exist,/libin/libin/libin2.txt this is file

exist,/libin/libin/libin3.txt this is file

exist,/libin/libin/libin6 this is file

exist,/libin/libin/libin.c this is file

exist,/libin/libin/libin.csv this is file

exist,/libin/libin/libin.txt this is file

exist,/libin/libin/log.220807 this is file

exist,/libin/libin/redhat.txt this is file

/libin/libin/rhca this is dir

exist,/libin/libin/rhce this is file

exist,/libin/libin/shell40 this is file

exist,/libin/libin/shell41 this is file

exist,/libin/libin/shell42 this is file

。。。

 

2:可以在for命令中列出多个目录通配符,将目录查找和列表合并进同一个for语句for语句首先使用了文件扩展匹配来遍历通配符生成的文件列表,然后它会遍历列表中的下一个文件。可以将任意多的通配符放进列表中。

 

[15:32:29 root@libin3 libin]# vim shell70

/bin/bash

# Senior for wildcard using 2

for i in /home/student/.b* /libin/libin/rhca /libin/libin/rhca/*

do

if [ -d "$i" ]

then echo "$i,this is dir"

elif [ -f "$i" ]

then echo "$i,this is file"

else

echo "$i,this not does dir and file"

fi

done

 

[15:35:18 root@libin3 libin]# chmod +x shell70

[15:36:52 root@libin3 libin]# ./shell70

/home/student/.bash_history,this is file

/home/student/.bash_logout,this is file

/home/student/.bash_profile,this is file

/home/student/.bashrc,this is file

/libin/libin/rhca,this is dir

/libin/libin/rhca/libin,this is file

/libin/libin/rhca/libin.save,this is file

 

13.2 C 语言风格的 for 命令

在C语言中,for循环通常定义一个变量,然后这个变量会在每次迭代时自动改变。通常程序员会将这个变量用作计数器,并在每次迭代中让计数器增一或减一。

13.2.1 C 语言的 for 命令

C语言的for命令有一个用来指明变量的特定方法,一个必须保持成立才能继续迭代的条件, 以及另一个在每个迭代中改变变量的方法。当指定的条件不成立时,for循环就会停止条件等式通过标准的数学符号定义。比如,考虑下面的C语言代码:

for (i = 0; i < 10; i++)

{

printf("The next number is %d\n", i);

}

这段代码产生了一个简单的迭代循环,其中变量i作为计数器。第一部分将一个默认值赋给该变量。中间的部分定义了循环重复的条件。当定义的条件不成立时,for循环就停止迭代。最

后一部分定义了迭代的过程。在每次迭代之后,最后一部分中定义的表达式会被执行。在本例中,i变量会在每次迭代后增一。

 

bashC语言风格的for循环的基本格式:

for (( variable assignment ; condition ; iteration process ))

 

C语言风格的变量引用方式不是shell风格的变量的引用方式。C语言风格的for命令为:

for (( a = 1; a < 10; a++ ))

 

(1)变量赋值可以有空格

(2)条件中的变量不以美元符号开头

(3)迭代过程的算式未用expr命令格式。

 

1:在bash shell中使用C语言风格的for命令

[17:12:34 root@libin3 libin]# vim shell71

#!/bin/bash

# Senior for C-style  wildcard using  

for ((i = 1; i <= 10; i++))

do

echo "the next value is $i"

done

 

[17:13:26 root@libin3 libin]# chmod +x shell71

[17:12:56 root@libin3 libin]# ./shell71

the next value is 1

the next value is 2

the next value is 3

the next value is 4

the next value is 5

the next value is 6

the next value is 7

the next value is 8

the next value is 9

the next value is 10

 

for循环通过定义好的变量(本例中是变量i)来迭代执行这些命令。在每次迭代中,$i

量包含了for循环中赋予的值。在每次迭代后,循环的迭代过程会作用在变量上,在本例中,变量增一。

13.2.2 使用多个变量

C语言风格的for命令也允许为迭代使用多个变量。循环会单独处理每个变量,你可以为每个变量定义不同的迭代过程。

1:变量a和b分别用不同的值来初始化并且定义了不同的迭代过程。循环的每次迭代在增加变量a的同时减小了变量b

18:01:19 root@libin3 libin]# vim shell72

#!/bin/bash

# for c_style v2 using

for ((libin1=1,libin2=10,libin3=20; libin1<=10; libin1++, libin2--, libin3--))

do

echo "$libin1-$libin2-$libin3"

done

 

[18:05:16 root@libin3 libin]# chmod +x shell72

[18:05:22 root@libin3 libin]# ./shell72

1-10-20

2-9-19

3-8-18

4-7-17

5-6-16

6-5-15

7-4-14

8-3-13

9-2-12

10-1-11

13.3 while 命令

13.3.1 while 的基本格式

 

while命令的格式是:

while test command

do

other commands

done

 

while命令中定义的test commandif-then语句中的格式一模一样。可以使用任何普通的bash shell命令,或者用test命令进行条件测试,比如测试变量值。

 

while命令的关键在于所指定的test command的退出状态码必须随着循环中运行的命令而 改变。如果退出状态码不发生变化, while循环就将一直不停地进行下去。

 

1test command的用法是用方括号来检查循环命令中用到的shell变量的值。

示例1

[22:25:45 root@libin3 libin]# vim shell73

#!/bin/bash

# using while bash shell command 1

libin=10

while [ $libin -gt 0 ]

do

echo $libin

libin=$[ $libin - 2 ]

done

 

[22:26:03 root@libin3 libin]# chmod +x shell73

[22:26:35 root@libin3 libin]# ./shell73

10

8

6

4

2

 

注: [ $libin -gt 0 ] 只要测试条件成立,while命令就会不停地循环执行定义好的命令,-gt 为是否大于0,所以后面输出并没有0,因为等于0时就不会在去调用while语句了。

libin=$[ $libin - 2 ],使用shell算术来将变量值减1

 

示例2-lt:是否小于

[22:57:10 root@libin3 libin]# vim shell74

#!/bin/bash

# while command using 2

libin=0

while [ $libin -lt 10 ]

do

echo $libin

libin=$[ $libin + 2 ]

done

 

[23:00:29 root@libin3 libin]# chmod +x shell74

[23:01:17 root@libin3 libin]# ./shell74

0

2

4

6

8

13.3.2 使用多个测试命令

while命令允许你在while语句行定义多个测试命令。只有最后一个测试命令的退出状态码

会被用来决定什么时候结束循环。

 

1-ge:是否大于或等于,

[23:35:17 root@libin3 libin]# vim shell75

#!/bin/bash

# while command using 3

libin=10

while echo $libin

[ $libin -ge 0 ]

do

echo "the is inside a loop"

libin=$[$libin - 1]

done

 

[23:36:23 root@libin3 libin]# chmod +x shell75

[23:36:22 root@libin3 libin]# ./shell75

10

the is inside a loop

9

the is inside a loop

8

the is inside a loop

7

the is inside a loop

6

the is inside a loop

5

the is inside a loop

4

the is inside a loop

3

the is inside a loop

2

the is inside a loop

1

the is inside a loop

0

the is inside a loop

-1

请仔细观察本例中做了什么。while语句中定义了两个测试命令。

while echo $var1

[ $var1 -ge 0 ]

第一个测试简单地显示了var1变量的当前值。第二个测试用方括号来判断var1变量的值。在循环内部,echo语句会显示一条简单的消息,说明循环被执行了。运行本例时输出是如何结束的。

the is inside a loop

-1

while循环会在var1变量等于0时执行echo语句,然后将var1变量的值减一。接下来再次执行测试命令,用于下一次迭代。另一处要留意的是该如何指定多个测试命令。注意,每个测试命令都出现在单独的一行上

 

示例3

[01:01:26 root@libin3 libin]# vim shell75

#!/bin/bash

# while command using 3

libin=10

while echo $libin

[ $libin -ge 0 ]

do

#echo "the is inside a loop"

libin=$[$libin - 1]

libin=$[$libin - 2]

done

 

[01:05:32 root@libin3 libin]# ./shell75

10

7

4

1

-2

13.4 until 命令

 

until命令和while命令工作的方式完全相反。until命令需要指定一个通常返回非零退出状态码的测试命令。只有测试命令的退出状态码不为0,bash shell才会执行循环中列出的命令。当测试命令返回退出码为0时,循环就结束。

 

unitl命令格式:

 

until test commands

do

other commands

done

 

例子1:可以在until命令语句中放入多个测试命令。只有最后一个命令的退出状态码决定了bash shell是否执行已定义的other commands

 

示例1:执行一个测试命令

[21:30:47 root@libin3 libin]# vim shell76

#!/bin/bash

# using the until command

libin=100

until [ $libin -eq 0 ]

do

echo $libin

libin=$[$libin-25]

done

 

[21:32:42 root@libin3 libin]# chmod +x shell76

[21:34:20 root@libin3 libin]# ./shell76

100

75

50

25

 

注:我这里libin变量决定了until循环何时停止。只要该变量的值等于0until命令就会停止循环。同while命令一样。

 

示例2:执行多个测试命令

[21:43:01 root@libin3 libin]# vim shell76

#!/bin/bash

# using the until command

libin=100

until echo $libin

[ $libin -lt 0 ]

do

echo This is value: $libin

libin=$[$libin-25]

done

 

[21:43:30 root@libin3 libin]# ./shell76

100

This is value: 100

75

This is value: 75

50

This is value: 50

25

This is value: 25

0

This is value: 0

-25

 

 

 

13.5 嵌套循环

 

循环语句可以在循环内使用任意类型的命令,包括其他循环命令。这种循环叫作嵌套循环(nested loop)。

在使用嵌套循环时,是在迭代中使用迭代,与命令运行的次数是乘积关系。

 

1:在for循环中嵌套for循环。这个被嵌套的循环(也称为内部循环,inner loop)会在外部循环的每次迭代中遍历一次它所有的值。两个循环的do和done命令没有任何差别。bash shell知道当第一个done命令执行时是指内部循环而非外部循环。

 

[21:43:49 root@libin3 libin]# vim shell77

#!/bin/bash

# using for loops more

for (( libin1=1; libin1<=3;libin1++ ))

do

echo "start loop $libin1:"

for (( libin2=1; libin2<=5; libin2++))

do

echo "This is loops: $libin2"

done

done

 

[21:55:34 root@libin3 libin]# chmod +x shell77

[21:58:53 root@libin3 libin]# ./shell77

start loop 1:

This is loops: 1

This is loops: 2

This is loops: 3

This is loops: 4

This is loops: 5

start loop 2:

This is loops: 1

This is loops: 2

This is loops: 3

This is loops: 4

This is loops: 5

start loop 3:

This is loops: 1

This is loops: 2

This is loops: 3

This is loops: 4

This is loops: 5

 

 

 

2:在while循环内放置一个for循环

 

示例1

[22:45:42 root@libin3 libin]# vim shell78

#!/bin/bash

# using while and for command

libin1=5

while [ $libin1 -ge 0 ]

do

echo "the loop: $libin1"

 

for (( libin2=1; libin2<3; libin2++ ))

do

libin3=$[ $libin1 * $libin2 ]

echo "the loop: $libin1 * $libin2 = $libin3"

done

libin1=$[ $libin1 - 1 ]

done

 

[22:47:30 root@libin3 libin]# chmod +x shell78

[22:46:16 root@libin3 libin]# ./shell78

the loop: 5

the loop: 5 * 1 = 5

the loop: 5 * 2 = 10

the loop: 4

the loop: 4 * 1 = 4

the loop: 4 * 2 = 8

the loop: 3

the loop: 3 * 1 = 3

the loop: 3 * 2 = 6

the loop: 2

the loop: 2 * 1 = 2

the loop: 2 * 2 = 4

the loop: 1

the loop: 1 * 1 = 1

the loop: 1 * 2 = 2

the loop: 0

the loop: 0 * 1 = 0

the loop: 0 * 2 = 0

===============================================================================

 

示例2:如果嵌套语句有点难已理解,那我们就将其拆分来看

1C 语言的 for 命令

[22:50:19 root@libin3 libin]# vim shell79

#!/bin/bash

libin1=5

for (( libin2=1; libin2<3; libin2++ ))                 #libin2<3这里限定了只会执行2

do

libin3=$[ $libin1 * $libin2 ]

echo "the loop: $libin1 * $libin2 = $libin3"

done

 

[22:51:44 root@libin3 libin]# chmod +x shell79

[22:51:57 root@libin3 libin]# ./shell79

the loop: 5 * 1 = 5

the loop: 5 * 2 = 10

 

2while语句拆分

[22:51:59 root@libin3 libin]# vim shell80

#!/bin/bash

# using while and for command

libin1=5

while [ $libin1 -ge 0 ]                     #-ge大于等于0,说明一共会执行5

do

echo "the loop: $libin1"

libin1=$[ $libin1 - 1 ]

done

[22:52:35 root@libin3 libin]# chmod +x shell80

[22:52:46 root@libin3 libin]# ./shell80

the loop: 5

the loop: 4

the loop: 3

the loop: 2

the loop: 1

the loop: 0

 

注:由于while嵌套了c语言for循环,因此,while每执行一次,for会执行2次,libin1=$[ $libin1 - 1 ],在后面又决定了2for的结果迭代下一条的结果要将libin1-1。

 

 

 

3:shell能够区分开内部for循环和外部while循环各自的do和done命令

 

示例1内建变量scale:保留多少个小数位
[23:32:00 root@libin3 libin]# vim shell83 

#!/bin/bash

#using until and while loops

libin1=3

until [ $libin1 -eq 0 ]                            #只要当$libin1不等于0

do

echo "this is loop: $libin1"

libin2=1

while [ $libin2 -lt 5 ]                             #-lt:小于,决定了会执行4

do

libin3=$(echo "scale=4; $libin1 / $libin2" | bc)

echo " the loop: $libin1 / $libin2 = $libin3"

libin2=$[ $libin2 + 1 ]

done

libin1=$[ $libin1 - 1 ]

done

 

[23:37:52 root@libin3 libin]# chmod +x shell83

[23:32:17 root@libin3 libin]# ./shell83

this is loop: 3

 the loop: 3 / 1 = 3.0000

 the loop: 3 / 2 = 1.5000

 the loop: 3 / 3 = 1.0000

 the loop: 3 / 4 = .7500

this is loop: 2

 the loop: 2 / 1 = 2.0000

 the loop: 2 / 2 = 1.0000

 the loop: 2 / 3 = .6666

 the loop: 2 / 4 = .5000

this is loop: 1

 the loop: 1 / 1 = 1.0000

 the loop: 1 / 2 = .5000

 the loop: 1 / 3 = .3333

 the loop: 1 / 4 = .2500

 

 

注:外部的until循环以值3开始,并继续执行到值等于0。内部while循环以值1开始并一直执行,只要值小于5。每个循环都必须改变在测试条件中用到的值,否则循环就会无止尽进行下去。

===============================================================================

示例2下面为拆分的写法

[23:14:34 root@libin3 libin]# vim shell81    

#!/bin/bash

#using until and while loops

libin1=3

until [ $libin1 -eq 0 ]

do

echo "this is loop: $libin1"

libin1=$[ $libin1 - 1 ]

done

 

[23:14:54 root@libin3 libin]# chmod +x shell81

[23:15:03 root@libin3 libin]# ./shell81

this is loop: 3

this is loop: 2

this is loop: 1

===============================================================================

[23:42:59 root@libin3 libin]# vim shell82

#!/bin/bash

#using until and while loops

libin1=3

libin2=1

while [ $libin2 -lt 5 ]

do

libin3=$(echo "scale=4; $libin1 / $libin2" | bc)

echo " the loop: $libin1 / $libin2 = $libin3"

libin2=$[ $libin2 + 1 ]                   #可以看出$libin2 为在迭代下一循环的变量

done

 

[23:43:17 root@libin3 libin]# chmod +x shell82

[23:43:16 root@libin3 libin]# ./shell82

 the loop: 3 / 1 = 3.0000

 the loop: 3 / 2 = 1.5000

 the loop: 3 / 3 = 1.0000

 the loop: 3 / 4 = .7500

 

13.6 循环处理文件数据

 

1使用嵌套循环

2修改IFS环境变量

通过修改IFS环境变量,就能强制for命令将文件中的每行都当成单独的一个条目来处理,即便数据中有空格也是如此。一旦从文件中提取出了单独的行,可能需要再次利用循环来提取行中的数据。

 

 

1:如处理/etc/passwd文件中的数据,处理/etc/passwd文件中的数据,你逐行遍历/etc/passwd文件,并将IFS变量的值改成冒号,这样就能分隔开每行中的各个数据段

 

[20:31:07 root@libin3 libin]# vim shell84

#!/bin/bash

#change the IFS value

 

IFS.OLD=$IFS

IFS=$'\n'

for i in $( cat /etc/passwd)

do

echo "the value in $i -"

IFS=:

for i in $i

do

echo "$i"

done

Done

 

[20:31:07 root@libin3 libin]# chmod +x shell84

[20:32:26 root@libin3 libin]# ./shell84

rhce

x

1006

1006

i am is libin,technology,10086,10086

/home/rhce

/bin/csh

 

注:该脚本使用了两个不同的IFS值来解析数据,第一个IFS值解析出/etc/passwd文件中的单独的行。内部for循环接着将IFS的值修改为冒号,允许你从/etc/passwd的行中解析出单独的值。

 

13.7 控制循环

1)break命令

2)continue命令

13.7.1 break 命令

1. 跳出单个循环

在shell执行break命令时,它会尝试跳出当前正在执行的循环。

1:for语句与break语句,但感觉写的没有体现出break的作用,把break替换成echo也是同样的输出

[21:25:12 root@libin3 libin]# vim shell85

#!/bin/bash

#breaking out of a for loop

for i in 2 3 4 5 6

do

if [ $i -eq 1 ]

then

break

fi

echo "the number: $i"

done

echo "the for loop is complate"

 

 

[21:50:55 root@libin3 libin]# chmod +x shell85

[21:50:52 root@libin3 libin]# ./shell85

the number: 2

the number: 3

the number: 4

the number: 5

the number: 6

the for loop is complate

 

注:for循环通常都会遍历列表中指定的所有值。但当满足if-then的条件时,shell会执行break命令,停止for循环。

 

2:while与until语句的使用,从两个例子我们可以看出,break就是在控制内循环,但是变量的值一定要不符合外循环的条件

[22:13:09 root@libin3 libin]# vim shell86

#!/bin/bash

#break command in while and until V2

libin1=1

while [ $libin1 -lt 10 ]

do

if [ $libin1 -eq 5 ]

then

break

fi

echo "this is: $libin1"

libin1=$[$libin1 + 1]

done

echo "the while loop is completed"

 

[22:12:58 root@libin3 libin]# chmod +x shell86

[22:13:06 root@libin3 libin]# ./shell86

this is: 1

this is: 2

this is: 3

this is: 4

the while loop is completed

2. 跳出内部循环

在处理多个循环时,break命令会自动终止你所在的最内层的循环。

[22:28:43 root@libin3 libin]# vim shell87

#!/bin/bash

#breaking the inner loop

for (( a = i; a<4; a++ ))

do

echo "see loop value is : $a"

for (( b = 1; b < 20; b++ ))

do

if [ $b -eq 5 ]

then

break

fi

echo "the loop is: $b"

done

done

 

[22:29:15 root@libin3 libin]# chmod +x shell87

[22:29:14 root@libin3 libin]# ./shell87

see loop value is : 0

the loop is: 1

the loop is: 2

the loop is: 3

the loop is: 4

see loop value is : 1

the loop is: 1

the loop is: 2

the loop is: 3

the loop is: 4

see loop value is : 2

the loop is: 1

the loop is: 2

the loop is: 3

the loop is: 4

see loop value is : 3

the loop is: 1

the loop is: 2

the loop is: 3

the loop is: 4

 

注:

for (( a = i; a<4; a++ ))这个值只会输出:

see loop value is : 1

see loop value is : 2

see loop value is : 3

see loop value is : 4

 

 

如果我们把for (( b = 1; b < 20; b++ ))改成for (( b = 5; b < 20; b++ ))时的输出就是:

see loop value is : 0

see loop value is : 1

see loop value is : 2

see loop value is : 3

这里if [ $b -eq 5 ]也决定了只会有$b的值只会到4

总结:

内部循环里的for语句指明当变量b等于20时停止迭代。但内部循环的if-then语句指明当

变量b的值等于5时执行break命令。注意,即使内部循环通过break命令终止了,外部循环依然继续执行。i代表从0开始

[22:38:46 root@libin3 libin]# vim shell87

king the inner loop

for (( a = i; a<4; a++ ))

do

echo "see loop value is : $a"

for (( b = 4; b < 20; b++ ))

do

if [ $b -eq 5 ]

then

break

fi

echo "the loop is: $b"

done

done

 

[22:41:34 root@libin3 libin]# ./shell87

see loop value is : 0

the loop is: 4

see loop value is : 1

the loop is: 4

see loop value is : 2

the loop is: 4

see loop value is : 3

the loop is: 4

3. 跳出外部循环

有时你在内部循环,但需要停止外部循环。break命令接受单个命令行参数值:

break -n

其中n指定了要跳出的循环层级。默认情况下,n1,表明跳出的是当前的循环。如果你 n设为2break命令就会停止下一级的外部循环。

 

1

[22:54:45 root@libin3 libin]# vim shell88

#!/bin/bash

#break using loop V3

for ((a=1; a<4; a++))

do

echo "see loop value is: $a"

for ((b=1; b<20; b++))

do

if [ $b -gt 4 ]

then

break 2

fi

echo "the loop: $b"

done

done

 

[22:51:15 root@libin3 libin]# chmod +x shell88

[22:54:53 root@libin3 libin]# ./shell88

see loop value is: 1

the loop: 1

the loop: 2

the loop: 3

the loop: 4

13.7.2 continue 命令

continue命令可以提前中止某次循环中的命令,但并不会完全终止整个循环。可以在循环内部设置shell不执行命令的条件。

 

1for循环中使用continue命令

[23:24:17 root@libin3 libin]# vim shell89

#!/bin/bash

#using the continue command

for ((libin1=1; libin1<15; libin1++))

do

if [ $libin1 -gt 5 ] && [ $libin1 -lt 10 ]

then

continue

fi

echo "the loop value: $libin1"

done

 

[23:25:27 root@libin3 libin]# chmod +x shell89

[23:25:20 root@libin3 libin]# ./shell89

the loop value: 1

the loop value: 2

the loop value: 3

the loop value: 4

the loop value: 5

the loop value: 10

the loop value: 11

the loop value: 12

the loop value: 13

the loop value: 14

 

注:当if-then语句的条件被满足时(值大于5且小于10),shell会执行continue命令,跳过此次循环中剩余的命令,但整个循环还会继续。因此6-9会被中止。。当if-then的条件不再被满足时,又会继续。

2:whileuntil中使用continue命令,shell执行continue命令时,它会跳过剩余的命令。如果你在其中某个条件里对测试条件变量进行增值,就会出现问题

[23:39:58 root@libin3 libin]# vim shell90

#!/bin/bash

#import using continue command in the while and until

libin1=0

while echo "while value: $libin1"

[ $libin1 -lt 15 ]

do

if [ $libin1 -gt 5 ] && [ $libin1 -lt 10 ]

then

continue

fi

echo "the value number is: $libin1"

libin1=$[ $libin1 +  1 ]

done

 

[23:43:55 root@libin3 libin]# chmod +x shell90

[23:45:19 root@libin3 libin]# ./shell90 | head -n20

[23:56:45 root@libin3 libin]# ./shell90 | head -n20

while value: 0

the value number is: 0

while value: 1

the value number is: 1

while value: 2

the value number is: 2

while value: 3

the value number is: 3

while value: 4

the value number is: 4

while value: 5

the value number is: 5

while value: 6

while value: 6

while value: 6

while value: 6

while value: 6

while value: 6

while value: 6

while value: 6

。。。。。。

注:需要确保将脚本的输出重定向到了more命令,这样才能停止输出。在if-then的条件成立之前,都很正常,当执行了执行continue命令时,它跳过了while循环中余下的命令。被跳过的部分正是$libin1计数变量增值的地方,而这个变量又被用于while测试命令中,这意味着这个变量的值不会再变化。

 

2和break命令一样,continue命令也允许通过命令行参数指定要继续执行哪一级循环:其中n定义了要继续的循环层级。

continue n

[23:56:47 root@libin3 libin]# vim shell91

#!/bin/bash

#continue and other command using

for ((libin1=1; libin1<=5; libin1++))

do

echo "the value is: $libin1"

for (( libin2=1; libin2<3; libin2++))

do

if [ $libin1 -gt 2 ] && [ $libin1 -lt 4 ]

then

continue 2

fi

libin3=$[$libin1 * $libin2 ]

echo "the result are $libin1 * $libin2 is $libin3"

done

done

 

[00:34:16 root@libin3 libin]# chmod +x shell91

[00:34:37 root@libin3 libin]# ./shell91

the value is: 1

the result are 1 * 1 is 1

the result are 1 * 2 is 2

the value is: 2

the result are 2 * 1 is 2

the result are 2 * 2 is 4

the value is: 3

the value is: 4

the result are 4 * 1 is 4

the result are 4 * 2 is 8

the value is: 5

the result are 5 * 1 is 5

the result are 5 * 2 is 10

注:此处用continue命令来停止处理循环内的命令,但会继续处理外部循环。值为3的那

次迭代并没有处理任何内部循环语句,因为尽管continue命令停止了处理过程,但外部循环依然会继续。

 

13.8 处理循环的输出

 

在shell脚本中,你可以对循环的输出使用管道或进行重定向。这可以通过在done命令之后添加一个处理命令来实现。

 

1:你可以对循环的输出使用管道或进行重定向,可以通过在done命令之后添加一个处理命令。shell会将for命令的结果重定向到文件/libin/libin/libin/shell92.txt中,而不是显示在屏幕上。

 

[21:02:31 root@libin3 libin]# vim shell92

#!/bin/bash

#appare for file /home/student

for i in /home/student/*

do

if [ -d "$i" ]

then echo "$i,this is dir"

elif [ -f "$i" ]

then echo "$i,this is file"

else

echo "$i,this not does dir and file"

fi

done > /libin/libin/libin/shell92.txt

 

[20:52:20 root@libin3 libin]# chmod +x shell92

[21:02:29 root@libin3 libin]# ./shell92

 

[21:03:32 root@libin3 libin]# cat /libin/libin/libin/shell92.txt

/home/student/centos-release-7-9.2009.0.el7.centos.x86_64.rpm,this is file

/home/student/openssh-8.8p1-1.s12.rpms.bundle.sp3(1).tar,this is file

/home/student/openssh-8.8p1.tar.gz,this is file

/home/student/test,this is file

 

2:shell创建了文件 /libin/libin/libin/shell92-1.txt并将for命令的输出重定向到这个文件。shell在for命令之后正常显示了echo语句。

 

[21:06:59 root@libin3 libin]# vim shell93

!/bin/bash

# using for output to a file

for ((libin1=1; libin1<10; libin1++))

do

echo "the number value is: $libin1"

done>/libin/libin/libin/shell92-1.txt

echo "the command is finished"

 

[21:09:39 root@libin3 libin]# chmod +x shell93

[21:09:47 root@libin3 libin]# ./shell93

the command is finished

 

[21:11:32 root@libin3 libin]# cat /libin/libin/libin/shell92-1.txt

the number value is: 1

the number value is: 2

the number value is: 3

the number value is: 4

the number value is: 5

the number value is: 6

the number value is: 7

the number value is: 8

the number value is: 9

 

13.9 实例

13.9.1 查找可执行文件

当从命令运行一个程序时,Linux系统会搜索到一系列的目录来查找对应文件,这些目录被定义在环境变量PATH中。如果需要找出系统中哪些可执行的文件可以使用,就只需要扫描PATH环境变量中所有的目录就行了。

 

1创建一个for循环语句,对环境变量目录进行迭代,在处理的时候需要设置IFS分隔符号。

 

IFS=:

for libin in $PATH

do

表示我已经将目录存放在变量$libin中,可以用另外一个for循环来迭代特定目录中的所有文件。

for file in $libin/*

do

最后就是检查文件是否具有可执行权限,可以使用if-then语句进行测试

if [ -x $file ]

then

echo " $file"

fi

 

实战1

[21:23:56 root@libin3 libin]# vim shell94

#!/bin/bash

#find file the PATH and test -x

IFS=:

for libin in $PATH

do

echo "$libin"

for file in $libin/*

do

if [ -x $file ]

then

echo " $file"

fi

done

done

 

[21:26:47 root@libin3 libin]# chmod +x shell94

[21:28:15 root@libin3 libin]# ./shell94 | head -n 10

/usr/local/bin

 /usr/local/bin/cifsiostat

 /usr/local/bin/iostat

 /usr/local/bin/libin.sh

 /usr/local/bin/mpstat

 /usr/local/bin/pidstat

 /usr/local/bin/sadf

 /usr/local/bin/sar

 /usr/local/bin/tapestat

/usr/local/sbin

...由于可执行文件太多,我只看前10

 

实战2:如果我们嫌弃屏幕无法去查看我们所有的结果,我们可以把所有输出多通过追加到一个CSV文件,这样就能很很清楚的去查看了

[21:38:26 root@libin3 libin]# vim shell94

#!/bin/bash

#find file the PATH and test -x

IFS=:

for libin in $PATH

do

echo "$libin,success">>/libin/libin/libin/shell94.csv

for file in $libin/*

do

if [ -x $file ]

then

echo " $file,success" >>/libin/libin/libin/shell94.csv

fi

done

done

 

[21:39:13 root@libin3 libin]# ./shell94

[21:39:15 root@libin3 libin]# cat /libin/libin/libin/shell94.csv  | head -n 10

/usr/local/bin,success

 /usr/local/bin/cifsiostat,success

 /usr/local/bin/iostat,success

 /usr/local/bin/libin.sh,success

 

13.9.2 创建多个用户账户

不用为每个需要创建的新用户账户手动输入useradd命令,而是可以将需要添加的新用户账户放在一个文本文件中,然后创建一个简单的脚本进行处理。这个文本文件的格式如下:

 

userid,user name

1个条目为新用户账户所选用的用户ID

2个条目就是用户的全名

两者之间用逗号分隔。这样就形成了一种名为逗号分隔值的文件格式(称为CSV)。这种文件格式在我们日常运维中基本都需要用到,已备我们读取数据及结果。

 

 

 

例子:我们首先需要用IFS分隔符号设置为逗号,并将其放入while语句的条件测试部分。再read命令读取文件中的各行。

 

 

 

while IFS=’,’ read –r userid name

read命令会自动读取.csv文本文件的下一行内容,所以不需要专门再写一个循环来处理。当read命令返回FALSE时(也就是读取完整个文件时),while命令就会退出

 

 

 

 

实战1:创建用户,注意需要root权限

 

[22:02:05 root@libin3 libin]# vim  useradd.csv

libin100,libin100 1900

libin101,libin101 1901

libin102,libin102 1902

libin103,libin103 1903

[22:02:28 root@libin3 libin]# vim shell95

#!/bin/bash

#useradd using while command -c:备注 -m:自动为用户茶创建家登入目录,-r:以,为分隔符合

input="useradd.csv"

while IFS=',' read -r userid name

do

echo "adding $userid"

useradd -c "$name" -m $userid

done < "$input"

echo "helloword" | passwd --stdin libin100         #甚至可以设置用户密码

 

[22:06:19 root@libin3 libin]# chmod +x shell95

[22:06:36 root@libin3 libin]# ./shell95

adding libin100

adding libin101

adding libin102

adding libin103

 

[22:08:31 root@libin3 libin]# tail /etc/passwd -n4

libin100:x:1007:1009:libin100 1900:/home/libin100:/bin/bash

libin101:x:1008:1010:libin101 1901:/home/libin101:/bin/bash

libin102:x:1009:1011:libin102 1902:/home/libin102:/bin/bash

libin103:x:1010:1012:libin103 1903:/home/libin103:/bin/bash

 

 

 

第十四章 处理用户输入

 

14.1 命令行参数

向shell脚本传递数据的最基本方法是使用命令行参数,命令行参数允许在运行脚本时向命令行添加数据。 

1:本例向脚本libin传递了两个命令行参数(10和30),脚本会通过特殊的变量来处理命令行参数。

 

./libin 10 30

 

14.1.1 读取参数

bash shell会将一些称为位置参数(positional parameter)的特殊变量分配给输入到命令行中的

所有参数。这也包括shell所执行的脚本名称。位置参数变量是标准的数字:

$0是程序名 

$1是第一个参数

$2是第二个参数 

依次类推 

直到第九个参数$9

1:在命令行用到数值

 

实战1:输入单个参数

[23:34:58 root@libin3 libin]# vim shell96

#!/bin/bash

#using one command line parameter

#

rhce=1

for ((libin1=1; libin1<=$1; libin1++))

do

rhce=$[ $rhce * $libin1 ]          这之间就会产生结果迭代

done

echo the value of $1 is $rhce

 

[23:35:20 root@libin3 libin]# chmod +x shell96

[23:49:31 root@libin3 libin]# ./shell96 1

the value of 1 is 1

[23:49:39 root@libin3 libin]# ./shell96 2

the value of 2 is 2

[23:49:43 root@libin3 libin]# ./shell96 3

the value of 3 is 6

[23:49:46 root@libin3 libin]# ./shell96 4

the value of 4 is 24

[23:49:48 root@libin3 libin]# ./shell96 5

the value of 5 is 120

 

实战2:输入更多的参数时,每个参数需要用空格隔开

[23:56:02 root@libin3 libin]# vim shell97

#!/bin/bash

#testing two command line parameters

#

rhcsa=$[ $1 * $2 ]

echo "one value is $1"

echo "two value is $2"

echo "the total value is $rhcsa"

 

[23:59:18 root@libin3 libin]# chmod +x shell97

[00:00:16 root@libin3 libin]# ./shell97 2 3

one value is 2

two value is 3

the total value is 6

 

2:可以在命令行上用文本字符串将文本字符串作为参数传递时,引号并非数据的一部分。它们只是表明数据的起止位置

 

实战1每个参数都是用空格分隔的,所以shell会将空格当成两个值的分隔符。要在参数值中

包含空格,必须要用引号(单引号或双引号均可)

 

 

[00:00:32 root@libin3 libin]# vim shell98

#!/bin/bash

#testing string parameters

#

echo hello $1,welcome to study linux

 

[00:04:52 root@libin3 libin]# chmod +x shell98

[00:05:02 root@libin3 libin]# ./shell98 libin

hello libin,welcome to study linux

 

[00:06:10 root@libin3 libin]# ./shell98 'libin rhca'

hello libin rhca,welcome to study linux

 

例子3:如果脚本需要的命令行参数不止9个,需要稍微修改一下变量名,第9个变量之后,你必须在变量数字周围加上花括号,比如${10}。

 

[00:06:38 root@libin3 libin]# cp shell97 shell99

[00:08:51 root@libin3 libin]# vim shell99

#!/bin/bash

#testing two command line parameters

#

rhcsa=$[ ${10} * ${11} ]

echo "one value is ${10}"

echo "two value is ${11}"

echo "the total value is $rhcsa"

 

[00:16:45 root@libin3 libin]# ./shell99 12

[00:17:14 root@libin3 libin]# ./shell99 1 2 3 4 5 6 7 8 9 10 11

one value is 10

two value is 11

the total value is 110

 

14.1.2 读取脚本名

可以用$0参数获取shell在命令行启动的脚本名。这在编写多功能工具时很方便。

1: 但是存在一个问题,如果用命令来运行shell脚本,命令会和脚本混在一起,出现在$0参数中。当传给$0变量的实际字符串不仅仅是脚本名,而是完整的脚本路径时,变量$0就会使用整个路径。

[16:53:28 root@libin3 libin]# vim shell100

#!/bin/bash

# testing the $0 parameter

echo the zero parameter is$0

#

[17:04:46 root@libin3 libin]# chmod +x shell100

[17:06:14 root@libin3 libin]# bash shell100

the zero parameter isshell100

[17:06:17 root@libin3 libin]# ./shell100

the zero parameter is./shell100

[17:10:52 root@libin3 libin]# bash /libin/libin/shell100

the zero parameter is/libin/libin/shell100

 

2:basename命令会返回不包含路径的脚本名

[17:11:03 root@libin3 libin]# vim shell101

#!/bin/bash

# using basename

libin=$(basename $0)

echo

echo "This is libin: $libin"

 

[17:20:22 root@libin3 libin]# chmod +x shell101

[17:20:28 root@libin3 libin]# ./shell101

 

This is libin: shell101

 

3:写基于脚本名执行不同功能的脚本shell102脚本创建了两个不同的文件名,一个通过复制文件创建suse),另外一个通过链接创建(centos),在两种情况下都会先获得脚本的基本名称,然后根据该值执行相应的功能。

[21:47:42 root@libin3 libin]# vim shell102

#!/bin/bash

#testing a using script

#

libin1=$(basename $0)           #suse执行表示$libin1=$suse,对比两个脚本名

#

if [ $libin1 = "suse" ]

then

libin2=$[ $1 + $2 ]

#

elif [ $libin1 = "centos" ]          #centsos执行表示libin1=centos

then

libin2=$[ $1 * $2 ]

fi

#

echo the value is $libin2

[21:48:16 root@libin3 libin]# chmod +x shell102

[21:48:33 root@libin3 libin]# cp shell102 suse

[21:50:01 root@libin3 libin]# ln -s shell102 centos

[21:50:09 root@libin3 libin]# ls -l *os

lrwxrwxrwx 1 root root 8 8月  25 21:50 centos -> shell102

[21:50:11 root@libin3 libin]# ./suse 2 5

the value is 7

[21:50:25 root@libin3 libin]# ./centos 2 5

the value is 10

14.1.3 测试参数-n

1shell脚本使用命令行参数时要小心。如果脚本不加参数运行就会出现问题。当脚本认为参数变量中会有数据而实际上并没有时,脚本很有可能会产生错误消息。在使用参数前需要检查其中是否存在数据。

[22:07:02 root@libin3 libin]# ./suse 2

./suse:8: 2 +  : 语法错误: 期待操作数 (错误符号是 "+  "

the value is

 

2:使用了-n测试来检查命令行参数$1中是否有数据

[22:12:49 root@libin3 libin]# vim shell103

#!/bin/bash

# testing parameter before using

#

if [ -n "$1" ]

then

echo hello $1,welcome to your study linux

else

echo "sorry,not does yourself"

fi

 

[22:16:46 root@libin3 libin]# chmod +x shell103

[22:16:58 root@libin3 libin]# ./shell103

sorry,not does yourself

[22:17:03 root@libin3 libin]# ./shell103 libin

hello libin,welcome to your study linux

[22:17:28 root@libin3 libin]# ./shell103 "libin rhca"

hello libin rhca,welcome to your study linux

14.2 特殊参数变量

在bash shell中有些特殊变量,它们会记录命令行参数。

14.2.1 参数统计$#

特殊变量$#含有脚本运行时携带的命令行参数的个数,可以在脚本中任何地方使用这个特殊变量,就跟普通变量一样。

1:

[23:23:29 root@libin3 libin]# vim  shell104

[23:26:50 root@libin3 libin]# cat shell104

#!/bin/bash

#using $# parameters

echo the $# using backing

 

[23:25:29 root@libin3 libin]# chmod +x shell104

[23:25:34 root@libin3 libin]# ./shell104

the 0 using backing

[23:26:54 root@libin3 libin]# ./shell104 1

the 1 using backing

[23:27:11 root@libin3 libin]# ./shell104 1 2 3 4 5 6

the 6 using backing

[23:27:31 root@libin3 libin]# ./shell104 libin

the 1 using backing

 

2:使用参数前测试参数的总数

[23:32:05 root@libin3 libin]# vim shell105

#!/bin/bash

#testing parameter

#

if [ $# -ne 2 ]                 #-ne是否不等于

then

echo the shell105 is success

else

total=$[$1 + $2]

echo the parameter total is $total

fi

 

[23:32:01 root@libin3 libin]# chmod +x shell105

[23:32:16 root@libin3 libin]# ./shell105

the shell105 is success

[23:33:47 root@libin3 libin]# ./shell105 1 2      #这时相当于有 $0,$1,$2

the parameter total is 3

[23:33:52 root@libin3 libin]# ./shell105 1 3

the parameter total is 4

[23:33:56 root@libin3 libin]# ./shell105 1 4

the parameter total is 5

 

3变量${!#}就代表了最后 一个命令行参数变量。不能在花括号内使用美元符。必须将美元符换成感叹号。

[23:45:38 root@libin3 libin]# vim shell106

#!/bin/bash

#test last parameter

echo the last parameter was ${!#}

 

[23:44:53 root@libin3 libin]# chmod +x shell106

[23:46:45 root@libin3 libin]# ./shell106 1 2 3 3 4

the last parameter was 4

[23:46:47 root@libin3 libin]# ./shell106 1 2 3 3 4 1111111111 111111111111112333

the last parameter was 111111111111112333

[23:46:53 root@libin3 libin]# ./shell106

the last parameter was ./shell106

14.2.2 抓取所有的数据

 

$*$@变量可以用来轻松访问所有的参数。这两个变量都能够在单个变量中存储所有的命令行参数。

(1)$*变量会将命令行上提供的所有参数当作一个单词保存。这个单词包含了命令行中出现的每一个参数值。基本上$*变量会将这些参数视为一个整体,而不是多个个体。

2$@变量会将命令行上提供的所有参数当作同一字符串中的多个独立的单词。这样你就能够遍历所有的参数值,得到每个参数。这通常通过for命令完成

 

 

1:\:转义,放止变量被调用

 

[19:55:03 root@libin3 libin]# vim shell107

#!/bin/bash

#

libin=1

#

for i in "$*"

do

echo "\$* are #$libin = $i"

libin=$[ $libin + 1 ]

done

 

echo

libin2=1

for i in "$@"

do

echo "\$@ are  #$libin = $i"

libin=$[ $libin + 1 ]

done

 

[19:58:54 root@libin3 libin]# chmod +x shell107

[19:59:03 root@libin3 libin]# ./shell107

 

[20:02:21 root@libin3 libin]# ./shell107 rhcsa rhce rhca hcia

$* are #1 = rhcsa rhce rhca hcia

 

$@ are  #2 = rhcsa

$@ are  #3 = rhce

$@ are  #4 = rhca

$@ are  #5 = hcia

 

 

 

2$# ${!#}$*$@的全量使用概括

 

[20:09:23 root@libin3 libin]# cp shell107 shell108

#!/bin/bash

#using $* $@ command

#*******************1($#:表示为命令行有几个变量的参数)*********

libin1=1

#

for i in "$#"

do

echo "\$# this is: $i"

done

#*******************2${!#}: 表示命令行的最后一个参数)*********

libin1=1

#

for i in "${!#}"

do

echo "\${!#} this is: $i"

done

#******************3($*: 表示将命令行的参数看作一个单词)******

libin1=1

#

for i in "$*"

do

echo "\$*  this is: $i"

done

#******************4($@: 表示将命令行的参数,以默认的空格为转义符,看作单个单词)

libin=1

#

for i in "$@"

do

echo "\$@ this is: $i"

done

#********************(结束)****************************************

 

[20:45:38 root@libin3 libin]# ./shell108 libin1 libin2 libin3

$# this is: 3

${!#} this is: libin3

$*  this is: libin1 libin2 libin3

$@ this is: libin1

$@ this is: libin2

$@ this is: libin3

 

 

14.3 移动变量

 

bash shell的shift命令能够用来操作命令行参数。shift命令会根据它们的相对位置来移动命令行参数。

在使用shift命令时,默认情况下它会将每个参数变量向左移动一个位置。所以,变量$3的值会移到$2中,变量$2的值会移到$1中,而变量$1的值则会被删除(注意,变量$0的值,也就是程序名,不会改变)。

 

1:通过测试第一个参数值的长度执行了一个while循环。当第一个参数的长度为零时,循环结束。测试完第一个参数后,shift命令会将所有参数的位置移动一个位置。

 

实战1

[21:22:11 root@libin3 libin]# vim shell109

#!/bin/bash

#using shift command

echo ""

libin=1

while [ -n "$1" ] #检测$1是否有数据

do

echo "this is $libin = $1"

libin=$[ $libin + 1 ]

shift

done

 

[21:27:55 root@libin3 libin]# ./shell109 rhcsa rhce rhca hcia

 

this is 1 = rhcsa

this is 2 = rhce

this is 3 = rhca

this is 4 = hcia

 

实战22次再去执行时命令行的的参数就会移动一个位置

[00:01:25 root@libin3 libin]# vim shell109

#!/bin/bash

#using shift command

echo ""

while [ -n "$1" ] #检测$1是否有数据

do

echo "this is #$libin = $1"

libin=$[ $libin + 1 ]

shift

done

 

[00:01:24 root@libin3 libin]# ./shell109 rhcsa rhce rhca hcia

 

this is # = rhcsa

this is #1 = rhce

this is #2 = rhca

this is #3 = hcia

 

注:使用shift命令的时候要小心。如果某个参数被移出,它的值就被丢弃了,无法再恢复

 

 

 

2:可以一次性移动多个位置,需要给shift命令提供一个参数,指明要移动的位置数。

 

[00:04:01 root@libin3 libin]# vim shell110

!/bin/bash

#using mv shift

#

echo ""

echo "The value is: $*"

shift 3

echo "this is first value: $1"

 

[00:09:45 root@libin3 libin]# chmod  +x shell110

[00:09:55 root@libin3 libin]# ./shell110 10 11 12 13

 

The value is: 10 11 12 13

this is first value: 13

 

14.4 处理选项

选项是跟在单破折线后面的单个字母,它能改变命令的行为。

14.4.1 查找选项

1. 处理简单选项

1:在提取每个单独参数时,用case语句)来判断某个参数是否为选项。case语句会检查每个参数是不是有效选项。如果是就运行对应case语句中命令。我们不管对命令行的参数怎么排序都不影响结果。

[00:10:53 root@libin3 libin]# vim shell111

#!/bin/bash

#处理简单的选项

#

echo ""

while [ -n "$1" ]

do

case "$1" in

-a)

echo "this is -a";;

-b)

echo "this is -b";;

-c)

echo "this is -c";;

*)

echo "$1 is not exist";;

esac

shift

done

 

[00:22:22 root@libin3 libin]# vim shell111

[00:25:22 root@libin3 libin]# chmod +x shell111

[00:25:31 root@libin3 libin]# ./shell111 -a -b -c -d

 

this is -a

this is -b

this is -c

-d is not exist

2. 分离参数和选项

同时处理使用选项参数的情况,Linux中处理这个问题的标准方式是用特殊字符来将二者分开,该字符会告诉脚本何时选项结束以及普通参数何时开始。

特殊字符是双破折线(--。shell会用双破折线来表明选项列表结束。双破折线之后,脚本就可以放心地将剩下的命令行参数当作参数,而不是选项来处理了。

 

1:要检查双破折线,在case语句中加一项。在遇到双破折线时,脚本用break命令来跳出while循环。过早地跳出了循环,需要再加一条shift命令来将双破折线移出参数变量。

[00:38:41 root@libin3 libin]# vim shell112

#!/bin/bash

#分离参数和选项

echo

while [ -n "$1" ]

do

case "$1" in

-a) echo "this is -a";;

-b) echo "this is -b";;

-c) echo "this is -c";;

--) shift

    break ;;

*) echo "$1 is not exist";;

esac

shift

done

 

libin=1

for i in $@

do

echo "this is $libin: $i"

libin=$[ $libin + 1 ]

done

 

[00:46:45 root@libin3 libin]# chmod +x shell112

[00:48:24 root@libin3 libin]# ./shell112 -a -b -c rhcsa rhce rhca

 

this is -a

this is -b

this is -c

rhcsa is not exist

rhce is not exist

rhca is not exist

 

2:用双破折线来将命令行上的选项和参数划分开来,当脚本遇到双破折线时,它会停止处理选项,并将剩下的参数都当作命令行参数

[00:58:19 root@libin3 libin]# ./shell112 -a -b -c -- rhcsa rhce rhca

 

this is -a

this is -b

this is -c

this is 1: rhcsa

this is 2: rhce

this is 3: rhca

3. 处理带值的选项

当命令行选项要求额外的参数时,脚本必须能检测到并正确处理。

 

例子1:case语句定义了三个它要处理的选项,-b选项还需要一个额外的参数值。由于要处理的参数是$1,额外的参数值就应该位于$2(因为所有的参数在处理完之后都会被移出)。只要将参数值从$2变量中提取出来就可以了。这个选项占用了两个参数位,所以还需要使用shift命令多移动一个位置。

[01:13:51 root@libin3 libin]# vim shell113

#!/bin/bash

#!extra command line using and value

echo

while [ -n "$1" ]

do

case "$1" in

-a) echo "this is -a";;

-b) libin="$2"

echo "this is -b, the libin value $libin"

shift ;;

 

-c) echo "this is -c";;

--) shift

break ;;

*) echo "$1 is not value";;

esac

shift

done

#

libin2=1

for i in "$@"

do

echo "see the $libin2: $i"

libin2=$[ $libin2 + 1 ]

done

 

[01:10:30 root@libin3 libin]# chmod +x shell113

[01:14:19 root@libin3 libin]# ./shell113 -a -b rhca -d

 

 

this is -a

this is -b, the libin value rhca

-d is not value

 

[01:19:06 root@libin3 libin]# ./shell113 -b  rhca  -a -d

 

this is -b, the libin value rhca

this is -a

-d is not value

 

[01:22:05 root@libin3 libin]# ./shell109 -ac

this is 1 = -ac

 

注:shell脚本有了处理命令行选项的基本能力,但还是有限制。如果将多个选项放进一个参数中时,它就不能工作。

 

14.4.2 使用 getopt 命令

getopt命令是一个在处理命令行选项和参数时非常方便的工具。它能够识别命令行参数,从而在脚本中解析它们时更方便。

1、getopt命令格式:

getopt命令可以接受一系列任意形式的命令行选项和参数,并自动将它们转换成适当的格

式。

getopt optstring parameters(选择字符串 参数)

 

注:optstring它定义了命令行有效的选项字母,还定义了哪些选项字母需要参数值。

在optstring中列出你要在脚本中用到的每个命令行选项字母。然后,在每个需要参数值的选项字母后加一个冒号。getopt命令会基于你定义的optstring解析提供的参数。

注:getopt命令的一个高级版本getopts(复数形式)

1、optstring 定义了四个有效选项字母:a、b、c和d。冒号(:)被放在了字母b后面,因为b选项需要一个参数值。当getopt命令运行时,它会检查提供的参数列表(-A -B libin1 -CD libin2 libin3)并基于提供的optstring进行解析,会自动将-CD选项分成两个单独的选项,并插入双破折线来分隔行中的额外参数。

实战1

[11:14:56 root@libin3 libin]# getopt AB:CD -A -B libin1 -CD libin2 libin3

 -A -B libin1 -C -D -- libin2 libin3

2、 这样就会将4个字母对应各自的值

[11:15:10 root@libin3 libin]# getopt A:B:C:D -A libin0 -B libin1 -C libin2 -D libin3

 -A libin0 -B libin1 -C libin2 -D -- libin3

 

3、如果指定一个不在在optstring中的选项,默认情况下,getopt命令会产生一条错误消息。

示例1

[11:25:54 root@libin3 libin]# getopt A:B:C:D -A libin0 -B libin1 -C libin2 -D libin3 -E libin4

getopt:无效选项 -- E

 -A libin0 -B libin1 -C libin2 -D -- libin3 libin4

 

示例2:如果要忽视这条错误信息,需要在命令后加上-q选项,并且我们注意到,给D加上:就会去找到自己对应的参数值

[11:28:01 root@libin3 libin]# getopt -q A:B:C:D -A libin0 -B libin1 -C libin2 -D libin3 -E libin4

 -A 'libin0' -B 'libin1' -C 'libin2' -D -- 'libin3' 'libin4'

[11:28:12 root@libin3 libin]# getopt -q A:B:C:D: -A libin0 -B libin1 -C libin2 -D libin3 -E libin4

 -A 'libin0' -B 'libin1' -C 'libin2' -D 'libin3' -- 'libin4'

2. 在脚本中使用getopt

可以在脚本中使用getopt来格式化脚本所携带的任何命令行选项或参数。是用getopt命令生成的格式化后的版本来替换已有的命令行选项和参数。用set命令能够做到。

set命令的选项之一是双破折线(--),它会将命令行参数替换成set命令的命令行值。该方法会将原始脚本的命令行参数传给getopt命令,之后再将getopt命令的输出传给set命令,用getopt格式化后的命令行参数来替换原始的命令行参数。

set -- $(getopt -q ab:cd "$@")

 

1:

[15:51:02 root@libin3 libin]# vim shell114

#!/bin/bash

# Extract command  values with getopt

#

set -- $(getopt -q ab:cd "$@")

#

echo

while [ -n "$1" ]

do

case "$1" in

-a) echo "this is -a";;

-b) value="$2"

echo "this is -b,the value is $value"

shift;;

-c) echo "this is -c";;

--) shift

break;;

*) echo "$1 is not a value"

esac

shift

done

#

libin=1

for i in "$@"

do

echo "see the value is $libin: $i"

libin=$[ $libin + 1 ]

done

 

[15:59:01 root@libin3 libin]# chmod +x shell114

[15:59:09 root@libin3 libin]# ./shell114 -ac

this is -a

this is -c

 

[15:59:36 root@libin3 libin]# ./shell114 -a -b rhcsa -cd rhce rhca hcie

this is -a

this is -b,the value is 'rhcsa'

this is -c

-d is not a value

see the value is 1: 'rhce'

see the value is 2: 'rhca'

see the value is 3: 'hcie'

[16:02:20 root@libin3 libin]# ./shell114 -a -b rhcsa -cd "rhce rhca" hcie

 

this is -a

this is -b,the value is 'rhcsa'

this is -c

-d is not a value

see the value is 1: 'rhce

see the value is 2: rhca'

see the value is 3: 'hcie'

 

注:getopt命令并不擅长处理带空格和引号的参数值。它会将空格当作参数分隔符,而不是根

据双引号将二者当作一个参数。

 

14.4.3 使用更高级的 getopts

getopts命令(注意是复数)内建于bash shell。对比getopt多了一些扩展功能。

 

与getopt不同,前者将命令行上选项和参数处理后只生成一个输出,而getopts命令能够和已有的shell参数变量配合默契。

每次调用它时,它一次只处理命令行上检测到的一个参数。处理完所有的参数后,它会退出并返回一个大于0的退出状态码。这让它非常适合用解析命令行所有参数的循环中。

getopts命令格式:

getopts optstring variable

 

有效的选项字母都会列在optstring中,如果选项字母要求有个参数值,就加一个冒号要去掉错误消息的话,可以在optstring之前加一个冒号。getopts命令将当前参数保存在命令行中定义的variable中。

getopts命令会用到两个环境变量。如果选项需要跟一个参数值,OPTARG环境变量就会保存这个值。OPTIND环境变量保存了参数列表中getopts正在处理的参数位置。

 

1:while语句定义了getopts命令,指明了要查找哪些命令行选项,以及每次迭代中存储它们的变量名(opt)。getopts命令解析命令行选项时会移除开头的单破折线,所以在case定义中不用单破折线。

[16:46:03 root@libin3 libin]# vim shell115

#!/bin/bash

#simple getopts command

#

echo

while getopts :ab:c opt

do

case "$opt" in

a) echo "this is -a value";;

b) echo "this is -b value",the value is $OPTARG;;

c) echo "this is -c value";;

*) echo "unknow option: $opt";;

esac

done

 

[16:47:09 root@libin3 libin]# chmod +x shell115

[16:47:16 root@libin3 libin]# ./shell115 -ab rhcsa

 

this is -a value

this is -b value,the value is rhcsa

 

注:

1getopts命令有几个好用的功能。可以在参数值中包含空格。

[17:01:25 root@libin3 libin]# ./shell115 -b "test1 test2" -a

this is -b value,the value is test1 test2

this is -a value

 

2)另一个好用的功能是将选项字母和参数值放在一起使用,而不用加空格,getopts命令能够从-b选项中正确解析出rhcsa值。

 

[17:06:34 root@libin3 libin]# ./shell115 -abrhcsa

this is -a value

this is -b value,the value is rhcsa

 

3)getopts还能够将命令行上找到的所有未定义的选项统一输出成问号,optstring中未定义的选项字母会以问号形式发送给代码

[20:44:34 root@libin3 libin]# ./shell115 -d

unknow option: ?

[20:47:41 root@libin3 libin]# ./shell115 -acde

this is -a value

this is -c value

unknow option: ?

unknow option: ?

 

2getopts命令知道何时停止处理选项,并将参数留给你处理。在getopts处理每个选项时,它会将OPTIND环境变量值增一。在getopts完成处理时,你可以使用shift命令和OPTIND值来移动参数

[22:37:38 root@libin3 libin]# vim shell116

#!/bin/bash

#simple getopts command V2

#

echo

while getopts :ab:c opt

do

case "$opt" in

a) echo "this is -a value";;

b) echo "this is -b value",the value is $OPTARG;;

c) echo "this is -c value";;

*) echo "unknow option: $opt";;

esac

done

#

shift $[ $OPTIND - 1 ]

#

echo

libin=1

for i in "$@"

do

echo "see the value is $libin: $i"

libin=$[ $libin + 1 ]

done

 

 

[22:37:58 root@libin3 libin]# chmod +x shell116

[22:38:09 root@libin3 libin]# ./shell116 -a -b libin1 -d libin2 libin3 libin4

 

this is -a value

this is -b value,the value is libin1

unknow option: ?

 

see the value is 1: libin2

see the value is 2: libin3

see the value is 3: libin4

 

14.5 将选项标准化

常用的Linux命令选项

-a

显示所有对象

-c

生成一个计数

-d

指定一个目录

-e

扩展一个对象

-f

指定读入数据的文件

-h

显示命令的帮助信息

-i

忽略文本大小写

-l

产生输出的长格式版本

-n

使用非交互模式(批处理)

-o

以安静模式运行

-r

递归地处理目录和文件

-s

以安静模式运行

-v

生成详细输出

-x

排除某个对象

-y

对所有问题回答yes

 

14.6 获得用户输入

想要在脚本运行时问个问题,并等待运行脚本的人来回答。bash shell为此提供了read命令。

14.6.1 基本的读取-n-p

1read命令从标准输入(键盘)另一个文件描述符中接受输入。在收到输入后,read命令会将数据放进一个变量。下面是read命令的最简单用法。

[21:39:23 root@libin3 libin]# vim shell117

#!/bin/bash

#test read -n command

#

echo -n "enter your name: "

read name

echo "hello,$name,welcom to study linux."

 

[21:41:37 root@libin3 libin]# chmod +x shell117

[21:41:45 root@libin3 libin]# ./shell117

enter your name: libin

hello,libin,welcom to study linux.

 

注:生成提示的echo命令使用了-n选项。该选项不会在字符串末尾输出换行符,允许脚本用户紧跟其后输入数据,而不是下一行。

 

2、-p选项,允许你直接在read命令行指定提示符

[21:58:07 root@libin3 libin]# vim shell118

#!/bin/bash

#test read -p command

#

read -p "exam rhce in your age: " days

rhce=$[ $days * 365 ]

echo "exam rhce $rhce  the  libin!"

 

[21:55:43 root@libin3 libin]# chmod +x shell118

[21:58:22 root@libin3 libin]# ./shell118

Exam  rhce in your age: 3

exam rhce 1095  the  libin!

[21:58:25 root@libin3 libin]#

 

3、也可以在read命令行中不指定变量。read命令会将它收到的任何数据都放进特殊环境变量REPLY中。REPLY环境变量会保存输入的所有数据,可以在shell脚本中像其他变量一样使用。

[22:06:21 root@libin3 libin]# vim shell119

#!/bin/bash

#test read REPLY environment variable

#

read -p "exam rhce in your name: "

echo

echo hello ,$REPLY pass rhce.

 

[22:15:54 root@libin3 libin]# chmod +x shell119

[22:17:08 root@libin3 libin]# ./shell119

exam rhce in your name: libin

 

hello ,libin pass rhce.

14.6.2 超时-t

如果不管是否有数据输入,脚本都必须继续执行,你可以用-t选项来指定一个计时器。-t选项指定了read命令等待输入的秒数。当计时器过期后,read命令会返回一个非零退出状态码。

1、

[22:22:18 root@libin3 libin]# vim shell120

#!/bin/bash

# test read -t command

#

if read -t 5 -p  "please enter your name: " name

then

echo "hello $name,pass rhce"

else

echo

echo "sorry,no pass"

fi

 

[22:24:55 root@libin3 libin]# chmod +x shell120

[22:26:51 root@libin3 libin]# ./shell120

please enter your name:                 #5秒不输入就会else

sorry,no pass

[22:26:58 root@libin3 libin]# ./shell120

please enter your name: libin

hello libin,pass rhce

注:计时过期,read命令退出状态码为非0,在本例子中,if语句不成立时,shell就会执行else部分的命令。

 

2、也可以不对输入过程计时,而是让read命令来统计输入的字符数。当输入的字符达到预设的字符数时,就自动退出,将输入的数据赋给变量。

[22:43:50 root@libin3 libin]# vim shell121

#!/bin/bash

#Senior read command useing

#

read -n1 -t 60 -p "Do you need to continue [Y/N]?" reply     

case $reply in

Y | y) echo

echo "Continue to the next step";;

N | n) echo

echo "ok,goodbye"

exit;;

esac

echo "the is end of the script"

 

[22:44:04 root@libin3 libin]# chmod +x shell121

[22:44:11 root@libin3 libin]# ./shell121

Do you need to continue [Y/N]?Y

Continue to the next step

the is end of the script

[22:44:17 root@libin3 libin]# ./shell121

Do you need to continue [Y/N]?n

ok,goodbye

 

注:-n与值1一起使用,可以告诉read命令在接收到单个字符后退出,只要按下单个字符键,read命令就会接收到并将它的输入信息传送给变量,无需按回车键

 

14.6.3 隐藏方式读取-s

有时你需要从脚本用户处得到输入,但又在屏幕上显示输入信息。其中典型的例子就是输入的密码,但除此之外还有很多其他需要隐藏的数据类型。

-s选项可以避免在read命令中输入的数据出现在显示器上(实际上,数据会被显示,只是read命令会将文本颜色设成跟背景色一样)。这里有个在脚本中使用-s选项的例子。

 

1、输入提示符输入的数据不会出现在屏幕上,但会赋给变量,以便在脚本中使用

[22:47:19 root@libin3 libin]# vim shell122

#!/bin/bash

#hide read input command

#

read -s -p "enther your passwd: " passwd

echo

echo "please input your password readly $passwd ?"

 

[01:19:24 root@libin3 libin]# chmod +x shell122

[01:21:01 root@libin3 libin]# ./shell122

enther your passwd:

please input your password readly libin ?

 

14.6.4 从文件中读取

也可以用read命令来读取Linux系统上文件里保存的数据。每次调用read命令,它都会从文件中读取一行文本。当文件中再没有内容时,read命令会退出并返回非零退出状态码。

 

1、

[01:29:20 root@libin3 libin]# cat ./libin6

rhcsa

rhce

rhca

hcia libin1

hcip libin2

hcie libin3

 

[01:25:36 root@libin3 libin]# vim shell123

#!/bin/bash

#reading  form a file content

#

libin=1

cat ./libin6 | while read line

do

echo "line $libin: $line"

libin=$[ $libin +  1 ]

done

echo "finish a file read"

 

[01:29:26 root@libin3 libin]# chmod +x shell123

[01:29:36 root@libin3 libin]# ./shell123

line 1: rhcsa

line 2: rhce

line 3: rhca

line 4: hcia libin1

line 5: hcip libin2

line 6: hcie libin3

 

注:while循环会持续通过read命令处理文件中的行,直到read命令以非零退出状态码退出。

第十五章 呈现数据

15.1 理解输入和输出

已知道的2种显示脚本输出的方法:

1)在显示器屏幕上显示输出

2)将输出重定向到文件中

 

15.1.1 标准文件描述符

Linux系统将每个对象当作文件处理。这包括输入和输出进程。Linux文件描述符(file descriptor)来标识每个文件对象。文件描述符是一个非负整数,可以唯一标识会话中打开的文件。每个进程一次最多可以有九个文件描述符。出于特殊目的,bash shell保留了前三个文 件描述符(012

Linux的标准文件描述符

 

文件描述符

0

STDIN

标准输入

1

STDOUT

标准输出

2

STDERR

标准错误

 

1. STDIN0

STDIN文件描述符代表shell的标准输入。对终端界面来说,标准输入是键盘。shell从STDIN文件描述符对应的键盘获得输入,在用户输入时处理每个字符。

在使用输入重定向符号(<)时,Linux会用重定向指定的文件来替换标准输入文件描述符。它会读取文件并提取数据,就如同它是键盘上键入的。

1、cat命令处理STDIN输入的数据,当输入cat命令时,它会从STDIN接收输入,输入一行,cat命令也会显示一行。

 

[20:13:03 root@libin3 ~]# cat

libin is rhcsa

libin is rhcsa

this is a test

this is a test

 

2、也可以通过STDIN重定向符号强制cat命令接受来自另一个非STDIN文件的输入。cat命令会用testfile文件中的作为输入。

 

[20:19:00 root@libin3 rhca]# cat < libin.save

`one libin-rhce three four five sixe libin-rhce three four five six

 

2. STDOUT1

STDOUT文件描述符代表shell的标准输出。在终端界面上,标准输出就是终端显示器。shell的所有输出(包括shell中运行的程序和脚本)会被定向到标准输出中,也就是显示器。

 

1通过输出重定向符号,通常会显示到显示器的所有输出会被shell重定向到指定的重定向文件

 

[20:19:07 root@libin3 rhca]# ls -l

总用量 8

-rw-r--r-- 1 root root 68 8月   6 20:49 libin

-rw-r--r-- 1 root root 68 8月   6 22:12 libin.save

drwxr-xr-x 2 root root  6 9月   4 20:18 libin-test

[20:28:43 root@libin3 rhca]# ls -l > libin2

[20:29:11 root@libin3 rhca]# cat libin2

总用量 8

-rw-r--r-- 1 root root 68 8月   6 20:49 libin

-rw-r--r-- 1 root root  0 9月   4 20:29 libin2

-rw-r--r-- 1 root root 68 8月   6 22:12 libin.save

drwxr-xr-x 2 root root  6 9月   4 20:18 libin-test

 

 

 

 

 

2也可以将数据追加到某个文件。可以用>>符号来完成

 

[20:32:22 root@libin3 rhca]# who >> libin2

[20:32:32 root@libin3 rhca]# cat libin2

总用量 8

-rw-r--r-- 1 root root 68 8月   6 20:49 libin

-rw-r--r-- 1 root root  0 9月   4 20:29 libin2

-rw-r--r-- 1 root root 68 8月   6 22:12 libin.save

drwxr-xr-x 2 root root  6 9月   4 20:18 libin-test

root     :0           2022-09-04 20:12 (:0)

root     pts/0        2022-09-04 20:13 (:0)

 

 

 

3当命令生成错误消息时,shell并未将错误消息重定向到输出重定向文件。shell创建了输出重定向文件 libin3,但错误消息只会在屏幕上。

 

[20:51:45 root@libin3 rhca]# ls -al testfile > libin3

ls: 无法访问testfile: 没有那个文件或目录

[20:52:08 root@libin3 rhca]# cat libin3

 

 

 

3. STDERR 2

shell通过特殊的STDERR文件描述符来处理错误消息。STDERR文件描述符代表shell的标准错误输出。shell或shell中运行的程序和脚本出错时生成的错误消息都会发送到这个位置。

默认情况下,STDERR文件描述符会和STDOUT文件描述符指向同样的地方(尽管分配给它们的文件描述符值不同)。也就是说,默认情况下,错误消息也会输出到显示器输出中。

 

15.1.2 重定向错误

1. 只重定向错误

1可以选择只重定向错误消息,将该文件描述符值放在重定向符号前。该值必须紧紧地放在重定向符号前,否则不会工作。该命令生成的任何错误消息都会保存在输出文件中。用这种方法,shell会只重定向错误消息,而非普通数据。

 

[20:53:04 root@libin3 rhca]# ls -al testfile 2> libin4

[20:53:17 root@libin3 rhca]# cat libin4

ls: 无法访问testfile: 没有那个文件或目录

 

 

 

2、将STDOUT和STDERR消息混杂在同一输出中的例子

 

[20:56:26 root@libin3 rhca]# ls -al libin2 libin3 testfile2 2> libin5

-rw-r--r-- 1 root root 299 9月   4 20:32 libin2

-rw-r--r-- 1 root root   0 9月   4 20:52 libin3

[20:57:23 root@libin3 rhca]# cat libin5

ls: 无法访问testfile2: 没有那个文件或目录

 

;ls命令的正常STDOUT输出仍然会发送到默认的STDOUT文件描述符,也就是显示器。令将文件描述符2的输出(STDERR)重定向到了一个输出文件,shell会将生成的所有错误消息直接发送到指定的重定向文件中。

 

2. 重定向错误和数据

1、想重定向错误和正常输出,必须用两个重定向符号。需要在符号前面放上待重定向数据

所对应的文件描述符,然后指向用于保存数据的输出文件。

 

[20:58:06 root@libin3 rhca]# ls -l

总用量 20

-rw-r--r-- 1 root root  68 8月   6 20:49 libin

-rw-r--r-- 1 root root 299 9月   4 20:32 libin2

-rw-r--r-- 1 root root   0 9月   4 20:52 libin3

-rw-r--r-- 1 root root  54 9月   4 20:53 libin4

-rw-r--r-- 1 root root  55 9月   4 20:57 libin5

-rw-r--r-- 1 root root  68 8月   6 22:12 libin.save

drwxr-xr-x 2 root root   6 9月   4 20:18 libin-test

 

[21:08:42 root@libin3 rhca]# ls -al libin.save libin1 errorfile 2> libin6 1>libin7

[21:09:40 root@libin3 rhca]# cat libin6

ls: 无法访问errorfile: 没有那个文件或目录

[21:09:46 root@libin3 rhca]# cat libin7

-rw-r--r-- 1 root root  0 9月   4 21:08 libin1

-rw-r--r-- 1 root root 68 8月   6 22:12 libin.save

 

注:shell利用1>符号将ls命令的正常输出重定向到了libin7文件而这些输出本该是进入STDOUT的。所有本该输出到STDERR的错误消息通过2>符号被重定向到了libin6文件

 

2、也可以将STDERRSTDOUT的输出重定向到同一个输出文件特殊的重定向符号&>

 

[21:13:46 root@libin3 rhca]# ls -al libin.save libin1 errorfile &> libin7

[21:15:04 root@libin3 rhca]# cat libin7

ls: 无法访问errorfile: 没有那个文件或目录

-rw-r--r-- 1 root root  0 9月   4 21:08 libin1

-rw-r--r-- 1 root root 68 8月   6 22:12 libin.save

 

注:当使用&>符时,命令生成的所有输出都会发送到同一位置,包括数据和错误。

相较于标准输出, bash shell自动赋予了错误消息更高的优先级。会在标注输出的前面。

 

15.2 在脚本中重定向输出

可以在脚本中用STDOUTSTDERR文件描述符以在多个位置生成输出,简单地重定向相应的文件描述符就行了。有两种方法来在脚本中重定向输出:

1)临时重定向行输出

2)永久重定向脚本中的所有命令

15.2.1 临时重定向

 

如果有意在脚本中生成错误消息,可以将单独的一行输出重定向到STDERR使用输出重定向符来将输出信息重定向到STDERR文件描述符。在重定向到文件描述符时,必须在文件描述符数字之前加一个&

 

[21:30:11 root@libin3 rhca]# echo "this is a errorfile">&2

 

 

 

1在脚本的STDERR文件描述符所指向的位置显示文本,而不是通常的STDOUT。

 

[21:33:18 root@libin3 rhca]# cd /libin/libin

[21:33:32 root@libin3 libin]# echo $PATH

/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin

[21:33:35 root@libin3 libin]# PATH=$PATH:/libin/libin/

[21:36:39 root@libin3 libin]# echo $PATH

/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/bin:/sbin:/root/bin:/libin/libin/

 

[21:37:23 root@libin3 libin]# vim shell124

!/bin/bash

#test $STDERR message

#

echo "this is am error message">&2

echo "this is output"

 

[21:39:48 root@libin3 libin]# chmod +x shell124

[21:39:54 root@libin3 libin]# ./shell124

this is am error message

this is output

 

[21:43:16 root@libin3 libin]# ./shell124 2> test1

this is output

[21:43:31 root@libin3 libin]# cat test1

this is am error message

 

注:通过STDOUT显示的文本显示在了屏幕上,而发送给STDERRecho语句的文本则被重定向到了输出文件。这个方法非常适合在脚本中生成错误消息。如果有人用了你的脚本,可以像上该例子中那样轻松地通过STDERR文件描述符重定向错误消息。

 

15.2.2 永久重定向

 

如果脚本中有大量数据需要重定向,那重定向每个echo语句就会很烦琐。取而代之,你可以用exec命令告诉shell在脚本执行期间重定向某个特定文件描述符

1、exec命令会启动一个新shell并将STDOUT文件描述符重定向到文件。脚本中发给STDOUT的所有输出会被重定向到文件。可以在脚本执行过程中重定向STDOUT

 

[21:56:55 root@libin3 libin]# vim shell125

#!/bin/bash

# redirecting all output to a file

exec 1>libinout

echo "them is rhcsa"

echo "them is rhce"

echo "them is rhca"

 

[21:59:20 root@libin3 libin]# chmod +x shell125

[21:59:26 root@libin3 libin]# ./shell12

[21:59:32 root@libin3 libin]# cat libinout

them is rhcsa

them is rhce

them is rhca

 

 

 

2、本用exec命令来将发给STDERR的输出重定向到文件testerror。脚本用echo语句向STDOUT显示了几行文本。随后再次使用exec命令来将STDOUT重定向到testout文件。尽管STDOUT被重定向了,仍然可以将echo语句的输出发给STDERR。

 

[23:22:29 root@libin3 libin]# vim shell126

#!/bin/bash

# test output and outerror

exec 2>libin2error          #因为这里执行报错的信息重定向到libin2error,所有没有内容

echo "hello don's not is rhcsa"

echo "hello1 don's not is rhce"

 

exec 1>libin3out

echo "this is output command"       #这里会输出到libin3out

echo "this is outerror command">&2  #输出重定向到STDERR,这里就是libin2error

 

[23:28:01 root@libin3 libin]# chmod +x shell126

[23:28:14 root@libin3 libin]# ./shell126

hello don's not is rhcsa

hello1 don's not is rhce

 

[23:29:05 root@libin3 libin]# cat libin2error

this is outerror command

[23:34:09 root@libin3 libin]# cat libin3out

this is output command

 

 

 

15.3 在脚本中重定向输入

 

使用与脚本中重定向STDOUT和STDERR相同的方法来将STDIN从键盘重定向到其他位置。exec命令允许你将STDIN重定向到Linux系统上的文件中:

exec 0< testfile

 

1、该命令会告诉shell它应该从文件testfile中获得输入,而不是STDIN。这个重定向只要在脚本需要输入时就会作用。该脚本与我shell123使用差不多

[20:42:23 root@libin3 libin]# vim shell127

#!/bin/bash

#test file input "0"

exec 0< libin6

libin1=1

 

while read line

do

echo "Line #$libin $line"

libin=$[ $libin + 1 ]

done

 

[20:44:22 root@libin3 libin]# cat libin6

rhcsa

rhce

rhca

hcia libin1

hcip libin2

hcie libin3

 

[20:43:59 root@libin3 libin]# chmod +x shell126

[20:42:57 root@libin3 libin]# ./shell127

Line # rhcsa

Line #1 rhce

Line #2 rhca

Line #3 hcia libin1

Line #4 hcip libin2

Line #5 hcie libin3

Line #6

Line #7

15.4 创建自己的重定向

在脚本中重定向输入和输出时,并不局限于这3个默认的文件描述符。在shell中最多可以有9个打开的文件描述符。其他6个从3~8的文件描述符均可用作输入或输出重定向。你可以将这些文件描述符中的任意一个分配给文件,然后在脚本中使用它们。

15.4.1 创建输出文件描述符

1exec命令将文件描述符3重定向到另一个文件。当脚本执行echo语句时,输出内

容会像预想中那样显示在STDOUT上。但你重定向到文件描述符3的那行echo语句的输出却进入了另一个文件。这样你就可以在显示器上保持正常的输出,而将特定信息重定向到文件中(比如日志文件)。也可以使用追加到现有文件>>

 

[20:52:23 root@libin3 libin]# vim shell128

#!/bin/bash

#using a file discriptor

exec 3>libintestfile3

echo "hello is an rhcsa"

echo "hello is an rhce"

echo "hello is an rhca">&3

 

[20:56:08 root@libin3 libin]# chmod +x shell128

[20:56:16 root@libin3 libin]# ./shell128

hello is an rhcsa

hello is an rhce

15.4.2 重定向文件描述符

怎么恢复已重定向的文件描述符。可以分配另外一个文件描述符给标准文件描述符,反之亦然。可以将STDOUT的原来位置重定向到另一个文件描述符,然后再利用该文件描述符重定向回STDOUT。

 

例1、首先,脚本将文件描述符3重定向到文件描述符1的当前位置,也就是STDOUT。说明任何发送给文件描述符3的输出都将出现在显示器上。

第二个exec命令将STDOUT重定向到文件,shell现在会将发送给STDOUT的输出直接重定向到输出文件中。但文件描述符3仍然指向STDOUT原来的位置,也就是显示器。如果此时将输出数据发送给文件描述符3,它仍然会出现在显示器上,尽管STDOUT已经被重定向。

在向STDOUT(现在指向一个文件)发送一些输出之后,脚本将STDOUT重定向到文件描述符3的当前位置(现在仍然是显示器)。这意味着现在STDOUT又指向了它原来的位置:显示器。

[20:56:37 root@libin3 libin]# vim shell129

#!/bin/bash

#using STDOUT,then coming back to it

 

exec 3>&1

exec 1>testlibinfile1

echo "this is an teacher"

echo "this is an student"

 

exec 1>&3

echo "now this is an boss"

 

[21:11:40 root@libin3 libin]# chmod +x shell129

[21:11:50 root@libin3 libin]# ./shell129

now this is an boss

 

[21:12:24 root@libin3 libin]# cat testlibinfile1

this is an teacher

this is an student

15.4.3 创建输入文件描述符

可以用和重定向输出文件描述符同样的办法重定向输入文件描述符。在重定向到文件之前,先将STDIN文件描述符保存到另外一个文件描述符,然后在读取完文件之后再将STDIN恢复到它原来的位置。

1、文件描述符6用来保存STDIN的位置。然后脚本将STDIN重定向到一个文件。read命令的所有输入都来自重定向后的STDIN(也就是输入文件)。

读取了所有行之后,脚本会将STDIN重定向到文件描述符6,从而将STDIN恢复到原先的

位置。

[21:37:45 root@libin3 libin]# cat libin6

rhcsa

rhce

rhca

hcia libin1

hcip libin2

hcie libin3

 

[21:12:50 root@libin3 libin]# vim shell130

#!/bin/bash

#redirected input file dscripts

exec 6<&0

exec 0< libin6

libin=1

while read line

do

echo "line #$libin $line"

libin=$[ $libin + 1 ]

done

exec 0<&6

read -p "your are name? " libin

case $libin in

Y|y) echo "yes,hello";;

N|n) echo "no,I'm sorry";;

*) echo "no,done's not";;

esac

 

[21:32:33 root@libin3 libin]# chmod +x shell130

[21:32:41 root@libin3 libin]# ./shell130

line #1 rhcsa

line #2 rhce

line #3 rhca

line #4 hcia libin1

line #5 hcip libin2

line #6 hcie libin3

line #7

line #8

your are name? y

yes,hello

15.4.4 创建读写文件描述符

可以打开单个文件描述符来作为输入和输出。可以用同一个文件描述符对同一个文件进行读写。由于你是对同一个文件进行数据读写,shell会维护一个内部指针,指明在文件中的当前位置。任何读或写都会从文件指针上次的位置开始。

1、exec命令将文件描述符3分配给文件testfile以进行文件读写。,它通过分配好的文件描述符,使用read命令读取文件中的第一行,然后将这一行显示在STDOUT上。最后,它用echo语句将一行数据写入由同一个文件描述符打开的文件中。read命令读取了第一行数据,所以它使得文件指针指向了第二行数据的第一个字符。在echo语句将数据输出到文件时,它会将数据放在文件指针的当前位置,覆盖了该位置的已有数据。

[21:39:51 root@libin3 libin]# vim shell131

#!/bin/bash

#testing input/output file descript

exec 3<> libin6

read line <&3

echo "read: $line"

echo "this is a test line" >&3

 

[21:53:29 root@libin3 libin]# chmod +x shell131

[21:53:42 root@libin3 libin]# ./shell131

[21:55:40 root@libin3 libin]# ./shell131

read: rhcsa

 

[21:56:26 root@libin3 libin]# cat libin6

rhcsa

this is a test line

1

hcip libin2

hcie libin3

15.4.5 关闭文件描述符&-

创建了新的输入或输出文件描述符,shell会在脚本退出时自动关闭它们。然而在有些情况下,你需要在脚本结束前手动关闭文件描述符。要关闭文件描述符,将它重定向到特殊符号&-。脚本如下:

exec 3>&-

 

1、该语句会关闭文件描述符3,不再在脚本中使用它。一旦关闭了文件描述符,就不能在脚本中向它写入任何数据,否则shell会生成错误消息。在关闭文件描述符时如果随后你在脚本中打开了同一个输出文件,shell会用一个新文件来替换已有文件。这意味着如果你输出数据,它就会覆盖已有文件。

[22:17:54 root@libin3 libin]# vim shell132

#!/bin/bash

#test close file dscriptor

exec 3>teselibinfile132

echo "this is a test line of data" >&3

exec 3>&-

echo "this wont't work" >&3

 

[22:21:43 root@libin3 libin]# chmod +x shell132

[22:21:54 root@libin3 libin]# ./shell132

./shell132:7: 3: 错误的文件描述符

 

2、在向testlibinfile133文件发送一个数据字符串并关闭该文件描述符之后,脚本用了cat命令来显示文件的内容。下一步,脚本重新打开了该输出文件并向它发送了另一个数据字符串。当显示该输出文件的内容时,你所能看到的只有第二个数据字符串。shell覆盖了原来的输出文件。

[22:31:27 root@libin3 libin]# vim shell133

#!/bin/bash

# testing close file descripts

exec 3>testlibinfile133

echo "hello is an rhcsa" >$3

exec 3>&-

 

cat testlibinfile133

exec 3> testlibinfile133

echo "this is bad" >&3

 

[22:34:20 root@libin3 libin]# chmod +x shell133

[22:34:28 root@libin3 libin]# ./shell133

./shell133:5: $3: 模糊的重定向

[22:34:32 root@libin3 libin]# cat testlibinfile133

this is bad

15.5 列出打开的文件描述符

lsof命令会列出整个Linux系统打开的所有文件描述符。它会向非系统管理员用户提供Linux系统的信息。在很多Linux系统中(如Fedora),lsof命令位于/usr/sbin目录。要想以普通用户账户来运行它,必须通过全路径名来引用:

[22:35:32 root@libin3 libin]# which lsof

/usr/sbin/lsof

 

该命令会产生大量的输出。它会显示当前Linux系统上打开的每个文件的有关信息。这包括后台运行的所有进程以及登录到系统的任何用户。有大量的命令行选项和参数可以用来帮助过滤lsof的输出。最常用的有-p-d,前者允许指定进程IDPID,后者允许指定要显示的文件描述符编号

 

1、要想知道进程的当前PID,可以用特殊环境变量$$(shell会将它设为当前PID)。-a选项用来对其他两个选项的结果执行布尔AND运算,这会产生如下输出:这里显示了当前进程(bash shell)的默认文件描述符(0、1和2)。

[22:59:44 root@libin3 libin]# lsof -a -p $$  -d 0,1,2

COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME

bash    3345 root    0u   CHR  136,0      0t0    3 /dev/pts/0

bash    3345 root    1u   CHR  136,0      0t0    3 /dev/pts/0

bash    3345 root    2u   CHR  136,0      0t0    3 /dev/pts/0

 

lsof的默认输出

COMMAND

正在运行的命令名的前9个字符

PID

进程的PID

USER

进程属主的登录名

FD

文件描述符号以及访问类型(r代表读,w代表写,u代表读写)

TYPE

文件的类型(CHR代表字符型,BLK代表块型,DIR代表目录,REG代表常规文件)

DEVICE

设备的设备号(主设备号和从设备号)

SIZE

如果有的话,表示文件的大小

NODE

本地文件的节点号

NAME

文件名

 

与STDIN、STDOUT和STDERR关联的文件类型是字符型。因为STDIN、STDOUT和STDERR件描述符都指向终端,所以输出文件的名称就是终端的设备名。所有3种标准文件都支持读和写

 

1该脚本创建了3个替代性文件描述符,两个作为输出(3和6),一个作为输入(7)。在脚本运行lsof命令时,可以在输出中看到新的文件描述符。我们去掉了输出中的第一部分,这样你就能看到文件名的结果了。文件名显示了文件描述符所使用的文件的完整路径名。它将每个文件 都显示成REG类型的,这说明它们是文件系统中的常规文件。

[23:05:46 root@libin3 libin]# vim shell134

#!/bin/bash

# test lsof with file descripts

exec 3> test1file1

exec 6> test1file2

exec 7< libin6

lsof -a -p $$ -d0,1,2,3,6,7

 

[23:37:36 root@libin3 libin]# chmod +x shell134

[23:37:41 root@libin3 libin]# ./shell134

COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF    NODE NAME

shell134 9534 root    0u   CHR  136,0      0t0       3 /dev/pts/0

shell134 9534 root    1u   CHR  136,0      0t0       3 /dev/pts/0

shell134 9534 root    2u   CHR  136,0      0t0       3 /dev/pts/0

shell134 9534 root    3w   REG  253,0        0  305216 /libin/libin/test1file1

shell134 9534 root    6w   REG  253,0        0  336783 /libin/libin/test1file2

shell134 9534 root    7r   REG  253,0       54 1335951 /libin/libin/libin6

 

15.6 阻止命令输出

有时候,你可能不想显示脚本的输出。将脚本作为后台进程运行时很常见。如果在运行在后台的脚本出现错误消息,shell会通过电子邮件将它们发给进程的属主。会很麻烦,尤其是当运行生成很多烦琐的小错误的脚本时。

 

解决方案:可以将STDERR重定向到一个叫作null文件的特殊文件。null文件跟它的名字很像,文件里什么都没有。在Linux系统上null文件的标准位置是/dev/null。你重定向到该位置的任何数据都会被丢掉,不会显示。

1、

[23:43:27 root@libin3 libin]# ls -al > /dev/null

[23:48:07 root@libin3 libin]# cat /dev/null

 

2

[23:49:13 root@libin3 libin]# ls -al libin6 badfiletest 2> /dev/null

-rw-r--r-- 1 root root 54 9月   5 21:55 libin6

 

3在输入重定向中将/dev/null作为输入文件,由于/dev/null文件不含有任何内容,通常用它来快速清除现有文件中的数据,而不用先删除文件再重新创建。这是清除日志文件的一个常用方法,因为日志文件必须时刻准备等待应用程序操作。

[23:51:28 root@libin3 libin]# cat libin6

rhcsa

this is a test line

1

hcip libin2

hcie libin3

[23:51:52 root@libin3 libin]# cat /dev/null >libin6

[23:53:37 root@libin3 libin]# cat libin6

[23:53:40 root@libin3 libin]#

 

15.7 创建临时文件mktemp

Linux系统有特殊的目录,专供临时文件使用。Linux使用/tmp目录来存放不需要永久保留的文件。大多数Linux发行版配置了系统在启动时自动删除/tmp目录的所有文件。

系统上的任何用户账户都有权限在读写/tmp目录中的文件。这个特性为你提供了一种创建临时文件的简单方法,而且还不用操心清理工作。

 

特殊命令可以用来创建临时文件。mktemp命令可以在/tmp目录中创建一个唯一的临时文件。shell会创建这个文件,但不用默认的umask值。它会将文件的读和写权限分配给文件的属主,并将你设成文件的属主。一旦创建了文件,你就在脚本中有了完整的读写权限,但其他人没法访问它(当然,root用户除外)。

15.7.1 创建本地临时文件

默认情况下,mktemp会在本地目录中创建一个文件。要用mktemp命令在本地目录中创建一个临时文件,你只要指定一个文件名模板就行了。模板可以包含任意文本文件名,在文件名末尾加上6X(大写)就行了

 

 

 

 

1mktemp命令会用6个字符码替换这6个X,从而保证文件名在目录中是唯一的。可以创建多个临时文件,它可以保证每个文件都是唯一的。

[21:22:58 root@libin3 tmp]# mktemp libin.XXXXXX

libin.uFYYk5

[21:23:05 root@libin3 tmp]# ls -al libin*

-rw------- 1 root root 0 9月   6 21:23 libin.uFYYk5

 

[21:23:23 root@libin3 tmp]# mktemp libin.XXXXXX

libin.20Fyy8

[21:24:53 root@libin3 tmp]# mktemp libin.XXXXXX

libin.YWJ28S

[21:24:55 root@libin3 tmp]# ls -al libin*

-rw------- 1 root root 0 9月   6 21:24 libin.20Fyy8

-rw------- 1 root root 0 9月   6 21:23 libin.uFYYk5

-rw------- 1 root root 0 9月   6 21:24 libin.YWJ28S

 

注:mktemp命令的输出正是它所创建的文件的名字。在脚本中使用mktemp命令时,可能要将文件名保存到变量中,这样就能在后面的脚本中引用了。

 

2用mktemp命令来创建临时文件并将文件名赋给$tempfile变量,接着将这个临时文件作为文件描述符3的输出重定向文件。在将临时文件名显示在STDOUT之后,向临时文件中写

入了几行文本,然后关闭了文件描述符。最后,显示出临时文件的内容,并用rm命令将其删除。

[21:26:46 root@libin3 libin]# vim shell135

#!/bin/bash

#create and use a temp file

libintempfile=$(mktemp rhcsa.XXXXXX)

exec 3>$libintempfile

echo "this file write to temp file $libintempfile"

 

echo "this is first" >&3

echo "this is second" >&3

echo "this is last" >&3

exec 3>&-

echo "finish,create temp file. the file contens are:"

cat $libintempfile

rm -f $libintempfile 2> /dev/null

 

[21:31:54 root@libin3 libin]# chmod +x shell135

[21:32:00 root@libin3 libin]# ./shell135

this file write to temp file rhcsa.DIgg0m

finish,create temp file. the file contens are:

this is first

this is second

this is last

 

15.7.2 /tmp 目录创建临时文件(-t

-t选项会强制mktemp命令来在系统的临时目录来创建该文件。在用这个特性时,mktemp命令会返回用来创建临时文件的全路径,而不是只有文件名。

 

1、由于mktemp命令返回了全路径名,你可以在Linux系统上的任何目录下引用该临时文件,不管临时目录在哪里。

[21:41:29 root@libin3 tmp]# mktemp -t libin.XXXXXX

/tmp/libin.SK9kEs

[21:41:43 root@libin3 tmp]# ls -al /tmp/libin.SK9kEs

-rw------- 1 root root 0 9月   6 21:41 /tmp/libin.SK9kEs

 

2在mktemp创建临时文件时,它会将全路径名返回给变量。这样你就能在任何命令中使用该值来引用临时文件了。

[21:43:26 root@libin3 libin]# vim shell136

#!/bin/bash

#create a temp file in /tmp

libintempfile=$(mktemp -t rhce.XXXXXX)

 

echo "this is a libintest file" > $libintempfile

echo "this is the second libintest file" >> $libintempfile

echo "the libintest file is located at: $libintempfile"

 

cat $libintempfile

rm -f $libintempfile

 

[21:47:45 root@libin3 libin]# chmod +x shell136

[21:47:52 root@libin3 libin]# ./shell136

the libintest file is located at: /tmp/rhce.pKAyya

this is a libintest file

this is the second libintest file

15.7.3 创建临时目录-d

-d选项告诉mktemp命令来创建一个临时目录而不是临时文件。这样你就能用该目录进行任何需要的操作了,比如创建其他的临时文件。

1、这段脚本在当前目录创建了一个目录,然后它用cd命令进入该目录,并创建了两个临时文件。之后这两个临时文件被分配给文件描述符,用来存储脚本的输出。

[21:49:31 root@libin3 libin]# vim shell137

#!/bin/bash

#using a temp dir

 

libintempdir=$(mktemp -d dir.XXXXXX)

cd $libintempdir

libintempfile1=$(mktemp temp.XXXXXX)

libintempfile2=$(mktemp temp.XXXXXX)

exec 7> $libintempfile1

exec 8> $libintempfile2

 

echo "sending data to dir $libintempdir"

echo "this is a test file of data for $libintempfile1" >&7

echo "this is a test file of data for $libintempfile2" >&8

 

[21:59:57 root@libin3 libin]# chmod +x shell137

[22:02:57 root@libin3 libin]# ./shell137

sending data to dir dir.yWLeVh

 

[22:02:59 root@libin3 libin]# ls -al dir.*

总用量 20

drwx------ 2 root root   44 9月   6 22:02 .

drwxr-xr-x 5 root root 8192 9月   6 22:02 ..

-rw------- 1 root root   44 9月   6 22:02 temp.BIYz0I

-rw------- 1 root root   44 9月   6 22:02 temp.JneIRD

[22:03:11 root@libin3 libin]# cd dir.yWLeVh/

[22:03:22 root@libin3 dir.yWLeVh]# ls -al

总用量 20

drwx------ 2 root root   44 9月   6 22:02 .

drwxr-xr-x 5 root root 8192 9月   6 22:02 ..

-rw------- 1 root root   44 9月   6 22:02 temp.BIYz0I

-rw------- 1 root root   44 9月   6 22:02 temp.JneIRD

[22:03:24 root@libin3 dir.yWLeVh]# cat temp.BIYz0I

this is a test file of data for temp.BIYz0I

[22:03:36 root@libin3 dir.yWLeVh]# cat temp.JneIRD

this is a test file of data for temp.JneIRD

15.8 记录消息tee -a

将输出同时发送到显示器和日志文件,这种做法有时候能够派上用场。你不用将输出重定向两次,只要用特殊的tee命令就行。

 

tee命令相当于管道的一个T型接头。它将从STDIN过来的数据同时发往两处。一处是STDOUT,另一处是tee命令行所指定的文件名:

tee filename

 

 

1由于tee会重定向来自STDIN的数据,你可以用它配合管道命令来重定向命令输出。输出出现在了STDOUT中,同时也写入了指定的文件中。默认情况下,tee命令会在每次使用时覆盖输出文件内容。

[22:03:41 root@libin3 dir.yWLeVh]# date | tee libin6

20220906日 星期二 22:11:53 CST

[22:11:53 root@libin3 dir.yWLeVh]# cat libin6

20220906日 星期二 22:11:53 CST

 

[22:12:33 root@libin3 dir.yWLeVh]# who | tee libin6

root     :0           2022-09-06 21:10 (:0)

root     pts/0        2022-09-06 21:11 (:0)

[22:14:11 root@libin3 dir.yWLeVh]# cat libin6

root     :0           2022-09-06 21:10 (:0)

root     pts/0        2022-09-06 21:11 (:0)

 

例1、-a选项将数据追加到文件中,既能将数据保存在文件中,也能将数据显示在屏幕中。

[22:14:17 root@libin3 dir.yWLeVh]# date | tee -a libin6

20220906日 星期二 22:15:32 CST

[22:15:32 root@libin3 dir.yWLeVh]# cat libin6

root     :0           2022-09-06 21:10 (:0)

root     pts/0        2022-09-06 21:11 (:0)

20220906日 星期二 22:15:32 CST

 

例子3、可以在为用户显示输出的同时再永久保存一份输出内容

22:18:37 root@libin3 libin]# vim shell138

#!/bin/bash

#using the tee command for loggong

libintempfile=libintempfile111

 

echo "this is first test file" | tee -a $libintempfile

echo "this is second test file" | tee -a $libintempfile

echo "this is end of test file" | tee -a $libintempfile

 

[22:21:34 root@libin3 libin]# chmod +x shell138

[22:22:27 root@libin3 libin]# ./shell138

this is first test file

this is second test file

this is end of test file

 

[22:23:05 root@libin3 libin]# cat libintempfile111

this is first test file

this is second test file

this is end of test file

15.9 实例

shell脚本使用命令行参数指定待读取的.csv文件。.csv格式用于从电子表格中导出数据,所以你可以把数据库数据放入电子表格中,把电子表格保存成.csv格式,读取文件,然后创建INSERT语句将数据插入MySQL数据库。

 

例1、解释:

(1)脚本中出现了三处重定向操作。while循环使用read语句从数据文件中读取文本。

(2)注意在done语句中出现的重定向符号:done < ${1}

3)当运行程序shell139时,$1代表第一个命令行参数。它指明了待读取数据的文件。

4read语句会使用IFS字符解析读入的文本,我们在这里将IFS指定为逗号。

5脚本中另外两处重定向操作出现在同一条语句中:cat >> $outfile << EOF

这条语句中包含一个输出追加重定向(双大于号)和一个输入追加重定向(双小于号)。输出重定向将cat命令的输出追加到由$outfile变量指定的文件中。cat命令的输入不再取自标准输入,而是被重定向到脚本中存储的数据。EOF符号标记了追加到文件中的数据的起止。

6)

INSERT INTO members (rhcsa rhce rhca hcia hcip hcie) VALUES \

('$rhcsa','$rhce','$rhca','$hcia','$hcip','$hcie');

上面的文本生成了一个标准的SQL INSERT语句。注意,其中的数据会由变量来替换,变量

中内容则是由read语句存入的。

7所以基本上while循环一次读取一行数据,将这些值放入INSERT语句模板中,然后将结果输出到输出文件中。

 

 

 

[22:48:06 root@libin3 libin]# cat libin.csv

libin1,libin2,libin3,libin4,libin5,libin6

test1,test2,test3,test4,test5,test6

redhat,suse,ubantu,bclinux,aix,sunos

 

[22:23:38 root@libin3 libin]# vim shell139

#!/bin/bash

#read file and create INSERT state for mysql

 

outfile='libin.sql'

IFS=','

while read rhcsa rhce rhca hcia hcip hcie

do

cat >>$outfile<<EOF

INSERT INTO members (rhcsa,rhce,rhca,hcia,hcip,hcie) VALUES \

('$rhcsa','$rhce','$rhca','$hcia','$hcip','$hcie');

EOF

done < ${1}

~             

[22:36:54 root@libin3 libin]# chmod +x shell139

[22:48:00 root@libin3 libin]# ./shell139 libin.csv

[22:59:35 root@libin3 libin]# cat libin.sql

INSERT INTO members (rhcsa,rhce,rhca,hcia,hcip,hcie) VALUES ('libin1','libin2','libin3','libin4','libin5','libin6 ');

INSERT INTO members (rhcsa,rhce,rhca,hcia,hcip,hcie) VALUES ('test1','test2','test3','test4','test5','test6');

INSERT INTO members (rhcsa,rhce,rhca,hcia,hcip,hcie) VALUES ('redhat','suse','ubantu','bclinux','aix','sunos');

 

 

第十六章 控制脚本

 

16.1 处理信号

Linux利用信号与运行在系统中的进程进行通信。4章介绍了不同的Linux信号以及Linux何用这些信号来停止、启动、终止进程。可以通过对脚本进行编程,使其在收到特定信号时执行某些命令,从而控制shell脚本的操作。

16.1.1 重温 Linux 信号

Linux系统和应用程序可以生成超过30个信号。下面为在Linux编程时会遇到的最常见的Linux系统信号。

 

Linux信号

 

 

 

1

SIGHUP

挂起进程

2

SIGINT

终止进程

3

SIGQUIT

停止进程

9

SIGKILL

无条件终止进程

15

SIGTERM

尽可能终止进程

17

SIGSTOP

无条件停止进程,但不是终止进程

18

SIGTSTP

停止或暂停进程,但不终止进程

19

SIGCONT

继续运行停止的进程

 

默认情况下,bash shell会忽略收到的任何SIGQUIT (3)SIGTERM (15)信号(正因为这样,

交互式shell才不会被意外终止)。但是bash shell会处理收到的SIGHUP (1)SIGINT (2)信号。

如果bash shell收到了SIGHUP信号,比如当你要离开一个交互式shell,它就会退出。但在退出之前,它会将SIGHUP信号传给所有由该shell所启动的进程(包括正在运行的shell脚本)。

通过SIGINT信号,可以中断shellLinux内核会停止为shell分配CPU处理时间。这种情况发生时,shell会将SIGINT信号传给所有由它所启动的进程,以此告知出现的状况。

shell会将这些信号传给shell脚本程序来处理。而shell脚本的默认行为是忽略这些信号。它们可能会不利于脚本的运行。要避免这种情况,你可以脚本中加入识别信号的代码,并执行命令来处理信号

 

16.1.2 生成信号

bash shell允许用键盘上的组合键生成两种基本的Linux信号。这个特性在需要停止或暂停失控程序时非常方便

1. 中断进程

Ctrl+C组合键会生成SIGINT信号,并将其发送给当前在shell中运行的所有进程。可以运行一条需要很长时间才能完成的命令,然后按下Ctrl+C组合键来测试它。Ctrl+C组合键会发送SIGINT信号,停止shell中当前运行的进程。sleep命令会使得shell暂停指定的秒数,命令提示符直到计时器超时才会返回。在超时前按下Ctrl+C组合键,就可以提前终止sleep命令。

 

[21:00:47 root@libin3 ~]# sleep 10

^C

[21:58:35 root@libin3 ~]#

 

2. 暂停进程

Ctrl+Z组合键会生成一个SIGTSTP信号,停止shell中运行的任何进程。停止(stopping)进程跟终止(terminating)进程不同:停止进程会让程序继续保留在内存中,并能从上次停止的位置继续运行。当用Ctrl+Z组合键时,shell会通知你进程已经被停止了。

 

1方括号中的数字是shell分配的作业号(job number)。shell将shell中运行的每个进程称为作业,并为每个作业分配唯一的作业号。它会给第一个作业分配作业号1,第二个作业号2,以此类推。如果你的shell会话中有一个已停止的作业,在退出shell时,bash会提醒你。

 

[21:58:35 root@libin3 ~]# sleep 10

^Z

[1]+  已停止               sleep 10

[22:05:00 root@libin3 ~]# sleep 14

^Z

[2]+  已停止               sleep 14

[22:05:05 root@libin3 ~]# sleep 100

^Z

[3]+  已停止               sleep 100

[22:05:47 root@libin3 ~]# exit

exit

有停止的任务。

 

2用ps命令来查看已停止的作业。在S列中(进程状态),ps命令将已停止作业的状态为显示为T。这说明命令要么被跟踪,要么被停止了。

如果在有已停止作业存在的情况下,你仍旧想退出shell,只要再输入一遍exit命令就行了。

shell会退出,终止已停止作业。或者,既然你已经知道了已停止作业的PID,就可以用kill命令来发送一个SIGKILL信号来终止它。

[22:07:25 root@libin3 ~]# sleep 105

^Z

[5]+  已停止               sleep 105

[22:07:33 root@libin3 ~]# ps -l

F S   UID    PID   PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD

4 S     0   3527   3519  0  80   0 - 29177 do_wai pts/0    00:00:00 bash

0 T     0   5502   3527  0  80   0 - 27013 do_sig pts/0    00:00:00 sleep

0 T     0   5561   3527  0  80   0 - 27013 do_sig pts/0    00:00:00 sleep

0 T     0   5576   3527  0  80   0 - 27013 do_sig pts/0    00:00:00 sleep

0 T     0   5642   3527  0  80   0 - 27013 do_sig pts/0    00:00:00 sleep

0 T     0   5656   3527  0  80   0 - 27013 do_sig pts/0    00:00:00 sleep

0 R     0   5722   3527  0  80   0 - 38331 -      pts/0    00:00:00 ps

 

[22:09:19 root@libin3 ~]# kill -9 5656

[5]+  已杀死               sleep 105

16.1.3 捕获信号

可以不忽略信号,在信号出现时捕获它们并执行其他命令。trap命令允许你来指定shell脚本要监看并从shell中拦截的Linux信号。如果脚本收到了trap命令中列出的信号,该信号不shell处理,而是交由本地处理。

trap命令的格式是:

trap commands signals

 

1在trap命令行上,你只要列出想要shell执行的命令,以及一组用空格分开的待捕获的信号。你可以用数值或Linux信号名来指定信号使用trap命令来忽略SIGINT信号,并控制脚本的行为

[23:23:32 root@libin3 libin]# vim shell140

#!/bin/bash

# test signal trapping

#

trap "echo 'sorry! I have trapped Ctrl-C'" SIGINT

#

echo this is a test script

#

libin=1

while [ $libin -le 10 ]

do

echo "Lookup #$libin"

sleep 1

libin=$[ $libin + 1 ]

done

#

echo "end,this is a test script"

 

[23:27:15 root@libin3 libin]# chmod +x shell140

[23:27:37 root@libin3 libin]# ./shell140

this is a test script

Lookup #1

Lookup #2

^Csorry! I have trapped Ctrl-C

Lookup #3

Lookup #4

Lookup #5

^Csorry! I have trapped Ctrl-C

Lookup #6

Lookup #7

Lookup #8

Lookup #9

Lookup #10

end,this is a test script

 

注:用到的trap命令会在每次检测到SIGINT信号时显示一行简单的文本消息。捕获这些

信号会阻止用户用bash shell组合键Ctrl+C来停止程序,每次使用Ctrl+C组合键,脚本都会执行trap命令中指定的echo语句,而不是处理该信号并允许shell停止该脚本。

 

16.1.4 捕获脚本退出

除了在shell脚本中捕获信号,也可以在shell脚本退出时进行捕获。这是在shell完成任务时执行命令的一种简便方法。

 要捕获shell脚本的退出,只要在trap命令后加上EXIT信号就行。

 

1当脚本运行到正常的退出位置时,捕获就被触发了,shell会执行在trap命令行指定的命令。如果提前退出脚本,同样能够捕获到EXIT。因为SIGINT信号并没有出现在trap命令的捕获列表中,当按下Ctrl+C组合键发送SIGINT信号时,脚本就退出了。但在脚本退出前捕获到了EXIT,于是shell执行了trap命令。

[23:56:45 root@libin3 libin]# vim shell141

#!/bin/bash

#test trap

trap "echo hello libin" EXIT

libin=1

while [ $libin -le 4 ]

do

echo "see the value #$libin"

sleep 2

libin=$[ $libin + 1 ]

done

 

[23:56:55 root@libin3 libin]# ./shell141

see the value #1

see the value #2

see the value #3

see the value #4

hello libin

16.1.5 修改或移除捕获

 

在脚本中的不同位置进行不同的捕获处理,只需重新使用带有新选项的trap命令

 

1修改了信号捕获之后,脚本处理信号的方式就会发生变化。但如果一个信号是在捕获被修改前接收到的,那么脚本仍然会根据最初的trap命令进行处理。

[21:46:11 root@libin3 libin]# vim shell142

#!/bin/bash

#modify a trap

#

trap "echo 'hellword...Ctrl-C is trapped.'" SIGINT

#

libin=1

while [ $libin -le 5 ]

do

echo "first Loop #$libin"

sleep 1

libin=$[ $libin + 1 ]

done

#

trap "echo 'my name is libin'" SIGINT

#

libin=1

while [ $libin -le 5 ]

do

echo "second Loop #$libin"

sleep 1

libin=$[ $libin + 1 ]

done

 

[21:42:26 root@libin3 libin]# chmod +x shell142

[21:47:33 root@libin3 libin]# ./shell142

first Loop #1

first Loop #2

first Loop #3

^Chellword...Ctrl-C is trapped.

first Loop #4

first Loop #5

second Loop #1

^Cmy name is libin

second Loop #2

second Loop #3

second Loop #4

second Loop #5

 

2第一个Ctrl+C组合键用于提前终止脚本。因为信号在捕获被移除前已经接收到了,脚本会照旧执行trap中指定的命令。捕获随后被移除,再按Ctrl+C就能够提前终止脚本了。

[21:55:36 root@libin3 libin]# vim shell143

#!/bin/bash

#remove a set trap

#

trap "echo 'hello...Ctrl-C is trapped.'" SIGINT

#

libin=1

while [ $libin -le 5 ]

do

echo "first LOOP #$libin"

sleep 1

libin=$[ $libin + 1]

done

#

#Remove the trap

trap -- SIGINT

echo "my name is libin"

#

libin=1

while [ $libin -le 5 ]

do

echo "second LOOP #$libin"

sleep 1

libin=$[ $libin + 1 ]

done

 

[21:55:34 root@libin3 libin]# chmod +x shell143

[21:55:50 root@libin3 libin]# ./shell143

first LOOP #1

first LOOP #2

first LOOP #3

first LOOP #4

first LOOP #5

my name is libin

second LOOP #1

second LOOP #2

second LOOP #3

^C

注:trap命令后使用单破折号来恢复信号的默认行为。单破折号和双破折号都可以 。正常发挥作用。

移除信号捕获后,脚本按照默认行为来处理SIGINT信号,也就是终止脚本运行。但如果信号是在捕获被移除前接收到的,那么脚本会按照原先trap命令中的设置进行处理。

16.2 以后台模式运行脚本

在用ps命令时,会看到运行在Linux系统上的一系列不同进程。显然,所有这些进程都不是运行在你的终端显示器上的。这样的现象被称为在后台(background)运行进程。在后台模式中,进程运行时不会和终端会话上的STDIN、STDOUT以及STDERR关联。后台运行而不用占用终端会话。

16.2.1 后台运行脚本&

以后台模式运行shell脚本非常简单。只要在命令后加个&符就行了

 

1当&符放到命令后时,它会将命令和bash shell分离开来,将命令作为系统中的一个独立的后台进程运行。方括号中的数字是shell分配给后台进程的作业号下一个数是Linux系统分配给进程的进程IDPID。Linux系统上运行的每个进程都必须有一个唯一的PID。

[22:26:23 root@libin3 libin]# vim shell144

test running in the background

#

libin=1

while [ $libin -le 6 ]

do

sleep 1

libin=$[ $libin + 1 ]

done

 

[22:27:43 root@libin3 libin]# chmod +x shell144

[22:27:52 root@libin3 libin]# ./shell144 &

[1] 4719

[22:33:44 root@libin3 libin]#         当后台进程结束时,它会在终端上显示出一条消息:

[1]+  完成                  ./shell144 

 

 

2当后台进程运行时,它仍然会使用终端显示器来显示STDOUTSTDERR消息。

[22:33:54 root@libin3 libin]# vim shell145

#!/bin/bash

#test running in the background with output

#

echo "Start the test script"

libin=1

while [ $libin -le 5 ]

do

echo "Loop #$libin"

sleep 5

libin=$[ $libin + 1 ]

done

#

echo "test script is commplete"

 

[22:38:47 root@libin3 libin]# chmod +x shell145

[22:38:53 root@libin3 libin]# ./shell145 &

[1] 5104

Start the test script

Loop #1

Loop #2

Loop #3

Loop #4

Loop #5

test script is commplete

 

[1] +  完成                  ./shell145

 

注:如果shell在后台运行时,也是可以敲命令的,但是脚本输出、输入的命令以及命令输出全都混在了一起了,建议时将后台的脚本的STDOUT和STDERR进行重定向,避免这种杂乱的输出。

16.2.2 运行多个后台作业

1可以在命令行提示符下同时启动多个后台作业。通过ps命令,可以看到

所有脚本处于运行状态。,在ps命令的输出中,每一个后台进程都和终端会话(pts/0)终端联系在一起。如果终端会话退出,那么后台进程也会随之退出。

 

[22:57:42 root@libin3 libin]# ./libin1 &

[1] 5868

[22:57:45 root@libin3 libin]# ./libin2 &

[2] 5881

[1]   完成                  ./libin1

[22:57:53 root@libin3 libin]# ./libin3 &

[3] 5891

[22:57:56 root@libin3 libin]# ps

   PID TTY          TIME CMD

  2942 pts/0    00:00:00 bash

  5881 pts/0    00:00:00 libin2

  5891 pts/0    00:00:00 libin3

  5909 pts/0    00:00:00 sleep

  5910 pts/0    00:00:00 sleep

  5911 pts/0    00:00:00 ps

 

16.3 在非控制台下运行脚本nohup命令)

nohup命令运行了另外一个命令来阻断所有发送给该进程的SIGHUP信号。这会在退出终端会话时阻止进程退出。

 

nohup命令的格式如下:

[23:44:52 root@libin3 libin]# cp shell145 shell146

[23:46:20 root@libin3 libin]# cat shell146

#!/bin/bash

#test running in the background with output

#

libin=1

while [ $libin -le 5 ]

do

echo "Loop #$libin"

sleep 1

libin=$[ $libin + 1 ]

done

 

[23:46:17 root@libin3 libin]# nohup ./shell146 &

[1] 7489

nohup: 忽略输入并把输出追加到"nohup.out"

[23:46:55 root@libin3 libin]# cat nohup.out

Loop #1

Loop #2

Loop #3

Loop #4

Loop #5

 

和普通后台进程一样,shell会给命令分配一个作业号,Linux系统会为其分配一个PID号。区别在于,使用nohup命令时,如果关闭该会话,脚本会忽略终端会话发过来的SIGHUP信号。

 

由于nohup命令会解除终端与进程的关联,进程也就不再同STDOUT和STDERR联系在一起。为了保存该命令产生的输出,nohup命令会自动将STDOUTSTDERR的消息重定向到一个名为nohup.out的文件中。

注:如果使用nohup运行了另一个命令,该命令的输出会被追加到已有的nohup.out文件中。当运行位于同一个目录中的多个命令时一定要当心,因为所有的输出都会被发送到同一nohup.out文件中。

16.4 作业控制jobs

作业控制中的关键命令是jobs命令。jobs命令允许查看shell当前正在处理的作业

 

1、脚本用$$变量来显示Linux系统分配给该脚本的PID,然后进入循环,每次迭代都休眠10秒,可以从命令行中启动脚本,再使用Ctrl+Z组合键来停止脚本。

[21:27:54 root@libin3 libin]# vim shell147

/bin/bash

#test job control

#

echo "the process ID: $$"

#

libin=1

while [ $libin -le 10 ]

do

echo "Loop #$libin"

sleep 10

libin=$[ $libin + 1 ]

done

echo "end of scripts..."

 

[21:33:44 root@libin3 libin]# chmod +x shell147

[21:34:13 root@libin3 libin]# ./shell147

the process ID: 8061

Loop #1

Loop #2

^Z

[1]+  已停止               ./shell147

 

2、使用同样的脚本,利用&将另外一个作业作为后台进程启动。脚本的输出被重定向到文件中,避免出现在屏幕上。

[21:37:39 root@libin3 libin]# ./shell147 > shell147.out &

[2] 8187

[21:39:06 root@libin3 libin]# jobs

[1]+  已停止               ./shell147

[2]-  运行中               ./shell147 > shell147.out &

1jobs命令可以查看分配给shell的作业。jobs命令会显示这两个已停止/运行中的作业,以及 它们的作业号和作业中使用的命令。

 

[21:39:09 root@libin3 libin]# jobs -l

[1]+  8061 停止                  ./shell147

[2]-  8187 完成                  ./shell147 > shell147.out

2要想查看作业的PID,可以在jobs命令中加入-l选项(小写的L)。

 

Jobs命令参数

-l

列出进程的PID以及作业号

-n

只列出上次shell发出的通知后改变了状态的作业

-p

只列出作业的PID

-r

只列出运行中的作业

-a

只列出已停止的作业

 

说明:

1jobs命令输出中的加号和减号。带加号的作业会被当做默认作业。使用作业控制命令时,如果未在命令行指定任何作业号,该作业会被当成作业控制命令的操作对象

2)当前的默认作业完成处理后,带减号的作业成为下一个默认作业任何时候都只有一个带加号的作业和一个带减号的作业,不管shell中有多少个正在运行的作业

 

 

3队列中的下一个作业在默认作业移除时是如何成为默认作业的。有3个独立的进程在后台被启动。jobs命令显示出了这些进程、进程的PID及其状态。调用了kill命令向默认进程发送了一个SIGHUP信号,终止了该作业。在接下来的jobs 命令输出中,先前带有减号的作业成了现在的默认作业,减号也变成了加号。

[21:47:08 root@libin3 libin]# ./shell147 > shell147

shell147      shell147.out  

[21:47:08 root@libin3 libin]# ./shell147 > shell147-1.out &

[2] 8723

[21:54:57 root@libin3 libin]# ./shell147 > shell147-2.out &

[3] 8731

[21:55:00 root@libin3 libin]# ./shell147 > shell147-3.out &

[4] 8757

[21:55:03 root@libin3 libin]# jobs -l

[1]+  8061 停止                  ./shell147

[2]   8723 运行中               ./shell147 > shell147-1.out &

[3]   8731 运行中               ./shell147 > shell147-2.out &

[4]-  8757 运行中               ./shell147 > shell147-3.out &

[21:55:08 root@libin3 libin]# kill 8723

[2]   已终止               ./shell147 > shell147-1.out

[21:56:13 root@libin3 libin]# jobs -l

[1]+  8061 停止                  ./shell147

[3]   8731 运行中               ./shell147 > shell147-2.out &

[4]-  8757 运行中               ./shell147 > shell147-3.out &

[21:56:18 root@libin3 libin]# kill 8731

[3]   已终止               ./shell147 > shell147-2.out

[21:56:25 root@libin3 libin]# jobs -l

[1]+  8061 停止                  ./shell147

[4]-  8757 运行中               ./shell147 > shell147-3.out &

16.4.2 重启停止的作业bgfg

在bash作业控制中,可以将已停止的作业作为后台进程或前台进程重启。前台进程会接管你当前工作的终端,所以在使用该功能时要小心了。

 

1、要以后台模式重启一个作业,可用bg命令加上作业号。

[22:16:32 root@libin3 libin]# ./test1

^Z

[4]+  已停止               ./test1

[22:16:39 root@libin3 libin]# bg

[4]+ ./test1 &

[22:16:41 root@libin3 libin]# jobs

[4]   运行中               ./test1 &

 

2该作业是默认作业(从加号可以看出),只需要使用bg命令就可以将其以后台模式重启。注意,当作业被转入后台模式时,并不会列出其PID。

[22:11:44 root@libin3 libin]# cp shell147 test1

[22:12:11 root@libin3 libin]# cp shell147 test2

[22:12:14 root@libin3 libin]# cp shell147 test3

[22:12:39 root@libin3 libin]# bg 4

[4]+ ./test3 &

[22:12:41 root@libin3 libin]# jobs

[1]   已停止               ./shell147

[2]-  已停止               ./test1

[3]+  已停止               ./test2

[4]   运行中               ./test3 &

 

[22:26:12 root@libin3 libin]# fg 1

./shell147

Loop #3

 

注:

1如果有多个作业,得在bg命令后加上作业号。命令bg 4用于将第4个作业置于后台模式。当使用jobs命令时,它列出了作业及其状态,即便是默认作业当前并未处于后台模式。

2)要以前台模式重启作业,可用带有作业号的fg命令。

3)作业是以前台模式运行的,直到该作业完成后,命令行界面的提示符才会出现。

 

16.5 调整谦让度

在多任务操作系统中(Linux就是),内核负责将CPU时间分配给系统上运行的每个进程。度优先级(scheduling priority)是内核分配给进程的CPU时间(相对于其他进程)。在Linux系统中,由shell启动的所有进程的调度优先级默认都是相同的。调度优先级是个整数值,从-20(最高优先级)到+19(最低优先级)。默认情况下,bash shell以优先级0来启动所有进程。

注:最低值-20是最高优先级,而最高值19是最低优先级越是“好”或高的值,获得CPU时间的机会越低。

 

16.5.1 nice 命令-n-)优先级

nice命令允许你设置命令启动时的调度优先级。要让命令以更低的优先级运行,只要用nice-n命令行来指定新的优先级级别

 

1将nice命令和要启动的命令放在同一行中。ps命令的输出验证了谦让度值(NI

列)已经被调整到了10。nice命令阻止普通系统用户来提高命令的优先级。指定的作业的确运行了,但是试图使用nice命令提高其优先级的操作却会失败。

[22:43:56 root@libin3 libin]# nice -n 10 ./test2 > test2.out &

[7] 10776

[22:44:02 root@libin3 libin]# ps -p 10776 -o pid,ppid,ni,cmd

   PID   PPID  NI CMD

 10776   2897  10 /bin/bash ./test2

 

 

2nice命令的-n选项并不是必须的,只需要在破折号后面跟上优先级就行了

[22:58:08 root@libin3 libin]# nice -10 ./test2 > test2.out &

[14] 11394

[23:00:45 root@libin3 libin]# ps -p 11394 -o pid,ppid,ni,cmd

   PID   PPID  NI CMD

 11394   2897  10 /bin/bash ./test2

 

16.5.2 renice 命令(调整优先级)

1、想改变系统上已运行命令的优先级这正是renice命令可以做到的。它允许你指定

运行进程的PID来改变它的优先级。

[23:03:17 root@libin3 libin]# ./test2 &

[16] 11510

the process ID: 11510

[23:03:21 root@libin3 libin]# ps -p 11510 -o pid,ppid,ni,cmd

   PID   PPID  NI CMD

 11510   2897   0 /bin/bash ./test2

[23:03:30 root@libin3 libin]# renice -n 10 -p 11510

11510 (进程 ID) 旧优先级为 0,新优先级为 10

[23:04:10 root@libin3 libin]# ps -p 11510 -o pid,ppid,ni,cmd

   PID   PPID  NI CMD

 11510   2897  10 /bin/bash ./test2

 

注:说明:renice命令会自动更新当前运行进程的调度优先级。和nice命令一样,renice命令也有一些限制:

1只能对属于你的进程执行renice。

2只能通过renice降低进程的优先级

3root用户可以通过renice来任意调整进程的优先级。如果想完全控制运行进程,必须以root账户身份登录或使用sudo命令

 

16.6 定时运行作业

Linux系统提供了多个在预选时间运行脚本的方法:at命令和cron表。每个方法都使用不同的技术来安排脚本的运行时间和频率。

16.6.1 at 命令来计划执行作业

at命令允许指定Linux系统何时运行脚本。at命令会将作业提交到队列中,指定shell何时运行该作业。at的守护进程atd会以后台模式运行,检查作业队列来运行作业。大多数Linux发行版会在启动时运行此守护进程。

 

atd守护进程会检查系统上的一个特殊目录(通常位于/var/spool/at)来获取用at命令提交的作业。默认情况下,atd守护进程会每60秒检查一下这个目录。有作业时,atd守护进程会检查作业设置运行的时间。如果时间跟当前时间匹配,atd守护进程就会运行此作业。

 1. at命令的格式

at [-f filename] time

 

time参数指定了Linux系统何时运行该作业。如果你指定的时间已经错过,at命令会在第二

天的那个时间运行指定的作业。at命令能识别多种不同的时间格式

1标准的小时和分钟格式,比如10:15

2)AM/PM指示符,比如10:15 PM

3特定可命名时间,比如nownoonmidnight或者teatime4 PM)。除了指定运行作业的时间,也可以通过不同的日期格式指定特定的日期。

4标准日期格式,比如MMDDYYMM/DD/YYDD.MM.YY

5文本日期,比如Jul 4Dec 25,加不加年份均可。

6也可以指定时间增量

1当前时间+25 min

2明天10:15 PM

310:15+7

 

注:你使用at命令时,该作业会被提交到作业队列job queue)。作业队列会保存通过at命令提交的待处理的作业。针对不同优先级,存在26种不同的作业队列。作业队列通常用小写字母a~z和大写字母A~Z来指代。

 

作业队列的字母排序越高,作业运行的优先级就越低(更高的nice值)。默认情况下,at的作业会被提交到a作业队列。如果想以更高优先级运行作业,可以用-q参数指定不同的队列字母。

2. 获取作业的输出(-f,-M)

当作业在Linux系统上运行时,显示器并不会关联到该作业。取而代之的是,Linux系统会将提交该作业的用户的电子邮件地址作为STDOUTSTDERR。任何发到STDOUTSTDERR的输出都会通过邮件系统发送给该用户。

 

1、at命令会显示分配给作业的作业号以及为作业安排的运行时间。-f选项指明使用哪个脚本文件,now指示at命令立刻执行该脚本。

[19:24:43 root@libin3 libin]# vim shell148

#!/bin/bash

# test using at command

#

echo "this is script ran at $(date +B%d,%T)"

echo

sleep 3

echo "end,finish the scripts..."

 

[19:28:00 root@libin3 libin]# chmod +x shell148

[19:28:18 root@libin3 libin]# at -f shell148 now

job 1 at Sat Sep 10 19:28:00 2022

 

2-M选项来屏蔽作业产生的输出信息

[19:33:10 root@libin3 libin]# vim shell149

#!/bin/bash

#test using at command

#

echo "this is script ran at $(date +%B%d,%T)" > shell149.out

echo >> shell149.out

sleep 3

echo "this is the script's end ..." >> shell149.out

 

[19:35:07 root@libin3 libin]# chmod +x shell149

[19:35:17 root@libin3 libin]# at -M -f shell149 now

job 2 at Sat Sep 10 19:35:00 2022

 

[19:36:04 root@libin3 libin]# cat shell149.out

this is script ran at 九月10,19:35:35

this is the script's end ...

3. 列出等待的作业atq

1atq命令可以查看系统中有哪些作业在等待

[19:36:09 root@libin3 libin]# at -M -f shell149 teatime

job 3 at Sun Sep 11 16:00:00 2022

[19:41:15 root@libin3 libin]# at -M -f shell149 tomorrow

job 4 at Sun Sep 11 19:41:00 2022

[19:41:26 root@libin3 libin]# at -M -f shell149 13:30

job 5 at Sun Sep 11 13:30:00 2022

[19:41:36 root@libin3 libin]# at -M -f shell149 now

job 6 at Sat Sep 10 19:41:00 2022

[19:41:41 root@libin3 libin]# atq

3 Sun Sep 11 16:00:00 2022 a root

4 Sun Sep 11 19:41:00 2022 a root

5 Sun Sep 11 13:30:00 2022 a root

4. 删除作业(atrm)

知道了哪些作业在作业队列中等待,就能用atrm命令来删除等待中的作业。只能删除你提交的作业,不能删除其他人的。

[19:41:44 root@libin3 libin]# atq

3 Sun Sep 11 16:00:00 2022 a root

4 Sun Sep 11 19:41:00 2022 a root

5 Sun Sep 11 13:30:00 2022 a root

[19:44:29 root@libin3 libin]# atrm 3

[19:44:48 root@libin3 libin]# atq

4 Sun Sep 11 19:41:00 2022 a root

5 Sun Sep 11 13:30:00 2022 a root

16.6.2 安排需要定期执行的脚本

Linux系统使用cron程序来安排要定期执行的作业。cron程序会在后台运行并检查一个特殊的

表(被称作cron时间表),以获知已安排执行的作业。

1. cron时间表

cron时间表采用一种特别的格式来指定作业何时运行。其格式如下:

min hour dayofmonth month dayofweek command

                         命令

 

1cron时间表允许你用特定值、取值范围(比如1~5)或者是通配符(星号)来指定条目,在dayofmonth、month以及dayofweek字段中使用了通配符,表明cron会在每个月每天的10:15

执行该命令。

例如:在每天的10:15运行一个命令,可以用cron时间表条目

15 10 * * * command

 

2、可以用三字符的文本值(mon、tue、wed、thu、fri、sat、sun)或数值(0为周日,6为周六) 来指定dayofweek表项。

例如:要指定在每周一4:15 PM运行的命令

15 16 * * 1 command

 

3、dayofmonth表项指定月份中的日期值(1~31)

例如:在每个月的第一天中午12点执行命令

00 12 1 * * command

 

注:因为无法设置dayofmonth的值来涵盖所有的月份。常用的方法是加一条使用date命令的if-then语句来检查明天的日期是不是01:

00 12 * * * if [`date +%d -d tomorrow` = 01 ] ; then ; command

 

4命令列表必须指定要运行的命令或脚本的全路径名。你可以像在普通的命令行中那样,添加 任何想要的命令行参数和重定向符号。 cron提交的用户必须要有脚本的访问权限和指定输出到文件的权限。

15 10 * * * /libin/libin/shell149 > shell.out

 

2. 构建cron时间表(-l-e

每个系统用户(包括root用户)都可以用自己的cron时间表来运行安排好的任务。Linux提供了crontab命令来处理cron时间表。要列出已有的cron时间表,可以用-l选项。用户的cron时间表文件并不存在。要为cron时间表添加条目,可以用-e选项

[22:34:05 root@libin3 libin]# crontab -l

#print dime by at 20220313

#*/1 * * * * date >>/tmp/time.log

# backup etc dir by /tmp/ at 12

#* * * * * sh /libin/scripts/bak.sh

#* * * * * sh /libin/scripts/ip.sh >>/tmp/time.log 2>&1

* * * * * /libin/libin/shell149 >>/libin/libin/shell149.out

3. 浏览cron目录

如果创建的脚本对精确的执行时间要求不高,用预配置的cron脚本目录会更方便。有4基本目录:hourly、daily、monthly和weekly

1、如果脚本需要每天运行一次,只要将脚本复制到daily目录cron就会每天执行它。

[22:35:48 root@libin3 libin]# ls /etc/cron.*ly

/etc/cron.daily:

logrotate  man-db.cron  mlocate  rhsmd

 

/etc/cron.hourly:

0anacron

 

/etc/cron.monthly:

 

/etc/cron.weekly:

 

4. anacron程序

1cronanacron程序的区别:

1)、cron程序的唯一问题是它假定Linux系统是7×24小时运行的。除非将Linux当成服务器环境来运行,否则此假设未必成立。如果某个作业在cron时间表中安排运行的时间已到,但这时候Linux系统处于关机状态,那么这个作业就不会被运行。当系统开机时,cron程序不会再去运行那些错过的作业。

2)、anacron知道某个作业错过了执行时间,它会尽快运行该作业。这意味着如果Linux系统关机了几天,当它再次开机时,原定在关机期间运行的作业会自动运行。这个功能常用于进行常规日志维护的脚本。如果系统在脚本应该运行的时间刚好关机,日志文件就不会被整理,可能会变很大。通过anacron,至少可以保证系统每次启动时整理日志文件。anacron程序只会处理位于cron目录的程序,比如/etc/cron.monthly。它用时间戳来决定作业是否在正确的计划间隔内运行了。每个cron目录都有个时间戳文件,该文件位于/var/spool/anacron

 

1、anacron程序使用自己的时间表(通常位于/etc/anacrontab)来检查作业目录。

[22:53:49 root@libin3 libin]# cat /var/spool/anacron/cron.daily

20220910

 

2anacron程序使用自己的时间表(通常位于/etc/anacrontab)来检查作业目录。

[22:53:57 root@libin3 libin]# cat /etc/anacrontab

# /etc/anacrontab: configuration file for anacron

 

# See anacron(8) and anacrontab(5) for details.

 

SHELL=/bin/sh

PATH=/sbin:/bin:/usr/sbin:/usr/bin

MAILTO=root

# the maximal random delay added to the base delay of the jobs

RANDOM_DELAY=45

# the jobs will be started during the following hours only

START_HOURS_RANGE=3-22

 

#period in days   delay in minutes   job-identifier   command

1 5 cron.daily nice run-parts /etc/cron.daily

7 25 cron.weekly nice run-parts /etc/cron.weekly

@monthly 45 cron.monthly nice run-parts /etc/cron.monthly

 

2、anacron时间表的基本格式和cron时间表略有不同

period delay identifier command

 

注:

1period条目定义了作业多久运行一次,以天为单位。anacron程序用此条目来检查作业的时间戳文件。delay条目会指定系统启动后anacron程序需要等待多少分钟再开始运行错过的脚本。

command条目包含了run-parts程序和一个cron脚本目录名。run-parts程序负责运行目录中传给它的任何脚本。

2anacron不会运行位于/etc/cron.hourly的脚本。这是因为anacron程序不会处理执行时间需求小于一天的脚本。

3identifier条目是一种特别的非空字符串,如cron-weekly。它用于唯一标识日志消息和错误邮件中的作业。

16.6.3 使用新 shell 启动脚本

依照下列顺序所找到的第一个文件会被运行,其余的文件会被忽略

1)$HOME/.bash_profile

2)$HOME/.bash_login

3)$HOME/.profile

1、将需要登录时运行的脚本放在上面第一个文件中。每次启动一个新shell时,bash shell都会运行.bashrc文件。可以这样来验证:在主目录下的.bashrc文件中加入一条简单的echo语句,然后启动一个新shell。

[23:10:32 root@libin3 libin]# cat  ~root/.bashrc

[23:14:07 root@libin3 libin]# echo "echo 'wlcome to study redhat-linux'">>$HOME/.bashrc

[23:11:17 root@libin3 libin]# bash

wlcome to study redhat-linux

 

注:.bashrc文件通常也是通过某个bash启动文件来运行的。因为.bashrc文件会运行两次:一次是当你登入bash shell时,另一次是当你启动一个bash shell时。如果你需要一个脚本在两个时刻都得以运行,可以把这个脚本放进该文件中。

第十七章 创建函数

bash shell提供的用户自定义函数功能可以解决这个问题。可以将shell脚本代码放进函数中封装起来,这样就能在脚本中的任何地方多次使用它了。

17.1 基本的脚本函数

函数是一个脚本代码块,你可以为其命名并在代码中任何位置重用。要在脚本中使用该代码块时,只要使用所起的函数名就行了(这个过程称为调用函数)。

17.1.1 创建函数

1、第一种格式:function

采用关键字function,后跟分配给该代码块的函数名。name属性定义了赋予函数的唯一名称。脚本中定义的每个函数都必须有一个唯一的名称。commands是构成函数的一条或多条bash shell命令。在调用该函数时,bash shell会按命令在函数中出现的顺序依次执行,就像在普通脚本中一样。

function name {

commands

}

1、第二种格式:name()

在bash shell脚本中定义函数的第二种格式更接近于其他编程语言中定义函数的方式。函数名后的空括号表明正在定义的是一个函数。这种格式的命名规则和之前定义shell脚本函数的格式一样。

name() {

commands

}

17.1.2 使用函数(脚本调用函数方法)

要在脚本中使用函数,只需要像其他shell命令一样,在行中指定函数名就行了。

 

1每次引用函数名libin时,bash shell会找到func1函数的定义并执行你在那里定义的命令。

[18:55:58 root@libin3 libin]# vim shell150

#!/bin/bash

# using a function in a script

function libin {

date

}

rhce=1                这里为1while循环语句

while [ $rhce -le 3 ]

do

libin

rhce=$[ $rhce + 1 ]

done

libin

echo "now thisi is the end of script"

 

[19:00:32 root@libin3 libin]# chmod +x shell150

[19:00:50 root@libin3 libin]# ./shell150

20220911日 星期日 21:07:44 CST

20220911日 星期日 21:07:44 CST

20220911日 星期日 21:07:44 CST

20220911日 星期日 21:07:44 CST

now thisi is the end of script

 

2脚本试图在func2函数被定义之前使用它。由于func2函数还没有定义,脚本运行函

数调用处时,产生了一条错误消息。函数名必须是唯一的,否则也会有问题。如果你重定义了函数,新定义会覆盖原来函数的定义,这一切不会产生任何错误消息。

[21:24:57 root@libin3 libin]# vim shell151

#!/bin/bash

# using a function located in the of a script

 

libin=1                       #该变量可以写在function前面也可以写在while语句前面

echo "welcome to study linux"

 

function rhce1 {

echo "this is a example of a function"

}

 

while [ $libin -le 3 ]

do

rhce1

libin=$[ $libin + 1 ]

done

 

rhce1

echo "end first script,hello libin go to study linux"

rhce2

echo "this is second a function"

function rhce2 {

echo "this is an function in the second script"

}

===============================================================================

[21:29:29 root@libin3 libin]# chmod +x shell151

[21:29:35 root@libin3 libin]# ./shell151

welcome to study linux

this is a example of a function

this is a example of a function

this is a example of a function

this is a example of a function

end first script,hello libin go to study linux

./shell151:20: rhce2: 未找到命令

this is second a function

 

 

3func1函数最初的定义工作正常,但重新定义该函数后,后续的函数调用都会使用第二个

定义。

[21:41:55 root@libin3 libin]# vim shell152

#!/bin/bash

# testing fuction three

 

function libin1 {

echo "rhcsa is linux Junior certification"

}

 

libin1

 

function libin1 {

echo "rhce is linux Intermediate certification"

echo "rhce is linux Advanced certification"

}

 

libin1

echo "end,closing instructions"

 

[21:45:26 root@libin3 libin]# chmod +x shell152

[21:45:34 root@libin3 libin]# ./shell152

rhcsa is linux Junior certification

rhce is linux Intermediate certification

rhce is linux Advanced certification

end,closing instructions

17.2 返回值

bash shell会把函数当作一个小型脚本,运行结束时会返回一个退出状态码,有3种不同的方法来为函数生成退出状态码。

17.2.1 默认退出状态码

不建议使用默认退出状态码

函数的退出状态码是函数中最后一条命令返回的退出状态码。在函数执行结束后,可以用标准变量$?来确定函数的退出状态码

1、函数的退出状态码是0,这是因为函数中的命令都成功运行。但是并无法知道函数中的其他命令是否也成功运行。

[22:01:45 root@libin3 libin]# vim shell153

#!/bin/bash

# test the exit return

 

libin1() {

echo "rhcsa is linux Junior certification"

ls -l libin6

}

 

echo "testing the function libin1: "

libin1

echo "the function libin1 exit status is: $?"

===============================================================================

[22:04:03 root@libin3 libin]# chmod +x shell153

[22:04:12 root@libin3 libin]# ./shell153

testing the function libin1:

rhcsa is linux Junior certification

-rw-r--r-- 1 root root 0 9月   5 23:53 libin6

the function libin1 exit status is: 0           #因为libin6 在当前目录下所有退出状态码为0

 

2、函数最后一条语句echo运行成功,该函数的退出状态码就是0,尽管其中有一条

命令并没有正常运行。使用函数的默认退出状态码是很危险的。

[22:16:49 root@libin3 libin]# vim shell154

!/bin/bash

# testing the exit status of second fuction

 

libin1() {

ls -l libin6666

echo "this was a test of a bad command"

}

 

echo "testing the function:"

libin1

echo "the libin1 exit status is: $?"

===============================================================================

[22:17:52 root@libin3 libin]# chmod +x shell154

[22:17:59 root@libin3 libin]# ./shell154

testing the function:

ls: 无法访问libin6666: 没有那个文件或目录

this was a test of a bad command

the libin1 exit status is: 0

 

17.2.2 使用 return 命令

bash shell使用return命令来退出函数并返回特定的退出状态码。return命令允许指定一个

整数值来定义函数的退出状态码,从而提供了一种简单的途径来编程设定函数退出状态码。

 

例1、libin1函数会将$value变量中用户输入的值翻倍,然后用return命令返回结果。脚本用$?变量显示了该值。

 

说明:

1)函数一结束就取返回值

2)退出状态码必须为0~255

3在用$?变量提取函数返回值之前执行了其他命令,函数的返回值就会丢失。$? 变量会返回执行的最后一条命令的退出状态码。

4界定了返回值的取值范围。由于退出状态码必须小于256,函数的结果必须生成一个小于256的整数值。任何大于256的值都会产生一个错误值。

5要返回较大的整数值或者字符串值的话,你就不能用这种返回值的方法

[22:36:58 root@libin3 libin]# vim shell155

#!/bin/bash

# using the return command in a function

 

function libin1 {

read -p "Enter a value: " value

echo "doubling the value"

return $[ $value * 2 ]

}

 

libin1

echo "this is new value are: $?"

===============================================================================

[22:37:29 root@libin3 libin]# chmod +x shell155

[22:39:06 root@libin3 libin]# ./shell155

Enter a value: 1

doubling the value

this is new value are: 2

17.2.3 使用函数输出

可以对函数的输出采用同样的处理办法。可以用这种技术来获得任何类型的函数输出,并将其保存到变量中:

result='libin1'

 

1新函数会用echo语句来显示计算的结果。该脚本会获取libin1函数的输出,而不是查看退出状态码。通过这种技术,你还可以返回浮点值和字符串值。这使它成为一种获取函数返回值的强大方法。

[23:03:00 root@libin3 libin]# vim shell156

#!/bin/bash

#using the echo to return a value

 

function libin1 {

read -p "Enter a value: " value

echo $[ $value * 2 ]

}

 

result=$(libin1)                            #result 并不是固定的变量可以随意取名

echo "the new value is $result"

===============================================================================

[23:03:05 root@libin3 libin]# chmod +x shell156

[23:03:10 root@libin3 libin]# ./shell156

Enter a value: 200

the new value is 400

[23:03:18 root@libin3 libin]# ./shell156

Enter a value: 1000

the new value is 2000

 

注:注意到libin1函数实际上输出了两条消息。read命令输出了一条简短的消息来向用户询问输入值。bash shell脚本并不将其作为STDOUT输出的一部分,并且忽略掉它。如果你用echo语句生成这条消息来向用户查询,那么它会与输出值一起被读进shell变量中

17.2.4 拓展使用函数

实战1、如何使用function 函数进行系统巡检

[23:55:04 root@libin3 libin]# vim shell157-1

#!/bin/bash

#test function scripts

 

libinpath="/tmp/libin-`date +%Y%m%d`.txt"

function libin {

echo "++++++++2022某公司系统基本运行情况检查+++++++++"

date=`date +"%Y-%m-%d-%H:%M:%S"`

ip_info=`ip a s  eth0 | awk "NR==3" |awk -F '[ /]' '{print $6}'`

system_time=`uptime | awk -F " " '{print $3,$4}' | awk -F "," '{print $1}'`

load_info=`uptime |awk '{print "Current Load: "$(NF-2)}'|sed 's/\,//g'`

 

echo ""

echo -e "\033[32m-------------------------------------------\033[1m"

echo 当前时间: ${date}

echo IP地址: ${ip_info}

echo 运行时长: ${system_time}

echo 系统当前负载: ${load_info}

echo -e "\033[32m-------------------------------------------\033[0m"

echo ""

}

 

libin >>/tmp/libin-`date +%Y%m%d`.txt

echo "end of function system check"

echo -e "\033[44;37m 主机巡检结果存放在:$libinpath   \033[0m"

 

[23:55:14 root@libin3 libin]# chmod +x shell157-1

[23:55:22 root@libin3 libin]# ./shell157-1

end of function system check

主机巡检结果存放在:/tmp/libin-20220912.txt  

 

[23:48:47 root@libin3 libin]# cat /tmp/libin-20220912.txt  

++++++++2022某公司系统基本运行情况检查+++++++++

-------------------------------------------

当前时间: 2022-09-11-23:48:43

IP地址: 192.168.124.134

运行时长: 7:04

系统当前负载: Current Load: 0.00

-------------------------------------------

 

实战2如何使用function 函数进行系统巡检2,为了能让脚本更加自动化

[23:55:30 root@libin3 libin]# vim shell157-2

#!/bin/bash

cat <<LIBIN

************某公司巡检脚本****************

LIBIN

#定义巡检结果存放目录

LOGPATH="/tmp"            

#比较测试LOGPATH是否存在或创建目录

[ -e $LOGPATH ] || mkdir -p $LOGPATH

#定义巡检结果的具体路径HostCheck-主机名-时间年月日

RESULTFILE="$LOGPATH/HostCheck-`hostname`-`date +%Y%m%d`.txt"

 

libin (){

echo "++++++++2022某公司系统基本运行情况检查+++++++++"

date=`date +"%Y-%m-%d-%H:%M:%S"`

ip_info=`ip a s  eth0 | awk "NR==3" |awk -F '[ /]' '{print $6}'`

system_time=`uptime | awk -F " " '{print $3,$4}' | awk -F "," '{print $1}'`

load_info=`uptime |awk '{print "Current Load: "$(NF-2)}'|sed 's/\,//g'`

 

echo ""

echo -e "\033[32m-------------------------------------------\033[1m"

echo 当前时间: ${date}

echo IP地址: ${ip_info}

echo 运行时长: ${system_time}

echo 系统当前负载: ${load_info}

echo -e "\033[32m-------------------------------------------\033[0m"

echo ""

}

 

#检查函数结果

check(){                  

libin

}

#将函数结果重定向到定义巡检具体路径

check > $RESULTFILE        

#输出提示巡检结果存放路径

echo -e "\033[44;37m 主机巡检结果存放在:$RESULTFILE   \033[0m"

 

[23:56:37 root@libin3 libin]# chmod +x shell157-2

[23:56:48 root@libin3 libin]# ./shell157-2

************某公司巡检脚本****************

 主机巡检结果存放在:/tmp/HostCheck-libin3.com-20220911.txt   

17.3 在函数中使用变量

17.3.1 向函数传递参数

函数可以使用标准的参数环境变量来表示命令行上传给函数的参数。例如,函数名会在$0变量中定义,函数命令行上的任何参数都会通过$1、$2等定义。也可以用特殊变量$#来判断传给函数的参数数目。

 

在脚本中指定函数时,必须将参数和函数放在同一行,例如:

 

func1 $value1 10

 

 

 

1、函数可以用参数环境变量来获得参数值。shell158脚本中的libin函数首先会检查脚本传给它的参数数目。如果没有任何参数,或者参数多于两个,libin会返回值-1。如果只有一个参数,libin会将参数与自身相加。如果有两个参数,libin会将它们进行相加。

 

[23:04:49 root@libin3 libin]# vim  shell158

#!/bin/bash

# passing parameters to a function

 

function libin {

if [ $# -eq 0 ] || [ $# -gt 2 ]

then

echo -1

elif [ $# -eq 1 ]

then

echo $[ $1 + $1 ]

else

echo $[ $1 + $2 ]

fi

}

echo -n "adding 10 and 15: "                      #2个参数进行相加

value=$(libin 10 15)        

                     

echo $value

echo -n "first,Let's try adding just one number: "      #参数与自身相加

value=$(libin 10)                                

 

echo $value

echo -n "second,Now trying adding no number: "      #返回-1

value=$(libin)                                 

 

 

echo $value

echo -n "finally,try adding three number: "            #返回-1

value=$(libin 10 15 20)                            

 

echo $value

 

[23:04:55 root@libin3 libin]# chmod +x shell158

[23:07:56 root@libin3 libin]# ./shell158

adding 10 and 15: 25

first,Let's try adding just one number: 20

second,Now trying adding no number: -1

finally,try adding three number: -1

 

 

 

2由于函数使用特殊参数环境变量作为自己的参数值,因此它无法直接获取脚本在命令行中的参数值。尽管函数也使用了$1$2变量,但它们和脚本主体中的$1$2变量并不相同。要在函数中使用这些值,必须在调用函数时手动将它们传过去。

 

[23:46:40 root@libin3 libin]# vim  shell159

#!/bin/bash

#trying  to access script parameters inside function

 

function libin {

echo $[ $1 * $2 ]

}

 

if [ $# -eq 2 ]

then

value=$(libin)

echo "the result is $value"

else

echo "Usage: libintest1 a b"

fi

 

[23:50:05 root@libin3 libin]# chmod +x shell159

[23:50:11 root@libin3 libin]# ./shell159

Usage: libintest1 a b

[23:50:17 root@libin3 libin]# ./shell159 5 10

./shell159:5: *  : 语法错误: 期待操作数 (错误符号是 "*  "

the result is

 

 

 

 

3通过将$1$2变量传给函数,它们就能跟其他变量一样供函数使用了。

 

[23:58:41 root@libin3 libin]# vim shell160

#!/bin/bash

#trying to access scripts parameters inside a function

 

function libin {

echo $[ $1 * $2 ]

}

if [ $# -eq 2 ]

then

value=$(libin $1 $2)

echo "the result is $value"

else

echo "Usage: libintest1 a b"

fi

 

[00:28:05 root@libin3 libin]# chmod +x shell160

[00:28:17 root@libin3 libin]# ./shell160

Usage: libintest1 a b

[00:28:22 root@libin3 libin]# ./shell160 5 10

the result is 50

 

 

 

 

17.3.2 在函数中处理变量

 

给shell脚本带来麻烦的原因之一就是变量的作用域。作用域是变量可见的区域。函数

中定义的变量与普通变量的作用域不同。意思就是对脚本的其他部分而言,它们是隐藏的。

 

函数使用的两种类型的变量:

(1)全局变量

(2)局部变量

1、全局变量

全局变量是在shell脚本中任何地方都有效的变量。如果在脚本的主体部分定义了一个全局变量,那么可以在函数内读取它的值如果在函数内定义了一个全局变量,可以在脚本的主体部分读取它的值。

默认情况下,你在脚本中定义的任何变量都是全局变量。在函数外定义的变量可在函数内正常访问。

 

例1、$value变量在函数外定义并被赋值。当libin函数被调用时,该变量及其值在函数中都依然有效。如果变量在函数内被赋予了新值,那么在脚本中引用该变量时,新值也依然有效。

[21:29:14 root@libin3 libin]# vim shell161

!/bin/bash

#using a globe variable to pass a value

 

function libin {

value=$[ $value * 2 ]

}

read -p "ether a value: " value

libin

echo "the new value is: $value"

 

[21:32:16 root@libin3 libin]# chmod +x shell161

[21:32:18 root@libin3 libin]# ./shell161

ether a value: 5

the new value is: 10

 

 

2由于函数中用到了$rhcsa变量,它的值在脚本中使用时受到了影响,产生了意想不到的后果。

实战1:这种写法没有问题

[21:50:07 root@libin3 libin]# vim shell162   

#!/bin/bash

#using a bad variables

 

#!/bin/bash

#using a bad variables

 

libin() {

rhcsa=$[ $rhce + 5 ]

rhce=$[ $rhcsa * 2 ]

}

rhcsa=2

rhce=4

 

libin

echo "the result is $rhce"

 

if [ $rhcsa -gt $rhce ]

then

echo "rhcsa is larger"

else

echo "rhcsa is smaller"

fi

[21:49:58 root@libin3 libin]# chmod +x shell162

[21:50:04 root@libin3 libin]# ./shell162

the result is 18

rhcsa is smaller

==============================================================================

实战2:貌似没有发现出问题所在,但是我们通过与local关键字就能看出问题,因为函数内部的变量改变了脚本主体中变量值。

[22:01:54 root@libin3 libin]# cp shell162 shell162-2

[22:03:21 root@libin3 libin]# vim shell162-2

#!/bin/bash

#using a bad variables

 

libin() {

rhcsa=$[ $rhce + 5 ]

result=$[ $rhcsa * 2 ]

}

rhcsa=2

rhce=4

 

libin

echo "the result is $result"

 

if [ $rhcsa -gt $rhce ]

then

echo "rhcsa is larger"

else

echo "rhcsa is smaller"

fi

 

[22:11:07 root@libin3 libin]# ./shell162-2

the result is 18

rhcsa is larger

 

1. 局部变量local

无需在函数中使用全局变量,函数内部使用的任何变量都可以被声明成局部变量。要实现这一点,只要在变量声明的前面加上local关键字就可以了。

local temp

也可以在变量赋值语句中使用local关键字:

local temp=$[ $value + 5 ]

 

1local关键字保证了变量只局限在该函数中。如果脚本中在该函数之外有同样名字的变量,那么shell将会保持这两个变量的值是分离的。现在就能很轻松地将函数变量和脚本变量隔离开,只共享需要共享的变量

[22:23:05 root@libin3 libin]# vim shell163

#!/bin/bash

#实现这local关键字

libin() {

local rhcsa=$[ $rhce + 5 ]

result=$[ $rhcsa * 2 ]

}

rhcsa=2

rhce=4

 

libin

echo "the result is $result"

if [ $rhcsa -gt $rhce ]

then

echo "rhcsa is larger"

else

echo "rhcsa is smaller"

fi

 

[22:27:21 root@libin3 libin]# chmod +x shell163

[22:27:27 root@libin3 libin]# ./shell163

the result is 18

rhcsa is smaller

 

注:libin函数中使用$rhcsa 变量时,并不会影响在脚本主体中赋给$rhcsa 变量的值。

 

17.4 数组变量和函数

 

使用数组来在单个变量中保存多个值的高级用法。在函数中使用数组变量值有点麻烦,而且还需要一些特殊考虑。

17.4.1 向函数传数组参数

向脚本函数传递数组变量的方法会有点不好理解。将数组变量当作单个参数传递的话,它不会起作用。

1、如果你试图将该数组变量作为函数参数,函数只会取数组变量的第一个值。要解决这个问题,必须将该数组变量的值分解成单个的值,然后将这些值作为函数参数使用。在函数内部,可以将所有的参数重新组合成一个新的变量。

[23:32:44 root@libin3 libin]# vim shell164

#!/bin/bash

#trying  to pass an anrray variable

function libin() {

echo "the value is: $@"

rhcsa=$1

echo "the receive rhcsa is ${rhcsa[*]}"

}

 

rhce=(1 2 3 4 5)

echo "first value is ${rhce[*]}"

libin $rhce

 

[23:38:03 root@libin3 libin]# chmod +x shell164

[23:39:20 root@libin3 libin]# ./shell164

first value is 1 2 3 4 5

the value is: 1

the receive rhcsa is 1

 

 

2、该脚本用$rhce变量来保存所有的数组元素,然后将它们都放在函数的命令行上。该函数随后从命令行参数中重建数组变量。在函数内部,数组仍然可以像其他数组一样使用。

[00:03:47 root@libin3 libin]# vim shell165

#!/bin/bash

#using variable to function test

 

function libin {

  local rhcsa

  rhcsa=$(echo "$@")

  echo "the new rhcsa value is: ${rhcsa[*]}"

}

 

rhce=(1 2 3 4 5)

echo "the first value is ${rhce[*]}"

libin ${rhce[*]}

 

[00:04:05 root@libin3 libin]# chmod +x shell165

[00:04:05 root@libin3 libin]# ./shell165

the first value is 1 2 3 4 5

the new rhcsa value is: 1 2 3 4 5

 

 

 

3、libin函数会遍历所有的数组元素,将它们累加在一起。你可以在rhce数组变量中放置任意多的值,libin函数会将它们都加起来。

[00:11:09 root@libin3 libin]# vim shell166

#!/bin/bash

#adding value in an $value

 

function libin() {

local libin1=0

local rhcsa

rhcsa=($(echo "$@"))

 

for i in ${rhcsa[*]}

do

libin1=$[ $libin1 + $i ]

done

echo $libin1

}

 

rhce=(1 2 3 4 5)

echo "the first value is: ${rhce[*]}"

rhca=$(echo ${rhce[*]})

 

result=$(libin $rhca)

echo "the result is $result"

 

[00:16:39 root@libin3 libin]# chmod +x shell166

[00:16:45 root@libin3 libin]# ./shell166

the first value is: 1 2 3 4 5

the result is 15

17.4.2 从函数返回数组

从函数里向shell脚本传回数组变量也用类似的方法。函数用echo语句来按正确顺序输出单个

数组值,然后脚本再将它们重新放进一个新的数组变量中。

 

1该脚本用$libin1变量将数组值传给libin函数。libin函数将该数组重组到新的数组变量中,生成该输出数组变量的一个副本。然后对数据元素进行遍历,将每个元素值翻倍,并将结果存入函数中该数组变量的副本。libin函数使用echo语句来输出每个数组元素的值。脚本用libin函数的输出来重新生成一个新的数组变量

[22:44:52 root@libin3 libin]# vim shell167

#!/bin/bash

#return an libin value

 

function libin() {

local rhcsa

local rhce

local rhca

local i

rhcsa=($(echo "$@"))

rhce=($(echo "$@"))

rhca=$[ $# - 1 ]

for ((i=0; i<=$rhca; i++))

{

rhce[$i]=$[ ${rhcsa[$i]} * 2 ]

}

echo ${rhce[*]}

}

libin1=(1 2 3 4 5)

echo "the first value is: ${libin1[*]}"

libin2=$(echo ${libin1[*]})

libin3=($(libin $libin2))

echo "the second value is: ${libin3[*]}"

 

[22:45:10 root@libin3 libin]# chmod +x shell167

[22:45:18 root@libin3 libin]# ./shell167

the first value is: 1 2 3 4 5

the second value is: 2 4 6 8 10

 

17.5 函数递归

局部函数变量的一个特性是自成体系。除了从脚本命令行处获得的变量,自成体系的函数不需要使用任何外部资源。

 

这个特性使得函数可以递归地调用,也就是说,函数可以调用自己来得到结果。通常递归函数都有一个最终可以迭代到的基准值。许多高级数学算法用递归对复杂的方程进行逐级规约,直到基准值定义的那级。

 

递归算法的经典例子是计算阶乘。一个数的阶乘是该数之前的所有数乘以该数的值。因此,

要计算5的阶乘,可以执行如下方程:

5! = 1 * 2 * 3 * 4 * 5 = 120

 

使用递归,方程可以简化成以下形式:

x! = x * (x-1)!

x的阶乘等于x乘以x-1的阶乘。这可以用简单的递归脚本表达为:

if [ $1 -eq 1 ]

then

echo 1

else

local libin1=$[ $1 - 1 ]

local libin2=’libin $libin1’

echo $[ $libin2 * $1 ]

fi

}

 

1、阶乘函数用它自己来计算阶乘的值:

[23:12:43 root@libin3 libin]# vim shell168

#!/bin/bash

#函数递归

 

function libin() {

if [ $1 -eq 1 ]

then

echo 1

else

local libin1=$[ $1 - 1 ]

local libin2=$(libin $libin1)

echo $[ $libin2 * $1 ]

fi

}

 

read -p "ether value: " value

rhcsa=$(libin $value)

echo "the libin of $value is: $rhcsa"

==============================================================================

[23:12:51 root@libin3 libin]# chmod +x  shell168

[23:13:06 root@libin3 libin]# ./shell168

ether value: 5

the libin of 5 is: 120           #5*4*3*2*1

 

17.6 创建库

bash shell允许创建函数库文件,然后在多个脚本中引用该库文件。这个过程的第一步是创建一个包含脚本中所需函数的公用库文件。这里有个叫作helloworld的库文件,它定义了3个简单的函数:

[00:24:05 root@libin3 libin]# cat helloworld

#my script function

 

function libin1 {

echo $[ $1 + $2 ]

}

function libin2 {

echo $[ $1 * $2 ]

}

function libin3 {

if [ $2 -ne 0 ]   

then

echo $[ $1 / $2 ]            #只能大的除小的,否则结果会为0

else

echo -1

fi

}

[00:24:31 root@libin3 libin]# chmod +x helloworld

下一步是在用到这些函数的脚本文件中包含helloworld库文件。

 

在shell函数的作用域上。和环境变量一样,shell函数仅在定义它的shell会话内有效。

如果你在shell命令行界面的提示符下运行helloworld shell脚本,shell会创建一个新的shell并在其中运行这个脚本。它会为那个新shell定义这三个函数,但当你运行另外一个要用到这些函数的脚本时,它们是无法使用的。这同样适用于脚本。如果你尝试像普通脚本文件那样运行库文件,函数并不会出现在脚本中。

[00:29:26 root@libin3 libin]# vim shell169

#!/bin/bash

#using a library file the wrong way

./helloword

 

result=$(libin1 5 10)

echo "The result is $result"

 

[00:29:14 root@libin3 libin]# chmod +x shell169

[00:29:22 root@libin3 libin]# ./shell169

[00:31:13 root@libin3 libin]# ./shell169

./shell169:3: ./helloword: 没有那个文件或目录

./shell169:5: libin1: 未找到命令

The result is

 

使用函数库的关键在于source命令。source命令会在当前shell上下文中执行命令,而不是

创建一个新shell。可以用source命令来在shell脚本中运行库文件脚本。这样脚本就可以使用库中的函数了。source命令有个快捷的别名,称作点操作符(dot operator)。要在shell脚本中运行helloworld 库文件,只需添加下面这行:

. ./helloworld

注:两个 . .之间有空格

 

实战1假定helloworld库文件和shell 脚本shell169-2位于同一目录。如果不是,需要使用相应路径访问该文件。

[00:31:14 root@libin3 libin]# vim shell169-2

#!/bin/bash

#using function defined in a library file

. ./helloworld

rhcsa=20

rhce=10

result1=$(libin1 $rhcsa $rhce)

result2=$(libin2 $rhcsa $rhce)

result3=$(libin3 $rhcsa $rhce)

 

echo "The libin1 value is: $result1"

echo "The libin2 value is: $result2"

echo "The libin3 value is: $result3"

 

[00:36:04 root@libin3 libin]# vim shell169-2

[00:40:51 root@libin3 libin]# chmod +x shell169-2

[00:40:58 root@libin3 libin]# ./shell169-2

The libin1 value is: 30

The libin2 value is: 200

The libin3 value is: 2

17.7 在命令行上使用函数

 

17.7.1 在命令行上创建函数

 

1shell会解释用户输入的命令,所以可以在命令行上直接定义一个函数。有两种方法。一种方法是采用单行方式定义函数。

 

1、

 

[01:06:51 root@libin3 libin]# function libin() { echo $[ $1 / $2 ]; }

[01:09:17 root@libin3 libin]# libin 10 2

5

 

 

 

2、当在命令行上定义函数时,你必须记得在每个命令后面加个分号,这样shell就能知道在哪里是命令的起止了。

 

1

 

[01:09:28 root@libin3 libin]# function libin() { read -p "enther value: " value; echo $[ $value * 2 ];}

[01:36:43 root@libin3 libin]# libin

enther value: 10

20

 

 

 

3另一种方法是采用多行方式来定义函数。在定义时,bash shell会使用次提示符来提示输入更

 

多命令。用这种方法,不用在每条命令的末尾放一个分号,只要按下回车键就行。在函数的尾部使用花括号,shell就会知道你已经完成了函数的定义。

 

1

 

[01:36:57 root@libin3 libin]# function libin() {

> echo $[ $1 * $2 ]

> }

[01:38:49 root@libin3 libin]# libin 2 5

10

 

 

 

注:在命令行上创建函数时要特别小心。如果你给函数起了个跟内建命令或另一个命令相同的名字,函数将会覆盖原来的命令。

 

17.7.2 .bashrc 文件中定义函数

 

.bashrc文件。bash shell在每次启动时都会在主目录下查找这个文件,不管是交互式shell还是从现有shell中启动的新shell。

 

1. 直接定义函数

 

可以直接在主目录下的.bashrc文件中定义函数。

 

1、

 

[23:44:14 root@libin3 libin]# cat ~root/.bashrc

# .bashrc

 

# User specific aliases and functions

 

alias rm='rm -i'

alias cp='cp -i'

alias mv='mv -i'

 

# Source global definitions

if [ -f /etc/bashrc ]; then

. /etc/bashrc

fi

PS1='\[\e[1;32m\][\[\e[0m\]\t \[\e[1;33m\]\u\[\e[36m\]@\h\[\e[1;31m\] \W\[\e[1;32m\]]\[\e[0m\]\$ '

echo 'wlcome to study redhat-linux'

[23:44:47 root@libin3 libin]# vim  ~root/.bashrc      #添加以下内容

function libin() {

echo $[ $1 + $2 ]

}

[00:02:06 root@libin3 ~]# source /root/.bashrc

[00:02:11 root@libin3 ~]# libin 10 5

15

 

注:该函数会在下次启动新bash shell时生效。随后你就能在系统上任意地方使用这个函数了。

 

1、读取函数文件

 

只要是在shell脚本中,都可以用source命令(或者它的别名点操作符)将库文件中的函数添加到你的.bashrc脚本中。

 

[00:04:17 root@libin3 ~]# cat /libin/libin/helloworld

#my script function

 

function libin1 {

echo $[ $1 + $2 ]

}

function libin2 {

echo $[ $1 * $2 ]

}

function libin3 {

if [ $2 -ne 0 ]

then

echo $[ $1 / $2 ]

else

echo -1

fi

}

 

[23:44:47 root@libin3 libin]# vim  ~root/.bashrc     

. /libin/libin/helloworld                        #添加以下内容

[00:03:05 root@libin3 ~]# source /root/.bashrc

[00:03:50 root@libin3 ~]# libin1 10 5

15

[00:04:06 root@libin3 ~]# libin2 10 5

50

[00:04:14 root@libin3 ~]# libin3 10 5

2

 

 

 

17.8 实例

 

17.8.1 下载及安装

 

将GNU shtool库下载并安装到你的系统中,这样才能在自己的shell脚本中使用这些库函数。要完成这项工作,可以使用FTP客户端或者图像化桌面中的浏览器。

 

 

 

shtool软件包的下载地址是:ftp://ftp.gnu.org/gnu/shtool/shtool-2.0.8.tar.gz使用火狐浏览器下载将其移动到/libin/libin/ 目录下:进行解压

[00:31:00 root@libin3 libin]# tar -zxvf shtool-2.0.8.tar.gz  

 

 

 

17.8.2 构建库

 

shtool文件必须针对特定的Linux环境进行配置。配置工作必须使用标准的configure和make命令,这两个命令常用于C编程环境。要构建库文件,只要输入:

 

[00:33:16 root@libin3 libin]# cd shtool-2.0.8/

[00:33:26 root@libin3 shtool-2.0.8]# ./configure

Configuring GNU shtool (Portable Shell Tool), version 2.0.8 (18-Jul-2008)

Copyright (c) 1994-2008 Ralf S. Engelschall <rse@engelschall.com>

checking whether make sets $(MAKE)... yes

checking for perl interpreter... /usr/bin/perl

checking for pod2man conversion tool... /usr/bin/pod2man

configure: creating ./config.status

config.status: creating Makefile

config.status: creating shtoolize

config.status: executing adjustment commands

[00:33:38 root@libin3 shtool-2.0.8]# make

 

测试模式会测试shtool库中所有的函数。如果全部通过测试,就可以将库安装到Linux系统中的公用位置,这样所有的脚本就都能够使用这个库了。要完成安装,需要使用make命令的install选项。得以root用户的身份运行该命令。

[00:34:52 root@libin3 shtool-2.0.8]# make install

 

 

 

17.8.3 shtool 库函数

 

shtool库提供了大量方便的、可用于shell脚本的函数。

 

 

 

shtool库函数

 

函数

描述

Arx

创建归档文件(包含一些扩展功能)

Echo

显示字符串,并提供了一些扩展构件

fixperm

改变目录树中的文件权限

install

安装脚本或文件

mdate

显示文件或目录的修改时间

mkdir

创建一个或更多目录

Mkln

使用相对路径创建链接

mkshadow

创建一棵阴影树

move

带有替换功能的文件移动

Path

处理程序路径

platform

显示平台标识

rotate

转置日志文件

Scpp

共享的C预处理器

Slo

根据库的类别,分离链接器选项

Subst

使用sed的替换操作

Table

以表格的形式显示由字段分隔field-separated)的数据

tarball

从文件和目录中创建tar文件

version

创建版本信息文件

 

每个shtool函数都包含大量的选项和参数,你可以利用它们改变函数的工作方式。下面是

 

shtool函数的使用格式:

 

shtool [options] [function [options] [args]]

用法: shtool [<options>] [<cmd-name> [<cmd-options>] [<cmd-args>]]

 

17.8.4 使用库

 

可以在命令行或自己的shell脚本中直接使用shtool函数。下面是一个在shell脚本中使platform函数的例子。

 

1、platform函数会返回Linux发行版以及系统所使用的CPU硬件的相关信息。

 

[00:45:14 root@libin3 libin]# vim shell170

#!/bin/bash

#使用库

shtool platform

[00:46:24 root@libin3 libin]# chmod +x shell170

[00:46:32 root@libin3 libin]# ./shell170

centos 7.9.2009 (AMD64)

 

2数prop函数。它可以使用\、|、/和-字符创建一个旋转的进度条。是一个非常漂亮的工具,可以告诉shell脚本用户目前正在进行一些后台处理工作。

 

[00:48:24 root@libin3 libin]# ls -al /usr/bin | shtool prop -p "waiting..."

waiting...    

 

注:prop函数会在处理过程中不停地变换进度条字符。在本例中,输出信息来自于ls命令。你能看到多少进度条取决于CPU能以多快的速度列出/usr/bin中的文件,-p选项允许你定制输出文本,这段文本会出现在进度条字符之前。

 

第十八章 图形化桌面环境中的脚本编程

 

说明:由于生产环境中,大多基于非图形化操作,我这里只对图形化桌面环境中的脚本编程做简单的讲述与示例,至于如何使用整合菜单布局和dialog包去制作窗口,望感兴趣的学者的探索。

 

18.1 创建文本菜单

 

1)创建交互式shell脚本最常用的方法是使用菜单。提供各种选项可以帮助脚本用户了解脚本能做什么和不能做什么。

 

2)通常菜单脚本会清空显示区域,然后显示可用的选项列表。用户可以按下与每个选项关联的字母或数字来选择选项。

 

18.1.1 创建菜单布局

 

1)创建菜单的第一步显然是决定在菜单上显示哪些元素以及想要显示的布局方式。

 

2)在创建菜单前,通常要先清空显示器上已有的内容。这样就能在干净的、没有干扰的环境中显示菜单了。

 

3clear命令用当前终端会话的terminfo数据来清理出现在屏幕上的文本。运行clear命令之后,可以用echo命令来显示菜单元素。

 

4默认情况下,echo命令只显示可打印文本字符。在创建菜单项时,非可打印字符通常也很有用,比如制表符和换行符。要在echo命令中包含这些字符,必须用-e选项。因此,命令如下:1

 

echo -e "1.\tDisplay disk space"

 

[00:50:12 root@libin3 libin]# echo -e "1.\libin clear space"

1.\libin clear space

[01:02:02 root@libin3 libin]# echo -e "1.\tDisplay disk space"

1. Display disk space

[01:02:31 root@libin3 libin]# echo -e "1.\tlibin clear space"

1. libin clear space

 

 

 

2、只需要几个echo命令,就能创建一个还行的菜单。最后一行的-en选项会去掉末尾的换行符。光标会一直在行尾等待用户的输入。创建菜单的最后一步是获取用户输入。这步用read命令,因为我们期望只有单字符输入,所以在read命令中用了-n选项来限制只读取一个字符。这样用户只需要输入一个数字,也不用按回车键:

 

[01:07:31 root@libin3 libin]# clear

[01:04:40 root@libin3 libin]# echo

[01:04:44 root@libin3 libin]# echo -e "\t\t\tSys Admin Menu\n"

Sys Admin Menu

[01:05:16 root@libin3 libin]# echo -e "\t1. Display disk space"

1. Display disk space

[01:05:43 root@libin3 libin]# echo -e "\t2. Display logged on users"

2. Display logged on users

[01:06:12 root@libin3 libin]# echo -e "\t3. Display memory usage"

3. Display memory usage

[01:07:24 root@libin3 libin]# echo -en "\t\tEnter option: "

Enter option: [01:07:31 root@libin3 libin]#

Enter option: [01:10:51 root@libin3 libin]# read -n 1 option

 

 

 

 

。。。

 

第十九章 认识sedgawk

 

shell脚本最常见的一个用途就是处理文本文件。检查日志文件、读取配置文件、处理数据元素,shell脚本可以帮助我们将文本文件中各种数据的日常处理任务自动化。但仅靠shell脚本命令来处理文本文件的内容有点勉为其难。如果想在shell脚本中处理任何类型的数据,你得熟悉Linux中的sedgawk工具。这两个工具能够极大简化需要进行的数据处理任务。

 

19.1 文本处理

Linux系统提供了两个常见的具备上述功能的工具。本节将会介绍Linux世界中最广泛使用的两个命令行编辑器:sed和gawk。

19.1.1 sed 编辑器-e-f

 

1sed编辑器被称作流编辑器stream editor),和普通的交互式文本编辑器恰好相反。在交互式文本编辑器中(比如vim),你可以用键盘命令来交互式地插入、删除或替换数据中的文本。流编辑器则会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流。

2sed编辑器可以根据命令来处理数据流中的数据,这些命令要么从命令行中输入,要么存储在一个命令文本文件中。

3sed编辑器会执行下列操作:

1一次从输入中读取一行数据。

 

2根据所提供的编辑器命令匹配数据。

 

3按照命令修改流中的数据。

 

4将新的数据输出到STDOUT。

 

 

 

4、在流编辑器将所有命令与一行数据匹配完毕后,它会读取下一行数据并重复这个过程。在流 编辑器处理完流中的所有数据行后,它就会终止。sed编辑器只需对数据流进行一遍处理就可以完成编辑操作。sed编辑器要比交互式编辑器快得多,你可以快速完成对数据的自动修改。

 

 

 

sed命令的格式如下:

 

sed options script file

 

 

 

sed命令选项

 

-e script

在处理输入时,将script中指定的命令添加到已有的命令中

-f file

在处理输入时,将file中指定的命令添加到已有的命令中

-n

不产生命令输出,使用print命令来完成输出

 

说明:

 

script参数指定了应用于流数据上的单个命令。如果需要用多个命令,要么使用-e选项在命令行中指定,要么使用-f选项在单独的文件中指定。有大量的命令可用来处理数据。

 

1. 在命令行定义编辑器命令

 

默认情况下,sed编辑器会将指定的命令应用到STDIN输入流上。这样你可以直接将数据通过管道输入sed编辑器处理。在运行这个例子时,结果应该立即就会显示出来。这就是使用sed编辑器的强大之处。就算编辑整个文件,处理速度也相差无几。所消耗的时间却只够一些交互式编辑器启动而已。

 

 

 

1

 

[14:56:00 root@libin3 ~]# echo "welcom to study linux" | sed 's/linux/redhat linux/'

welcom to study redhat linux

 

注:s命令用斜线间指定的第二个文本字符串来替换第一个文本字符串模式。因此linux被替换成了redhat linux。

 

 

 

例子2

 

[15:09:13 root@libin3 libin]# cat sedfile.txt

welcome to study linux redhat.

welcome to study linux redhat.

welcome to study linux redhat.

 

[15:09:24 root@libin3 libin]# sed 's/redhat/suse' /libin/libin/sedfile.txt

sed-e 表达式 #1,字符 13:未终止的“s”命令

[15:09:55 root@libin3 libin]# sed 's/redhat/suse/' /libin/libin/sedfile.txt

welcome to study linux suse.

welcome to study linux suse.

welcome to study linux suse.

 

[15:10:14 root@libin3 libin]# cat /libin/libin/sedfile.txt

welcome to study linux redhat.

welcome to study linux redhat.

welcome to study linux redhat.

 

说明:我们注意到被替换的字符串后面要加/sed命令几乎瞬间就执行完并返回数据。在处理每行数据的同时,结果也显示出来了。sed编辑器并不会修改文本文件的数据。它只会将修改后的数据发送到STDOUT。如果你查看原来的文本文件,它仍然保留着原始数据。

 

2. 在命令行使用多个编辑器命令

 

1要在sed命令行上执行多个命令时,只要用-e选项就可以了,两个命令都作用到文件中的每行数据上。命令之间必须用分号隔开,并且在命令末尾和分号之间不能有空格。

 

 

 

[16:37:38 root@libin3 libin]# cat sedfile.txt

welcome to study linux redhat.

welcome to study linux redhat.

welcome to study linux redhat.

 

[16:37:39 root@libin3 libin]# sed -e 's/study/read/; s/redhat/suse/' /libin/libin/sedfile.txt

welcome to read linux suse.

welcome to read linux suse.

welcome to read linux suse.

 

 

 

2如果不想用分号,也可以用bash shell中的次提示符来分隔命令。输入第一个单引号标示

 

出sed程序脚本的起始(sed编辑器命令列表),bash会继续提示你输入更多命令,直到输入了标示

 

结束的单引号。

 

[16:42:39 root@libin3 libin]# sed -e '

> s/study/read/

> s/to/go/

> s/redhat/suse/' /libin/libin/sedfile.txt

welcome go read linux suse.

welcome go read linux suse.

welcome go read linux suse.

 

注意:要在封尾单引号所在行结束命令。bash shell一旦发现了封尾的单引号,就会执行

命令。开始后,sed命令就会将你指定的每条命令应用到文本文件中的每一行上。

 

3. 从文件中读取编辑器命令

 

如果有大量要处理的sed命令,将它们放进一个单独的文件中通常会更方便。可以在sed命令中用-f选项来指定文件。

 

 

 

1把替换内容放在单独文件中,不用在每条命令后面放一个分号。sed编辑器知道每行都是一条单独的命令。sed编辑器会从指定文件中读取命令,并将它们应用到数据文件中的每一行上。

 

为了避免我们将sed编辑器与bash shell搞混,可以以.sed脚本文件的扩展名命名。

 

[16:52:11 root@libin3 libin]# cat script1.sed

s/to/go/

s/study/read/

s/redhat/bclinux/

 

[16:52:19 root@libin3 libin]# cat sedfile.txt

welcome to study linux redhat.

welcome to study linux redhat.

welcome to study linux redhat.

 

[16:52:30 root@libin3 libin]# sed -f script1.sed sedfile.txt

welcome go read linux bclinux.

welcome go read linux bclinux.

welcome go read linux bclinux.

 

 

 

 

19.1.2 gawk 程序

 

虽然sed编辑器是非常方便自动修改文本文件的工具,但其也有自身的限制。通常你需要一个用来处理文件中的数据的更高级工具,它能提供一个类编程环境来修改和重新组织文件中的数据。这正是gawk能够做到的。

 

说明:在所有的发行版中都没有默认安装gawk程序。如果所用的Linux发行版中没有包含gawkgawk程序是Unix中的原始awk程序的GNU版本。gawk程序让流编辑迈上了一个新的台阶,它提供了一种编程语言而不只是编辑器命令。

 

1)定义变量来保存数据;

 

2)使用算术和字符串操作符来处理数据;

 

3)使用结构化编程概念(比如if-then语句和循环)来为数据处理增加处理逻辑;

 

4)通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告。

 

1. gawk的命令格式

 

gawk程序的基本格式如下:

gawk options program file

 

 

 

gawk选项

 

选项

描述

-F fs

指定行中划分数据字段的字段分隔符

-f file

从指定的文件中读取程序

-v var=value

定义gawk程序中的一个变量及其默认值

-mf N

指定要处理的数据文件中的最大字段数

-mr N

指定数据文件中的最大数据行数

-W keyword

指定gawk的兼容模式或警告等级

 

2. 从命令行读取程序脚本

 

gawk程序脚本用一对花括号来定义。必须将脚本命令放到两个花括号({})中。如果错误地使用了圆括号来包含gawk脚本,就会得到一条类似于下面的错误提示。

 

 

 

1

 

[17:34:18 root@libin3 libin]# gawk '(print "hello world!"}'

gawk: cmd. line:1: (print "hello world!"}

gawk: cmd. line:1:  ^ syntax error

 

由于gawk命令行假定脚本是单个文本字符串,必须将脚本放到单引号中。

[18:08:00 root@libin3 libin]# gawk '{print "hello world!"}'

hello

hello world!

this is a test

hello world!

这个程序脚本定义了一个命令:print命令。这个命令它会将文本打印到STDOUT。只运行命令不会有任何显示:原因在于没有在命令行上指定文件名,所以gawk程序会从STDIN接收数据。在运行这个程序时,它会一直等待从STDIN输的文本。

 

如果一直回车键,gawk会对这行文本运行一遍程序脚本。由于程序脚本被设为显示一行固定的文本字符串,因此不管你在数据流中输入什么文本,都会得到同样的文本输出。

 

终止这个gawk程序,bash shell提供了一个组合键来生成EOF(End-of-File)字符。Ctrl+D组合键会在bash中产生一个EOF字符。

 

 

 

3. 使用数据字段变量-F

 

gawk的主要特性之一是其处理文本文件中数据的能力。它会自动给一行中的每个数据元素分

 

配一个变量。默认情况下,gawk会将如下变量分配给它在文本行中发现的数据字段:

 

1)$0代表整个文本行;

 

2)$1代表文本行中的第1个数据字段;

 

3)$2代表文本行中的第2个数据字段;

 

4)$n代表文本行中的第n个数据字段。

 

gawk中默认的字段分隔符是任意的空白字符(例如空格或制表符)

 

 

 

1gawk程序读取文本文件,只显示第1个数据字段的值。该程序用$1字段变量来仅显示每行文本的第1个数据字段。

 

[18:18:55 root@libin3 libin]# cat gawk.txt

one lines of test text.

two lines of test text.

three lines of test text.

 

[18:18:59 root@libin3 libin]# gawk '{print $1}' gawk.txt

one

two

three

 

 

 

2、要读取采用了其他字段分隔符的文件,可以用-F选项指定。

 

实战1

[18:22:51 root@libin3 libin]# gawk -F "lines" '{print $2}' gawk.txt

 of test text.

 of test text.

 of test text.

 

实战2、显示了系统中密码文件的第1个数据字段

[18:24:54 root@libin3 libin]# gawk -F: '{print $1}'  /etc/passwd | head -n 5

root

bin

daemon

adm

lp

 

4. 在程序脚本中使用多个命令

 

如果一种编程语言只能执行一条命令,不会有太大用处。gawk编程语言允许你将多条命令组合成一个正常的程序。

 

 

 

1、要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号|即可。

 

[18:35:16 root@libin3 libin]# echo "my name is libin" | gawk '{$4="LIBIN"; print $0}'

my name is LIBIN

 

说明:第一条命令会给字段变量$4赋值。第二条命令会打印整个数据字段。输出的内容也是被替换后的新值。

 

 

 

2、也可以用次提示符一次一行地输入程序脚本命令。

 

[18:42:50 root@libin3 libin]# gawk '{

> $4="LIBIN"

> print $0}'

my name is libin

my name is LIBIN

[18:43:27 root@libin3 libin]#

 

说明:在你用了表示起始的单引号后,bash shell会使用次提示符来提示你输入更多数据。你可以每次在每行加一条命令,直到输入了结尾的单引号。因为没有在命令行中指定文件名,gawk程序会从STDIN中获得数据。退出程序,只需按下Ctrl+D组合键。

 

5. 从文件中读取程序

 

1、跟sed编辑器一样,gawk编辑器允许将程序存储到文件中,然后再在命令行中引用。

 

[18:51:04 root@libin3 libin]# cat script2.gawk

{print $1 "'s home directory is " $6}

 

[18:51:47 root@libin3 libin]# gawk -F: -f script2.gawk /etc/passwd

root's home directory is /root

bin's home directory is /bin

daemon's home directory is /sbin

adm's home directory is /var/adm

lp's home directory is /var/spool/lpd

[...]

libin101's home directory is /home/libin101

libin102's home directory is /home/libin102

libin103's home directory is /home/libin103

 

说明:script2.gawk程序脚本会再次使用print命令打印/etc/passwd文件的主目录数据字段(字段变量$6),以及userid数据字段(字段变量$1)。

 

 

 

2、可以在程序文件中指定多条命令。要这么做的话,只要一条命令放一行即可,不需要用分号。

 

[21:23:39 root@libin3 libin]# cat script3.gawk

{

text="'s home directory is "

print $1 text $6

}

 

[21:23:16 root@libin3 libin]# gawk -F: -f script3.gawk /etc/passwd

root's home directory is /root

bin's home directory is /bin

daemon's home directory is /sbin

adm's home directory is /var/adm

lp's home directory is /var/spool/lpd

[...]

libin101's home directory is /home/libin101

libin102's home directory is /home/libin102

libin103's home directory is /home/libin103

 

说明:script3.gawk程序脚本定义了一个变量来保存print命令中用到的文本字符串。注意,gawk程序在引用变量值时并未像shell脚本一样使用美元符。

 

 

6. 在处理数据前运行脚本BEGIN

 

gawk还允许指定程序脚本何时运行。默认情况下,gawk会从输入中读取一行文本,然后针对该行的数据执行程序脚本。有时可能需要在处理数据前运行脚本,比如为报告创建标题。BEGIN关键字就是用来做这个的。它会强制gawk在读取数据前执行BEGIN关键字后指定的程序脚本。

 

 

 

1、次print命令会在读取数据前显示文本。但在它显示了文本后,它会快速退出,不等待任

 

何数据。

 

[21:52:16 root@libin3 libin]# gawk 'BEGIN {print "hello world"}'

hello world

 

 

 

2、如果想使用正常的程序脚本中处理数据,必须用另一个脚本区域来定义程序。gawk执行了BEGIN脚本后,它会用第二段脚本来处理文件数据。这么做时要小心,两段脚本仍然被认为是gawk命令行中的一个文本字符串。你需要相应地加上单引号

 

[21:55:08 root@libin3 libin]# cat script4.gawk

libin 1

libin 2

libin 3

 

[21:56:51 root@libin3 libin]# gawk 'BEGIN {print "the script4 file contents:"}

> {print $0}' script4.gawk

the script4 file contents:

libin 1

libin 2

libin 3

 

 

7. 在处理数据后运行脚本END

 

BEGIN关键字类似,END关键字允许你指定一个程序脚本,gawk会在读完数据后执行它。

 

 

 

1、当gawk程序打印完文件内容后,它会执行END脚本中的命令。这是在处理完所有正常数据后给报告添加页脚的最佳方法。

 

[21:58:01 root@libin3 libin]# gawk 'BEGIN {print "the script4 file contents:"}

> {print $0}

> END {print "end of file"}' script4.gawk

the script4 file contents:

libin 1

libin 2

libin 3

end of file

 

 

 

2、将所有这些内容放到一起组成一个程序脚本文件,用它从一个简单的数据文件中创建一份完整的报告。

 

[22:12:25 root@libin3 libin]# cat script5.gawk

BEGIN {

print "the latest list of users and shells"

print " UserID \t Shell"

print "------- \t-------"

FS=":"

}

{

print $1 "\t "$7

}

END {

print "this concludes the listing"

}

 

 

[22:12:11 root@libin3 libin]# gawk -f script5.gawk /etc/passwd

the latest list of users and shells       

 UserID  Shell

-------- -------

root  /bin/bash

bin  /sbin/nologin

daemon  /sbin/nologin

[...]

libin101  /bin/bash

libin102  /bin/bash

libin103  /bin/bash

this concludes the listing

 

 

19.2 sed 编辑器基础

 

posted @ 2022-07-25 22:09  LB_运维技术  阅读(334)  评论(0编辑  收藏  举报