标签列表

everest33

自制力

导航

Linux命令行与shell脚本编程大全第三版 学习笔记 (一)

除了man 命令由于查看帮助手册外,还有如下命令可

 Linux自己的文件资料: /usr/share/doc (在你的Linux系统中)  https://www.osyunwei.com/archives/290.html

https://www.gnu.org/software/bash/manual/【bash的官方文档,注意不包含第三方软件。另外这个www.gnu.org网站值得探索一番】

打开的黑色窗口叫做terminal,输入命令进行交互的程序叫做shell

 0. 注意Linux中一切皆文件,本文中的文件指的是广义的文件,包括文件、目录、硬件设备 等等。

1. 初识Linux shell


1.1, Linux分为四个部分: Linux内核,GNU工具集,图形化桌面环境,应用软件

1.1.1, Linux内核主要负责四种功能:系统内存管理,软件程序管理,硬件设备管理,文件系统管理

1.1.1.1, 物理内存&虚拟内存;交换空间(swap space),换入(swapping in), 换出(swapping out)

1.1.1.2, 进程;init进程;`/etc/initab` (TODO:被systemd取代了); runlevel(共有5个运行等级);

1.1.1.3,任何与Linux系统通信的硬件设备,都需要在Linux内核代码中加入驱动程序代码;内核模块;Linux系统将硬件设备当成特殊的文件(`Linux系统一切皆文件`),称为设备文件,分为三类(1字符型设备文件,2块设备文件,3网络设备文件);Linux系统为系统上的每个设备都创建了一种称为节点的特殊文件,每个节点都有唯一的数值对供Linux内核标识它,数值对包含一个主设备号和一个次设备号。

1.1.1.4, Linux支持的文件系统类型(ext, ext4, msdos,ntfs,proc,vfat[即fat32]....);Linux内核采用虚拟文件系统(Vitual File System)作为和每个文件系统交互的接口。

1.1.2,GNU(GNU's Not Unix)是GNU组织在开源软件(Open Source Software,OSS)理念下模仿Unix操作系统开发的一系列标准的计算机系统工具;Linux内核和GNU工具结合在一起就构成了Linux操作系统,也叫GNU/Linux操作系统。

1.1.2.1, GNU核心工具包(coreutils软件包)由三部分构成:用以处理文件的工具,用以操作文本的工具,用以管理进程的工具。

1.1.2.2, shell是GNU工具集中的一部分,Linux发行版默认的shell一般是GNU bash shell,这是GNU开发的作为 标准Unix Shell--Bourne Shell(开发者名字命名)的替代品,bash即Bourne Again Shell;其他shell类型。

1.1.3,X.org等软件包实现了一套软件X Windows,XWondows 软件是直接和显卡和显示器打交道的底层程序, 用以产生图形化显示环境,建立在XWindows系统软件之上的桌面环境可以供用户操作文件或开启程序;比较流行的Linux桌面环境有:KDE(K DeskTop Environment), GNOME(the GNU Network Object Model Environment),Unity桌面环境(Ubuntu开发),其他低内存消耗的Linux桌面环境;桌面环境都配有各种应用软件。


 1.2,Linux的4个组成部分汇集起来组成一个易于安装的包就是Linux发行版;不同的Linux发行版可归为三类:完整的Linux发行版,特定用途的发行版,LiveCD测试发行版。

1.2.3, 多数PC可以从CD启动而不是必须从标准硬盘启动,基于这点,一些Linux发行版创建了含有Linux样本系统(称为Linux LiveCD)的可引导CD,Linux LiveCD是一种无需将Linux安装到硬盘就能体验Linux的发行版



 2. 走进Shell

2.1,终端

  • 不涉及GUI的终端:控制台终端(ctrl+alt+f2~f7)属于虚拟终端(运行在内存中),可以使用setterm -background yellow 等命令设置控制台终端样式;
  • GUI中的终端:桌面环境下的终端属于仿真终端;

3. 基本的Bash Shell 命令

3.1,启动shell

3.1.1, GNU bash shell 能够提供对Linux系统的交互式访问。它是作为普通程序运行的。除了Bash Shell还有一些其他的shell,如dash shell, tcsh等等。用户登录终端时启动的shell类型取决于用户账户如何配置, `/etc/passwd` 文件包含了系统所有用户账户列表以及每个用户的基本配置信息,例如其中一个用户条目:`tonux:x:1000:1000:Tonux:/home/tonux:/bin/bash` ,每个条目有七个字段,字段之间用分号分开,每个字段赋予用户账户某些特定特性,最后一个字段指定了用户使用的shell程序。

3.1.2,Bash Shell是在用户登录系统时自动启动的,但是是否会出现shell命令行界面(CLI)则依赖于使用的登录方式。如果是采用虚拟化终端登录,CLI命令提示符会自动出现,可以输入shell命令;如果是通过图形化桌面环境登录Linux的话,则需要启动一个图形化终端仿真器来访问shell 命令行界面(CLI)。


3.2,bash shell 的默认提示符是美元符号$, 可以修改。


3.3,bash手册

3.3.1, man 命令用来访问存储在Linux系统上的手册页面。如果养成了阅读手册的习惯,尤其是阅读第一段或是 DESCRIPTION 部分的前两段,最终你会学到各种技术行话,手册页也会变得越来越有用。

3.3.2,`man -k keyword` 可以查找关键词相关的命令(正则匹配keyword>,还会列出命令所在的模块,忘记命令具体名称时可以使用此技巧。等同于命令·apropos keyword·

  • `man -f <command>` // 等同于命令·whatis <command>· 显示手册页中有哪些关于command(精确匹配命令)的说明以及位于哪些模块。

3.3.3, man命令手册分为9个模块,有的命令在不同的模块都有介绍,比如 hostname 命令,在第一个模块和第七个模块都有介绍。想要查看特定模块内的内容,可以使用`man 7 hostname`.

3.3.4,查看9个模块的介绍,使用如下命令: `man 1 intro`, `man 2 intro`......,各模块的类别信息如下:

模块号 所涵盖的内容
1 可执行程序或shell命令
2 系统调用
3 库调用
4 特殊文件
5 文件格式与约定
6 游戏
7 概览、约定及杂项
8 超级用户和系统管理员命令
9 内核例程

  

3.3.5,除了man 命令由于查看帮助手册外,还有如下命令可查看shell命令的帮助信息:

  • 1.  info 命令也能查看命令帮助信息,就内容来说,info页面比man page编写得要更好、更容易理解,也更友好,但man page使用起来确实要更容易得多。一个man page只有一页,而info页面几乎总是将它们的内容组织成多个区段(称为节点),每个区段也可能包含子区段(称为子节点)。理解这个命令的窍门就是不仅要学习如何在单独的Info页面中浏览导航,还要学习如何在节点和子节点之间切换。可能刚开始会一时很难在info页面的节点之间移动和找到你要的东西;
    • ·info [options] <command>· // n键-next node; p键-previous node;space键-下一页;backspace/del键:上一页;q键-退出;
  • 2,help命令,built-in 命令需要通过 help命令查看,如 help cd, help history等等, help help 可以查看help本身的相关信息;
  • 3,还可以使用命令的 --help 或 -help 或 -h 选项查看帮助信息, 如 cp --help ;

3.4, 浏览文件系统

3.4.1, Linux文件系统,

  • 在Windows中,PC上安装的物理驱动器决定了文件的路径名。Windows会为每个物理磁盘驱动器分配一个盘符,每个驱动器都会有自己的目录结构,以便访问存储其中的文件。

  • Linux则采用了一种不同的方式。Linux将文件存储在单个目录结构中,这个目录被称为虚拟目录(virtual directory)。虚拟目录将安装在PC上的所有存储设备的文件路径纳入单个目录结构中。Linux虚拟目录结构只包含一个称为根(root)目录的基础目录。在Linux PC上安装的第一块硬盘称为根驱动器。根驱动器包含了虚拟目录的核心,其他目录都是从那里开始构建的。Linux会在根驱动器上创建一些特别的目录,我们称之为挂载点(mount point)。挂载点是虚拟目录中用于分配额外存储设备的目录。虚拟目录会让文件和目录出现在这些挂载点目录中,然而实际上它们却存储在另外一个驱动器中。

  • https://zhidao.baidu.com/question/1645248554140989660.html
  • https://www.cnblogs.com/sammyliu/p/5729026.html

表3-3 常见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 可变目录,用以存放经常变化的文件,比如日志文件

3.4.2,`cd`, `pwd(present working directory)`


 3.5, 文件和目录列表

3.5.0,文件的inode元信息中的三个时间: atime(access time) , ctime(change time), mtime(modification time):

  • atime:访问时间。文件被访问,其时间就会被更新。ls -ul 显示atime.
  • ctime: 改变时间。文件的属性或内容发生变化,这个ctime就会被更新。如:chmod命令更改了文件的属性,ctime就会更新。ls -cl显示ctime.
  • mtime: 更改时间。文件的内容发生变化,这个mtime就会被更新。 ls -l默认显示的是这个时间。
  • 创建时间为何被忽略掉,至今仍不清楚。目前好像无法查询到 文件的创建时间.....
  • `stat <filename>` //查看文件的inode信息。
  • `env LANG=en_US.UTF-8 stat <fileName>` //输出stat命令的英文版内容。这里的env的作用是设置此次命令(stat filename)的环境变量,只影响本次的命令的环境变量,不影响本机的环境变量

3.5.1,`ls 命令`

  • ls -F 用以区分文件类型:目录后加/, 可执行文件加 *,软连接加@,
  • ls -d 只列出目录本身的信息,不列出其中的内容,
  • ls -i 显示文件/目录的 inode编号(文件或目录的inode编号是一个用于标识的唯一数字,这个数字由内核分配给文件系统中的每一个对象。)
  • ls -a 列出隐藏文件
  • ls -R 递归列出子目录
  • ls -l  显示长列表, 输出第一行表示目录中包含的总块数。ls默认显示的时间是最后一次内容修改的时间,相当于选项 --time=mtime (modify time)

     文件类型,比如目录( d )、文件( - )、字符型文件( c )或块设备( b );
     文件的权限(参见第6章);
     文件的硬链接总数;
     文件属主的用户名;
     文件属组的组名;
     文件的大小(以字节为单位);
     文件的上次修改时间;
     文件名或目录名。

  • ls -ul 显示的时间是访问时间,-u 相当于 --time=atime (access time)
  • ls -cl 显示的时间是状态改变时间, -c相当于--time=ctime (change time)
  • `ls -l 过滤器`,元字符通配符(metacharacter wildcards)
    • 问号( ? )代表一个字符;
    • 星号( * )代表零个或多个字符;
    • [ai],[a-i],[!a]

3.5.2,ls输出详解

例如:lrwxrwxrwx. 1 root root 7 Oct 3 02:33 bin -> usr/bin

  • 第一个字符的含义:

    • -:常规文件
    • b:块特殊文件
    • c:字符特殊文件
    • C:高性能(”连续数据“)文件
    • d:目录
    • D:门(Solaris 2.5及以上版本)
    • l:符号链接
    • M:离线(”前已“)文件(Cray DMF)
    • n:网络专用文件(HP-UX)
    • p:FIFO(命名管道)
    • P:断开(Solaros 10及以上)
    • s:套接字
    • ?:其他文件
  • 第二个字符的含义:

    • r:属主的读权限
  • 第三个字符的含义:

    • w:属主的写权限
  • 第四个字符的含义:

    • x:属主的执行权限

    • S:设置了SUID,没有执行权限

    • s:设置了SUID,具有执行权限

  • 第五个字符的含义:

    • r:属组的读权限
  • 第六个字符的含义:

    • w:属主的写权限
  • 第七个字符的含义:

    • x:属组执行权限

    • S:设置了SGID,没有执行权限

    • s:设置了SGID,具有执行权限

  • 第八个字符的含义:

    • r:其他人的读权限
  • 第九个字符的含义:

    • w:其他人的写权限
  • 第十个字符的含义:

    • x:其他人的执行权限
    • T:设置了粘滞位,没有执行权限
    • t:设置了粘滞位,具有执行权限
  • 第十一个字符的含义:

    • .:没有任何其他替代访问方法的SELinux安全上下文(没有设置ACL)
    • +:具有任何其他组合访问方法的SELinux安全上下文(设置了ACL)
  • 第十二个字符的含义:该文件的硬链接数量

  • 第十三个字符的含义:该文件的属主

  • 第十四个字符的含义:该文件的属组

  • 第十五个字符的含义:该文件的大小

  • 第十六到第十八个字符的含义:最后一次修改的时间

  • 第十九个字符的含义:文件或目录的名称

  • 第二十个字符的含义:链接符号

  • 第二十一个字符的含义:链接文件的源文件

3.5.3:setuid, setgid: 

一个文件都有一个所有者,表示该文件是谁创建的.同时,该文件还有一个组编号,表示该文件所属的组,一般为文件所有者所属的组.如果是一个可执行文件,那么在执行时,一般该文件只拥有调用该文件的用户具有的权限.而setuid,setgid可以来改变这种设置.

3.5.3.1.chmod u+s用法:

  • setuid:设置使文件在执行阶段具有文件所有者的权限.典型的文件是 /usr/bin/passwd.如果一般用户执行该文件,则在执行过程中,该文件可以获得root权限,从而可以更改用户的密码.当命令执行完毕,该用户的身份立即消失
  • chmod u+s temp — 为temp文件加上setuid标志.(setuid只对文件有效)

3.5.3.2.chmod g+s用法:

  • setgid:该权限只对目录有效.目录被设置该位后,任何用户在此目录下创建的文件都具有和该目录所属的组相同的组.
  • chmod g+s tempdir — 为tempdir目录加上setgid标志 (setgid只对目录有效)

3.5.3.3.chmod o+t用法:

  • sticky bit:该位可以理解为防删除位.一个文件是否可以被某用户删除,主要取决于该文件所属的组是否对该用户具有写权限.如果没有写权限,则这个目录下的所有文件都不能被删除,同时也不能添加新的文件.如果希望用户能够添加文件但同时不能删除文件,则可以对文件使用sticky bit位.设置该位后,就算用户对目录具有写权限,也不能删除该文件.
  • chmod o+t temp — 为temp文件加上sticky标志 (sticky只对文件有效)

3.5.3.4. 其他设置方式:

  • 设置SetUid权限: chmod 4xxx filename
  • 取消SetUid权限: chmod xxx  filename
  • 设置SetGid权限: chmod 2xxx filename
  • 取消SetGid权限: chmod xxx  filename
  • 如果执行“chmod 6xxx filename”命令即可同时为指定文件设置SetUid和SetGid,
  • 执行命令“chmod 0xxx filename”,即可同时取消指定文件的SetUid和SetGid权限。

3.5.3.4: 尽量使用setgid而避免使用setuid: setuid位的设立是有风险的!如果某个普通进程执行一个设置了setuid位,且所有者是“root”的文件,立刻具有了超级用户的权利,万一该程序有BUG,就留下了被人入侵系统的可能。因此,把文件按性质分类,分配合适的组,最后再设置setgid位来达到目的是一个不错的办法。

3.5.3.5: 禁用setuid: 对于存放在敏感数据的分区而言,有时可能希望禁用SetUID权限设置功能。例如对"/home" 分区禁用SetUID权限,可以找到修改其配置文件“/etc/fstab”, 执行命令“vi /etc/fstab”,可以看到“LABEL=/home /home ext3 defaults 1 2”等数据,我们只需在上述“default”关键字的后面添加“nosuid” 关键字即可,例如使用“vi”命令将其修改为“LANBEL=/home /home ext3 default,nosuid 1 2”,之后执行命令“mount -o remount /home”,这样即使对“/home”分区上的任何可执行文件配置了SetUID权限,也是无效的。这样就在很大程度上保护了系统的安全

3.5.3.6, 记录一个问题:89.51上使用mv或其他一些命令时,报错:sudo: /usr/bin/sudo 必须属于用户 ID 0(的用户)并且设置 setuid 位。经排查是以下两个文件的权限被人修改了,现在权限是这样的:

  • -rwxr-xr-x 1 root root 151424 1月  26 2023 /usr/bin/sudo
  • -rwxrwxrwx 1 root root 423104 1月  26 2023 /usr/libexec/sudo/sudoers.so

和89.50服务器的文件做对比,原来应该是这样的

  • ---s--x--x. 1 root root 147336 10月  1 2020 /usr/bin/sudo // 注:另一个主机上是这样的:-rwsr-xr-x 1 root root 232416  4月  4  2023 /usr/bin/sudo*
  • -rw-r--r--. 1 root root 423096 10月  1 2020 /usr/libexec/sudo/sudoers.so

通过以下命令修改即可:

  • chmod 4111 /usr/bin/sudo
  • chmod 644 /usr/libexec/sudo/sudoers.so

3.6,处理文件

3.6.1,创建文件, `touch`

  • 访问时间[ access time ], 读取一次文件内容,便会将access time 更新为当前系统时间. 如less/more命令会更新access time, 但是ls, stat命令不会修改access time;,注意测试时发现访问时间始终显示的是文件被修改后第一次访问的时间,不改变文件的情况下访问文件后 访问时间并不更新。
  • 修改时间[ modify time],对文件内容修改一次便会更新该时间; 
  • 状态改变时间[ change time ], 更改文件的属性便会更新该时间;
  • `stat filename` 查看文件详细信息

3.6.3,`cp`:     cp source destination

  • source 和 destination 参数都是文件名时, cp 命令将源文件复制成一个新文件,并且以destination 命名。新文件就像全新的文件一样,有新的修改时间。

  • 默认覆盖同名文件。加上`-i`参数可以询问是否覆盖, 只有回答y才会继续覆盖文件。-n参数不覆盖。

  • `cp -a <source> <dest>` //-a参数保留source的所有属性
  • ·cp -n <source> <dest>· // -n复制source到dest,如果dest已有则不覆盖
  • ==========================
  • `cp -r sDir dDir` // -r递归复制。注意:如果dDir开始不存在,那么结果是将sDir复制一份,生成dDir。如果dDir原来就存在,那么结果是将sDir整个文件夹复制到dDir文件夹中!
  • `cp -n -a sDir/* dDir` // 恢复文件夹内容时常用的命令,将sDir下的所有文件保留属性复制到dDir文件夹中, dDir中已有的文件则保留原有的(即不覆盖).

3.6.4, 文件链接 

  • 链接是目录中指向文件真实位置的占位符。在Linux中有两种不同类型的文件链接: 1,符号链接(软连接)symbolic link; 2,硬链接 hard link;
  • 符号链接就是一个实实在在的文件,它指向存放在虚拟目录结构中某个地方的另一个文件。符号链接和其指向的文件是两个独立的文件。使用ls -l查看可以看到两个文件大小并不一致,使用ls -il查看发现两个文件的inode编号也不一样。
    • `ln -s targetFile symbolicLink`
  • 硬链接会创建独立的虚拟文件,其中包含了原始文件的信息及位置。但是它们从根本上而言是同一个文件,引用硬链接文件等同于引用了源文件。修改一个文件另一个也会被修改,但是删除时可以分别删除任何一个文件而不会影响另外一个。注意只能对位于同一个物理存储设备的文件创建硬链接,不同的物理存储设备中的文件只能创建符号链接。
    • ln targetFile hardLink
  •  

3.6.5,移动文件、重命名文件

  • `mv 命令`可以将文件和目录移动到另一个位置或重新命名,可以移动位置的同时重命名。mv命令只移动或重命名文件,文件的inode编号和时间戳保持不变

  • `mv source destination`, -i 交互式提醒是否覆盖
  • mv 移动目录时,不需要加递归参数-R

3.6.6,删除文件

  • `rm -i fileName`, -i 命令参数提示你是不是要真的删除该文件。bash shell中没有回收站或垃圾箱,文件一旦删除,就无法再找回。因此,在使用 rm 命令时,要养成总是加入 -i 参数的好习惯。
  • ·rm -f fileName` -f参数强制删除。小心为妙!
  •  

3.7,处理目录

3.7.1, 创建目录

  • `mkdir newDirName`
  • `mkdir -p dir1/dir2/dir3...`  同时创建多个目录和子目录,需要加入 -p 参数

3.7.2,删除目录

  • `rmdir dirName` rmdir命令只允许删除空目录。
  • rmdir 并没有 -i 选项来询问是否要删除目录。这也是为什么说 rmdir 只能删除空目录还是有好处的原因。

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

  • 对 rm 命令而言, -r 参数和 -R 参数的效果是一样的。但是shell命令中,不同大小写的参数一般代表不同的功能。
  • `rm -rf dirName`命令既没有警告信息,也没有声音提示。这肯定是一个危险的工具,尤其是在拥有超级用户权限的时候。务必谨慎使用,请再三检查你所要进行的操作是否符合预期。

  •  

 

3.8,查看文件内容

3.8.1,查看文件类型

  • ·file fileName·  file命令能够探测文件的内部,并确定文件的类型。
  •  

3.8.2,查看整个文件

  • cat 命令
    • ·cat fileName· 查看整个文件的内容
    • `cat -n fileName` 添加行号
    • ·cat -b fileName· 只给有文本的行添加行号
    • ·cat -T fileName· 将制表符用^I 代替
  • tac 命令: cat命令反过来,文件内容也反过来,即最后一行最先显示。
  • more 命令:more不能搜索。
    • `more fileName` 一次显示一屏文本
  • less 命令: less命令实际上是more命令的升级版,less命令的命名实际上是个文字游戏(来自俗语“less is more”),能够实现在文本文件中前后翻动,而且还有一些高级搜索功能
    • ·less fileName· 

3.8.3,查看部分文件

  • tail 命令
    • `tail fileName` 默认显示文件的最后10行;
    • ·tail -n fileName· 显示文件最后 n 行;
    • ·tail -f fileName· -f 参数是 tail 命令的一个突出特性。它允许你在其他进程使用该文件时查看文件的内容。tail 命令会保持活动状态,并不断显示添加到文件中的内容。这是实时监测系统日志的绝妙方式。
  • head 命令
    • ·head fileName· 默认显示文件的头10行
    • 文件头部信息一般不会改变,所以head命令不支持 -f 参数;
    •  

 



4. 更多的Shell 命令

4.1,监测程序

4.1.1, 探查进程 (ps 命令)

  • ps命令有两个版本:Unix风格参数(单破折号参数) 和 BSD(Berkeley software distribution,BSD)风格参数(不带破折号), 后来GNU开发人员又加入了另外一些长参数(双破折号),这些长参数可以配合前面任意一种风格使用。不同风格的参数显示的信息也有所差别。
  • ·ps· // ps 命令默认只会显示运行在当前控制台下的属于当前用户的进程。

4.1.1.1, Unix参数风格的ps, 常用参数如下

  • ·ps -e·  // -e 参数指定显示所有运行在系统上的进程;
  • ·ps -f` // -f 参数则扩展了输出,这些扩展的列包含了有用的信息。
    • UID:启动这些进程的用户
    •  PID:进程的进程ID。
    •  PPID:父进程的进程号(如果该进程是由另一个进程启动的)。
    •  C:进程生命周期中的CPU利用率。
    • STIME:进程启动时的系统时间
    •  TTY:进程启动时的终端设备。
    •  TIME:此进程累计消耗的CPU时间
    • CMD:启动的程序名称
  • ·ps -l` -l参数获取更多字段
    •  F :内核分配给进程的系统标记。
    • S :进程的状态(O代表正在运行;S代表在休眠;R代表可运行,正等待运行;Z代表僵化,指进程完成了,但父进程没有响应;T代表停止)。
    •  PRI :进程的优先级(越大的数字代表越低的优先级)。
    •  NI :谦让度值用来参与决定优先级
    •  ADDR :进程的内存地址
    •  SZ :假如进程被换出,所需交换空间的大致大小
    •  WCHAN :进程休眠的内核函数的地址。
    •  

4.1.1.2, BSD风格的参数(不带破折号)

  • ·ps l· l参数与unix风格输出不同的字段如下
    • %MEM:进程使用物理内存所占百分比。
    • VSZ:进程使用虚拟内存大小。以千字节(KB)为单位。
    • RSS:进程使用物理内存大小,我们会重点关注这个值。
    • STAT:代表当前进程状态的双字符状态码。
  •  

4.1.1.3, GNU长参数

  • ·ps --forest·  --forest 参数会显示进程的层级信息,并用ASCII字符绘出可爱的图表。
  • ·ps aux --sort=rss | tail -10· //按照rss字段排序(由小到大),取最后10个(即rss值最大的10个)。
  • ·pa aux --sort=rss | head -10· //取值最小的10个。

4.1.1.4,  ps搭配watch命令可以达到top的效果。

  • ·watch -n 1 "ps aux"· // -n 1表示每隔1秒更新一次。
  • ·watch -n 1 "ps aux |grep mysqld"· 

4.1.2,top专题:实时监测进程: top命令。【注:还有个更友好的 htop 命令】

  • 第一行显示了当前时间、系统的运行时间、登录的用户数以及系统的平均负载。load average:平均负载有3个值:最近1分钟的、最近5分钟的和最近15分钟的平均负载,每5秒钟检查一次。值越大说明系统的负载越高,一般要求不超过核数,比如对于单核情况要 < 1。如果机器长期处于高于核数的情况,说明机器 CPU 消耗严重了。

    • 上图中的第一行说明:当前时间:11:37:19,这个时间的更新频率即是top命令的刷新频率,可以通过内部命令s重新设置; 卍,系统运行时间:up 479 days, 11:30,即运行了479天11小时30分钟 卍,当前已登录的用户数:3users卍,最近1,5,15分钟的负载:1.35, 1.08, 0.64
  • 第二行显示了进程概要信息—— top 命令的输出中将进程叫作任务(task):有多少进程处在运行、休眠、停止或是僵化状态(僵化状态是指进程完成了,但父进程没有响应)。

  • 第三行%Cpu(s):表示当前 CPU 的使用情况,如果要查看所有核(逻辑核)的使用情况,可以按下数字 “1” 查看。这里有几个参数,表示如下:
    • - us    用户空间占用 CPU 时间比例
    • - sy    系统占用 CPU 时间比例
    • - ni    用户空间改变过优先级的进程占用 CPU 时间比例
    • - id    CPU 空闲时间比
    • - wa    IO等待时间比(IO等待高时,可能是磁盘性能有问题了)
    • - hi    硬件中断
    • - si    软件中断
    • - st    steal time
    • 上图中的第三行说明:4.0us(user space): 用户使用的CPU占4%;卍,3.1sy(sysctl):系统内核占用的cpu占3.1%;卍,68.2id(idle):空闲CPU占68.2%;卍,0.0ni:改变过优先级的进程占用CPU的百分比;卍,23.3wa(wait):IO等待占用CPU23.3%;
  • 第四行显示了内存的使用情况。其中可用内存 = free + buff/cache。
    • 使用中的内存总量(used)指的是现在系统内核控制的内存数,
    • 空闲内存总量(free)是内核还未纳入其管控范围的数量。
    • 纳入内核管理的内存不见得都在使用中,还包括过去使用过的现在可以被重复利用的内存,内核并不把这些可被重新使用的内存交还到free中去,因此在linux上free内存会越来越少,但不用为此担心。
    • ------列表标题中三个关于内存的字段说明如下-------
    • VIRT:virtual memory usage,也叫做VSS(Virtual Set Size),进程占用的虚拟内存大小。如果没有单位,则单位是kb。

    • RES:resident memory usage,也叫做RSS(Resident Set Size),进程常驻内存大小,也就是实际内存占用情况,一般我们看进程占用了多少内存,就是看的这个值如果没有单位,则单位是kb

    • SHR:shared memory,共享内存大小,不常用。如果没有单位,则单位是kb

  • 第五行显示了swap交换分区信息。我们要时刻监控这一行的used,如果这个数值在不断的变化,说明内核在不断进行内存和swap的数据交换,这是真正的内存不够用了。
  • top的交互命令:默认情况下, top 命令在启动时会按照 %CPU 值对进程排序。通过top命令的交互命令可以对进程的显示方式进行控制,交互命令如下:
    • h - 显示交互命令帮助!
    • q – 退出 top。
    • 上下左右方向键 - 移动画面。
    • s 或 d - 改变进程列表刷新频率,默认是3s。按s后再输入一个数值即可改变。可以通过第一行的当前时间看出刷新频率。
    • l(小写的L) - 关闭或开启 第一部分第一行top信息的展示。
    • t -  关闭或开启第一部分第二行 Tasks 和第三行 Cpus 信息的表示。
    • m – 关闭或开启第一部分第四行 Mem 和 第五行 Swap 信息的表示。
    • F - 自定义排序字段:按F进入选择页面(注意看帮助文字),方向键选择排序字段,s 键确认,然后按q键返回top页面即可按自定义排序展示。内置的排序快捷键如下:
      • N – 以 PID 的大小的顺序排列表示进程列表,从大到小排序。
      • P – 以 CPU 占用率大小的顺序排列进程列表,这也是默认排序方式。
      • M – 以内存占用率大小的顺序排列进程列表。
    • n – 设置在进程列表所显示进程的数量。
    • i - 忽略闲置和僵死进程。
    • k - 终止进程。先输入一个进程ID,然后输入发给此进程的信号signal。
    • H - 线程模式,列出进程下面的各个线程的使用情况
  • top命令的参数: top -hv | -bcEHiOSs1 -d secs -n max -u|U user -p pid(s) -o field -w [cols]
    • ·top -p <pid>` // 只监控某个进程的状态
    • top -H -p <pid> // -H参数可以显示进程pid中的每个线程的情况,分析Java进程经常用!
    • ·top -u <userName>· // 只监控某个用户下的进程的状态
    • ·top -s` // -s(小s) 参数使top命令在安全模式中运行。这将去除交互命令所带来的潜在危险,如 k 交互指令将被禁用。
    • `top -n <num>` // -n指定执行次数
    • ·top -b· //使用批处理模式输出(即每执行一次就输出一次,而非默认的刷新。而且显示的是所有的进程而不是默认的指标靠前的部分进程)。一般和"-n"选项合用,用于把 top 命令重定向到文件中;
    • top -b -n 1 > /root/top.log #让top命令只执行一次,然后把执行结果保存到top.log文件中,这样就能看到所有的进程了

4.1.3,


4.2,监测磁盘空间

du命令的英文全称是“Disk Usage”,即用于查看磁盘占用空间的意思。但是与df命令不同的是du命令是对文件和目录磁盘使用的空间的查看,而不是某个分区。

  • `-h`显示单位为 human-readable, 以K,M,G为单位,提高信息的可读性,换算单位为1024。
  • ·du -H` 同-h参数,但是换算单位为1000;
  • ·du -b/-k/-m· 显示大小单位为 byte/kb/mb
  • ·du <fileName>· 统计指定文件占用大小。
  • `du <dirName>` 统计指定目录及各级子目录占用空间大小。不统计单个文件。
    • `du` 等同于·du ./· 即默认是当前目录。
  • `du -a` 统计 目录 及 各级子目录 及 各个文件 占用空间大小。
  • `du <dirName>/*` 等同于对<dirName>下各个目录 及 文件使用du。此时不会再统计<dirName>本身的大小。
  • ·du -s <dirName>· (只)统计指定目录的大小。·du -s <fileName>·等同于·du <fileName>·
  • ·du -S <dirName>· 统计指定目录及各级子目录占用空间大小,但是父目录大小计算时不包含子目录的大小。不统计单个文件。
  • `du -d <N> <dirName>`或·du --max-depth=<N> <dirName>· 统计目录深度最深为N。
    • `du -d 0 ./` 目录深度为0,即当前目录,此时相当于-s参数。
    • ·du -d 2 ./· 统计最大目录深度为2。0级1级2级目录会统计,之后目录不会统计。注意:不统计文件。
    • ·du -d 2 ./*·统计最大目录深度为2. 相当于对./下面的所有文件和目录使用du -d 2命令。
  • ·du -t <num> [k,K,m,M,G]· 如果num是正数,排除比num小的统计项,如果num是负值,排除比num大的统计项。num后的单位有(k,K,m,M,G)
    • ·du -h -t 3M· 小于3M的全排除掉
    • `du -b -t 10000` 以-b作为单位,num可以不加单位,默认为byte.其他情况都要加单位。
  • ·du --exclude="*keyword*" ./· 排除含有关键字keyword的目录、子目录。注意:du不加-a不统计单个文件。
    • du -sh * --exclude=home // 不能如下:du -sh * --exclude=/home, 这样不生效
  • ·du --time ./· 统计目录、子目录近修改时间
    • · du --time --time-style="+%s" ./ · --time-style和date命令格式一样。
    • ·du --time --time-style="+%F %T" ./·
  • 常用组合:
  • ·du -sh *· 等同于对当前目录下的目录、文件使用du -sh.
  • ·du -sh * | sort -nr` 按占用空间大小倒序排列
  • du命令测试记录
    [root@mytest aaaDir]#tree
    .
    ├── 22
    │   ├── 222
    │   │   └── green.jpg
    │   └── green.jpg
    ├── 33
    │   └── green.jpg
    └── green.jpg
    
    3 directories, 4 files
    
    [root@mytest aaaDir]#du -h green.jpg 
    692K    green.jpg
    [root@mytest aaaDir]#
    
    [root@mytest aaaDir]#du -h 
    692K    ./22/222
    1.4M    ./22
    692K    ./33
    2.8M    .
    [root@mytest aaaDir]#
    
    [root@mytest aaaDir]#du -h 22/
    692K    22/222
    1.4M    22/
    [root@mytest aaaDir]#
    [root@mytest aaaDir]#du -ah
    692K    ./22/green.jpg
    692K    ./22/222/green.jpg
    692K    ./22/222
    1.4M    ./22
    692K    ./green.jpg
    692K    ./33/green.jpg
    692K    ./33
    2.8M    .
    [root@mytest aaaDir]#
    [root@mytest aaaDir]#du -h ./*
    692K    ./22/222
    1.4M    ./22
    692K    ./33
    692K    ./green.jpg
    [root@mytest aaaDir]#
    [root@mytest aaaDir]#du -sh .
    2.8M    .
    [root@mytest aaaDir]#du -sh 22/
    1.4M    22/
    [root@mytest aaaDir]#
    [root@mytest aaaDir]#du -Sh .
    692K    ./22/222
    692K    ./22
    692K    ./33
    692K    .
    [root@mytest aaaDir]#
    [root@mytest aaaDir]#du -sh *
    1.4M    22
    692K    33
    692K    green.jpg
    [root@mytest aaaDir]#
    [root@mytest aaaDir]#du -hd 0 .
    2.8M	.
    [root@mytest aaaDir]#du -hd 1 .
    1.4M	./22
    692K	./33
    2.8M	.
    [root@mytest aaaDir]#du -h .
    692K	./22/222
    1.4M	./22
    692K	./33
    2.8M	.
    [root@mytest aaaDir]#du -hd 2 .
    692K	./22/222
    1.4M	./22
    692K	./33
    2.8M	.
    [root@mytest aaaDir]#du -hd 3 .
    692K	./22/222
    1.4M	./22
    692K	./33
    2.8M	.
    

4.3,处理数据文件

sort 命令(行级检测):参见此博文(关于sort的非常好的博文!!!!)。

  • ·sort file· 默认对文件中的所有行进行排序,默认排序规则是按照默认语言的排序规则,会将数字识别成文本
  • ·sort file file ...` sort 支持对多个文件的排序(合并内容后排序)
  • ·sort -n file` 将数字识别成数字而不是文本
  • ·sort -r file` 倒序排
  • ·sort -t ":" file` 用冒号":" 分割file中的内容,常常与-k参数连用。-t用于指定分隔符,默认的分隔符为空白字符和非空白字符之间的空字符;
  • `sort -k pos1[,pos2] file` 排序从pos1开始,如果指定了pos2则到pos2结束。注意此参数必须与 -t 参数一起用才有意义,
  • ·sort -c <file>` // 检查<file>是否已排序,该操作不会执行排序·
  • ·sort -u <file>· // 输出排序后去重的结果

uniq 命令(行级检测)

  • ·uniq <file>`  // <只>检查<file>文件中的相邻是否重复,如果重复则去重。
  • `sort <file> | uniq` //排序后去重,效果和 ·sort -u <file>·相同

4.4, 搜索数据

关于grep工具涉及到的正则表达式,和平常的正则表达式规则有一些差异,主要是正则表达式有好几个流派:PCRE(Perl Compatible Regular Expression,这也是在Java和其他编程语言中平常用的正则),POSIX规则下(posix可以看做是unix操作系统的规范,防止不同版本差异过大带来的不便)的正则表达式(又分成2类:BRE :basic regular expression; ERE :extended regular expression,虽然命名叫扩展,但并不要求兼容bre,实际是自成一派),具体可参阅这篇文章:Linux/Unix 工具与正则表达式的 POSIX 规范 。

  • ·grep [options]... pattern [file]...`  global(G) search regular expression(RE) and print out the line。 使用正则表达式搜索文本,并把匹配的行打印出来

默认情况下,grep使用的pattern是BRE。可以是用-G、-F、-E、-P指定不同的正则表达式。

  • ·grep -F· // 等同于 `fgrep`命令,将pattern中的字符视为普通字符,不再特殊。-F, --fixed-strings       PATTERN is a set of newline-separated fixed strings
  • ·grep -G· // pattern使用BRE,这也是默认的场景。-G, --basic-regexp        PATTERN is a basic regular expression (BRE)
  • ·grep -E· //等同于·egrep·命令。pattern使用ERE。-E, --extended-regexp     PATTERN is an extended regular expression (ERE)
  • ·grep -P· //pattern使用 PCRE。-P, --perl-regexp         PATTERN is a Perl regular expression
    • `grep -aPoz` // 注意grep使用多行正则时,-z参数是必要的
  • ·grep -z·// -z:a data line ends in 0 byte, not newline。
  • ·grep -a· //-a将二进制文件当做普通文本文件处理。
  • `grep pattern file1 file2 file3...` 可以在多个文件中查找;
  • ·grep -v pattern file· 反向搜索,输出不包含pattern的行;
  • ·grep -n pattern file` 显示匹配行所在的行号;
  • ·grep -c pattern file` 只显示匹配行的行数;
  • ·grep -e pattern1 -e pattern2 file` 使用-e参数指定多个匹配模式;
  • ·grep -o pattern file` 只输出匹配部分
  • ·-l· // 小写的L.查询多文件时只输出包含匹配字符的文件名。 -l, --files-with-matches  print only names of FILEs containing matches
  • ·-L· // 查询多文件时只输出包含匹配字符的文件名
  • ·-i·:忽略大小写。 -i, --ignore-case         ignore case distinctions
  • `-r`: 递归查询。-r, --recursive    like --directories=recurse
    • ·grep -r "someWord" ./*· 最后一个参数./*, 递归匹配当前文件夹下的所有非隐藏文件以及文件夹。
    • ·grep -r "someWord" ./· 最后一个参数./不加*,经测试匹配当前文件夹下的所有文件以及文件夹,包括隐藏的。
  •  

示例:

  • ·docker images | grep "\-webapp"·  // -webapp不加引号不加转义都不能查找出 含有 -webapp 的镜像。
  • ·grep ^[^#].* redis.conf· //查找redis.con文件中所有不以#开头的行,即生效的配置项。

4.x, ·find·命令查找文件。查找文件的工具除了find还有·locate·以及·mlocate·

语法:find [路径...] [选项] [表达式]

  • 路径:可以为多个,用空格隔开。注意find命令默认就是递归查找文件夹的。
  • 表达式:常用的表达式有如下三个:
    • ·-print·。这也是默认值。`find ./a/ ./b/ -name "fileName"` 等同于·find ./a/ ./b/ -name "fileName" -print·
    • ·-exec command {} \;· // `find . -type f -name "*.txt" -exec cat {} \;> /all.txt` 查找当前目录下所有.txt文件并把他们拼接起来写入到all.txt文件中
    • ·find . -name "mysqlbackup_*" -type f -mmin +100 -exec rm -rf {} \;· //删除100分钟前的xx文件
    • ·find . -name "mysqlbackup_*" -type f -mtime +30 -delete· //删除30天前的xx文件
    • `-ok command {} \;` // -ok和-exec的作用相同,只不过以一种更为安全的模式来执行该参数所给出的shell命令,在执行每一个命令之前,都会给出提示,让用户来确定是否执行。
    • 除了默认的-print,其他的都很少用,一般都是结合 管道、xargs 以及其他Linux命令来使用。
  • 选项包括很多,常用的如下:
    • ·find .· //没有任何选项,列出当前目录及子目录下所有文件和文件夹
    • `-name "<名称>"` //名称区分大小写,名称中可以使用通配符。·find . -name "aa*"·
      • *表示  通配任意的字符
      • ?表示  通配任意的单个字符
      • [ ] 表示 通配括号里面的任意一个字符
    • `-iname "<名称>"` //根据名称查找,不区分大小写。
    • `-type b/c/d/f/l/p/s` // b 块设备;c 字符设备;d 目录;f 普通文件;l 符号连接;p Fifo;s 套接字;
    • ·-path· // 匹配文件路径或者文件。`find /usr/ -path "*local*"`
    • ·-user·
    • `-group`
    • `-size 文件大小单元`//根据文件大小来找。
      • b —— 块(512字节)
      • c —— 字节
      • w —— 字(2字节)
      • k —— 千字节
      • M —— 兆字节
      • G —— 吉字节
      • ·find . -type f -size +10k· // 搜索大于10KB的文件
      • ·find . -type f -size -10k·搜索小于10KB的文件
      • `find . -type f -size 10k` 搜索等于10KB的文件
    • `find. -maxdepth 3 -type f` 向下最大深度限制为3
    • `find. -mindepth 2 -type f` 搜索出深度距离当前目录至少2个子目录的所有文件
    • 根据文件时间戳进行搜索。UNIX/Linux文件系统每个文件都有三种时间戳:
      • 访问时间 (-atime/天,-amin/分钟):用户最近一次访问时间。
      • 修改时间 (-mtime/天,-mmin/分钟):文件最后一次修改时间。
      • 变化时间 (-ctime/天,-cmin/分钟):文件数据元(例如权限等)最后一次修改时间。
        ·find. -type f -atime -7·搜索最近七天内被访问过的所有文件
        ·find. -type f -atime 7·搜索恰好在七天前被访问过的所有文件
        ·find. -type f -atime +7·搜索超过七天内被访问过的所有文件
        `find . -type f -amin +10`搜索访问时间超过10分钟的所有文件
        `find . -type f -newer file.log`找出比file.log修改时间更新的所有文件
      • newerXY: newermt
        • `find /dir1 -type f -newermt '2018-5-26 21:00' ! -newermt '2018-5-26 22:00' -exec cp {} /dir2 \;` #时间左闭右开。将/dir1目录下2018-5-26 21:00到2018-5-26 22:00时间段内修改或生成的文件拷贝到/dir2目录下
    • ·-perm· 
      • `find . -type f -perm 777` // 当前目录下搜索出权限为777的文件
      • `find . -type f -name "*.php" ! -perm 644`找出当前目录下权限不是644的php文件
      • `find  /tmp  -perm  +222`//表示只要有一类用户(属主,属组,其他)的匹配写权限就行
      • `find  /tmp  -perm  -222`//表示必须所有类别用户都满足有写权限
    • `-a`或者·-and· 连接两个不同的条件(两个条件必须同时满足)
    • `-o`或者·-or· 连接两个不同的条件(两个条件满足其一即可)`find . -name "hello" -o -name "world"`
    • `!`或者·-not· `find /home ! -name "*.txt"` //反向查找,找出/home下不是以.txt结尾的文件
      • find ./ -not -name "\." -and -not -name "*.zip" -and -not -name "*.pbobj" //查找不以zip结尾且不以pbobj结尾的所有文件。【!注意!将当前目录加入排除项】。【教训】不加当前目录的话查出来的结果包含`./`,配合xargs rm -rf时一定要小心小心再小心(还好有个rm有个安全机制refusing remove . or ..)
    • ·-regex· // 基于正则表达式匹配文件路径。`find . -regex ".*\(\.txt\|\.pdf\)$"`
    • ·-iregex· //同regex,但忽略大小写。
    • `find. -empty` 列出所有长度为零的文件
  • find命令中的通配符踩坑记录:参考此文
    • 在一次执行`find ./ -name *.py`命令时报错:find: paths must precede expression。
    • 【解决方法】将 【*.py】用引号(单引号或双引号)引起来·find ./ -name "*.py*· 。或者讲*转义一下
    • 【原因】
    • 知识点1(通过apt-cache madison命令验证了下):Linux中使用*通配符时会根据当前文件夹下的内容进行匹配,如果匹配上了就会展开为具体的文件(匹配多个就展开为多个),并将展开后的名称传递给shell命令执行,如果没匹配上就不会展开并将带有通配符的命令传递给shell命令执行。
    • 知识点2:find的语法中 -name后面的参数只能有一个名称。如果写了多个参数就会报上面的错误。如果使用通配符*,通配符会展开,展开方法会根据当前文件夹下的文件进行展开,如果展开的时候如果刚好是多个文件那么就会报错。比如
      • 文件夹c下有两个文件:Dockerfile,Jenkinsfile。如果在c文件夹下执行·find . -name *file`那么*file会根据当前文件夹下的文件进行展开,刚好匹配了Dockerfile和Jenkinsfile,相当于执行了·find . -name Dockerfile Jenkinsfile`所以会报错:find: paths must precede expression: `Jenkinsfile'   find: possible unquoted pattern after predicate `-name'?。改为·find . -name "*file"· OR`find . -name '*file'` OR `find . -name \*file`都可以。
      • 但是如果在c的上层文件夹下执行·find ./c -name *file·却不会报错!因为此时*file展开根据当前文件夹下的文件(只有一个c文件夹)展开,没有匹配任何文件。所以没报错。 
  • `find ./ -name "test" | xargs grep "tong"` //查找名称为test的文件,并查询此文件中是否还有字符串"tong"。
    • xargs 命令将find命令的查询结果作为参数传递给grep命令
    • 如果不加xargs则是将find的查询结果(标准输出)作为标准输入传递给grep命令。那么此时grep将匹配的是文件的名称中是否还有tong,而不是文件的内容是否还有tong.

4.5,压缩数据

  •  `gzip [options]... [file]...`
  • `gzip a.txt b.log` 将a.txt b.log压缩至a.txt.gz b.log.gz压缩文件中,注意是将每个文件分别压缩进不同的包里,源文件也不存在了。
  • ·gzip -r myDir` 递归将文件夹 myDir 中的所有文件压缩
  • ·gunzip a.txt.gz` 解压缩
  • ·gunzip -r myDir` 递归解压文件夹下的压缩包
  • ·zip / unzip· gzip 压缩率好于 zip

4.6,归档数据 (Tar命令): 将许多文件一起保存至一个单独的磁带或磁盘归档,并能从归档中单独还原所需文件。tar命令为打包命令,可以结合gzip, bzip2等命令在打包的同时进行压缩。

  • `tar [options]... [file]....`
  • -c 新建打包文件, -t查看打包文件, -x解包打包文件,这三个参数互斥
  • -v 显示过程
  • -f 这个参数后面必须紧跟打包的包名,不能再有其他参数
  • -r, --append               追加文件至归档结尾
  • -u, --update               仅追加比归档中副本更新的文件
  • -----------------------------
  • `tar -cvf path/to/myTar.tar dir1 dir2 ...` // 将目录dir1 dir2... 打包到 path/to/myTar.tar 包中。打包成的tar文件中包含目录的路径信息。
  • ·tar zcvpf path/to/myTar.tar.gz --exclude=/tmp/excludeDir /tmp· //将/tmp文件夹下除了excludeDir之外的文件夹打包成tar.gz文件
    • p参数表示保留原有权限
    • --exclude参数是有顺序的选项(positional options),必须出现在待打包的目录的前面才能生效,否则不生效!!!
  • -----------------------------
  • `tar -tf path/to/myTar.tar` //查看包中的内容
  • `tar -tf btop.tbz bin/btop` //只查看btop.tbz包中的bin/btop文件
  • `tar -tf path/to/myTar.tar | awk -F / '{print $1}' | sort -u` // 只显示tar中的顶级目录。
  • ·tar -tf path/to/myTar.tar | awk -F / '{print $1}' | sort | uniq· //同上,只显示tar中的顶级目录
  • -----------------------------
  • `tar -xvf path/to/myTar.tar` //将myTar.tar包中的内容解包到当前目录(解包包括包内的目录结构)
  • `tar -xvf path/to/myTar.tar dir/file` //只解包包中指定的文件 /dir/file, 也可以只解包包内某个目录。如果/dir/file在包内不存在,此命令会报错:dir/file: Not found in archive。
  • `tar -xvf path/to/myTar.tar -C dir` //-C参数指定解包目录,即将包中的所有内容(包含目录结构)解包到指定目录dir 中
  • `tar -xvf btop.tbz -C /usr/local bin/btop`// 注意local和bin之间有空格,最后一个参数表示只提取btop.tbz中的bin/btop文件,将其解压到/usr/local文件夹下。
  • `tar -xvf path/to/myTar.tar -C dir --strip-component NUMBER` //--strip-component 参数可以指定一个数字来剥离包中的前导目录,--strip-component 2 即剥离前两层目录
  • -----------------------------
  • `tar -zcvf path/to/myTar.tar.gz dir1 dir2` //-z参数在打包的同时调用gzip命令进行压缩,命名一般用 .tar.gz 或 .tgz来标志这是一个gzip压缩的tar包
  • `tar -zxvf path/to/myTar.tar.gz` //-z解压 tar.gz 包
  • -----------------------------
  • `tar -jcvf path/to/myTar.tar.bz2 dir1 dir2` // -j 参数在打包的同时调用bzip2 命令进行压缩,命名一般用tar.bz2来标志。
  • `tar -jxvf ....` // -j解压 tar.bz2包
  • -----------------------------
  • `tar -rvf path/to/myTar.tar a.txt`//将a.txt文件追加到myTar.tar包中。注意只能添加到非压缩的tar包中。如果使用了z或j参数对tar进行了压缩则无法追加。
  • -----------------------------
  • `tar -uvf path/to/myTar.tar a.txt` //原来的myTar.tar中含有a.txt,使用更新的a.txt更新tar包中的a.txt。注意只能更新非压缩的tar包,如果使用了z或j参数对tar进行了压缩则无法更新。

4.7,



5. 理解 shell

5.1, shell的类型。

5.1.1,系统启动什么样的shell程序取决于你个人的用户ID配置。在/etc/passwd文件中,在用户ID记录的第7个字段中列出了默认的shell程序。最常见的shell有 /bin/bash。其他shell还有/bin/tcsh, /bin/dash, /bin/csh等。

默认的交互式shell会在用户登录某个虚拟控制台终端或在GUI中运行终端仿真器时启动。不过还有另外一种默认shell是 /bin/sh,它作为默认的系统shell用于那些需要在启动时使用的shell脚本。有些Linux发行版的默认交互式shell和默认的系统shell是相同的,如centos,而另外一些Linux发行版则不同,如ubuntu。

并不是必须一直使用默认的交互shell。可以使用发行版中所有可用的shell,只需要输入对应的文件名就行了。例如,你可以直接输入命令 /bin/dash来 启动dash shell。输入exit可以退出dash shell。

5.2,shell的父子关系。

  • 用于登录某个虚拟控制器终端或在GUI中运行终端仿真器时所启动的默认的交互shell,是一个父shell。在CLI提示符后输入 /bin/bash 命令或其他等效的 bash 命令时,会创建一个新的shell程序。这个shell程序被称为子shell(child shell)。子shell也拥有CLI提示符,同样会等待命令输入。当输入 bash 、生成子shell的时候,你是看不到任何相关的信息的,可以使用 ·ps -f·命令来查看父子shell。子shell(child shell,也叫subshell)可以从父shell中创建,也可以从另一个子shell中创建。·ps --forest·可以查看图形化的父子关系。
  • 在生成子shell进程的时候,只有部分父shell进程的环境被复制到子shell环境中。这会对包括变量在内的一些东西造成影响。
  • bash shell程序可以使用命令行参数修改shell启动方式。bash --help查看帮助
    -c string: 从 string 中读取命令并进行处理
    -i 启动一个能够接收用户输入的交互shell
    -l 以登录shell的形式启动
    -r 启动一个受限shell,用户会被限制在默认目录中
    -s 从标准输入中读取命令
  • 运行shell脚本也能够创建出子shell。就算是不使用bash shell命令或是运行shell脚本,你也可以生成子shell。一种方法就是使用进程列表。

5.2.2,进程列表。

  • 你可以在一行中指定要依次运行的一系列命令。这可以通过命令列表来实现,只需要在命令之间加入分号(;)即可。如:pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls。

  • 所有的命令依次执行,不存在任何问题。不过这并不是进程列表。命令列表要想成为进程列表,这些命令必须包含在括号里。如:(pwd ; ls ; cd /etc ; pwd ; cd ; pwd ; ls)。尽管多出来的括号看起来没有什么太大的不同,但起到的效果确是非同寻常。括号的加入使命令列表变成了进程列表,生成了一个子shell来执行对应的命令。

  • 要想知道是否生成了子shell,得借助一个环境变量,`echo $BASH_SUBSHELL`。如果该命令返回 0 ,就表明没有子shell。如果返回1 或者其他更大的数字,就表明存在子shell。
    • pwd; ls; echo $BASH_SUBSHELL //打印0;
    • (pwd; ls; echo $BASH_SUBSHELL) //打印1;
  • 在shell脚本中,经常使用子shell进行多进程处理。但是采用子shell的成本不菲,会明显拖慢处理速度,因为还必须为子shell创建出一个全新的环境。在交互式的CLI shell会话中,子shell同样存在问题。它并非真正的多进程处理,因为终端控制着子shell的I/O。

5.2.3,别出心裁的子shell的用法。在交互式的shell CLI中,还有很多更富有成效的子shell用法。进程列表、协程和管道等都利用了子shell。它们都可以有效地在交互式shell中使用。下面介绍其中一些子shell的用法。

※,后台模式。

在后台模式中运行命令可以在处理命令的同时让出CLI,以供他用。在命令后加一个 & 符号即可让命令进入后台模式。当它被置入后台,在shell CLI提示符返回之前,会出现两条信息。第一条信息是显示在方括号中的后台作业(background job)号(1)。第二条是后台作业的进程ID(2396)。可以使用 jobs 命令来显示后台作业信息。 jobs 命令可以显示出当前运行在后台模式中的所有用户的进程(作业)。·jobs·;`jobs -l`;后台模式非常方便,它可以让我们在CLI中创建出有实用价值的子shell。

在CLI中运用子shell的创造性方法之一就是将进程列表置入后台模式。你既可以在子shell中进行繁重的处理工作,同时也不会让子shell的I/O受制于终端。创建备份文件是有效利用后台进程列表的实用例子。·(tar -cf Rich.tar /home/rich ; tar -cf My.tar /home/christine)&·

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

  • ·coproc sleep 10·// 除了会创建子shell之外,协程基本上就是将命令置入后台模式。当输入 coproc 命令及其参数之后,你会发现启用了一个后台作业。屏幕上会显示出后台作业号( 1 )以及进程ID( 2544 )。jobs 命令能够显示出协程的处理状态。输入jobs命令后可以看到在子shell中执行的后台命令是 coproc COPROC sleep 10 。 COPROC是 coproc 命令给进程起的名字。你可以使用命令的扩展语法自己设置这个名字。·coproc My_Job { sleep 10; }·//注意前后两个花括号和命令之间都必须有一个空格!!!!还必须保证命令以分号(;)结尾。
  • 协程能够让你尽情发挥想象力,发送或接收来自子shell中进程的信息。只有在拥有多个协程的时候才需要对协程进行命名,因为你得和它们进行通信。否则的话,让 coproc 命令将其设置成默认的名字 COPROC 就行了。

  • 你可以发挥才智,将协程与进程列表结合起来产生嵌套的子shell。只需要输入进程列表,然后把命令 coproc 放在前面就行了。·coproc ( sleep 10; sleep 2 )·。但是记住生成子shell的成本不低,而且速度还慢。创建嵌套子shell更是火上浇油!

5.2.4,组命令和子shell

组命令:·{ command1; command2; }· //格式要求:命令间用分号隔开,整体用大括号包裹,鉴于 bash 实现组命令的方式,花括号与命令之间必须有一个空格(经测试左半边的{有空格即可),并且最后一个命令必须用一个分号或者一个换行符终止。

子shell:·(command1; command2; ...)·//格式要求:命令间用分号隔开,整体用小括号包裹即可。

虽然组命令和子 shell 看起来相似,并且它们都能用来在重定向中合并流,但是两者之间有一个很重要的不同之处。然而,一个组命令在当前 shell 中执行它的所有命令,而一个子 shell(顾名思义)在当前 shell 的一个子副本中执行它的命令。这意味着运行环境被复制给了一个新的shell 实例。当这个子 shell 退出时,环境副本会消失,所以在子 shell 环境(包括变量赋值)中的任何更改也会消失。如下例子:

echo "foo" | read
echo $REPLY
该 REPLY 变量的内容总是为空,是因为这个 read 命令在一个子 shell 中执行,所以当该子 shell 终止的时候,它的 REPLY 副本会被毁掉。

管道中的命令总是在子 shell 中执行,任何给变量赋值的命令都会遭遇这样的问题。幸运地是,shell 提供了一种奇异的展开方式,叫做进程替换,它可以用来解决这种麻烦。进程替换有两种表达方式:

  • 适用于产生标准输出的进程:·<(list)·//这是一个整体,中间不能有空格。可以将其整体视为一个普通文件。list是一串命令列表。
  • 适用于接受标准输入的进程:·>(list)·

上面管道的问题可以如下解决:

read < <(echo "foo")//第一个<是输入重定向,第二个<是进程替换整体的一部分
echo $REPLY
进程替换允许我们把一个子 shell 的输出结果当作一个用于重定向的普通文件。。事实上,因为它是一种展开形式,我们可以检验它的真实值:
$ echo <(echo "foo")
/dev/fd/63
通过使用 echo 命令,查看展开结果,我们看到子 shell 的输出结果,由一个名为 /dev/fd/63 的文件提供。

进程替换经常被包含 read 命令的循环用到。这里是一个 read 循环的例子,处理一个目录列表的内容,内容创建于一个子 shell: 

#!/bin/bash
# pro-sub : demo of process substitution
while read attr links owner group size date time filename; do
    cat <<-EOF
Filename: $filename
Size: $size
Owner: $owner
Group: $group
Modified: $date $time
Links: $links
Attributes: $attr
EOF
done < <(ls -l | tail -n +2)

这个循环对目录列表的每一个条目执行 read 命令。列表本身产生于该脚本的最后一行代码。这一行代码把从进程替换得到的输出重定向到这个循环的标准输入。这个包含在管道中
的 tail 命令,是为了消除列表的第一行文本,这行文本是多余的。

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

5.3.1,外部命令。

外部命令,有时候也被称为文件系统命令,是存在于bash shell之外的程序。它们并不是shell程序的一部分。外部命令程序通常位于 /bin, /usr/bin, /sbin/, /usr/sbin中。可以使用which 和 type命令查看

  • `type -a <command>` 查看command程序的位置, 是内置命令还是外部命令。-a选项可以查看命令程序的所有实现。
  • ·which -a <command>` 查看command程序的位置。-a选项同上。

当外部命令执行时,会创建出一个子进程。这种操作被称作 衍生(forking)。当进程必须执行衍生操作时,它需要花费时间和精力来设置新子进程的环境。所以说,外部命令多少还是有代价的。就算衍生出子进程或是创建了子shell,你仍然可以通过发送信号与其沟通,这一点无论是在命令行还是在脚本编写中都是极其有用的。发送信号(signaling)使得进程间可以通过信号进行通信。

5.3.2,内建命令。

内建命令和外部命令的区别在于前者不需要使用子进程来执行。它们已经和shell编译成了一体,作为shell工具的组成部分存在。不需要借助外部程序文件来运行。如 cd 和 exit命令都是内建命令。

有些命令有多种实现,例如 echo 和 pwd 既有内建命令也有外部命令。两种实现略有不同。要查看命令的不同实现,使用 type 命令的 -a 选项。对于有多种实现的命令,默认使用的内建命令,如果想要使用其外部命令实现,直接指明对应的文件就可以了。例如,要使用外部命令 pwd ,可以输入 /bin/pwd。

一些常用的内建命令:

※,history

  • ·history· 
    • ·!!· 代表上一条命令,!!等价于上一条命令,可以在!!的基础上再组合其他参数。如上一条命令是 ls , 则可以执行 !! -al,等价于ls -al;
    • `!n`  代表 ·history·输出中对应号码的命令。
    • ·!-n· 代表往前n条命令, !-1  代表 上一条命令,等于!!;!-2代表上上条命令;!-1执行后也算一条其所代表的的命令,即在history中也会增加一条记录;
    • ·!$` 代表上一条命令中的最后一个参数,当这个参数很长时,就会很省事。
    • ·!字符串` 代表最近一条以指定字符串开头的命令。
    • ·!?字符串?·   模糊匹配,代表最近一条包含指定字符串的命令。
  • ·history -a· // bash命令的历史记录是先存放在内存中的,当shell退出时才被写入到.bash_history文件中。如果想在shell退出之前强制将命令历史记录写入.bash_history文件中,可以使用-a选项。
  • ·history -n· // 如果你打开了多个终端会话,仍然可以使用 history -a 命令在打开的会话中向.bash_history文件中添加记录。但是对于其他打开的终端会话,历史记录并不会自动更新。这是因为.bash_history文件只有在打开首个终端会话时才会被读取。要想强制重新读取.bash_history文件,更新终端会话的历史记录,可以使用 history -n 命令。
  • 隐藏history历史的方法:参考此文,记录三个比较好的方法
    • cat | bash // 只会记录此命令,之后的命令都不会再记录
    • set +o history //此后的命令都不会再记录,set -o history后会重新开始记录历史命令。
    • export HISTCONTROL=ignoredups:ignorespace,执行此命令后,在以后的命令前添加一个空格,则历史记录中就不会记录此条命令了。
  • =======记录更详细的history操作日志=========
  • 环境变量 HISTSIZE  的值表示history命令显示的条目数量
  • 环境变量 HISTFILE 的值表示用来记录历史命令的文件,默认指向 ~/.bash_history文件
  • 环境变量 HISTFILESIZE 的值表示文件中记录的最大条目数量,超过这个数量最前面的记录会被抛弃。默认是1000.
  • 环境变量 HISTCONTROL 的值 ·HISTCONTROL=ignoredups:ignorespace· 表示命令前带有空格的不记录到history中,连续两条重复的也不记录到history中。
  • 环境变量 HISTTIMEFORMAT="%F %T" 定义时间显示格式(虽然名字叫时间格式,但其实叫HISTFORMAT更合适,可以添加其他内容进去,如用户IP等,见下文脚本内容),这里的格式与date命令(`date "+%F %T`)后的“+%F %T”是一致的;HISTTIMEFORMAT作为history的时间变量将值传递给history命令。
    • 经过实测,改变HISTTIMEFORMAT的格式后,之后history命令的输出会变成所定义的格式,HISTFILE所指向的文件中的内容并未变,只是在每个命令的上面添加了一个带有#号的时间戳。
    • 如果想在文件中自定义格式记录用户操作命令,需要借助环境变量 PROMPT_COMMAND . 此环境变量的作用是在用户每次执行命令的时候都执行此环境变量的值。
  • 将用户操作历史命令更为详细的记录到文件中,脚本如下
    # 此段脚本作用如下:
    # 优化history命令的输出格式,HISTFILE文件保留系统默认的history记录
    # 另外自定义一个文件保留自定义格式的history记录
    # 两个记录可以互相参考
    
    # 注意这里的EOF被双引号引起来了。如果没有这个双引号,那么这段脚本会直接被解释,写入文件中的命令是解释后的命令,比如USER_IP=192.168.35.36
    # 加上双引号这段脚本就会被原样写入文件,这是我们想要的结果。
    cat >>/etc/profile.d/history.sh <<"EOF"
    USER_IP=`who -u am i 2>/dev/null| awk '{print $NF}'|sed -e 's/[()]//g'`
    MYHISTDIR=/usr/share/.history
    if [ -z $USER_IP ]
    then
        USER_IP=`hostname`
    fi
    
    if [ ! -d $MYHISTDIR ]
    then
        mkdir -p $MYHISTDIR
        chmod 777 $MYHISTDIR
    fi
    
    if [ ! -d $MYHISTDIR/${LOGNAME} ]
    then
        mkdir -p $MYHISTDIR/${LOGNAME}
    fi
    
    export HISTSIZE=4000
    DT=`date +%Y%m%d_%H%M%S`
    
    export HISTFILESIZE=5000
    export HISTTIMEFORMAT="$USER_IP [%Y.%m.%d %H:%M:%S] " #注意这个环境变量只能改变history命令的输出格式,无法改变HISTFILE文件的格式
    
    if [ ! -f $MYHISTDIR/${LOGNAME}/.bash_history ];then
            touch $MYHISTDIR/${LOGNAME}/.bash_history
            chmod -R 777 $MYHISTDIR/${LOGNAME} 2>/dev/null# 注意权限,x代表可以进入这个目录(文件夹),w代表可以往目录下的文件写入。此处770的前提是
    # 当前用户在root组中,否则以root登录后,再降级为其他用户时会报错无权限写入。不想折腾就用777.。。
    fi
    
    export PROMPT_COMMAND="history 1 >> $MYHISTDIR/${LOGNAME}/.bash_history" #自定义文件记录用户操作历史
    EOF

※,alias。alias 命令是另一个shell的内建命令。命令别名允许你为常用的命令(及其参数)创建另一个名称,从而将输入量减少到最低。

  • ·alias -p·// 查看当前可用的别名。

  • ·alias li='ls -li'· // 创建属于自己的别名,和定义变量一样等号两边不能有空格!!!!注意:因为命令别名属于内部命令,一个别名仅在它所被定义的shell进程中才有效。对子shell、新打开的shell都无效。想要让别名在不同的shell(子shell、新打开的shell)中都有效,可以和下文论述的持久化变量一样,在bash shell启动文件(又叫环境文件,即bash shell启动时会先读取这些文件中的内容,这些文件如 /etc/profile, ~/.bashrc等文件,具体详情见下文持久化变量论述)中配置此别名(如:直接将 alias li='ls -li'写入~/.bashrc文件中)。



6.使用Linux环境变量

6.0,首先区分两对概念:局部变量 和 全局变量;临时变量 和 永久变量

局部变量和全局变量在被持久化(持久化环境变量见下文)前都是临时变量,意味着它们(局部变量和全局变量)对 其他shell以及新开的shell都是不可见的。局部变量和全局变量的区别在于对当前shell会话的子shell是否可见

6.0.1, 局部变量 和 全局变量: export 关键字可以定义全局变量或将一个局部变量升级为全局变量。

  • 局部变量只对创建它们的shell可见。Linux系统也默认定义了标准的局部环境变量。局部变量只对当前shell有效(对 子shell、其他shell以及新开的shell都无效)。关闭当前shell此局部变量即消失。
  • 全局环境变量对于shell会话和所有生成的子shell都是可见的。全局变量对当前shell、当前shell的子shell有效,对其他shell以及新开的shell都无效。关闭当前shell此全局变量即消失。
    • 何时会启动子shell:最常见的情况就是shell脚本的执行,比如一个脚本文件叫hello.sh,当执行:./hello.sh或者sh hello.sh,这两种执行shell脚本的方式,会使用一个新的shell环境来执行脚本中的命令,也就是说,脚本是在子shell内执行的。
    • 亦即:局部变量在shell脚本里无效,全局变量在shell脚本里有效(变量带着$符号)。

6.0.2, 临时变量 和 永久变量

  • 在将局部变量或全局变量持久化到文件中前,都是临时变量,只对当前shell(或子shell)有效,关闭即消失。
  • 持久化时,局部变量和全局变量的区别和未持久化时一样,即有没有export关键字。
  • 直接在文件中持久化变量后需要新开的shell才会生效,如果需要当前shell生效,需要使用命令source <file>或 . <file>

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

6.2,系统环境变量(包括系统的全局变量和局部变量)基本上都是使用全大写字母,以区别于普通用户的环境变量。变量名区分大小写。在涉及用户定义的局部变量时坚持使用小写字母,这能够避免重新定义系统环境变量可能带来的灾难。要查看全局变量,可以使用 env 或 printenv 命令。要显示个别环境变量的值,可以使用 printenv 命令,但是不能用 env 命令。

查看全局变量:

  • `printenv`
  • `env`
  • ·printenv HOME· printenv 好像有些全局变量打印不出来,所以使用 echo $XXX 比较稳妥
  • ·env HOME` 报错
  • `echo $HOME` 必须加$符号
  • ·ls $HOME` 
  •  

查看局部变量: 

  • ·set`  在Linux系统并没有一个只显示局部环境变量的命令。 set 命令会显示为某个特定进程设置的所有环境变量,包括局部变量、全局变量以及用户定义变量。

命令` env 、 printenv 和 set 之间的差异`很细微。 set 命令会显示出全局变量、局部变量以及用户定义变量。它还会按照字母顺序对结果进行排序。 env 和 printenv 命令同 set 命令的区别在于前两个命令不会对变量排序,也不会输出局部变量和用户定义变量。在这种情况下, env 和 printenv 的输出是重复的。不过 env 命令有一个 printenv 没有的功能,这使得它要更有用一些。

6.3,设置用户自定义局部变量

  • `my_variable=Hello` 变量、等号、值 之间不能有任何空格!!!
  • `my_variable="Hello World"` 如果值包含空格,必须用 单引号 或 双引号
    • $my_variable; // 直接将my_variable的值当做shell命令执行。利用此特性可以简化一些启动命令,比如设置变量 mytest='java -jar test.jar' 即可直接使用$mytest,相当于运行 java -jar test.jar。
    • echo $my_variable;
    • echo ${my_variable};
    • echo ${#my_variable};//打印变量长度
    • echo ${my_variable:0:5}//打印变量从位置0开始长度为5的字节。类似python切片。

6.4,设置用户自定义全局变量

  • ·export my_variable` // 通过export命令可以将一个已有局部变量变成全局变量,变量名前面不需要加 $。
  • ·export my_variable='java -jar test.jar'· //直接export 一个新的变量 也可以直接定义一个全局变量,变量、等号、值之间不能有任何空格。
  • 修改子shell中全局环境变量并不会影响到父shell中该变量的值,子shell甚至无法使用 export 命令改变父shell中全局环境变量的值。

6.5,删除环境变量

  • ·unset my_variable·  // unset命令删除环境变量,不需要加$符号。多个变量之间用空格分隔。
  • 和修改变量一样,在子shell中删除全局变量后,你无法将效果反映到父shell中。

6.6,默认的 shell 环境变量

  •  `RANDOM`  返回一个0~32767的随机数(对其的赋值可作为随机数生成器的种子)
  • `PATH`  shell查找命令的目录列表,由冒号分隔

  •  

不是所有的默认环境变量都会在运行 set 命令时列出。尽管这些都是默认环境变量,但并不是每一个都必须有一个值

6.7,设置 PATH 环境变量(PATH变量和其他环境变量没有任何区别,只是其功能常用常被单独讲解)

  • 当你在shell命令行界面中输入一个外部命令时(参见第5章),shell必须搜索系统来找到对应的程序。 PATH 环境变量定义了用于进行命令和程序查找的目录。

  • 如果命令或者程序的位置没有包括在 PATH 变量中,那么如果不使用绝对路径的话,shell是没法找到的。如果shell找不到指定的命令或程序,它会产生一个错误信息。

  • PATH 中各个目录之间是用冒号分隔的,可以把新的搜索目录添加到现有的 PATH 环境变量中, 你只需引用原来的 PATH 值,然后再给这个字符串添加新目录就行了: ·PATH=$PATH:/home/tonux/scripts`

  • 常把当前目录·.· 添加到PATH变量中: ·PATH=$PATH:.·
  • 对 PATH 变量的修改只能支持到退出或重启系统(和其他环境变量一样,参见上文关于临时变量和永久变量的论述)。
  •  

6.8,持久化 环境变量

6.8.1, 在你登入Linux系统启动一个bash shell时,默认情况下bash会在几个文件中查找命令。这些文件叫做启动文件或环境文件。bash检查哪些启动文件取决于 你启动 bash shell 的方式,参考此文:五花八门的Shell 的相关概念和配置方法。shell的启动按照不同的分类方法可以分成交互式/非交互式shell, 登录shell/非登录shell。两种分类方式互无关系,比如交互式shell可以是登录也可以是非登录。具体说明如下:

什么是交互式/非交互式 Shell
Interactive Shell(交互式 Shell)与登录 Shell 都是指 Shell 所处的运行状态, 每个操作系统中可能会运行多个 Shell,这些 Shell 可能会处于下面的任何一种运行状态。
Interactive Shell(交互式Shell)是指可以让用户通过键盘进行交互的Shell。 我们在使用的CLI都是交互式Shell。
Non-interactive Shell(非交互式Shell)是指被自动执行的脚本, 通常不会请求用户输入,输出也一般会存储在日志文件中。

什么是登录/非登录 Shell
Login Shell(登录Shell)是指该Shell被运行时用于用户登录,比如TTY中的Shell就是以登录Shell的状态在运行。
Non-login Shell(非登录Shell)是指在用户已登录情况下启动的那些Shell。 被自动执行的Shell也属于非登录Shell,它们的执行通常与用户登录无关。

总结下,启动bash shell 有三种方式:

  • 登录时作为默认交互式登录 shell , 即在文本模式下输入用户名密码,作为服务器的Linux基本都是这种方式;
  • 作为非登录shell 的交互式shell,即在图形桌面下启动一个bash shell,和人交互的shell
  • 作为运行脚本的非登录非交互式shell,脚本中的命令启动的 bash shell

6.8.2, 登录shell

要留意的是有些Linux发行版使用了可拆卸式认证模块(Pluggable AuthenticationModules ,PAM)。在这种情况下,PAM文件会在bash shell启动之前处理,这些文件中可能会包含环境变量。PAM文件包括 /etc/environment 文件和 $HOME/.pam_environment 文件。PAM更多的相关信息可以在http://linux-pam.org中找到。

  • 作为 登录shell 启动时,bash shell会在以下几个地方查找命令和环境变量
    • `/etc/profile` 此文件是bash shell 作为登录shell时默认的的主启动文件。此文件中的命令可能会涉及到另外两个文件(目录): ·/etc/bash.bashrc· 和 `/etc/profile.d 目录`, 他们也会含有环境变量
    • 以下4个文件提供一个用户专属的启动文件来定义该用户所用到的环境变量,按以下顺序查找,运行第一个被找到的文件,余下的则被忽略!
    • ·$HOME/.bash_profile·
    • `$HOME/.bash_login`
    • `$HOME/.profile`
  • `$HOME/.bashrc` // ~/.bashrc只会被交互式的、非登录Bash读取(实际测试的确如此,在只有~/.bashrc的时候,通过ssh远程登录bash shell时此文件未被调用!)。 因此往往会在.bash_profile或.profile等文件中调用~/.bashrc来让Bash作为登录Shell时也读取~/.bashrc。

6.8.3,非登录交互shell

  • 作为非登录交互式shell启动的bash shell 不会读取 /etc/profile 文件, 只会检查 ~/.bashrc 文件。

6.8.4,非交互shell

  • 优先读取 一个全局环境变量 ·BASH_ENV`,如果此变量设置了一个文件,那么非交互shell 就会读取此被设置的文件中的命令作为环境变量。
  • 如果没有设置BASH_ENV,则此脚本运行时产生的shell的环境变量分为两种:
    • 如果不产生子 shell,那么就以当前shell的环境变量作为其环境变量, [注释:所谓的当前shell就是运行shell脚本的shell环境,即上面两种启动shell中的一种]
    • 如果产生了子shell,则会继承父shell中的全局环境变量(注意无法继承父shell的局部环境变量),【注释:所谓的当前shell就是运行shell脚本的shell环境,即上面两种启动shell中的一种】

6.8.5,持久化环境变量的方法

  • 对于系统上的所有用户持久化环境变量
    • 此时只针对于登录shell,由于所有登录用户都会访问 /etc/profile,所以可以将自定义需要持久化的变量存入此文件中,但是由于Linux发行版升级时会更新此文件,更新后所有自定义的变量都会消失。所以最好的实践是在 /etc/profile.d 目录下建立一个以 .sh 结尾的脚本文件。把所有新的或修改过的全局环境变量设置放在这个文件中。
  • 对于系统上的个人用户持久化环境变量
    • 对于登录shell 以及 交互式 shell, 存储个人用户永久性bash shell变量的地方是$HOME/下的 .bashrc 或 .bash_profile等文件,

    • 对于非交互shell,如果设置了 BASH_ENV 环境变量,则个人用户持久化的地方就是此变量所设置的文件,如果没有设置则持久化的文件位置取决于脚本运行时启动的shell类型。

6.10,数组变量:数组变量在不同shell间的可移植性并不好,Linux脚本中很少用数组变量。

参考此手册数组部分                       另一个参考手册

  • ·my_arr_variable=(1 2 3 4 5)` 注意bash shell 中数组定义时不是用逗号,而是用空格!!
  • ·echo $my_arr_variable· 只显示第零个元素1
  • ·echo ${my_arr_variable[2]}· 显示第3个元素3
  • ·echo ${my_arr_variable[*]}· 显示所有元素的值。*也可以使用@.
  • `echo ${!my_arr_variable[*]}` 显示所有的元素的键。*也可以使用@
  • ·echo ${#my_arar_variable[*]}·显示数组长度
  • 使用seq生成数组:
    aNumList=$(seq 10);
    echo $aNumList # 1 2 3 4 5 6 7 8 9 10
    # aNumList得到是字符串,不同之处以空格分隔开。在linux里面,可以把它看作是list(即以空格分开的字符串). 可以通过for…in  循环读取:
    for i in $aNumList;do echo $i;done;
    
    # 如果需要生成array只需要将$(seq 10) 再加个”()”即可
    aNumList=($(seq 10));
    echo ${#aNumList[@]} # 10
  • 使用内部运算符{begin..end}生成数组:
    ·echo {1..10}· //1 2 3 4 5 6 7 8 9 10
    for a in {1..10};do echo $a;done; 
    # {begin..end}速度比seq调用快了不少
  • =========
  • declare -a var //声明索引数组,可以不写
  • declare -A var //声明关联数组,必须声明
  • declare -A myMap;myMap["my03"]="03"
  • declare -A myMap=(["my01"]="01" ["my02"]="02")
  • `echo ${!myMap[@]}` //显示关联数组的所有键。@也可以使用*
  • ·echo ${myMap[@]}·// 显示关联数组的所有值。@也可以使用*。
  • ·echo ${#myMap[@]}` //显示关联数组的长度
  • echo "遍历所有的key:"
    for key in ${!myMap[@]};do
     echo "key:"$key
     echo "value:"${myMap[$key]}
    done
     
    echo "遍历所有value:"
    for val in ${myMap[@]};do
     echo "value:"$val
    done
  • ===========
  • ·unset my_arr_variable[1]· 删除数组中的第2个元素,删除后第二个元素位置值为空,但是索引依然还在!,即my_arr_variable[1] 为空
  • ·unset my_arr_variable· 删除整个数组。


7. 理解Linux文件权限

7.1,Linux用户系统

7.1.1, Linux安全系统的核心是用户账户。每个能进入Linux系统的用户都会被分配唯一的用户账户。用户对系统中各种对象的访问权限取决于他们登录系统时用的账户。

7.1.2, `/etc/passwd` 文件存放了系统上所有用户的信息。/etc/passwd文件的字段包含了如下信息:

  •  登录用户名
  •  用户密码
  •  用户账户的UID(数字形式)
  •  用户账户的组ID(GID)(数字形式),也就是当前用户所属的默认组
  •  用户账户的文本描述(称为备注字段)
  •  用户HOME目录的位置
  •  用户的默认shell

root:x:0:0:root:/root:/bin/bash

www:x:1000:1000::/home/www:/bin/bash

eiduo:x:1008:1009::/home/eiduo:/bin/bash

root用户账户是Linux系统的管理员,固定分配给它的UID是 0 。就像上例中显示的,Linux系统会为各种各样的功能创建不同的用户账户,而这些账户并不是真的用户。这些账户叫作系统账户,是系统上运行的各种服务进程访问资源用的特殊账户。所有运行在后台的服务都需要用一个系统用户账户登录到Linux系统上。Linux为系统账户预留了500以下的UID值。有些服务甚至要用特定的UID才能正常工作。

 /etc/passwd文件中的密码字段都被设置成了x。在早期的Linux上,/etc/passwd文件里有加密后的用户密码。但鉴于很多程序都需要访问/etc/passwd文件获取用户信息,这就成了一个安全隐患。现在,绝大多数Linux系统都将用户密码保存在另一个单独的文件中(叫作shadow文件,位置在/etc/shadow)。只有特定的程序(比如登录程序)才能访问这个文件。

/etc/passwd是一个标准的文本文件。你可以用任何文本编辑器在/etc/password文件里直接手动进行用户管理(比如添加、修改或删除用户账户)。但这样做极其危险。如果/etc/passwd文件出现损坏,系统就无法读取它的内容了,这样会导致用户无法正常登录(即便是root用户)。用标准的Linux用户管理工具去执行这些用户管理功能就会安全许多。

7.1.3, `/etc/shadow` 文件:/etc/shadow文件对Linux系统密码管理提供了更多的控制。只有root用户才能访问/etc/shadow文件,这让它比起/etc/passwd安全许多。

eiduo:$6$knAbzvfw$VYq2ZjwSzy4f/2afcWOcnVFLigxwQY0PmtfnEEGj/Q3hEm745lwYV306.lFHuUkafFIToDtTlcFjLim3f5CX..:18184:0:99999:7:::

在/etc/shadow文件的每条记录中都有9个字段:

  •  与/etc/passwd文件中的登录名字段对应的登录名
  •  加密后的密码,注意新建用户或伪用户的密码都是【!!】或【*】,代表这个用户没有密码,是不能登录的
    • ①.该列留空,即"::",表示该用户没有密码。
    • ②.该列为"!",即":!:",表示该用户被锁,被锁将无法登陆,但是可能其他的登录方式是不受限制的,如ssh公钥认证的方式,su的方式。
    • ③.该列为"*",即":*:",也表示该用户被锁,和"!"效果是一样的。
    • ④.该列以"!"或"!!"开头,则也表示该用户被锁。
    • ⑤.该列为"!!",即":!!:",表示该用户从来没设置过密码。
    • ⑥.如果格式为"$id$salt$hashed",则表示该用户密码正常。其中$id$的id表示密码的加密算法,$1$表示使用MD5算法,$2a$表示使用Blowfish算法,"$2y$"是另一算法长度的Blowfish,"$5$"表示SHA-256算法,而"$6$"表示SHA-512算法,
    • ========================手动生成密码=================================
    • openssl passwd -<N> -salt <yoursalt>  <yourpass>
      • N传递-1将生成MD5密码,-5SHA256和-6SHA512(推荐)。centos7中的openssl是老版本的,只支持 -1,不支持-5,-6。
      • -salt参数不传递则随机生成
      • openssl passwd -1 '123456' // 生成MD5密码, salt随机生成
      • openssl passwd -1 -salt 'abcdefg' '123456' // 生成MD5密码,指定salt
    • 使用python的crypt函数生成:python官网的crypt说明
      # 自定义salt:salt的格式必须是 $6$yoursalt 的形式,其中6也可以是1或5
      python -c 'import crypt;pw="123456";salt="$6$yousalt";print(crypt.crypt(pw,salt))'
      # 随机生成salt
      python -c 'import crypt;pw="123456";print(crypt.crypt(pw))'
      
      [root@server1 ~]# a=$(python -c 'import crypt;pw="123456";print(crypt.crypt(pw))')
      [root@server1 ~]# echo $a
      $6$uKhnBg5A4/jC8KaU$scXof3ZwtYWl/6ckD4GFOpsQa8eDu6RDbHdlFcRLd/2cDv5xYe8hzw5ekYCV5L2gLBBSfZ.Uc166nz6TLchlp.
    • 【未验证】mkpasswd --method=SHA-512 --stdin
    • centos 6: `grub-crypt --sha-512`, grub-crypt实际是个python脚本,内容如下:
      grub-crypt 内容
      [root@server1 ~]# cat /sbin/grub-crypt 
      #! /usr/bin/python
      
      '''Generate encrypted passwords for GRUB.'''
      
      import crypt
      import getopt
      import getpass
      import sys
      
      def usage():
          '''Output usage message to stderr and exit.'''
          print >> sys.stderr, 'Usage: grub-crypt [OPTION]...'
          print >> sys.stderr, 'Try `$progname --help\' for more information.'
          sys.exit(1)
      
      def gen_salt():                      # 生成随机的salt
          '''Generate a random salt.'''
          ret = ''
          with open('/dev/urandom', 'rb') as urandom:
              while True:
                  byte = urandom.read(1)
                  if byte in ('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
                              './0123456789'):
                      ret += byte
                      if len(ret) == 16:
                          break
          return ret
      
      def main():
          '''Top level.'''
          crypt_type = '$6$' # SHA-256
          try:
              opts, args = getopt.getopt(sys.argv[1:], 'hv',
                                         ('help', 'version', 'md5', 'sha-256',
                                          'sha-512'))
          except getopt.GetoptError, err:
              print >> sys.stderr, str(err)
              usage()
          if args:
              print >> sys.stderr, 'Unexpected argument `%s\'' % (args[0],)
              usage()
          for (opt, _) in opts:
              if opt in ('-h', '--help'):
                  print (
      '''Usage: grub-crypt [OPTION]...
      Encrypt a password.
      
        -h, --help              Print this message and exit
        -v, --version           Print the version information and exit
        --md5                   Use MD5 to encrypt the password
        --sha-256               Use SHA-256 to encrypt the password
        --sha-512               Use SHA-512 to encrypt the password (default)
      
      Report bugs to <bug-grub@gnu.org>.
      EOF''')
                  sys.exit(0)
              elif opt in ('-v', '--version'):
                  print 'grub-crypt (GNU GRUB 0.97)'
                  sys.exit(0)
              elif opt == '--md5':
                  crypt_type = '$1$'
              elif opt == '--sha-256':
                  crypt_type = '$5$'
              elif opt == '--sha-512':
                  crypt_type = '$6$'
              else:
                  assert False, 'Unhandled option'
          password = getpass.getpass('Password: ')
          password2 = getpass.getpass('Retype password: ')
          if not password:
              print >> sys.stderr, 'Empty password is not permitted.'
              sys.exit(1)
          if password != password2:
              print >> sys.stderr, 'Sorry, passwords do not match.'
              sys.exit(1)
          salt = crypt_type + gen_salt()
          print crypt.crypt(password, salt)      # 生成最终的加密密码
      
      if __name__ == '__main__':
          main()
    •  
    • ========================替换root密码=======================
    • # 生成密码后,直接将其拷贝或替换到shadow文件的第二列即可。例如:替换root用户的密码
      shell> field=$(awk -F ':' '/^root/{print $2}' /etc/shadow)
      shell> password=$(openssl passwd -1 123456)
      shell> sed -i '/^root/s%'$field'%'$password'%' /etc/shadow
  •  自上次修改密码后过去的天数密码(自1970年1月1日开始计算)
  •  多少天后才能更改密码
  •  多少天后必须更改密码
  •  密码过期前提前多少天提醒用户更改密码
  •  密码过期后多少天禁用用户账户
  •  用户账户被禁用的日期(用自1970年1月1日到当天的天数表示)
  •  预留字段给将来使用

7.1.4, 添加新用户 ·useradd· 

  • ·useradd·命令创建的新用户的的 系统默认值被设置在 `/etc/default/useradd`文件中。
  • ·useradd -D` 查看所用Linux系统中的这些默认值,即查看 `/etc/default/useradd` 的内容。
    • # /usr/sbin/useradd -D
      GROUP=100
      HOME=/home
      INACTIVE=-1
      EXPIRE=
      SHELL=/bin/bash
      SKEL=/etc/skel
      CREATE_MAIL_SPOOL=yes
      #
      
      在创建新用户时,如果你不在命令行中指定具体的值, useradd 命令就会使用 -D 选项所显示
      的那些默认值。这个例子列出的默认值如下:
        新用户会被添加到GID为 100 的公共组;
        新用户的HOME目录将会位于/home/loginname;
        新用户账户密码在过期后不会被禁用;
        新用户账户未被设置过期日期;
        新用户账户将bash shell作为默认shell;
        系统会将/etc/skel目录下的内容复制到用户的HOME目录下,目录下是bash shell环境的标准启动文件;
        系统为该用户账户在mail目录下创建一个用于接收邮件的文件。
  • 可以在 -D 选项后跟上一个指定的值来修改系统默认的新用户设置(即修改文件内容)。如,·useradd -D -s /bin/tsch· ,具体参数选项含义见下
  • 也可以在使用useradd命令具体创建一个用户时指定此用户的默认配置,各种参数如下:
    • 查看代码
      ·useradd userName` 创建一个用户,默认不会创建HOME目录;
          如果开始没有创建HOME目录,后面想创建HOME目录可以直接手动创建文件夹,但是注意使用root创建文件夹后需要将这个文件夹chown到用户名下和组下!
          新建用户ls没有颜色:~/.bashrc下添加:alias ls="ls --color"
      ·useradd -m userName` -m参数会创建默认的HOME目录;
      ·useradd -m userName -s /bin/bash·// -s参数指定用户默认的shell。不指定则为/bin/sh,这个shell在用xshell连接时会有问题(删除方向键乱码)。
      通常是使用useradd命令创建一个无密码的用户,然后使用passwd设置密码。
      创建好用户后也可以通过usermod命令 或 chsh命令重新指定shell。见下文
      
      -b default_home 更改默认的创建用户HOME目录的位置
      -c comment 给新用户添加备注
      -d home_dir 为主目录指定一个名字(如果不想用登录名作为主目录名的话
      -e expire_date 用YYYY-MM-DD格式指定一个账户过期的日期
      -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 为用户账户指定默认密码(这里的密码需是加密后的!),这个选项其实没有实际用处,常用的创建用户的方法是创建一个无密码的用户,然后使用passwd命令设置其密码。
      -s shell 指定默认的登录shell,要拒绝系统用户登录(即不授予用户shell的访问权限),可以将其 shell 设置为 /sbin/nologin 或 /usr/sbin/nologin 或者 /bin/false。/bin/false 什么也不做只是返回一个错误状态,
      然后立即退出。将用户的 shell 设置为 /bin/false,用户会无法登录,并且不会有任何提示。nologin 会礼貌的向用户显示一条信息,并拒绝用户登录。
      -u uid 为账户指定唯一的UID

7.1.5, 删除用户

  • ·userdel userName· 默认情况下,userdel 命令会只删除/etc/passwd文件中的用户信息,而不会删除系统中属于该账户的任何文件。
  • `userdel -r userName` 加上-r 参数, userdel 会删除用户的HOME目录以及邮件目录。

7.1.6, 修改用户: Linux提供了一些不同的工具来修改已有用户账户的信息,每种工具都提供了特定的功能来修改用户账户信息。usermod 命令是用户账户修改工具中最强大的一个。

  • ·usermod` 修改用户账户的字段,还可以指定主要组以及附加组的所属关系。

它能用来修改/etc/passwd文件中的大部分字段,只需用与想修改的字段对应的命令行参数就可以了。参数大部分跟 useradd 命令的参数一样(比如, -c 修改备注字段, -e 修改过期日期, -g 修改默认的登录组)。除此之外,还有另外一些可能派上用场的选项。

  -l (小L)修改用户账户的登录名。

  -L 锁定账户,使用户无法登录。-L 选项尤其实用。它可以将账户锁定,使用户无法登录,同时无需删除账户和用户的数据。要让账户恢复正常,只要用 -U 选项就行了。

  -p 修改账户的密码。

  -U 解除锁定,使用户能够登录。

 -G 将用户添加到指定组中。

  • ·usermod -aG <groupName1,groupName2,...> <userName>` 将用户username添加到组groupname中(可通过查看/etc/group文件查看最后一个字段),多个group可以使用逗号分割。
    • 如果添加后没有生效,可以更新下用户组:·newgrp <gropuName>· //比如将用户添加到docker组后,docker ps还是报没权限,可以更新下用户组再试试。
    • -G,--groups GROUPS   新的附加组列表 GROUPS。不带-a选项会将用户所属的指定的组之外的其他组都删除,但是不会删除userName所属的默认组!
    • -a选项 将用户添加至-G选项中提到的附加组中,并不从其他组中删除此用户。(测试了下,如果不加-a则会从其他组中删除此用户,但是不会从默认组中删除。)
  • ·gpasswd -d userName groupName· 将用户userName从组groupName中删除。
    • 也可以用不带-a选项的命令·usermod -G <groupname> <username>· 删除某些组
  • ·id <username>· 可以查看当前username的用户ID,组ID,以及所属的组名及组ID。注意带上用户名,否则可能不显示默认组之外的其他组
    • groups <userName> //查看用户所在的组
  • 禁止普通用户(如sungrower)通过sudo修改root密码
    • visudo修改/etc/sudoers文件
      • 添加一行:sungrower ALL=(root) /sbin/*,/usr/bin/*,!/usr/bin/su,!/usr/bin/chattr,!/usr/bin/passwd,!/usr/sbin/visudo,!/usr/sbin/useradd,!/usr/sbin/adduser,!/usr/sbin/userdel,!/usr/sbin/deluser
      • 删除或注释此行:%sudo  ALL=(ALL:ALL) ALL
    • chattr +i /etc/sudoers // 给/etc/sudoers文件添加i属性,不得任意更改此文件。设置后即使root用户也无法更改此文件,visudo命令也无法更改。想解除此限制,只需执行chattr -i /etc/sudoers命令即可!

 -g 更改用户的默认组。·usermod -g newDefaultGroupName userName` 将username的默认组改为newDefaultGroupName 。可以查看/etc/passwd第四个GID字段验证。

 -s 更改用户的默认登录shell。 ·usermod -s /bin/bash <userName>· 等价于命令 chsh

  • `passwd`   修改已有用户的密码,改变用户密码的一个简便方法就是用 passwd 命令。
    • ·passwd userName` 回车后会两次输入密码。(如果提示无效的密码,少于8个字符,不用理会,实际上是有效的。)
    • 系统上的任何用户都能改自己的密码,但只有root用户才有权限改别人的密码。-e 选项能强制用户下次登录时修改密码。你可以先给用户设置一个简单的密码,之后再强制在下次登录时改成他们能记住的更复杂的密码。

  • `gpasswd`: this command is used to administer /etc/group, and /etc/gshadow. Every group can have administrators, members and apassword.

  • `chpasswd` 从文件中读取登录名密码对,并更新密码。如果需要为系统中的大量用户修改密码, chpasswd 命令可以事半功倍。 chpasswd 命令能从标准输入自动读取登录名和密码对(由冒号分割)列表,给密码加密,然后为用户账户设置。你也可以用重定向命令来将含有 userid:passwd 对的文件重定向给该命令。
    • ·echo 用户名:密码 | chpasswd ·
    •  ·chpasswd < users.txt·
    • -e :如果使用了-e选项,口令将只能以加密的方式传递       如果未使用-e选项,口令将按明文的形式传递
  • ·chage· 帮助管理用户账户的有效期,如修改密码的过期日期。日期值可以用下面两种方式中的任意一种:1.YYYY-MM-DD格式的日期;2.时间戳。
  • ·chfn· 修改用户账户的备注信息, 命令提供了在/etc/passwd文件的备注字段中存储信息的标准方法。 chfn 命令会将用于Unix的 finger 命令的信息存进备注字段,而不是简单地存入一些随机文本(比如名字或昵称之类的),或是将备注字段留空。如果在使用 chfn 命令时没有参数,它会向你询问要将哪些适合的内容加进备注字段。
    • ·finger userName· finger 命令可以非常方便地查看Linux系统上的用户信息。出于安全性考虑,很多Linux系统管理员会在系统上禁用 finger 命令,不少Linux发行版甚至都没有默认安装该命令。
  • ·chsh· 修改用户账户的默认登录shell,使用时必须用shell的全路径名作为参数,不能只用shell名。
    • ·chsh -s /bin/bash <userName>· //将用户<userName>的默认shell修改为 /bin/bash
    • 此命令等价于·usermod -s /bin/bash <userName>·
  •  

7.1.7,

7.2,Linux组:用户账户在控制单个用户安全性方面很好用,但涉及在共享资源的一组用户时就捉襟见肘了。为了解决这个问题,Linux系统采用了另外一个安全概念——组(group)。每个组都有唯一的GID——跟UID类似,在系统上这是个唯一的数值。除了GID,每个组还有唯一的组名。Linux系统上有一些组工具可以创建和管理你自己的组。组可以包含一个或多个用户以支持对系统资源的共享访问。

7.2.1. ·/etc/group·  文件可以查看系统上的所有组。和UID一样,GID在分配时也采用了特定的格式。系统账户用的组通常会分配低于500的GID值,而用户组的GID则会从500开始分配。/etc/group文件有4个字段:

docker:x:994:
tong:x:1000:
hstong:x:1001:
docker:x:135:tong,daemon //组名称:docker;密码:x(假的);组ID:135;属于组docker的用户名称:tong,daemon,注意:以组docker作为默认组的用户本也应该列在这里,但是Linux默认将这些用户省略了。

 组名
 组密码;组密码允许非组内成员通过它临时成为该组成员。这个功能并不很普遍,但确实存在。
 GID
 属于该组的用户列表;用户账户列表某种意义上有些误导人。你会发现,在列表中,有些组并没有列出用户。这并不是说这些组没有成员。当一个用户在/etc/passwd文件中指定某个组作为默认组时,用户账户不会作为该组成员再出现在/etc/group文件中。多年以来,被这个问题难倒的系统管理员可不是一两个呢

千万不能通过直接修改/etc/group文件来添加用户到一个组,要用 usermod 命令。在添加用户到不同的组之前,首先得创建组。

7.2.2. 创建新组:·groupadd`

  • `groupadd groupName` 新建一个组。 usermod命令可以分配用户到某个组。

7.2.3.修改组:·groupmod· 

  • ·groupmod -g newGID groupName`  将组groupName的GID改为 newGID。
  • ·groupmod -n newGroupName groupName` 将组groupName改名。修改组名时,GID和组成员不会变,只有组名改变。由于所有的安全权限都是基于GID的,你可以随意改变组名而不会影响文件的安全性。

7.2.4. 

7.3, Linux文件权限

7.3.1. ls 命令输出结果的第一个字段就是描述文件和目录权限的编码。这个字段的第一个字符代表了对象的类型:

 - 代表文件
 d 代表目录
 l 代表链接
 c 代表字符型设备
 b 代表块设备
 n 代表网络设备

rwx分别代表读、写、可执行权限。r对应数值100(八进制为4), w--010(2), x--001(1) , 所以6即表示rw, 7表示rwx.

7.3.2. 默认文件权限:当创建一个文件或文件夹时,系统会赋予其一个默认权限。这个权限由·umask`命令控制。umask 命令可以显示和设置这个默认权限。在大多数Linux发行版中, umask 值通常会设置在/etc/profile启动文件中,不过有一些是设置在/etc/login.defs文件中的(如Ubuntu)。

  • ·umask` 显示默认权限,一般默认值是“0022”,这个数值是8进制的。
    • 第一位0代表了一个特别的安全特性“粘着位(sticky bit)”。后面的022代表的是文件默认权限的掩码,即用全权限减去此掩码可以获得文件的权限。对于文件来说,全权限是666(这是个8进制数值,rw-rw-rw-,即所有用户都有读写权限),对于文件夹来说全权限是777。所以默认的文件权限是666-022=644,即读写、读、读。默认的文件夹权限是777-022=755,即rwxr-xr-x.
  • ·umask 026` 设置默认权限。

7.3.3. 改变安全性设置

7.3.3.0 su、sudo、sudo su、sudo -i 、sudo -l 的用法和区别, 参考此文

在sudo于1980年前后被写出之前,一般用户管理系统的方式是利用su切换为超级用户。但是使用su的缺点之一在于必须要先告知超级用户的密码。

su用法:

  • [su]只输入su后面不加账户名称时,系统默认切换到root账户,密码也为root的密码。没有时间限制
  • [su]和[su -]区别:
    • [su]只是切换了root身份,但Shell环境仍然是普通用户的Shell,[su]切换成root用户以后,pwd一下,发现工作目录仍然是普通用户的工作目录
    • 而[su -]连用户和Shell环境一起切换成root身份了,[su -]命令切换以后,工作目录变成root的工作目录了

sudo用法:sudo 允许一个已授权用户以超级用户或者其它用户的角色运行一个命令。当然,能做什么不能做什么都是通过安全策略来指定的。sudo 支持插件架构的安全策略,并能把输入输出写入日志(在 ubuntu 中,sudo 的日志默认被记录在 /var/log/auth.log 文件中)。默认的安全策略记录在 /etc/sudoers 文件中(root用户使用visudo命令打开此文件,export VISUAL=vim控制visudo命令使用vim打开,默认好像使用nano)。sudo暂时切换到超级用户模式以执行超级用户权限,提示输入密码时该密码为当前用户的密码,而不是超级账户的密码。不过有时间限制,Ubuntu默认为一次时长15分钟。

  • sudo su表示前用户暂时申请root权限,所以输入的不是root用户密码,而是当前用户的密码。只切换了root身份,Shell环境仍然是普通用户的Shell
    • 效果和sudo -s类似
  • sudo su -用户和shell环境一起切换。(完整命令为:sudo su - root)
    • 效果和sudo -i类似
  • sudo -i表示为了频繁的执行某些只有超级用户才能执行的权限,而不用每次输入密码,可以使用该命令。提示输入密码时该密码为当前账户的密码。没有时间限制。执行该命令后提示符变为“#”而不是“$”。想退回普通账户时可以执行“exit”或“logout” 。运行结果 PWD=/root
  • [sudo -u userb ls -l]指定用户执行命令
  • [sudo -l]列出目前的权限。

解决 【使用sudo命令提权时出现的报错:<user>不在 sudoers 文件中。此事将被报告】的问题

  • sudo概述:sudo命令用于给非root用户提权,想要用sudo命令需要此用户在sudo组中,有些系统没有sudo组就需要编辑/etc/sudoers文件给特定用户赋予sudo权限。
    • 注意:sudo的目的就是给非root用户提权,所以赋予了权限之后,普通用户可以访问/root目录下的文件。如果既想让普通用户期权且使用sudo无密码访问,又想限制访问/root目录则比较难办到,暂时只能想到sudo配置中的COMMAND中一个一个设置允许普通用户使用的sudo命令。
  • 将用户添加至组 sudo 中`usermod -aG sudo tonux`,即可. 有时候没有sudo组,则需要如下解决:
  • 修改 /etc/sudoers 文件(默认是440权限只读,无法编辑,可以使用root先修改为可编辑权限【2023年4月7日15:48:02新增】这个文件的内容非常重要, 如果文件内容损坏或者格式不正确就有可能导致无法进入系统. 所以最佳实践是使用visudo命令进行编辑/etc/sudoers, 此命令在退出时候会校验文件内容是否正确. 这样可以保证在退出编辑时文件内容是正确的。,在 root ALL=(ALL) ALL一行后添加一行:<user> ALL=(ALL) ALL即可。如果(进一步)需要<user>输入sudo命令后不输入密码,则可以使用如下格式: `<user> ALL=(ALL) NOPASSWD: ALL`。通用格式为:·USER_FLAG HOST_FLAG=(RUNAS_FLAG) COMMAND_FLAG·,各个参数的意义如下,参考此文
    • USER_FLAG表示:需要添加sudo权限的用户
    • HOST_FLAG(即用户名后面的ALL,也可以是某个具体的主机的hostname):表示网络中的主机,尤其对于分布式系统而言,可以指定具体的主机名。表示允许<user>用户在此主机上执行命令。
    • RUNAS_FLAG(第二个括号里的ALL):表示目标用户,也就是<user>用户以谁的身份(如root)去执行命令。
    • COMMAND_FLAG(第三个ALL): 表示命令名称,也就是<user>用户使用sudo可以执行哪些操作,可以使用!对命令取反,即不允许执行某些命令。
      • `%<group> ALL=(ALL) NOPASSWD:ALL,!/usr/bin/reboot,!/sbin/reboot` //以百分号开头表示用户组。 属于此用户组的用户免密执行sudo命令但不能执行reboot命令。

7.3.3.1 改变权限`chmod` :只有文件的属主才能改变文件或目录的权限。不过root用户可以改变系统上任意文件或目录的安全设置。

关于Linux中的文件(夹)权限问题,参考此文!全文如下:

问题

Q1: read write excute 权限对于文件文件夹分别意味着什么?文件夹的可执行权限是什么意思

Q2: 为什么other权限为0的文件还是被人重命名了?甚至被人删除了?

Q3: 根目录下这个tmp文件夹这个 t 是什么权限?
​ drwxrwxrwt 7115 root root 262144 Dec 2 22:28 tmp/

Q4: 这里原本应该是x, 这个s代表什么?有什么作用?
​ -rwsr-xr-x 1 root root 54256 May 17 2017 /usr/bin/passwd*

基础

  • 如果你不知道 drwxrwxrwx 表示权限777的文件夹
  • 如果你不知道 -rw-rw-r-- 表示权限664的普通文件
  • 如果你不知道第一个位置 l 代表链接文件,c代表character设备, p代表管道....
  • 如果你不知道这三个数字与 owner, group, other 权限的关联
  • 如果你不知道为什么需要权限管理
  • 如果你不知道修改权限并不能修改文件的拥有者(chown)

请,先查阅资料,本文假设这些你都知道: )

传统unix 权限管理系统

  • 对于文件
    r: 读文件内容 w:写文件内容 x: 执行文件
  • 对于文件夹
    r: 读文件夹中文件/文件夹的名字
    x: 搜索/访问权限,访问文件夹中的文件/文件夹
    w: 创建 、删除、遍历(如cd)、移动子文件/文件夹

看起来很简单,其实有很多坑,文件夹的x权限定义其实并不明确,我只观察到现象,但不是很明白原因。

正常的文件夹都有x权限

正常的文件夹都有x权限,如果你不想给你个文件夹x权限,绝大多数情况下你不需要给另外两种权限。例如,你不想让某个文件夹被访问,r=0,w=0,x=0就好

下面会讨论没有x权限的文件夹,却设置了r,w造成的奇怪现象

没有x的文件夹,w没有意义

搜索/访问权限被认为是创建、删除、遍历、移动的基础。没有x权限,w权限给定的一切都无从谈起。而且这样的文件夹它的子文件/文件夹的权限也没有意义,因为它们都不能被访问,管它有什么权限都无济于事。

(下面ls 一大堆问号不是编码错误,而是没有x权限导致无法访问到子文件/文件夹的meta-info)

$ chmod 600 test
$ ll test
ls: cannot access 'test/b': Permission denied
ls: cannot access 'test/..': Permission denied
ls: cannot access 'test/m': Permission denied
ls: cannot access 'test/c2': Permission denied
ls: cannot access 'test/.': Permission denied
total 0
d????????? ? ? ? ?            ? ./
d????????? ? ? ? ?            ? ../
-????????? ? ? ? ?            ? b
-????????? ? ? ? ?            ? c2
d????????? ? ? ? ?            ? m/
$ rm -f test/b
rm: cannot remove 'test/b': Permission denied
$ mv test/b test/c
mv: failed to access 'test/c': Permission denied

因此,不同于文件如果你想的是给一个文件夹开放所有读和写的权限,chmod 666 是不对的,而应该chomd 777

如果你一不小心sudo chmod -R 666 / 这将带来巨大的权限问题,没有x权限,所有非root运行的程序都因不能访问而无法执行...

没有x的文件夹,r有意义

$ ll test
ls: cannot access 'test/b': Permission denied
ls: cannot access 'test/..': Permission denied
ls: cannot access 'test/m': Permission denied
ls: cannot access 'test/c2': Permission denied
ls: cannot access 'test/.': Permission denied
total 0
d????????? ? ? ? ?            ? ./
d????????? ? ? ? ?            ? ../
-????????? ? ? ? ?            ? b
-????????? ? ? ? ?            ? c2
d????????? ? ? ? ?            ? m/

没有x的文件夹,w无意义,但r却是有意义的。如以上所示,这样的文件夹能读到子文件/文件夹的名字,但仅限于此。更多的信息都不能读到(meta-info ,创建时间,权限、所有者等等)

有x没有r的文件夹,权限很高

你可能认为不给文件夹读的权限,ls都看不了,其它操作肯定也是寸步难行。但其实不是这样的, 拥有wx权限的文件夹几乎拥有所有权限

这样的文件夹,仅仅是不能读到目录中名字,如果你知道你要访问对象的名字,一切操作正常, 图谋不轨的人若想知道这个文件夹下的所有文件是可以爆破得到的。

$ chmod 100 test
$ cd test
$ pwd
/home/ubuntu/test
$ ls
ls: cannot open directory '.': Permission denied
$ ls a
-rwxrwxrwx 1 ubuntu ubuntu 0 Dec  2 23:46 a* ### 虽然没有r权限,但是指定名字后任然能读到它的所有信息
$ cat a
## 下面这两个还需要写权限 chmod 300  

$ mv b a
$ cp a b

移动/重命名/删除/创建文件是上一级文件夹的权限

如果上一级文件夹有w和x权限,你自己的文件什么权限都没有,仍然可以被移动/重命名/删除

所以,即使你给other用户关闭了w权限(例如 chmod 700), other用户虽然不能修改你文件的内容,但却可以把它删掉,甚至是替换成另一个同名的文件。如果在你的发布的应用中没有注意这一点,将带来极大的安全隐患。

$ chmod 700 .
$ chmod 000 a
$ ls -l .
drwx------  3 ubuntu ubuntu 4096 Dec  3 00:11 ./
drwxr-xr-x 23 ubuntu ubuntu 4096 Dec  2 23:46 ../
----------  1 ubuntu ubuntu    0 Dec  2 23:46 a

对应地,拥有一个文件的所有权限,但如果上级文件夹没有wx权限(例如仅有rx),你任然无法对它重命名/移动/删除

$ chmod 500 test
$ cd test
$ chmod 777 b
$ rm -f b
rm: cannot remove 'b': Permission denied

这种情况时有发生,如果你不了解这个机制就会给你带来困惑。如果别的用户让你在他的目录下操作一个文件,他把这个文件全部权限都给你,chmod 777 ,甚至把owner 转移给你,你发现你还是不能对这个文件进行重命名/移动/删除,他没有考虑到外面那个文件夹在默认创建时的权限是775, 没有w权限。

默认权限

  • 在你创建(touch, mkdir 等),cp (不管它原来文件的权限怎么样),文件默认权限 664 文件夹默认权限 775
  • 不管cp前文件拥有者是谁,cp后的文件属于你
  • gcc 编译生成的可执行文件权限是775, 其它用户可执行,但不能修改

root

root用户拥有所有权限,哪怕它的权限是000。root 就是这么厉害,不要轻易使用root用户

更复杂的细粒度权限管理

sticky bit

根目录下这个tmp文件夹这个 t 是什么权限?

ll -d /tmp
drwxrwxrwt 7115 root root 262144 Dec  3 11:55 /tmp/

/tmp 文件夹是一个允许所有用户在其中存放临时文件的目录。它允许随意添加,删除,修改,移动自己创建的文件,但是不能动别人的东西,仅用rwx权限管理系统做不到这一点,它不能做到用户级的区分,只能粗略地区分owner、group、other。

这种共享的文件夹其实还是很实用的,你可能需要在多人共享的服务器上建立一个共享资源的文件夹,777的权限显然是不可取的,我们并不希望别人删除自己放的东西,这个时候你就需要sticky bit了

设置方法很简单:chmod 1777 xxxx,然后ls -l 可以看到权限最后一个x变为了t

需要注意的是在linux中,t权限对文件夹有效,对文件无意义

setuid

如果让一个程序在运行时拥有owner的权限呢? 甚至是root的权限呢?

例如sudo和passwd 这两个程序,执行它们显然需要root权限,我们可以看到它们都被设置了setuid

$ ll /usr/bin/sudo
-rwsr-xr-x 1 root root 136808 Jul  4  2017 /usr/bin/sudo*
ll /usr/bin/passwd
-rwsr-xr-x 1 root root 54256 May 17  2017 /usr/bin/passwd*

那,怎么给我们自己的程序设置setuid标记呢:

chmod u+x
chmod 4775
###设置过后原来的x会变为s
-rwsr-xr-x

除此之外还有setgid,它和setuid 类似,只不过是让程序运行时拥有group的权限。

这里需要注意的是如果文件没有可执行权限,setuid 和setgid 标记是没用的,s 会显示为S。

小结

传统的unix权限管理其实有很多的缺陷,不能细粒度地指定权限,不能指定某个用户对某个文件有哪些权限。这一套机制只能说基本够用,如果你有更复杂的权限管理需要,ACL对你可能有用。

参考文献

[1] Wikipedia, https://en.wikipedia.org/wiki/File_system_permissions#Traditional_Unix_permissions

  • `chmod [options] mode file/dir`  file/dir 支持使用通配符
  • `chmod -R 777 myDir` 将目录myDir以及其子文件子文件夹权限改为全权限777,-R表示递归
  • ·chmod [ugoa...] [+-=] [rwxXstugo]` chmod的另一种用法;
    •  u-属主,g-属组,o-其他用户,a-以上所有三种用户;
    • +:新增权限,-:减少,=:赋予;
    •  X :如果对象是目录或者它已有执行权限,赋予执行权限。
       s :运行时重新设置UID或GID。
       t :保留文件或目录。
       u :将权限设置为跟属主一样。
       g :将权限设置为跟属组一样。
       o :将权限设置为跟其他用户一样。
  • ·chmod o+r file` 给file文件的其他用户赋予读的权限
  • `chmod -R go+w ./client` //将目录client及其子目录子文件的组用户和其他用户添加 写 权限。
  • ·chmod g=u file`
  • `chmod u-u file`
  • `chmod g-u file`
  • `chmod g+u file`

7.3.3.1.1 关于setUid(SUID) , setGid(SGID), sticky bit(SBIT) 位:

  • Linux中的文件/目录 除了rwx 权限外,还有一种特殊的权限控制,称为suid(用s表示), sgid(用s表示), sbit(用t表示);
  • suid, sguid,sbit用属主、群组、其他用户 的可执行位来表示。在三位权限表示法基础上再添加一位,其中suid对应4,sgid对应2,sbit对应1
    • 属主原来有可执行权限x,如果还设置了suid,那么属主的可执行位会变成s,如果原来没有x,则变成S。如drwS------(无x,对应4600), drws------(有x,对应4700)
    • sguid在群组的可执行位上,与suid规则一直。如:d---rwS---(原来群组无x权限,对应2060), d---rws---(原来群组有x权限,对应2070)
    • sbit在其他用户的可执行位上,原来有x权限则变为t,原来无x权限则变为T。如d------rwT(原来其他用户无x权限,对应1006),d------rwt(原来其他用户位有x权限,对应1007)
      • t权限对文件夹有效,对文件无意义
  • ·chmod 7777 file/dir` 第一个7代表的就是这种特殊权限,7 表示对属主(4)、属组(2)、其他用户(1) 都设置这种权限,5代表4+1,即属主和其他用户设置这种权限。
  • ·chmod u+s file/dir` // -rwSrwxrwx
  • `chmod g-s file/dir`
  • `chmod o+t file/dir` 
  • 这种特殊权限出现在文件/目录原来的权限rwx中的x的位置上,
    • 属主的x位如果原来有可执行权限x,则给文件/目录属主设置suid权限则属主最终的权限为rws(小写s),如原来没有可执行权限x,则设置suid后文件/目录属主权限变为rwS(大写);
    • 属组的x 同上,这个权限常用来设置供所有用户使用的共享文件夹:设置一个共享组如名叫share的group,然后将各个用户加入到这个共享组里,然后就可以使用SGID创建一个共享目录。sgid权限作用是 使该目录下创建文件或文件的所属组继承该目录的所属组。
    • 其他用户的x同上,只是用字母 t 代替而非 s 。即粘着位。其作用是确保放在该目录中的文件只能由它们的用户和root用户删除。
  • SUID作用举例:
    chmod 4755 netlogin
    chmod 4755与chmod 755对比多了附加权限值4,这个4表示其他用户执行文件时,具有与所有者同样的权限(设置了SUID)。
    
    为什么要设置4755 而不是 755? 
    假设netlogin是root用户创建的一个上网认证程序,如果其他用户要上网也要用到这个程序,那就需要root用户运行chmod 755 netlogin命令
    使其他用户也能运行netlogin。但假如netlogin执行时需要访问一些只有root用户才有权访问的文件,那么其他用户执行netlogin时可能因为
    权限不够还是不能上网。这种情况下,就可以用 chmod 4755 netlogin 设置其他用户在执行netlogin也有root用户的权限,从而顺利上网。

7.3.3.2. 改变所属关系: root改属主,属主改属组(还必须是改前改后的属组成员)

  • `chown` 命令用来改变文件的属主,只有root用户才能改变文件/目录 的属主。
    • ·chown [options] owner[.group] file`  file支持通配符
    • `chown -R user1 dir`  递归将目录dir的 属主改为user1
    • ·chmod user1.group1 file` 将file的属主改为user1,属组改为group1; 属主和属组之间用点号和冒号都可以。
    •  
  • `chgrp` 命令用来改变文件/目录的默认属组,必须是文件的属主才可以更改文件/目录的属组,而且需要是原属组和新属组的成员,可以使用usermod -G 将一个用户添加到某个组中,注意更改后需要重新登录才生效。
    • ·chgrp group1 file` 将file的属组改为group1.

7.4.



第八章:管理文件系统(已迁移)



9. 安装软件程序:本章将介绍Linux上能见到的各种包管理系统(package management system,PMS),以及用来进行软件安装、管理和删除的命令行工具。

9.1,包管理基础。PMS工具及相关命令在不同的Linux发行版上有很大的不同。Linux中广泛使用的两种主要的PMS基础工具是 dpkg 和 rpm。

  • 基于Debian的发行版(如Ubuntu和Linux Mint)使用的是dpkg命令。  dpkg 工具是命令行与PMS的接口,用来安装、管理和删除软件包。
  • 基于Red Hat 的发行版(如Fedora、openSUSE和Mandriva)使用的是 rpm 命令,该命令是其PMS的底层基础。类似于 dpkg 命令, rmp 命令能够列出已安装包、安装新包和删除已有软件。

9.2,基于Debian的系统。

9.2.1,Debian系PMS工具包含如下程序:

  • dpkg:dpkg命令是Debian系pms工具的核心。
  • apt-get
  • apt-cache
  • aptitude:最常用。aptitude工具本质上是apt工具和 dpkg 的前端。 dpkg 是软件包管理系统工具,而aptitude则是完整的软件包管理系统。

※,apt专题:参考另一篇博文

9.3,基于Red Hat的系统。

和基于Debian的发行版类似,基于Red Hat的系统也有几种不同的可用前端工具。常见的有以下3种。这些前端都是基于 rpm 命令行工具的。

  •  yum :在Red Hat和Fedora中使用。
  •  urpm :在Mandriva中使用。
  •  zypper :在openSUSE中使用。
  •  

※,rpm专题:https://developer.aliyun.com/mirror/ 各个模块的镜像,点进去后相关连接下可以查看每个模块的rpm包列表。

★,rpm包命名规则: tree-1.6.0-10.el7.x86_64.rpm

★,rpm包来源:

★,rpm包安装命令

  • -i: 安装
  • -v : 详细输出
  • -h: 软件包安装的时候列出哈希标记 (和 -v 一起使用效果更好)
  • --test : 测试是否能够安装成功。仅测试不实际安装。
  • --force: 强制重新安装。尽量不用
  • --nodeps : 忽略依赖关系。尽量不用
  • rpm -ivh xxx.rpm // 本地安装rpm包。
  • rpm -ivh http://xxx.rpm //远程安装rmp包.
    • ·rpm -ivh xxx.rpm·有时候会报错依赖不满足,这时候可以yum的方式安装此rpm,·yum install xxx.rpm·, 不满足的依赖yum会自动安装

★,rpm 包查询命令:

说明:注意区分两个概念:

 net-tools-2.0-0.25.20131004git.el7.x86_64.rpm // 这个是rpm包文件,是个实体的文件。

 net-tools-2.0-0.25.20131004git.el7.x86_64 //这个是rpm包名,这个包名简称为net-tools(即版本号之前的单词,简称和全称效果相同)。这个是个概念,不是实体。

下文中的pkgName指的是rpm包名,rpm安装文件指的是实体文件。

  • rpm -q <pkgName> // 查看指定软件包(包名必须是全匹配,不能是包名的一部分)是否安装。由于常常不知道包的全名,更常用 rpm -qa | grep <keyword>
  • rpm -qa //列出本机所有已安装的rpm包。
  • rpm -qa [pkgName] // -qa可以带包名只查询指定包名。
  • rpm -qi <pkgName>// 查看指定软件包的详细信息
  • rpm -ql <pkgname> // 小写的L。查询指定软件包的安装目录、文件列表。使用yum安装的包也可以通过rpm查询,如rpm -ql openvpn
  • rpm -qc <pkgName> //查询指定软件包的配置文件。
  • rpm -qd <pkgName> //查询指定软件包的帮助文档。
  • rpm -qf <file / dir> //查询文件或者目录属于哪个RPM软件包。如:rpm -qf $(which vim)
    • 只能查询系统中已存在的文件/目录所属的rpm包。
  • rpm -q --scripts // 查询rpm包安装前和安装后执行的脚本。
  • rpm -qip <rpm安装文件> //查询某个rpm安装文件的详细信息,不会安装,仅查询。
  • rpm -qlp <rpm安装文件> // 查询某个rpm安装文件安装后会产生哪些文件,不会安装,仅查询。

★,rpm包升级命令:

  • -U: 如果老版本不存在则全新安装,如果存在新版本则升级。rpm -Uvh xxxx.rpm
  • -f : 老版本必须存在

★,rpm包卸载命令:

  • -e :  rpm -e <pkgName>

★,rpm损坏后恢复:参考文章

  • 1. 删除rpm 锁文件:rm -rf /var/lib/rpm/__db.00*
  • 2. 重建rpm 数据库:rpm --rebuilddb

※,yum专题:yum是管理rpm包的。yum是RedHat以及Centos中的软件包管理器,能够通过互联网下载以rpm结尾的包,并且安装,并可以自动处理依赖关系,无需繁琐的一次次手动下载rpm包安装。

★,yum源:要使用yum安装软件就需要一个一个包含各种rpm软件的repository,这个repository就称为yum源或yum仓库。这个源可以是本地的也可以是网络的。像 rpmfusion.org 这种优秀的仓库站点会列出必要的使用步骤。有时这些仓库网站会提供一个可下载的rpm文件,可以用 yum localinstall 命令进行安装。这个rpm文件在安装过程会为你完成所有的仓库设置工作。

  • /etc/yum.repo.d/文件夹下repo结尾的文件都是yum源设置文件。
  • `yum repolist` // 等同于·yum repolist enabled·查看本机有哪些yum源(已启用的)
  • ·yum repolist all· // 已启用和未启用都会显示
  • yum源有很多,如阿里,清华等等,有些软件还提供了独立的源仓库,如nginx,zabbix等有独立的yum源仓库。要使用这些源仓库,只需把这些仓库的repo配置文件放在/etc/yum.repo.d文件夹下即可。
  • yum阿里云源配置:参考此文
    • mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup
    • wget -O /etc/yum.repos.d/CentOS-Base.repo https://mirrors.aliyun.com/repo/Centos-7.repo
    • sed -i -e '/mirrors.cloud.aliyuncs.com/d' -e '/mirrors.aliyuncs.com/d' /etc/yum.repos.d/CentOS-Base.repo
    • mv /etc/yum.repos.d/epel.repo /etc/yum.repos.d/epel.repo.backup
    • mv /etc/yum.repos.d/epel-testing.repo /etc/yum.repos.d/epel-testing.repo.backup
    • wget -O /etc/yum.repos.d/epel.repo https://mirrors.aliyun.com/repo/epel-7.repo
    • 运行 yum makecache 生成缓存

★,建立自己的yum仓库

  • /etc/yum.repo.d/下建立local.repo文件,内容如下
    [localrepo]
    name=This is tonghuangshan's local yum repository
    baseurl=file:///root/package/    #可以使用ftp(vsftpd包),http(nginx),https协议等
    enabled=1
    gpgcheck=0
  • 将baseurl指定的主机的相关目录设置为yum仓库: `createrepo /root/package/`。createrepo命令可以通过yum install -y createrepo命令安装。
  • ·yum clean all· // 清除缓存,会将/var/cache/yum相关目录下的东西删除。
    • 实在不行可以手动删除目录下的所有东西:rm -rf /var/cache/yum/*
  • ·yum makecache· //重新生成cache
  • ·vim /etc/yum.conf· //此文件中有个配置项 keepcache,设置为1表示开启缓存,安装包时会将rpm包缓存至本地 /var/cache/yum/相关目录(yum.conf中可以配置)下,·find /var/cache/yum/x86_64/7/ -type f -name "*.rpm"·可以查看到缓存的rpm包。
  •  

★,处理损坏的包依赖关系。

有时在安装多个软件包时,某个包的软件依赖关系可能会被另一个包的安装覆盖掉。这叫作损坏的包依赖关系(broken dependency)。解决方法有如下:

  • 方法一:先运行·yum clean all·,然后运行·yum update(此命令会更新所有软件包,并更新内核。非常危险)`。有时候只要清理了放错位置的文件就可以了。
  • 方法二:如果方法一解决不了问题,尝试运行·yum deplist <package-name>`,这个命令显示了某个包所有的库依赖关系以及什么软件可以提供这些库依赖关系。一旦知道某个包需要的库,你就能安装它们了。
  • 方法三:如果这样仍未解决问题,还有最后一招,·yum update --skip-broken·,--skip-broken 选项允许你忽略依赖关系损坏的那个包,继续去更新其他软件包。这可能救不了损坏的包,但至少可以更新系统上的其他包。

★,yum命令详解

  • =======================安装========================
  • ·yum install nginx --disablerepo="*" --enablerepo="local"· //指定某个yum仓库安装。注意--disablerepo和 --enablerepo顺序不能颠倒。
  • ·yum localinstall <pkgname>· // 安装本地的rpm包,如果有依赖将自动从软件仓库中下载所需依赖(非来自.repo文件定义的软件仓库)
    • rpm -ivh <rpmFileName>  使用rpm安装本地rpm包时,如果有依赖是无法安装成功的。所以需要使用yum localinstall命令安装。
  • ·yum reinstall <pkgName>· //重新安装
  • ·yum install <pkgName>-<version>· //安装指定版本的包,注意包名和版本号之间用的是短横线。不同版本的包可以使用`yum list <pkgName> --showduplicates`列出。
  • ★`yum install *.rpm` // 根据man yum中的文档介绍,yum install后面的如果是一个文件名,那么yum install就等同于yum localinstall,如果名字不匹配yum仓库中的任何package,那么yum 首先会使用provides命令搜索包含指定名称的所有packages,然后将搜索出来的所有包进行安装!所以yum install *.rpm 命令如果实在含有rpm包的目录执行就会安装所有的本地包!如果不是那么则会安装所有yum provides *.rpm搜索出来的packages。
  • =======================更新========================
  • ·yum check-update· //本机需要更新的包。
  • ·yum update <pkgName>· //更新某个包
  • ·yum update· // 此命令会更新所有软件包,并更新内核。非常危险!!!!
  • =====================卸载==================
  • ·yum remove <package-name>`//删除软件包及其依赖,但是保留配置文件和数据文件。由于会卸载依赖,所以在这个命令很危险,尽量不使用。可以使用 rpm -e <pkgName>只卸载pkgName包,而不卸载其依赖。
  • ·yum erase <package-name>· // 删除软件包及其依赖及其配置文件和数据文件。
  • =============查询===============
  • `yum list` // 查询所有的安装包,已安装和未安装的都会列出来。其中已安装的会用@符号标记出来。
    • ·yum list installed·  // 查询已安装的包
    • `yum list <pkgName>` //查询某个包的信息
    • ·yum list <pkgName> --showduplicates· // --showduplicates参数作用:在 list/search 命令下,显示源里重复的条目。即可以列出所有的小版本。版本号有的带着数字加冒号,含义参考此文
    • ·yum list |grep -i <keyworkd>· //查询包含关键词的包。
    • ·yum list available· // 所有可用的包,应该和yum list没啥差别
    • ·yum --disablerepo="*" --enablerepo="openresty" list available· // 查看名为 openresty 的yum仓库中可用的包。
  • ·yum info <pkgName>· // 查看包的详细信息,安装或未安装的包皆可查询.pkgName中可以使用通配符
    • ·yum info vim*·  
  • ·yum provides <cmd | 文件绝对路径 | 通配符>· // 命令 或者是 文件的绝对路径,查询属于哪个rpm包。类似于 rpm -qf <file | dir>命令。
    • ·yum provides <cmd>· //
      • 当 cmd 命令已经安装已安装时,可以查询 cmd 命令属于哪个rpm包。如ifconfig 属于net-tools包。
      • 当cmd 命令未安装时,如果此命令安装后位于/usr/bin/目录下,则直接yum provides <cmd>可以查询出来此命令属于哪个包,如mkpasswd命令属于expect包。如果此命令安装后位于其他目录则无法搜索出结果,此时需要使用通配符扩大搜索范围。
    • ·yum provides /etc/hosts· //查询系统中的文件所属的 rpm包。
    • ·yum provides *ifconfig·// 使用通配符,当ifconfig命令未安装时,可以查询ifconfig命令属于哪个rpm包。route, ifconfig等命令属于net-tools包。
  • ·yum whatprovides <cmd | 文件绝对路径 | 通配符>`// 同yum provides 命令
  • ·yum search <keyword>· //  查询包含关键词的包,只查询名称和简介中包含keyword的包。
    • ·yum search all <keyword>· // 扩大搜索范围,基本可以查询出某个命令属于哪个包。如 ·yum search all ifconfig· 会列出几个包名。
  • ============================yum包 组相关 命令====================
  • yum group 和 yum groups命令都可以。
  • ·yum groups list· //列出可用组。
  • ·yum groups install <groupName>· //安装包组。
  • ·yum groups remove <groupName>· //移除
  • ========================yum 历史相关命令=====================
  • ·yum history· // ·man yum·搜索history查看具体细节
  • ·yum history list·//查看列表
  • ·yum history list vim-enhanced· //必须是具体的包名才能正常显示
  • ·yum history list vim*· //使用通配符,查看和vim相关的yum操作。配合·yum history list <transactionId>· //查看某个transactionId对应的yum操作信息。
  • ·yum history info N· //N是 yum history输出中的ID。不给出N的值则默认是最近那条yum命令的ID。
  • ·yum history undo N· // 撤销ID为N的那条操作。直接yum remove <pkgName>只会移除pkgName自己,不会移除相关依赖,想移除依赖就要通过yum history undo N来进行。
  • 1

★,yum问题记录:

  • Linux centos7 yum下载安装报错:软件包与预期下载的不符。建议:运行 yum --enablerepo=updates clean metadata尝试其他镜像。
    • 查看代码
      rm -f /var/lib/rpm/__*
      rpm --rebuilddb -v -v
      yum clean dbcache
      yum clean metadata
      yum clean rpmdb
      yum clean headers
      yum clean all
      rm -rf /var/cache/yum/timedhosts.txt
      rm -rf /var/cache/yum/*
      yum makecache
      链接:https://www.jianshu.com/p/9592791bfc2b
  • Metadata file does not match checksum。HTTP缓存问题。下次再出现此问题,可以试试上面的各种清理操作。本次好像是等待HTTP缓存自动过期自动好了。

★,源码包安装: 源码包安装方便管理,使用--prefix选项可以将所有文件放在一个文件夹下。

以nginx源码包安装为例:每一步是否执行成功可以使用 echo $? 命令来查看,为0 为成功,其他为失败。

  • 下载源码包: wget http://nginx.org/download/nginx-1.20.1.tar.gz
  • 生成:`./configure --prefix=/root/software/nginx` // --prefix指定安装目录。 ./configure --help可以查看各种选项。
    • 这一步会报错,需要安装各种依赖: yum install gcc gcc-c++ glibc pcre-devel zlib-devel openssl-devel
  • 编译: make
  • 安装:make install 

★,将自己的软件打包成 rpm 包:

步骤一:安装fpm 打包软件

  • ·yum install ruby rubygems ruby-devel· //安装ruby, 目的是为了使用 gem 命令。就像yum是python写的,想用yum必须现有python环境一样。
  • `gem sources --add https://repo.huaweicloud.com/repository/rubygems/ --remove https://rubygems.org/` //将gem源替换为华为的
  • ·gem sources  list· // 查看当前gem 源
  • ·gem install fpm -v 1.3.3· // 安装指定版本的fpm。
  • ==========本地安装fpm===============
  • 下载fpm压缩包:http://test.driverzeng.com/other/fpm-1.3.3.x86_64.tar.gz
  • 解压,里面有很多gem后缀的文件。
  • 进入解压目录 ·gem install *.gem· 即可。

步骤二:将本地软件打包成rpm包

  • ·fpm -s dir -t rpm -n nginx -v 1.20.1 -d 'pcre-devel,openssl-devel' --post-install /path/to/nginx_rpm.sh -f /root/software/nginx/·
    • -s dir //指定打包的是目录
    • --post-install: 指定脚本,可以不写
    • -f :指定打包的具体目录。
  • 生成一个rpm包,可以直接使用 rpm -ivh nginx-xxxx.rpm安装,也可以将其放在yum仓库里,使用yum install nginx安装。安装后的目录就是/root/software/nginx
  •  

★,yum损坏后重装

  • rpm -qa|grep yum|xargs rpm -ev --allmatches --nodeps #这一步一定要执行,而且要将所有的组件卸载掉,如果卸载不干净,后面安装会有问题
  • cat /etc/redhat-release // CentOS Linux release 7.9.2009 (Core)
  • 下载系统对应版本yum包
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/python-chardet-2.2.1-3.el7.noarch.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/python-devel-2.7.5-89.el7.x86_64.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/python-iniparse-0.4-9.el7.noarch.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/python-libs-2.7.5-89.el7.x86_64.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/python-pycurl-7.19.0-19.el7.x86_64.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/python-setuptools-0.9.8-7.el7.noarch.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/python-urlgrabber-3.10-10.el7.noarch.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/rpm-python-4.11.3-45.el7.x86_64.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/libxml2-python-2.9.1-6.el7.5.x86_64.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/python-2.7.5-89.el7.x86_64.rpm
    
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/yum-3.4.3-168.el7.centos.noarch.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/yum-metadata-parser-1.1.4-10.el7.x86_64.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/yum-plugin-aliases-1.1.31-54.el7_8.noarch.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/yum-plugin-fastestmirror-1.1.31-54.el7_8.noarch.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/yum-plugin-protectbase-1.1.31-54.el7_8.noarch.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/yum-updateonboot-1.1.31-54.el7_8.noarch.rpm
    wget https://mirrors.163.com/centos/7/os/x86_64/Packages/yum-utils-1.1.31-54.el7_8.noarch.rpm
  • 安装下载的这些yum包:`rpm -ivh --force *.rpm --nodeps` 
  • ·yum clean all· // 清除缓存,会将/var/cache/yum相关目录下的东西删除
    • 实在不行可以手动删除目录下的所有东西:rm -rf /var/cache/yum/*
  • ·yum makecache· //重新生成cache
    • 报错:一些源不存在,根据报错提示信息可以使用如下命令禁用:yum-config-manager --disable <reponame>
    • 报错:Damaged repomd.xml file,具体如下
      failure: repodata/repomd.xml from base: [Errno 256] No more mirrors to try.
      http://mirrors.aliyun.com/centos/7/os/x86_64/repodata/repomd.xml: [Errno -1] Error importing repomd.xml for base: Damaged repomd.xml file

      原因是网络外网不通,可以ping通www.baidu.com,但是实际上外网需要认证(弹出认证页面),10.0.100.92就是认证页面

      curl www.baidu.com
      <script type="text/javascript">window.location.href="http://10.0.100.92/?bas=10.0.100.92&tbac=mirrorac&manip=10.0.100.92&tbclientip4=192.168.89.14"
  •  

★,

★,

★, 

★,

★,

 

9.4.1,VMWare中的Linux虚拟机和宿主机设置共享文件夹方法。【官方文档

  • 虚拟机---->设置----->选项----->共享文件夹,设置宿主机共享文件夹目录。
  • 根据官方文档,Linux内核4.0之前和之后方法不一样,但自测虽然centos7使用的内核为3.10(`cat /proc/version`查看),但是依然只能用4.0之后的命令才能成功:·sudo /usr/bin/vmhgfs-fuse .host:/ /mnt/hgfs -o subtype=vmhgfs-fuse,allow_other·


10. Linux编辑器

※,vim编辑器:vim即 vi improved。更详细的说明见 vim标签下的文章。

  • ·readlink /usr/bin/vi· //查看软连接 /usr/bin/vi 指向的文件
  • `readlink /proc/$$/ns/net` //查看当前进程下的net文件。 $$表示当前进程ID。echo $$。
  • ·readlink -f /usr/bin/vi· //-f参数可以立刻找出链接文件的最后一环。

1,vim基础

vim编辑器在内存缓冲区中处理数据。如在启动vim时未指定文件名,或者这个文件不存在,vim会开辟一段新的缓冲区域来编辑。如果你在命令行下指定了一个已有文件的名字,vim会将文件的整个内容都读到一块缓冲区域来准备编辑。

  •  PageDown (或Ctrl+F):下翻一屏。
  •  PageUp (或Ctrl+B):上翻一屏

v进入可视模式。

2,编辑数据

命 令 描 述
x 删除当前光标所在位置的字符
dd 删除当前光标所在行
dw 删除当前光标所在位置的单词(光标后的单词)
d$ 删除当前光标所在位置至行尾的内容
J 删除当前光标所在行行尾的换行符(拼接行)
u 撤销前一编辑命令
a 在当前光标后追加数据
A 在当前光标所在行行尾追加数据
r char 用 char 替换当前光标所在位置的单个字符
R text 用 text 覆盖当前光标所在位置的数据,直到按下ESC键

vim中复制命令是 y (代表yank)。可以在 y 后面使用和 d 命令相同的第二字符( yw 表示复制一个单词, y$ 表示复制到行尾,yy复制光标所在行)。p 粘贴。

3,

※,nano编辑器

※,emacs编辑器

※,KWrite编辑器

※,Kate编辑器

※,GNOME编辑器



第19章:初识 sed  和 gawk

※,sed 和gawk是Linux世界中最广泛使用的命令行编辑器

卍,sed专题

※,sed编辑器被称作流编辑器(stream editor)。和普通的交互式文本编辑器恰好相反。在交互式文本编辑器中(比如vim),你可以用键盘命令来交互式地插入、删除或替换数据中的文本。流编辑器则会在编辑器处理数据之前基于预先提供的一组规则来编辑数据流。

★,sed可以处理 输入(命令行输入或从文件中输入(读取))的数据,sed编辑器的执行流程如下,注意sed命令不会改变原始的数据:

  • 一次从输入中读取一行数据。
  • 根据所提供的编辑器命令匹配数据。
  • 按照命令修改流中的数据。
  • 将新的数据输出到STDOUT中。

★,sed命令的格式如下:sed [options] script [file]

script参数(即sed的核心部分,如's/test/tonus/'就是sed的script,这部分最好使用单或双引号将命令包起来)指定了应用于数据流上的单个命令,如果需要使用多个命令,可以使用 -e 选项或-f选项。

options可以修改sed命令的行为,一些常用的选项(options)如下:

  • -e script 在处理输入时,将script中指定的命令添加到已有的命令中,即应用多个script规则。
  • -f file 在处理输入时,将file中指定的命令添加到已有的命令中。
  • -n 不产生命令输出,使用print命令来完成输出。sed命令默认情况下的输出是所有行(无论是否匹配到),使用-n可以取消这个默认行为,即不再产生默认打印。如果某行被匹配处理了则输出的是处理后的行内容,使用了-n不会影响被匹配的行输出。
  • -i 直接修改文件内容,而不是输出到终端。如果-i后面提供了后缀,那么还会备份原始文件(备份文件名为:原始文件名称后加上后缀).
    • sed -i 's#test#TONG#' data.txt
  • -r sed默认使用的正则是BREs, -r 参数可以开启EREs正则,但是不支持更强大的PCRE正则。

★,sed 中的正则表达式:sed命令中的script中匹配模式(比如/pattern1/s/pattern2/replacement/中的两个pattern, 或 /pattern/p中的pattern) 是BRE正则表达式,使用-r参数可以开启ERE正则,但是不支持更强大的PCRE正则。

  • 显示ps命令的头部说明字段和指定的进程(grafana-server进程):·ps -ef |sed -n '/PPID\|grafana/p'· //其中PPID是头部的一个字段,
    [root@sungrow27 ~]$ps -ef |sed -n '/PPID\|grafana/p'
    UID          PID    PPID  C STIME TTY          TIME CMD
    root      744796  578219  0 11:19 pts/0    00:00:00 sed -n /PPID\|grafana/p
    grafana  3313114       1  0  2022 ?        06:22:42 /usr/sbin/grafana-server --config=/etc/grafana/grafana.ini --pidfile=/run/grafana/grafana-server.pid --packaging=deb cfg:default.paths.logs=/var/log/grafana cfg:default.paths.data=/var/lib/grafana cfg:default.paths.plugins=/var/lib/grafana/plugins cfg:default.paths.provisioning=/etc/grafana/provisioning
    [root@sungrow27 ~]$

★,sed的基本使用方法示例:

  • ·sed '/^$/d' pod.yaml·//删除pod.xml中的所有空行。输出的是删除空行后的文本。如果带有-n参数则只会输出处理后的行,这里由于是删除命令,处理的行是被删除,所以什么都不会输出。
    • ·sed -i '/^$/d' pod.yaml· //直接作用于文件,删除空行。此处不能带-n,如果带-n则文件为空
  • `sed 's/test/tonus/'` // 默认情况下,sed编辑器会将指定的命令应用到 STDIN 输入流上。注意总共是3个斜杠,最后一个斜杠不能省略,这和vim中的s命令不一样。
  • `echo "this is a test" | sed 's/test/tonus/'` // 通过管道
  • `sed 's/test/tonus/' data.txt` // 将输入指定为文件 data.txt。sed编辑器并不会修改文本文件的数据。它只会将修改后的数据发送到STDOUT 。如果你查看原来的文本文件,它仍然保留着原始数据。
  • `sed -e 's/test/tonus/;s/ is / EVEREST /' data.txt` // -e选项指定多个命令(多个命令好像也不需要使用-e选项,见下面叙述),命令之间用分号隔开。除了使用分号,也可以使用 bash shell的次提示符来分割多个命令,如下
    windows@Tonus:~/shellScript$ sed -e '
    > s/test/tonus/
    > s/ is /Eve/
    > ' data.txt // 要在封尾单引号所在行结束命令。bash shell一旦发现了封尾的单引号,就会执行命令。
  • `sed -f script1.sed data.txt` // 从文件 script1.sed中读取命令处理data.txt中的数据。sed编辑器会将script1.sed文件中的每一行当做一条单独的命令,因此每条命令后不必加分号。

★,sed的多条命令的用法总结: 首先多个指令用分号隔开

  • ·sed '/test/p;p' data.txt· // 相当于这两条这令: /test/p 和 p,也就是含test的行打印2次,其余行,打印一次
  • `sed '/test/{p;p}' data.txt`//用大括号括起来,意思为:对含有test的行执行大括号中的指令,即含有test的行打印2此,其余行不打印。
  • -e选项好像无关紧要,加不加都行。

※,sed编辑器语法基础:

1. 更多的替换选项:  sed的 s 命令( substitute )用来在行中替换文本。默认情况下s命令只替换每行中出现的第一处,可以使用替换标记改变默认行为。

s/pattern/replacement/flags,有四种flags,可以组合使用

 数字,表明新文本将替换第几处模式匹配的地方;

  • ·sed 's/test/tonus/2' data.txt· //替换每行中的第二个test 为 tonus。

 g ,表明新文本将会替换所有匹配的文本;

 p ,表示打印出匹配到的行处理后的结果,注意由于默认情况下sed命令的输出是处理后的所有行,使用p标记后匹配的行会打印两遍。实际上p标记经常和-n选项一起使用,效果是只打印匹配到的行;

  • ·sed -n 's/test/tonus/p' data.txt· // 只打印匹配到的行(也就是被处理的行) 处理后的结果

 w file ,将替换的结果写到文件中。

  • ·sed 's/test/tonus/w out.txt' data.txt· // w标记和p标记类似,不过会将匹配到的行处理后的内容输出到out.txt文件中。替换每一行中第一个test。
  • ·sed 's/test/tonus/gw out.txt' data.txt· // w标记必须紧跟文件名称。替换每一行中所有的test.

i或I(大写的i), 表示模式匹配不区分大小写,即大小写不敏感。

  • ·sed 's/test/tonus/ip' data.txt· //将data.txt文件中的test(不区分大小写)替换为tonus

1.1, 替换字符:sed的s命令默认使用正斜杠作为字符串分隔符,也可以使用其他字符作为分隔符(注意只有s命令可以使用其他分隔符),如:

  • ·sed 's!/bin/bash/!/bin/sh!' data.txt· //将data.txt中每一行中第一个/bin/bash替换为 /bin/sh。

2. 使用地址:默认情况下,sed编辑器中使用的命令会作用于文本数据的所有行。可以使用行寻址(line addressing)使命令作用于特定的行或某些行。sed编辑器有两种寻址形式。

 以数字形式表示行区间: [address]command 或 address {command1;command2;...}

  • ·sed '3s/dog/cat/' data.txt· //将第三行中的第一个dog替换为cat
  • ·sed '2,23s/dog/cat/' data.txt· // 将第2至23行中的第一个dog替换为cat。
  • ·sed '2,$s/dog/cat/' data.txt· // 第2至最后一行,$表示最后一行。
  • `sed '2{s/dog/cat/;s/brown/green/}' data.txt` // 将两个命令(只)作用于第二行.等价于 `sed '2s/dog/cat/;2s/brown/green/' data.txt`
  •  

 用文本模式来过滤出行: /pattern/command,必须用正斜线将要指定的 pattern 封起来,sed编辑器会将命令只作用于包含指定文本模式的行上。pattern还可以使用正则表达式。

  • `sed '/tonus/s/bash/csh/' /etc/passwd` // 将包含 tonus的行 中的 第一个 bash 替换为 csh。
  • ·sed '/tonus/{s/tong/TONG/;s/shan/SHAN/}' data.txt· // 将包含tonus的行,替换2个值。
  • ·sed '/tonus/{2,3{s/tong/TONG/;s/shan/SHAN/}}' data.txt· //文本过滤和数字寻址结合使用,并使用多个命令,效果是在交集上执行命令。
  • 文本模式不区分大小写(大小写不敏感)有两种方式:
    • ·sed -n '[Tt][Oo][Nn][Uu][Ss]' data.txt· //利用正则规则表示不区分大小写,注意正则里一个[]只能匹配一个字符。
    • ·sed  -n '/tonus/Ip' data.txt·  // 大写的i表示不区分大小写,注意只能大写,小写的i没效果。
  • ·sed -i '/tonus/,+9d' data.txt· #删除包含关键词tonus的行及后面9行(9行的计算不包含tonus那一行)

【厉害了!总结】这两种模式都可以指定一个区间,还可以把两种模式混用

  • 比如2,5指定第2至第5行;
  • /word1/,/word2/command,使用文本模式指定区间,这里的原理是:sed会查询含有word1的行(查询过后此行的循环就算过了,也就是说后面查word2时不会再算这一行),查到了就开启command的执行,然后再找含有word2的行,如果查到含有word2的行,就关闭command的执行,然后再此查找含有word1的行,即下一个循环又开始了;如果查不到word2则command会一直执行。具体更详细的解释见下面删除行中的说明
  • /word1/,$ 指定从含有word1的行一直到最后一行。

3. 删除行:删除命令 d 名副其实,它会删除匹配指定寻址模式的所有行。使用该命令时要特别小心,如果你忘记加入寻址模式的话,流中的所有文本行都会被删除。只是在sed编辑器的输出的结果中删除,原始文本中的数据不会被删除!

  • ·sed 'd' data.txt· // 删除文件data.txt中的所有行。
  • ·sed '3,4d' data.txt· //删除文件第三和第四行。
  • ·sed '/tonus/d' data.txt· //删除包含tonus的行。

sed删除命令可以指定两个文本模式来删除某个区间内的行,但这么做时要小心。你指定的第一个模式会“打开”行删除功能,第二个模式会“关闭”行删除功能。sed编辑器会删除两个指定行之间的所有行(包括指定的行)。

  • ·sed '/line1/, /line3/d' data.txt· // 删除从 匹配line1的行 到 匹配line3的行

除此之外,你要特别小心,因为只要sed编辑器在数据流中匹配到了开始模式,删除功能就会打开。这可能会导致意外的结果。

$ cat data7.txt
This is line number 1.
This is line number 2.
This is line number 3.
This is line number 4.
This is line number 1 again.
This is text you want to keep.
This is the last line in the file.
$
$ sed '/1/,/3/d' data7.txt
This is line number 4.
$
第二个出现数字“1”的行再次触发了删除命令,因为没有找到停止模式,所以就将数据流中的剩余行全部删除了。当然,如果你指定了一个从未在文本中出现的停止模式,显然会出现另外一个问题。
$ sed '/1/,/5/d' data7.txt
$
因为删除功能在匹配到第一个模式的时候打开了,但一直没匹配到结束模式,所以整个数据流都被删掉了。

4. 插入和附加文本

  •  插入( insert )命令( i )会在指定行前增加一个新行;
  •  附加( append )命令( a )会在指定行后增加一个新行。

4.1,一般用法示例

  • ·echo "Test Line 2" | sed 'a\New Line 1'· // 在 Test Line 2之后添加新行 New Line 1。
  • ·sed '3i\hello world' data.txt· // 在data.txt文件中第三行之前插入新行 hello world.
  • `sed '2,5i\hello world' data.txt` //在data.txt文件的第二至第五行之前插入行 hello world.
  • `sed '$a\hello world' data.txt` // 在最后一行后面添加新行 hello world
  • ·sed '/line2/i\good morning' data.txt· //在(所有)含有line2的行之前插入新行good morning

4.2,插入或附加多个新行(2中方式如下):

  • ·sed '1,3i\new line1\nnew line2' data.txt· // 使用 \n换行符插入多个行
  • windows@Tonus:~/shellScript$ sed '1,3i\
    > good morning\   //使用bash shell 的次提示符,每个新行都使用反斜杠
    > fuck you' data.txt
    good morning
    fuck you
    line1
    good morning
    fuck you
    line2
    good morning
    fuck you
    line3
    line4
    line5
    line6

4.3, 附加(append)指定文件中的内容:read命令(r)允许你将一个独立文件中的数据附加(即append)到数据流中。读取命令的格式如下·[address]r filename·在读取命令中使用地址区间,只能指定单独一个行号或文本模式地址。sed编辑器会将文件中的文本插入到指定地址后。

  • `sed '3r test.txt' data.txt` //在data.txt的第三行之添加test.txt中的所有内容。
  • ·sed -n '$r test.txt' data.txt· //在data.txt的最后一行之后添加test.txt中的所有内容。
  • `sed -n '/LIST/{r list.txt;d}' email.txt` //经测试这么写语法不对(奇怪),必须使用下面这种方式才行
    windows@Tonus:~/shellScript$ sed '/line3/{
    > r line3
    > d
    > }' data.txt
    line1
    line2
    new line 3
    another new line3
    line4
    line5
    line6
    line7
    line8
    line9
    line10
    windows@Tonus:~/shellScript$

    或在脚本中这么写

    #!/bin/bash
    sed '/line3/{ 
    r line3
    d 
    }' data.txt

5. 修改行:修改命令(change)可以修改整行的内容。使用方式和插入/附加 新行一样。

  • ·sed '3c\changed line' data.txt· //将第三行修改为 changed line.
  • `sed '/line2/c\good morning' data.txt` // 将所有含有line2的行替换为 good morning.
  • `sed '2,4c\changed line' data.txt` // 这个要注意,效果是将指定的行 changed line 替换数据流中的3行(2,3,4),而不是逐一修改这三行文本。
  • `sed '3cgood luck' data.txt`// c后面的反斜杠可以不写,但是写了更清晰. 将第三行替换为 good luck。

6. 转换命令:transform 命令(y)是唯一可以处理单个字符的的sed编辑器命令。转换命令格式如下:

  • ·[address]y/inchars/outchars/· //注意是三个斜杠。

转换命令会对 inchars 和 outchars 值进行一对一的映射。 inchars 中的第一个字符会被转换为 outchars 中的第一个字符,第二个字符会被转换成 outchars 中的第二个字符。这个映射过程会一直持续到处理完指定字符。如果 inchars 和 outchars 的长度不同,则sed编辑器会产生一条错误消息。转换命令是一个全局命令,也就是说,它会在匹配的文本行中找到的所有指定字符自动进行转换,而不会考虑它们出现的位置。你无法限定只转换在特定地方出现的字符。

  • ·sed 'y/123/456/' data.txt· //将data.txt中所有行中的1->4, 2->5,3->6.
  • ·sed '1,3y/123/456/' data.txt· // 将第1至3行中的所有 1->4, 2->5, 3->6.
  •  

7. 回顾打印:sed编辑器有三个命令可以用来打印数据流中的信息:

 p 命令用来打印文本行;
 等号( = )命令用来打印行号;
 l (小写的L)命令用来列出行。

7.1,打印行,p命令:和替换命令中的p标记差不多。实际使用时多和-n选项一起使用。

  • ·sed -n '/line1/p' data.txt· //打印data.txt中包含line1的所有行。
  • ·sed -n '1,3p' data.txt· //打印data.txt中的1至3行。
  •  

7.2,打印行号,等号命令(=)会打印行在数据流中的当前行号。行号由数据流中的换行符决定。每次数据流中出现一个换行符,sed编辑器会认为一行文本结束了。

  • ·sed '=' data.txt· //sed编辑器在实际的文本行出现前打印了行号。
  • ·sed -n /line1/{=;p}· //利用 -n 选项,你就能让sed编辑器只显示包含匹配文本模式的行的行号和文本
  •  

7.3,列出行,列出( list )命令( l )可以打印数据流中的文本和不可打印的ASCII字符。任何不可打印字符要么在其八进制值前加一个反斜线,要么使用标准C风格的命名法(用于常见的不可打印字符),比如 \t 来代表制表符, \a代表响铃(beep, 蜂鸣)。

  • ·sed 'l' data.txt· // 输出的每行的行尾有一个$符号,这个行尾的美元符表示换行符。
  • ·echo -e \\a | sed 'l'· // echo的 -e 选项开启转义功能。响铃被sed打印为 \a.
  • `echo -e \\a >> data.txt` // 查看data.txt,发现响铃的表示方法是 ^G. ^G在Linux中输入的方法是 ctrl + v,然后ctrl + g。cat data.txt不会看到响铃字符,但是会听到响铃。sed -n l data.txt可以查看到响铃字符。

8. 使用sed处理文件:替换命令s有一个w标记可以将匹配的行替换后的结果写入指定文件,还有一些sed编辑器命令也可以实现同样的目标,不一定非得替换文本。

8.1,写入文件:w命令可以用来向文件写入行。w命令的格式如下:·[address]w filename· //注意需要有filename的写权限。

  • ·sed '1,3w test.txt' data.txt· //将data.txt中的 1至3行打印至test.txt文件中。当然,如果你不想让行显示到 STDOUT 上,你可以用 sed 命令的 -n 选项。
  • ·sed -n '/tonus/w tonus.txt' data.txt· // sed编辑器会只将包含文本模式的数据行写入目标文件。
  •  

8.2,读取文件数据。见上文 附加(append)指定文件中的内容,即read命令。

※,sed编辑器进阶

9, 多行命令:next命令(单行n, 多行N)

9.1, 单行的 next命令(n):小写的 n 命令会告诉sed编辑器移动到数据流中的下一文本行。正常情况下,sed编辑器在移动到数据流中的下一文本行之前,会在当前行上执行完所有定义好的命令。单行 next 命令改变了这个流程。

  • sed '/line1/{n;p}' data.txt // 此命令会找到含有 line1 的行,然后移动到此行的下一行并打印这个下一行。 对 line1所在的行并不做任何操作。

9.2,多行的next命令(N):单行next命令(n)会了数据流中的下一文本行移动到sed编辑器的工作空间(又称模式空间)。而多行next命令(N)会将下一文本行添加到工作空间已有的文本行后(即同时处理2行,暂还不知同时处理2行以上的命令)。使用N后,数据流中的两个文本行合并到同一个工作空间中,文本行之间仍然用换行符分隔(linux中的换行符是 \n ),但sed编辑器现在会将两行文本当成一行来处理。

windows@Tonus:~/shellScript/sed$ cat data.txt 
On Tuesday, the Linux System
Administrator's group meeting will be held.
All System Administrators should attend.
Thank you for your attendance.
windows@Tonus:~/shellScript/sed$ sed "N;l" data.txt 
On Tuesday, the Linux System\nAdministrator's group meeting will be h\ 
eld.$
On Tuesday, the Linux System
Administrator's group meeting will be held.
All System Administrators should attend.\nThank you for your attendan\
ce.$
All System Administrators should attend.
Thank you for your attendance.
windows@Tonus:~/shellScript/sed$ 

从上面 sed的l(小写L)命令可以看到Linux换行符是\n。单行模式下这个换行符会被l命令打印成$,多行模式下则打印成\n。而且在多行模式下正则表达式中的点号可以匹配这个换行符(单行模式下点号是不会匹配换行符的)!

windows@Tonus:~/shellScript/sed$ sed '/Tuesday/{N;s/\n/仝/}' data.txt  // 换行符的正则直接写 \n,无需 \\n
On Tuesday, the Linux System仝Administrator's group meeting will be held.
All System Administrators should attend.
Thank you for your attendance.
windows@Tonus:~/shellScript/sed$
windows@Tonus:~/shellScript/sed$ sed 'N; s/System Administrator/黄山/' data.txt 
On Tuesday, the Linux System
Administrator's group meeting will be held.
All 黄山s should attend.
Thank you for your attendance.
windows@Tonus:~/shellScript/sed$ sed 'N; s/System.Administrator/黄山/' data.txt //这里可以看出多行模式下,点号可以匹配中间的那个换行符
On Tuesday, the Linux 黄山's group meeting will be held.
All 黄山s should attend.
Thank you for your attendance.
windows@Tonus:~/shellScript/sed$ 

注意:多行模式下,点号可以匹配中间的那个换行符,但是当它匹配了换行符之后,System\nAdministrator就会被"黄山"替换,导致两行合并成了一行,如果不想替换这个换行符,可以用两个替换命令,如下:

windows@Tonus:~/shellScript/sed$ sed 'N; s/System\nAdministrator/黄\n山/;s/System Administrator/黄山/' data.txt 
On Tuesday, the Linux 黄
山's group meeting will be held.
All 黄山s should attend.
Thank you for your attendance.

//或者用下面的写法:
windows@Tonus:~/shellScript/sed$ sed '
> N
> s/System\nAdministrator/黄\n山/
> s/System Administrator/黄山/
> ' data.txt
On Tuesday, the Linux 黄
山's group meeting will be held.
All 黄山s should attend.
Thank you for your attendance.
windows@Tonus:~/shellScript/sed$ 

上面的sed脚本中仍有个小问题,这个脚本总是在执行sed编辑器命令前将下一行文本读入到当前工作空间中,如果data.txt中的行数是奇数,当sed编辑器读到最后一行文本时,就没有下一行可读了,所以N命令会叫sed编辑器停止。如果匹配的数据刚好在最后一行,命令就会错误这个要匹配的数据,如下例子。

windows@Tonus:~/shellScript/sed$ sed "N;s/System.Administrator/黄山/" data.txt 
On Tuesday, the Linux 黄山's group meeting will be held.
All System Administrators should attend.// 由于 System Administrator 文本出现在了数据流中的最后一行, N 命令会错过它,因为没有其他行可读入到模式空间跟这行合并。
windows@Tonus:~/shellScript/sed$

解决上述问题可以使用单行模式替换单行中出现的短语,同时使用多行模式匹配跨行出现的短语,脚本如下:

windows@Tonus:~/shellScript/sed$ sed 's/System.Administrator/黄山/;N;s/System\nAdministrator/黄\n山/' data.txt 
On Tuesday, the Linux 黄
山's group meeting will be held.
All 黄山s should attend.

// 或如下写法
windows@Tonus:~/shellScript/sed$ sed '
> s/System.Administrator/黄山/
> N
> s/System\nAdministrator/黄\n山/
> ' data.txt
On Tuesday, the Linux 黄
山's group meeting will be held.
All 黄山s should attend.
windows@Tonus:~/shellScript/sed$ 

9.3,多行删除命令:

  • ·sed 'N ; /System\nAdministrator/d' data.txt· //单行删除命令(d)会在不同的行中查找单词System和Administrator,然后在模式空间中将两行都删掉。
  • `sed 'N ; /System\nAdministrator/D' data.txt`// sed编辑器提供了多行删除命令 D ,它只删除模式空间中的第一行。该命令会删除到换行符(含换行符)为止的所有字符。
    这里有个例子,它会删除数据流中出现在第一行前的空白行。
    $ cat data5.txt
    
    This is the header line.
    This is a data line.
    
    This is the last line.
    $
    $ sed '/^$/{N ; /header/D}' data5.txt
    This is the header line.
    This is a data line.
    This is the last line.
    $
    sed编辑器脚本会查找空白行,然后用 N 命令来将下一文本行添加到模式空间。如果新的模式空间内容含有单词header,
    则 D 命令会删除模式空间中的第一行。如果不结合使用 N 命令和 D 命令,就不可能在不删除其他空白行的情况下只删除第一个空白行。

D命令在删除模式空间的第一行之后会强制sed编辑器返回命令列表的起始处,但不会读取新行,对工作空间中的内容重新执行这些命令,而n命令在删除行后会结束这一次执行,然后读取下一行,使用--debug很容易看出来。例如:

·sed -n 'N; /System\nAdministrator/D' data.txt· // 读取两行,假设找到了匹配行,删除第一行(此时工作空间还有一行,即第二行),然后sed编辑器会回到命令列表的起始处(即N命令),N命令会将文件流中待处理的下一行加载至工作空间中,然后继续匹配,如果找到继续删除第一行,然后循环往复。另外例子见9.3中。

9.3,多行打印命令:多行打印命令( P )只打印多行模式空间中的第一行。这包括模式空间中直到换行符为止的所有字符。多行模式下,小p命令会将两行作为一个整体打印(其实原理是:p命令打印工作空间的所有内容,大P命令打印工作空间的第一行内容)。

windows@Tonus:~/shellScript/sed$ cat d3
tong
huang tong
huang
good
bad
ugly
windows@Tonus:~/shellScript/sed$ sed  -n 'N; /tong\nhuang/D;p' d3
huang
good
bad
ugly
windows@Tonus:~/shellScript/sed$ sed   'N; /tong\nhuang/D;p' d3
huang
good
huang
good
bad
ugly
bad
ugly
windows@Tonus:~/shellScript/sed$ 

10,保持空间:模式空间(pattern space,又叫工作空间)是一块活跃的缓冲区,在sed编辑器执行命令时它会保存待检查的文本。但它并不是sed编辑器保存文本的唯一空间。sed编辑器有另一块称作保持空间(hold space)的缓冲区域。在处理模式空间中的某些行时,可以用保持空间来临时保存一些行。有5条命令可用来操作保持空间。

sed编辑器的保持空间命令
命令 描述
h 先清空保持空间,然后将模式空间复制到保持空间
H 将模式空间附加到保持空间
g 先清空模式空间,然后将保持空间复制到模式空间
G 将保持空间附加到模式空间
x 交换模式空间和保持空间的内容
$ cat data2.txt
This is the header line.
This is the first data line.
This is the second data line.
This is the last line.
$
$ sed -n '/first/ {h ; p ; n ; p ; g ; p }' data2.txt
This is the first data line.
This is the second data line.
This is the first data line.
$
我们来一步一步看上面这个代码例子:
(1) sed脚本在地址中用正则表达式来过滤出含有单词first的行;
(2) 当含有单词first的行出现时, h 命令将该行放到保持空间;
(3)  p 命令打印模式空间也就是第一个数据行的内容;
(4)  n 命令提取数据流中的下一行( This is the second data line ),并将它放到模式空间;
(5)  p 命令打印模式空间的内容,现在是第二个数据行;
(6)  g 命令将保持空间的内容( This is the first data line )放回模式空间,替换当前文本;
(7)  p 命令打印模式空间的当前内容,现在变回第一个数据行了。

11,排除命令:sed编辑器可以将命令应用到数据流中的每一个文本行或是由单个地址或地址区间特别指定的多行。你也可以配置命令使其不要作用到数据流中的特定地址或地址区间。

感叹号命令( ! )用来排除( negate )命令,也就是让原本会起作用的命令不起作用。

  • ·sed -n '/header/!p' data2.txt· // 打印 除了包含header的行 之外的所有行

之前的例子中有一种场景:使用N命令时,如果文本行有奇数行,那么最后一行将无法被sed处理,因为处理最后一行时,N命令没有新的行可以加载至工作空间,这时候N命令就会强制sed编辑器退出。使用排除命令可以解决这一问题。

$ sed 'N;
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/.
> ' data4.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All System Administrators should attend.
$
$ sed '$!N;
> s/System\nAdministrator/Desktop\nUser/
> s/System Administrator/Desktop User/
> ' data4.txt
On Tuesday, the Linux Desktop
User's group meeting will be held.
All Desktop Users should attend.
$

注释:美元符表示数据流中的最后一行文本,所以当sed编辑器到了最后一行时,它没有执行 N 命令,但它对所有其他行都执行了这个命令。

【顿悟】:理解工作空间是使用sed命令的关键!比如n命令,会将下一行文本流加载至工作空间,当前工作空间中的文本行会被这个新行替换,另外,n命令提取的新行由于已经被加载至工作空间了,下一次循环时将会跳过n提取的那一行,直接提取那一行的下一行。再比如p命令打印的是工作空间的所有内容。

使用排除命令和保持空间,你可以反转数据流中文本行的顺序(其实Linux有一个命令tac直接可以做到这一点,tac的名称刚好是cat反过来)。

windows@Tonus:~/shellScript/sed$ cat d1
1
2
3
4
windows@Tonus:~/shellScript/sed$ sed -n 'G;h;$p' d1//最后多一个空行
4
3
2
1

windows@Tonus:~/shellScript/sed$ sed -n 'G;h;$p' d1 --debug //debug模式666
SED PROGRAM:
  G
  h
  $ p
INPUT:   'd1' line 1
PATTERN: 1
COMMAND: G
PATTERN: 1\n
COMMAND: h
HOLD:    1\n
COMMAND: $ p
END-OF-CYCLE:
INPUT:   'd1' line 2
PATTERN: 2
COMMAND: G
PATTERN: 2\n1\n
COMMAND: h
HOLD:    2\n1\n
COMMAND: $ p
END-OF-CYCLE:
INPUT:   'd1' line 3
PATTERN: 3
COMMAND: G
PATTERN: 3\n2\n1\n
COMMAND: h
HOLD:    3\n2\n1\n
COMMAND: $ p
END-OF-CYCLE:
INPUT:   'd1' line 4
PATTERN: 4
COMMAND: G
PATTERN: 4\n3\n2\n1\n
COMMAND: h
HOLD:    4\n3\n2\n1\n
COMMAND: $ p
4
3
2
1

END-OF-CYCLE:
windows@Tonus:~/shellScript/sed$ sed -n '1!G;h;$p' d1
4
3
2
1
windows@Tonus:~/shellScript/sed$ 

12,改变流:通常,sed编辑器会从命令列表的顶部开始,一直执行到命令列表的结尾,然后读取新行再执行命令列表,循环进行直至结束( D 命令是个例外,它会强制sed编辑器返回到命令列表的顶部重新执行命令列表而不读取新的行)。sed编辑器提供了一些方法(2个)来改变命令脚本的执行流程,其结果与结构化编程类似。

12.1,分支:分支命令b的格式如下:·[address]b [label]· // 意思是 if (是address指定的区间){直接跳转至label处,类似goto语句。}。label不指定则是直接跳转至命令结尾,即跳过后续所有命令。用b命令的地方有时也可以使用排除命令(!)完成相同的功能,但是使用b命令更加清晰明了。

windows@Tonus:~/shellScript/sed$ cat d3
tong
huang tong
huang
good
bad
ugly
windows@Tonus:~/shellScript/sed$ sed -n 'p;1,2b;p' d3
tong
huang tong
huang
huang
good
good
bad
bad
ugly
ugly
windows@Tonus:~/shellScript/sed$ sed -n '1,2!p;p' d3
tong
huang tong
huang
huang
good
good
bad
bad
ugly
ugly
windows@Tonus:~/shellScript/sed$ 
View Code

要是不想直接跳到脚本的结尾,可以为分支命令定义一个要跳转到的标签。标签以冒号开始最多可以是7个字符长度。

windows@Tonus:~/shellScript/sed$ cat d1
1
2
3
4
windows@Tonus:~/shellScript/sed$ sed -n '1,2b mylabel; p; :mylabel; p' d1 //第一个标签无冒号,第二个有冒号
1
2
3
3
4
4
windows@Tonus:~/shellScript/sed$ //1,2两行打印一次,其他行打印2次。

还可以跳转到脚本中靠前面的标签上,这样就达到了循环的效果 666。

windows@Tonus:~/shellScript/sed$ cat d2
This, is, a, test, to, remove, commas.
windows@Tonus:~/shellScript/sed$ sed -n 's/,//1p' d2 //1表示只替换第一个逗号
This is, a, test, to, remove, commas.
windows@Tonus:~/shellScript/sed$ sed -n ':start s/,//1p;b start' d2 //此命令脚本永远不会结束,无穷循环,不停地查找逗号,直到使用Ctrl+C组合键发送一个信号手动停止这个脚本
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
^C
windows@Tonus:~/shellScript/sed$ sed -n ':start s/,//1p; /,/b start' d2 //现在分支命令只会在行中有逗号的情况下跳转。在最后一个逗号被删除后,分支命令不会再执行,sed正常结束
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.,,
windows@Tonus:~/shellScript/sed$ 

12.2,(替换结果)测试:类似于分支命令,测试( test )命令( t )也可以用来改变sed编辑器脚本的执行流程。测试命令会根据替换命令的结果跳转到某个标签,而不是根据地址进行跳转。t命令前面的替换命令(可以有多个替换命令)只要有(一个)替换成功的就会跳转,t命令貌似只是针对替换命令的结果作判断。·[address]t [label]·

windows@Tonus:~/shellScript/sed$ cat d2
This, is, a, test, to, remove, commas.
windows@Tonus:~/shellScript/sed$ sed -n ':start; s/,//p; t start' d2 //默认s命令只替换第一个,等价于s/,//1p
This is, a, test, to, remove, commas.
This is a, test, to, remove, commas.
This is a test, to, remove, commas.
This is a test to, remove, commas.
This is a test to remove, commas.
This is a test to remove commas.
windows@Tonus:~/shellScript/sed$ sed -n ':start; s/,//p; t 3start' d2

13,模式替换:

1. & 符号:sed中 & 符号可以用来代表替换命令中的匹配的模式。不管模式匹配的是什么样的文本,你都可以在替代模式中使用 & 符号来使用这段文本。

  $ echo "The cat sleeps in his hat." | sed 's/.at/"&"/g'
  The "cat" sleeps in his "hat".  
  $

2,子模式替换:sed编辑器用圆括号来定义替换模式中的子模式。你可以在替代模式中使用特殊字符来引用每个子模式。替代字符由反斜线和数字组成。数字表明子模式的位置。sed编辑器会给第一个子模式分配字符 \1 ,给第二个子模式分配字符 \2 ,依此类推。当在替换命令中使用圆括号时,必须用转义字符将它们标示为分组字符而不是普通的圆括号。这跟转义其他特殊字符正好相反。

  $ echo "That furry cat is pretty" | sed 's/furry \(.at\)/\1/'
  That cat is pretty
  $

※,在脚本中使用sed

1, 在shell脚本中,可以将普通的shell变量及参数和sed编辑器命令脚本一起使用

windows@Tonus:~/shellScript/sed$ cat reverse.sh 
#!/bin/bash
sed -n '1!G;h;$p' $1
windows@Tonus:~/shellScript/sed$ bash reverse.sh data.txt
4
3
2
1
windows@Tonus:~/shellScript/sed$ 

2,重定向sed的输出:默认情况下,sed编辑器将会将命令列表的执行结果输出到STDOUT上。你可以在shell脚本中使用各种标准方法对sed编辑器的输出进行重定向。例如:

  • 使用$()将sed编辑器的输出重定向到一个变量中,以备后用。
    windows@Tonus:~/shellScript/sed$ cat reverse.sh 
    #!/bin/bash
    result=$(sed -n '1!G;h;$p' $1) #换行符消失了
    echo  $result
    sed -n '1!G;h;$p' $1
    windows@Tonus:~/shellScript/sed$ bash reverse.sh d1
    4 3 2 1
    4
    3
    2
    1
    windows@Tonus:~/shellScript/sed$ 
  • 其他各种重定向技术

※,创建sed使用工具

1,加倍行间距,即添加空行...

  • ·sed -n 'G' data.txt· // 每一行后添加一个空行
  • ·sed -n $!G' data.txt· //除最后一行,其他每一行后添加一个空行
  • `sed -n '/^$/d;$!G' data.txt` // 删除原有的空行,然后在除最后一行外的每行后添加一个空行。注意n命令删除空行后这一次循环就结束了,后面的命令不会执行,继续读取下一行执行下一个循环。--debug可以清楚的看出来。

2,给文件中的行添加编号:

  • ·sed '=' data.txt· // =命令可以打印行号,但是行号在每一行的上面,很难看。
  • ·sed '=' data.txt | sed 'N; s/\n/ /'· 将第一个sed的输出通过管道输出给另一个sed编辑器,这样行号就和文本行在一行了。

另外bash shell中有些命令也能添加行号,但它们会另外加入一些东西(有可能是不需要的间隔)。

$ nl data2.txt  // nl是number line的缩写,l是小L.
1 This is the header line.
2 This is the first data line.
3 This is the second data line.
4 This is the last line.
$
$ cat -n data2.txt
1 This is the header line.
2 This is the first data line.
3 This is the second data line.
4 This is the last line.
$

3,打印末尾若干行:

  • ·sed -n '$p' data.txt· //打印最后一行,很easy
  • ·sed ':start; $q; N; 4,$D; b start' data.txt· //当第四行被读取工作空间时,第一行被删除,第五行被读入时,第二行也被删除。亦即保留了最后三行。q命令是退出sed。此命令的详细解释如下:

    这个脚本会首先检查这行是不是数据流中最后一行。如果是,退出( quit )命令会停止循环。 N 命令会将下一行附加到模式空间中当前行之后。如果当前行在第3行后面,4,$D 命令会删除模式空间中的第一行。这就会在模式空间中创建出滑动窗口效果。因此,这个sed程序脚本只会显示出data7.txt文件最后3行。

4,删除不需要的空白行:删除数据流中的所有空白行很容易,但要选择性地删除空白行则需要一点创造力。

4.1, 删除连续空白行:删除连续空白行的关键在于创建包含一个非空白行和一个空白行的地址区间。如果sed编辑器遇到了这个区间,它不会删除行。但对于不匹配这个区间的行(两个或更多的空白行),它会删除这些行。这可以用地址区间中的文本模式完成(地址区间也可以使用文本匹配模式指定!!!!),即: ·/./,/^$/·。区间的开始地址是 /./,会匹配任何含有只要一个字符的行,区间的结束地址/^$/会匹配空白行,开始和结束区间之间用逗号隔开。

  • ·/./,/^ $/!d· //在这个区间内的行不会被删除,其余行(即连续空白行)会被删除。无论文件的数据行之间出现了多少空白行,在输出中只会在行间保留一个空白行。

4.2,删除开头的空白行:

  • ·/./,$!d· // 文本模式和行号混用指定区间!!!这个区间从含有字符的行开始,一直到数据流结束。在这个区间内的任何行都不会从输出中删除。这意味着含有字符的第一行之前的任何行都会删除。

5,删除HTML标签:假如一段html: <div>hello world</div>,目的是即删除 <div>, </div>,但是不能删除div中间的文本。

  • ·sed -n 's/<.*>//g' data.txt· //这会将div中间的文本也删除掉,不是我们想要的结果。
  • ·sed -n 's/<[^>]*>//g' data.txt· //

卍,gawk专题

※,awk 

1. 权威工具书 《The_AWK_Programming_Language》

2. awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理。数据可以来自标准输入、一个或多个文件,或其它命令的输出(即管道)。它支持用户自定义函数和 动态正则表达式等先进功能,是linux/unix下的一个强大编程工具。它在命令行中使用,但更多是作为脚本来使用。

awk的处理文本和数据的方式是这 样的,它逐行扫描文件,从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。如果没有指定处理动作,则把匹配的行显示到标准输出 (屏幕),即默认处理动作是print;如果没有指定模式,则所有被操作所指定的行都被处理,即默认指定模式是全部。awk分别代表其作者姓氏的第一个字母。因为它的作者是三个人,分别是Alfred Aho、Brian Kernighan、Peter Weinberger。gawk是awk的GNU版本,它提供了Bell实验室和GNU的一些扩展。

像shell一样,awk也有好几种,常见的如awk、nawk、mawk、gawk,其中

awk:最初在1 9 7 7年完成,1 9 8 5年发表了一个新版本的awk,它的功能比旧版本增强了不少,awk 能够用很短的程序对文档里的资料做修改、比较、提取、打印等处理,如果使用C 或 Pascal 等语言编写程序完成上述的任务会十分不方便而且很花费时间,所写的程序也会很大;

nawk: 在 20 世纪 80 年代中期,对 awk语言进行了更新,并不同程度地使用一种称为 nawk(new awk) 的增强版本对其进行了替换。许多系统中仍然存在着旧的awk 解释器,但通常将其安装为 oawk (old awk) 命令,而 nawk 解释器则安装为主要的 awk 命令,也可以使用 nawk 命令。Dr. Kernighan 仍然在对 nawk 进行维护,与 gawk 一样,它也是开放源代码的,并且可以免费获得;

mawk:mawk 是 awk 编程语言的解释器。awk语言在多媒体数据文件以及文本的检索和处理,算法的原型设计和试验都有广泛的使用。mawk带给awk新的概念,它实现了在《The AWK Programming Language》(Aho, Kernighan and Weinberger, The AWK Programming Language, Addison-Wesley Publishing, 1988.被认为是 AWK 手册。)中定义的 awk语言。mawk遵循 POSIX 1003.2 (草案 11.3)定义的 AWK 语言,包含了一些没有在AWK 手册中提到的特色,同时 mawk 提供一小部分扩展,另外据说mawk是实现最快的awk;

gawk: 是 GNU Project 的awk解释器的开放源代码实现。尽管早期的 GAWK 发行版是旧的 AWK 的替代程序,但不断地对其进行了更新,以包含 NAWK 的特性;

目前,大家都比较倾向于使用awk和gawk,本文中要介绍的awk是以GUN的gawk为例的。Ubuntu系统中的各种awk的选项设置,可以通过sudo update-alternatives --config awk来完成,实际上你通过手动修改软链接也能实现。Debian最小化安装的时候awk的链接是指向mawk的。

※,参考文章

※,gawk是比sed编辑器更高级的工具。gawk程序是一个来自GNU组织的工具,它模仿并扩展了Unix中awk程序的功能。gawk程序内建了编程语言,可用来编写处理数据的脚本。你可以用gawk程序从大型数据文件中提取数据元素,并将它们按照需要的格式输出。这非常便于处理大型日志文件以及从数据文件中生成定制报表。在gawk编程语言中,你可以做下面的事情:

  •  定义变量来保存数据;
  •  使用算术和字符串操作符来处理数据;
  •  使用结构化编程概念(比如 if-then 语句和循环)来为数据处理增加处理逻辑;
  •  通过提取数据文件中的数据元素,将其重新排列或格式化,生成格式化报告。

,gawk 命令格式

  • `gawk [options] program file(s)` // options是参数选项,可以使用--help查看所有选项。program是用于处理数据的程序脚本。file指定需要处理的文件。
  • `gawk [options] -f scriptfile file(s)`

★1,options(选项)

  • -F fs 指定行中划分数据字段的字段分隔符
  • -f scriptfile 从指定的文件中读取程序
  • -v var=value 这里定义的变量和在 gawk 脚本中定义的变量不是一回事,这里定义的变量相对gawk是外部变量,即-v是传外部参数(非来自stdin)的方法。有两种格式:
      • ·gawk -v var1=value1 -v var2=value2 '{print $0var1 var2}' myfile·// 在BEGIN 块之前通过-v选项指定变量,一个变量需要跟着一个-v。
      • `gawk '{print var1 var2, $0}' var1=value1 var2=value2 myfile` // 在END块之后,直接定义变量,变量之间用空格分隔作为awk的命令行参数。
      • ·ls -1 | gawk '{print var1$0}' var1=$(pwd)"/"·//打印文件的全路径名称。-1是数字1
  • -mf N 指定要处理的数据文件中的最大字段数
  • -mr N 指定数据文件中的最大数据行数
  • -W keyword 指定gawk的兼容模式或警告等级

★2, program(脚本):gawk同sed一样也是一行一行地处理数据。

一个awk脚本通常由:BEGIN语句块、能够使用模式匹配的通用语句块、END语句块3部分组成,这三个部分都是可选的。任意一个部分都可以不出现在脚本中。gawk脚本基本结构为:·gawk 'BEGIN{ print "start" } pattern{ actions} END{ print "end" }' file·。由于 gawk 命令行假定脚本是单个文本字符串,你必须将脚本放到单引号(注意只能是单引号,不能是双引号)中。action放置在一对花括号中,pattern不能放在花括号中.

★2.0,gawk的工作原理

 'BEGIN{ commands } pattern{ commands } END{ commands }';例子:·awk 'BEGIN{ i=0 } { i++ } END{ print i }' filename·

  • 第一步:执行BEGIN{ commands }语句块中的语句;BEGIN语句块* 在awk开始从输入流中读取行 之前 被执行,这是一个可选的语句块,比如变量初始化、打印输出表格的表头等语句通常可以写在BEGIN语句块中。
  • 第二步:从文件或标准输入(stdin)读取一行,然后执行pattern{ commands }语句块,它逐行扫描文件,从第一行到最后一行重复这个过程,直到文件全部被读取完毕。pattern语句块* 中的通用命令是最重要的部分,它也是可选的。如果没有提供pattern语句块,则默认执行{ print },即打印每一个读取到的行,awk读取的每一行都会执行该语句块。
  • 第三步:当读至输入流末尾时,执行END{ commands }语句块。END语句块* 在awk从输入流中读取完所有的行 之后 即被执行,比如打印所有行的分析结果这类信息汇总都是在END语句块中完成,它也是一个可选语句块。

★2.1,模式(pattern): awk使用的正则是EREs

pattern 匹配, 结果就只有两种情况, 要么匹配(即为真),就执行后面的 action, 要么不匹配(即为假), 就不会执行后面的action。可以使用 &&,||, !运算符组合这些pattern。模式pattern可以是以下任意一个:

  • 空pattern。即没有写pattern,那么表示匹配输入记录永远成功, 也就是说永远为真, 这是规定. action永远都会执行。
    •  
    • 我们知道, awk 的rule 就是由一系列 pattern 和 action 构成, 这里所谓的空 pattern, 也可以说是省略了 pattern, 那么 action 可不可以省略呢?
    • 事实上, action也是可以省略的, 但如果省略了的话,它就会有一个默认的action行为, 即 { print $0 } !
    • 很多人新手经常搞不懂一下的代码:
    • `awk '{a=1}1' myfile`
    • 经常会碰到有人问, 这个代码后面的1是干嘛的? 其实, 我们拆分下 awk 的 rule 就明白了.
    • awk '{a=1}1' 这个语句包含了两条规则:
    • awk '
    •         [空pattern] {a=1}    #第一条规则
    •         1 [{省略的action}]   #第二条规则
    • '
    • 第一条rule: {a=1} , 这条规则这里有一个{}大括号包裹着, 表示这是一个 action, 但是省略了pattern, 即空pattern, 上面说了, 空pattern是永远匹配为真的, 
    • 所以{a=1}这个action会针对每条输入记录对执行,只是我们看不到它的具体表现而已.
    •  
    • 第二条rule: 1, 这条规则仅仅只有一个1字, 没有被{}大括号包裹着, 
    • 所以这个1是一个pattern, 省略了{action}, 而这个数字 1 , 实际上就是一个常量表达式pattern, 因它为非0的数字,所以pattern匹配成功,为真,就执行action,
    • 因为这里省略了{action},就触发默认的行为,而默认的action行为是print $0,即打印这条记录.
    • 再如: awk '1;1' ,省略了两个action, 所以这条实际上就是两个 {print $0}{print $0}.
    • 所以, 凡是action只是要输出这条记录的, 通通都可以省略这个 action.
  • 常量表达式:一个数字, 或者一个字符串, 都可以做为一个常量。凡是非0的数字, 就表示pattern匹配成功, 也就是pattern为真. 否则表示匹配失败, 为假.凡是非空的字符串, 就表示pattern匹配成功, 也就是pattern为真. 否则表示匹配失败, 为假. 注意: 字符串是由引号引起来的! 比如数字 0 与 字符串 "0" 不是一样的. 数字0为假, 字符串"0"为真(不为空).
    • ·gawk '1 {print $0}' myfile· // 匹配模式为1,即为真,效果是匹配每一行,打印每一行。
    • ·gawk '0 {print $0}' myfile· // 匹配模式为0,即为假,效果是不匹配任何一行,什么都不打印。
    • ·gawk '"tong" {print $0}' myfile· //打印每一行。简写:·gawk '"tong"' myfile·
    • ·gawk '""{print $0}' myfile· //什么都不打印。简写:·gawk '""' myfile·
    • `gawk 'a=1' myfile` // 这里a是变量,值为1,匹配模式为真,打印每一行。
    • `gawk 'a=0' myfile`// 匹配模式为假,什么都不打印。
    • `gawk 'x-1 {print (x-1)}' myfile`// x 为未定义的变量, 做数学运算, 结果为 -1(可以从打印的结果看出)。因 -1 也是非0的数字常量, pattern 匹配成功, 为真, print $0.
    • `gawk 'xxoo' myfile` // xxoo为变量,未赋值,什么都不打印。
  • 正则表达式:awk工具使用的正则是EREs。
    • `gawk '/tong/ {print $0} myfile'` // 输出myfile中含有tong的行。不含有tong的行则不输出。
    • ·gawk '/tong/' myfile· // 如果没有提供action,则默认执行 { print }
  • 关系表达式:使用运算符进行操作。关系运算符”~”与关系运算符”!~”都需要配合”正则模式”使用。~比单纯的正则表达式模式更灵活一些。
    关系运算符 含义 用法示例
    < 小于 x < y
    <= 小于等于 x <= y
    == 等于 x == y
    != 不等于 x != y
    >= 大于等于 x >= y
    > 大于 x > y
    ~ 与对应的正则匹配则为真 x ~ /正则/
    !~ 与对应的正则不匹配则为真 x !~ /正则/

    • ·gawk 'NR%2 {print $0}' myfile· // NR为gawk内置变量,表示行号。匹配奇数行,只打印奇数行。简写·gawk 'NR%2' myfile·
    • ·gawk '!(NR%2) {print $0}' myfile· // 匹配偶数行,只打印偶数行。简写·gawk '!(NR%2)' myfile·
    • `gawk '$2~/^tong$/ {print $0}' myfile` //第二个字段列匹配/^tong$/时,即第二个字段是tong时,打印此行。
    • `ps -ef| awk '$0 ~ /[pP][iI][Dd]/'`  // 查找ps输出中的含有pid(不区分大小写)的行。初始目的是只看输出的第一行行头,因为输出太多看不到行头,这个目的可以用head -1实现...也可以用sed实现。
      • [pP]表示不区分大小写,Linux的正则表达式中没有类似/regex/i 中的 i 标记。
  • 特殊的pattern:BEGIN语句块、pattern语句块、END语句块:参见awk的工作原理。BEGIN 在读入文件之前匹配成功, 即为在读入文件之前这条 rule 就已经执行了.END 在处理完文件之后才匹配成功, 即在处理完文件之后才会执行这条 rule.
    • ·gawk 'BEGIN { n=5 } NR==n { print $0 } END { print $0 }' myfile· //输出第五条记录和最后一条记录。
      • 首先, 它包含三条规则
      • 1. BEGIN { n=5 }  ,BEGIN 为 特殊pattern, {n=5} 为 action
      • 2. NR==n { print $0 }, NR==n 是一个比较表达式pattern, { print $0 } 为action.
      • 3. END { print $0 }, END 也是一个特殊的pattern.
      • BEGIN 模式一般读入文件之前常用的是做一些相关的定义操作, 这里设定变量n=5. 
        然后gawk开始处理记录, 当 NR==n 时, 即处理到我们定义的那条记录时(第5条记录时), 执行 print $0, 输出这条记录.
        最后是END模式, gawk处理完文件里, END模式匹配成功,执行print $0, 即输出最后一条记录.
        所以, 结果是输出第5条记录和最后一条记录.
  • 模式范围 beginpattern, endpattern:这个和sed的模式范围匹配类似,即flip flop模式。

    这个模式范围, 是由两个 pattern 组成, 每个 pattern可以是任意的非特殊类型(非BEGIN/END模式类型)的pattern类型(可以是正则,比较,常量等pattern).
    这个模式范围匹配的规则有点儿特殊, 这里以一个"开闸放水"的例子做为一个类比.
    1. 首先以第一个pattern匹配输入记录, 如果第一个pattern匹配成功,就"打开放水的开关开始放水",[开关的状态: 开], 即会立即执行后面的action.此时不管第二个pattern是否匹配.
    2. 接着再匹配第二pattern, 如果第二pattern匹配失败,开关的状态不变,即还是会执行action.
    3. 接着继续以第二个pattern匹配下一条输入记录,直到第二个pattern匹配成功. 就"关闭放水的开关停止放水",[开关的状态: 关], 模式范围匹配结束.
    4. 以1,2,3步骤进入下一轮模式范围匹配
    如:

    1 $ awk 'NR==4, /555-3430/ { print $0}' myfile
    2 Bill       555-1675  bill.drowning@hotmail.com        A
    3 Broderick  555-0542  broderick.aliquotiens@yahoo.com  R
    4 Camilla    555-2912  camilla.infusarum@skynet.be      R
    5 Fabius     555-1234  fabius.undevicesimus@ucb.edu     F
    6 Julie      555-6699  julie.perscrutabor@skeeve.com    F
    7 Martin     555-6480  martin.codicibus@hotmail.com     A
    8 Samuel     555-3430  samuel.lanceolis@shu.edu         A

    先执行 NR==4 这个 pattern 匹配, 当第四条记录匹配成功时, 放水开关打开, 开始放水了, 即开始执行action , 执行 print $0 , 输出第四条记录.
    接着使用 /555-3430/ 当前记录, 不成功.
    第5条记录继续执行 action.继续以第二个pattern匹配这条记录..不成功..,开关是开的
    第6条记录继续执行 action.继续以第二个pattern匹配这条记录..不成功..,开关是开的
    ...
    ..
    直到匹配 /555-3430/, 第二个pattern匹配成功. 开关关闭. 模式范围匹配结束..
    开始下一轮模式范围匹配..
    以上就是各种 pattern 的简要解说了.. 至于 action, 也没啥可说的, action一般做具体的事情.那些个流程控制语句 if/for/while 等一般都属于action了, 不能做为 pattern了.

★2.2,操作(action)

操作由一个或多个命令、函数、表达式组成,之间由换行符或分号隔开,并位于大括号内,主要部分是:

  • 变量或数组赋值
  • 输出命令
  • 内置函数
  • 控制流语句

※,gawk的一些示例

gawk会自动给一行中的每个数据元素分配一个变量。默认情况下,gawk会将如下变量分配给它在文本行中发现的数据字段: $0 代表整个文本行; $1 代表文本行中的第1个数据字段;以此类推。在文本行中,每个数据字段都是通过字段分隔符划分的。gawk在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。gawk中默认的字段分隔符是任意的空白字符(例如空格或制表符)。

  • ·gawk  -F: '{print $1}' /etc/passwd·  // -F 选项指定其他字段分隔符,此例指定冒号为分隔符
  • `echo "My name is Rich" | gawk '{$4="Christine"; print $0}'` //要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可。gawk程序在输出中已经将原文本中的第四个数据字段替换成了新值
  • `gawk -f <programFile> data.txt` // 用脚本文件pgrgramFile中的指令处理data.txt。 <programFile>文件中的脚本用大括号包起来,多个命令可以写在一行用分号隔开,也可以写在多行(此时无需分号)
  • `gawk 'BEGIN {program1} {program2} END {program3}' data.txt` // BEGIN {p1},在处理数据前先执行p1程序,注意p1中并没有$0,$1这些变量,因为还没开始处理数据。处理完数据后再执行p3程序,program3中也没有$0,$1这些变量,因为最后一行之后没有数据了。
  • `gawk '/test/{print $0}' data.txt` // 模式匹配。只处理data.txt中含有test的行,打印此行。
  • 左对齐输出:·iptables -t filter -vL INPUT|awk '{printf "%-10s%-10s%-30s%-10s%-10s%-10s%-10s%-10s%-10s%-10s%-10s\n",$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11}'· //#%-30s表示输出字符串,宽度30位,左对齐.%-10s左对齐,宽度10.两个百分号之间可以没有空格.使用\n对每一行的输出加上换行符.。

示例:从一个简单的数据文件中创建一份完整的报告。

$ cat script4.gawk
BEGIN {
print "The latest list of users and shells"
print " UserID \t Shell"
print "-------- \t -------"
FS=":" #// 定义了一个叫作 FS 的特殊变量(FS实际是awk的内建变量)。这是定义字段分隔符的另一种方法。这样你就不用依靠脚本用户在命令行选项中定义字段分隔符了。
}
{
print $1 " \t " $7
}
END {
print "This concludes the listing"
}
$
#脚本输出如下:
$ gawk -f script4.gawk /etc/passwd
The latest list of users and shells
UserID Shell
-------- -------
root /bin/bash
bin /sbin/nologin
daemon /sbin/nologin
[...]
Christine /bin/bash
mysql /bin/bash
Samantha /bin/bash
Timothy /bin/bash
This concludes the listing
$

※, gawk进阶:gawk是一门功能丰富的编程语言,你可以通过它所提供的各种特性来编写高级程序处理数据。

1,使用变量:gawk编程语言支持两种不同类型的变量:  内建变量,  自定义变量

1.1,内建变量:gawk中有如下内置变量,

    • 数据字段变量,即$0代表整行文本,$1代表第一个字段,以此类推
    • FIELDWIDTHS: 由空格分隔的一列数字,定义了每个数据字段确切宽度。一旦设置了 FIELDWIDTH 变量,gawk就会忽略 FS 变量,并根据提供的字段宽度来计算字段。
      • $ cat data1b
        1005.3247596.37
        115-2.349194.00
        05810.1298100.1
        $ gawk 'BEGIN{FIELDWIDTHS="3 5 2 5"}{print $1,$2,$3,$4}' data1b
        100 5.324 75 96.37
        115 -2.34 91 94.00
        058 10.12 98 100.1
    • FS:输入字段分隔符:(除了定义FS变量指定分隔符外[如FS=":"],-F参数也能指定分隔符)。注意定义FS一般放在BEGIN中,如果放在数据处理程序中,则处理第一行数据时使用的还是默认的FS值,第二行之后才是自定义的FS值!!!
    • OFS: 输出字段分隔符。gawk将 OFS 设成一个空格,print 命令会自动将 OFS 变量的值放置在输出中的每个字段间。
      $ gawk 'BEGIN{FS=","; OFS="<-->"} {print $1,$2,$3}' data1
      data11<-->data12<-->data13
      data21<-->data22<-->data23
      data31<-->data32<-->data33
      $
    • 输入记录分隔符:RS、输出记录分隔符:ORS。变量 RS 和 ORS 定义了gawk程序如何处理数据流中的记录。默认情况下,gawk将 RS 和 ORS 设为换行符。默认的 RS 值表明,输入数据流中的每行新文本就是一条新纪录。处理多行数据的记录时,可以把FS 变量设置成换行符,把 RS 变量设置成空字符串,然后在数据记录间留一个空白行,gawk会把每个空白行当作一个记录分隔符。
      $ cat data2
      Riley Mullen
      123 Main Street
      Chicago, IL 60601
      (312)555-1234
      
      Frank Williams
      456 Oak Street
      Indianapolis, IN 46201
      (317)555-9876
      
      Haley Snell
      4231 Elm Street
      Detroit, MI 48201
      (313)555-4938
      $ gawk 'BEGIN{FS="\n"; RS=""} {print $1,$4}' data2
      Riley Mullen (312)555-1234
      Frank Williams (317)555-9876
      Haley Snell (313)555-4938
      $
      太好了,现在gawk把文件中的每行都当成一个字段,把空白行当作记录分隔符。

      FS, RS的一个实际应用场景:年前现网业务巡检,想从错误业务日志中提取出来特定错误的一段报错信息(如NullPointerException),开始想用grep -P正则匹配来实现,太难了!awk则是使用FS、RS很轻松实现:

      ·cat /tmp/error.log.2022-01-05.log |awk 'BEGIN {RS="2022-01-0";FS="\n"} /NullPointerException/ {print RS $0}'· //字段分隔符为换行符,行分隔符为"2022-01-0".

      在shell脚本里需要在awk脚本中使用一些shell变量,此时注意格式(单引号、双引号),·cat $errLog | awk 'BEGIN{RS="2022-01-0";FS="\n"} /'$excpt'/{print "'$errLog'" RS $0}'·

    • NF:数据文件中的字段总数。
      NF变量可以让你在不知道具体位置的情况下指定记录中的最后一个数据字段。如:
      打印第一个车字段和最后一个字段和倒数第二个字段(注意是$()小括号,awk中的$()和shell中的$()是两个独立的不同的东西)
      $ gawk 'BEGIN{FS=":"; OFS=":"} {print $1,$NF,$(NF-1)}' /etc/passwd
      rich:/bin/bash
      testy:/bin/csh
      mark:/bin/bash
      dan:/bin/bash
      mike:/bin/bash
      test:/bin/bash
      $
      NF变量含有数据文件中最后一个数据字段的数字值。可以在它前面加个美元符将其用作字段变量。
    • FNR: 当前数据文件中的已处理的记录数.
    • NR: gawk已处理的输入记录数
      FNR 和 NR 变量虽然类似,但又略有不同。 FNR 变量含有当前数据文件中已处理过的记录数,NR 变量则含有已处理过的记录总数。区别如下例所示
      $ gawk 'BEGIN{FS=","}{print $1,"FNR="FNR}' data1 data1
      data11 FNR=1
      data21 FNR=2
      data31 FNR=3
      data11 FNR=1
      data21 FNR=2
      data31 FNR=3
      $
      在这个例子中,gawk程序的命令行定义了两个输入文件(两次指定的是同样的输入文件)。
      这个脚本会打印第一个数据字段的值和 FNR 变量的当前值。注意,当gawk程序处理第二个数据文
      件时, FNR 值被设回了 1 。
      现在,让我们加上 NR 变量看看会输出什么。
      $ gawk '
      > BEGIN {FS=","}
      > {print $1,"FNR="FNR,"NR="NR}
      > END{print "There were",NR,"records processed"}' data1 data1
      data11 FNR=1 NR=1
      data21 FNR=2 NR=2
      data31 FNR=3 NR=3
      data11 FNR=1 NR=4
      data21 FNR=2 NR=5
      data31 FNR=3 NR=6
      There were 6 records processed
      $
      FNR 变量的值在 gawk 处理第二个数据文件时被重置了,而 NR 变量则在处理第二个数据文件时继续计数。结果就是:如果只使用一个数据文件作为输入,  
      FNR 和 NR 的值是相同的;如果使用多个数据文件作为输入,  FNR 的值会在处理每个数据文件时被重置,而 NR 的值则会继续计数直到处理完所有的数据文件。
    • ARGC: 当前命令行参数个数
    • ARGV: 包含命令行参数的数组
    •  
    • ARGIND当前文件在 ARGV 中的位置
ARGC 和 ARGV 变量允许从shell中获得命令行参数的总数以及它们的值。但这可能有点麻烦,因为gawk并不会将程序脚本当成命令行参数的一部分。
$ gawk 'BEGIN{print ARGC,ARGV[1]}' data1
2 data1
$
ARGC 变量表明命令行上有两个参数。这包括 gawk 命令和 data1 参数(记住,程序脚本并不算参数)。 ARGV 数组从索引 0 开始,代表的是命令,即gawk。第一个数组值是 gawk 命令后的第一个命令行参数。
  • ENVIRON: 当前shell环境变量及其值组成的关联数组.
    ENVIRON它使用关联数组来提取shell环境变量。关联数组用文本作为数组的索引值,而不是数值。
    数组索引中的文本是shell环境变量名,而数组的值则是shell环境变量的值。下面有个例子。
    $ gawk '
    > BEGIN{
    > print ENVIRON["HOME"]
    > print ENVIRON["PATH"]
    > }'
    /home/rich
    /usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin
    $
  •  
  • CONVFMT 数字的转换格式(参见 printf 语句),默认值为 %.6 g
  •  
  • ERRNO 当读取或关闭输入文件发生错误时的系统错误号
  •  
  • FILENAME 用作gawk输入数据的数据文件的文件名
  •  
  • IGNORECASE 设成非零值时,忽略 gawk 命令中出现的字符串的字符大小写 
  •  
  • OFMT 数字的输出格式,默认值为 %.6 g
  •  
  • RLENGTH 由 match 函数所匹配的子字符串的长度
  • RSTART 由 match 函数所匹配的子字符串的起始位置

1.2, 自定义变量:变量名可以是任意数目的字母、数字和下划线,但不能以数字开头。重要的是,要记住 gawk 变量名区分大小写。自定义变量可以在gawk的脚本的任何地方定义,即可以在BEGIN块中,可以在Pattern中,可以在action中,可以在END块中

  • ·gawk 'a=2 {b=4; print $0,a,b}' myfile· //打印每一行,每一行后面接着a和b的值(即2和4)。匹配模式中定义了变量a,action中定义了变量b。

1.2.1,在脚本中给变量赋值: 在gawk程序中给变量赋值跟在shell脚本中赋值类似,都用赋值语句。

$ gawk 'BEGIN{x=4; x= x * 2 + 3; print x}'
11
$

1.2.2, 在命令行上给变量赋值:也可以用 gawk 命令行来给程序中的变量赋值。这允许你在正常的代码之外赋值,即时改变变量的值。

$ cat script1
BEGIN{FS=","}
{print $n}
$ gawk -f script1 n=2 data1
data12
data22
data32
$ gawk -f script1 n=3 data1
data13
data23
data33
$
使用命令行参数来定义变量值会有一个问题。在你设置了变量后,这个值在代码的 BEGIN 部分不可用。可以用 -v 命令行参数来解决这个问题。
它允许你在 BEGIN 代码之前设定变量。在命令行上,-v 命令行参数必须放在脚本代码之前。
$ cat script2
BEGIN{print "The starting value is",n; FS=","}
{print $n}
$ gawk -f script2 n=3 data1
The starting value is
data13
data23
data33
$
$ gawk -v n=3 -f script2 data1
The starting value is 3
data13
data23
data33
$

2, 使用数组:

2.1,var[index] = element

$ gawk 'BEGIN{
> capital["Illinois"] = "Springfield"
> print capital["Illinois"]
> }'
Springfield
$

2.2,遍历数组:

如果要在gawk中遍历一个关联数组,可以用 for 语句的一种特殊形式。
for (var in array)
{
    statements
}
$ gawk 'BEGIN{
> var["a"] = 1
> var["g"] = 2
> var["m"] = 3
> var["u"] = 4
> for (test in var)
> {
> print "Index:",test," - Value:",var[test]
> }
> }'
Index: u - Value: 4
Index: m - Value: 3
Index: a - Value: 1
Index: g - Value: 2
$
注意:索引值不会按任何特定顺序返回!

2.3,删除数组变量: delete array[index]

3,使用模式:gawk程序支持多种类型的匹配模式来过滤数据记录,这一点跟sed编辑器大同小异。 BEGIN 和 END 关键字是用来在读取数据流之前或之后执行命令的特殊模式。类似地,你可以创建其他模式在数据流中出现匹配数据时执行一些命令。

3.1,正则表达式:awk可以扩展正则表达式(EREs)来选择程序脚本作用在数据流中的哪些行上。在使用正则表达式时,正则表达式必须出现在它要控制的程序脚本的左花括号前。

$ gawk 'BEGIN{FS=","} /11/{print $1}' data1
data11
$
正则表达式 /11/ 匹配了数据字段中含有字符串 11 的记录。gawk程序会用正则表达式对记录中所有的数据字段进行匹配,包括字段分隔符。

3.2,匹配操作符:匹配操作符(matching operator)允许将正则表达式限定在记录中的特定数据字段。匹配操作符是波浪线( ~ )。可以指定匹配操作符、数据字段变量以及要匹配的正则表达式。

·$1 ~ /^data/·
$1 变量代表记录中的第一个数据字段。这个表达式会过滤出第一个字段以文本 data 开头的所有记录。
这可是件强大的工具,gawk程序脚本中经常用它在数据文件中搜索特定的数据元素。
$ gawk -F: '$1 ~ /rich/{print $1,$NF}' /etc/passwd
rich /bin/bash
$
这个例子会在第一个数据字段中查找文本 rich 。如果在记录中找到了这个模式,它会打印该记录的第一个和最后一个数据字段值。
你也可以用 ! 符号来排除正则表达式的匹配。
·$1 !~ /expression/·
如果记录中没有找到匹配正则表达式的文本,程序脚本就会作用到记录数据。
$ gawk –F: '$1 !~ /rich/{print $1,$NF}' /etc/passwd
root /bin/bash
daemon /bin/sh
bin /bin/sh
sys /bin/sh
--- output truncated ---
$
在这个例子中,gawk程序脚本会打印/etc/passwd文件中与用户ID  rich 不匹配的用户ID和登
录shell。

3.3,数学表达式:除了正则表达式,你也可以在匹配模式中用数学表达式。这个功能在匹配数据字段中的数字值时非常方便。

举个例子,如果你想显示所有属于root用户组(组ID为 0 )的系统用户,可以用这个脚本。
$ gawk -F: '$4 == 0{print $1}' /etc/passwd
root
sync
shutdown
halt
operator
$
这段脚本会查看第四个数据字段含有值 0 的记录。在这个Linux系统中,有五个用户账户属于root用户组。
可以使用任何常见的数学比较表达式。
  x == y :值x等于y。
  x <= y :值x小于等于y。
  x < y :值x小于y。
  x >= y :值x大于等于y。
  x > y :值x大于y。
也可以对文本数据使用表达式,但必须小心。跟正则表达式不同,表达式必须完全匹配。数据必须跟模式严格匹配。
$ gawk -F, '$1 == "data"{print $1}' data1
$
$ gawk -F, '$1 == "data11"{print $1}' data1
data11
$
第一个测试没有匹配任何记录,因为第一个数据字段的值不在任何记录中。第二个测试用值data11 匹配了一条记录。

4, 结构化命令:和一般编程语言差不多。

4.1,if 语句

if (condition)
    statement1
也可以将它放在一行上,像这样:
if (condition) statement1
下面这个简单的例子演示了这种格式的。
$ cat data4
10
5
13
50
34
$ gawk '{if ($1 > 20) print $1}' data4
50
34
$
并不复杂。如果需要在 if 语句中执行多条语句,就必须用花括号将它们括起来。
$ gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> }
> }' data4
100
68
$
注意,不能弄混 if 语句的花括号和用来表示程序脚本开始和结束的花括号。gawk 的 if 语句也支持 else 子句,允许在 if 语句条件不成立的情况下执行一条或多条语句。
这里有个使用 else 子句的例子。
$ gawk '{
> if ($1 > 20)
> {
> x = $1 * 2
> print x
> } else
> {> x = $1 / 2
> print x
> }}' data4
5
2.5
6.5
100
68
$
可以在单行上使用 else 子句,但必须在 if 语句部分之后使用分号。
if (condition) statement1; else statement2

4.2, while, do-while, for语句

5,格式化打印:你可能已经注意到了 print 语句在gawk如何显示数据上并未提供多少控制。你能做的只是控制输出字段分隔符( OFS )。如果要创建详尽的报表,通常需要为数据选择特定的格式和位置。解决办法是使用格式化打印命令,叫作 printf 。如果你熟悉C语言编程的话,gawk中的printf 命令用法也是一样,允许指定具体如何显示数据的指令。下面是 printf 命令的格式:`printf "format string", var1, var2 . . .`

$ gawk 'BEGIN{FS="\n"; RS=""} {printf "%s %s\n", $1, $4}' data2
Riley Mullen (312)555-1234
Frank Williams (317)555-9876
Haley Snell (313)555-4938
$
你需要在 printf 命令的末尾手动添加换行符来生成新行。没添加的话, printf 命令会继续在同一行打印后续输出。

6,函数

6.1 内建函数:

gawk数学函数
函 数  描 述
atan2(x, y)
x/y的反正切,x和y以弧度为单位
cos(x)
x的余弦,x以弧度为单位
exp(x)
x的指数函数
int(x)
x的整数部分,取靠近零一侧的值
log(x)
x的自然对数
rand( )
比0大比1小的随机浮点值
sin(x)
x的正弦,x以弧度为单位
sqrt(x)
x的平方根
srand(x)
为计算随机数指定一个种子值

gawk字符串函数
函 数  描 述
asort(s [,d])
将数组s按数据元素值排序。索引值会被替换成表示新的排序顺序的连续数字。另外,
如果指定了d,则排序后的数组会存储在数组d中
asorti(s [,d])
将数组s按索引值排序。生成的数组会将索引值作为数据元素值,用连续数字索引来表
明排序顺序。另外如果指定了d,排序后的数组会存储在数组d中
gensub(r, s, h [, t])
查找变量$0或目标字符串t(如果提供了的话)来匹配正则表达式r。如果h是一个以g
或G开头的字符串,就用s替换掉匹配的文本。如果h是一个数字,它表示要替换掉第h
处r匹配的地方
gsub(r, s [,t])
查找变量$0或目标字符串t(如果提供了的话)来匹配正则表达式r。如果找到了,就
全部替换成字符串s
index(s, t)
返回字符串t在字符串s中的索引值,如果没找到的话返回 0
length([s])
返回字符串s的长度;如果没有指定的话,返回$0的长度
match(s, r [,a])
返回字符串s中正则表达式r出现位置的索引。如果指定了数组a,它会存储s中匹配正
则表达式的那部分
split(s, a [,r])
将s用 FS 字符或正则表达式r(如果指定了的话)分开放到数组a中。返回字段的总数
sprintf(format,
variables)
用提供的format和variables返回一个类似于printf输出的字符串
sub(r, s [,t])
在变量$0或目标字符串t中查找正则表达式r的匹配。如果找到了,就用字符串s替换
掉第一处匹配
substr(s, i [,n])
返回s中从索引值i开始的n个字符组成的子字符串。如果未提供n,则返回s剩下的部
分
tolower(s)
将s中的所有字符转换成小写
toupper(s)
将s中的所有字符转换成大写
gawk的时间函数
函 数  描 述
mktime(datespec)
将一个按YYYY MM DD HH MM SS [DST]格式指定的日期转换成时间戳值 ①
strftime(format
[,timestamp])
将当前时间的时间戳或timestamp(如果提供了的话)转化格式化日期(采用shell
函数date()的格式)
systime( )
返回当前时间的时间戳
$ gawk 'BEGIN{x=exp(100); print x}'
26881171418161356094253400435962903554686976
$ gawk 'BEGIN{x=exp(1000); print x}'
gawk: warning: exp argument 1000 is out of range
inf
$ gawk 'BEGIN{x= 10 * rand();print x}' 
9.24046


除了标准数学函数外,gawk还支持一些按位操作数据的函数。
  and(v1, v2) :执行值 v1 和 v2 的按位与运算。
  compl(val) :执行 val 的补运算。
  lshift(val, count) :将值 val 左移 count 位。
  or(v1, v2) :执行值 v1 和 v2 的按位或运算。
  rshift(val, count) :将值 val 右移 count 位。
  xor(v1, v2) :执行值 v1 和 v2 的按位异或运算。
位操作函数在处理数据中的二进制值时非常有用。

6.2,自定义函数:要定义自己的函数,必须用 function 关键字。和一般编程语言无异。

function myrand(limit)
{
  return int(limit * rand()) #int()是内建函数,类似JavaScript中的floor()函数
}
你可以将函数的返回值赋给gawk程序中的一个变量:
x = myrand(100)
这个变量包含函数的返回值。

在定义函数时,它必须出现在所有代码块之前(包括 BEGIN 代码块)。乍一看可能有点怪异,但它有助于将函数代码与gawk程序的其他部分分开。

$ gawk '
> function myprint()
> {
> printf "%-16s - %s\n", $1, $4
> }
> BEGIN{FS="\n"; RS=""}
> {
> myprint()
> }' data2
Riley Mullen - (312)555-1234
Frank Williams - (317)555-9876
Haley Snell - (313)555-4938
$
一旦定义了函数,你就能在程序的代码中随意使用

6.3,创建函数库:显而易见,每次使用函数都要重写一遍并不美妙。不过,gawk提供了一种途径来将多个函数放到一个库文件中,这样你就能在所有的gawk程序中使用了。

首先,你需要创建一个存储所有gawk函数的文件。
$ cat funclib
function myprint()
{
printf "%-16s - %s\n", $1, $4
}
function myrand(limit)
{
return int(limit * rand())
}
function printthird()
{
print $3
}
$
funclib文件含有三个函数定义。需要使用 -f 命令行参数来使用它们。很遗憾,不能将 -f 命令行参数和内联gawk脚本放到一起使用,不过可以在同一个命令行中使用多个 -f 参数。
因此,要使用库,只要创建一个含有你的gawk程序的文件,然后在命令行上同时指定库文件和程序文件就行了。$ cat script4
BEGIN{ FS="\n"; RS=""}
{
myprint()
}
$ gawk -f funclib -f script4 data2
Riley Mullen - (312)555-1234
Frank Williams - (317)555-9876
Haley Snell - (313)555-4938
$
你要做的是当需要使用库中定义的函数时,将funclib文件加到你的 gawk 命令行上就可以了。

7,实例:略




第20章:通配符与正则表达式

※,通配符

通配符   意义
* 匹配任意多个字符(包括零个或一个或多个)
? 匹配任意一个字符(只能匹配一个,不包含0个)
[characters]  匹配任意一个属于字符集中的字符
[!characters]  匹配任意一个不是字符集中的字符,正则中是[^characters]
[[:class:]]  匹配任意一个属于指定字符类中的字符,如[:alnum:] 匹配任意一个字母或数字

※,在Linux中有三种不同的正则表达式引擎:

  • POSIX基础正则表达式(basic regular expression, BRE)引擎
  • POSIX扩展正则表达式(extended regular expression, ERE)引擎
  • Perl兼容的正则表达式:Perl Compatible Regular Expressions,PCRE

大多数Linux工具都至少符合POSIX BRE引擎规范,能够识别该规范定义的所有模式符号。但有些工具只符合了BRE引擎规范的子集,这是出于速度方面考虑。(sed编辑器默认使用的就是BREs,但是可以通过-r参数开启EREs)。gawk程序用ERE引擎来处理它的正则表达式模式。

※,BRE 和 ERE 之间有什么区别呢?

这是关于元字符的问题。BRE 可以辨别以下元字符:^ $ . [ ] *其它的所有字符被认为是文本字符。ERE 添加了以下元字符(以及与其相关的功能):( ) { } ? + |。然而(这也是有趣的地方),在 BRE 中,以下字符( ) { } ? + |用反斜杠转义后,被看作是元字符!相反在 ERE 中,在任意元字符之前加上反斜杠会导致其被看作是一个文本字符。总结如下:

  • BRE支持的元字符:^ $ . [ ] *以及\( \) \{ \} \? \+ \|
  • ERE支持的元字符:^ $ . [ ] * ( ) { } ? + |

具体的区别参考此文

字符

说明

Basic RegEx

Extended RegEx

python RegEx

Perl regEx

转义

 

\

\

\

\

^

匹配行首,例如'^dog'匹配以字符串dog开头的行(注意:awk 指令中,'^'则是匹配字符串的开始)

^

^

^

^

$

匹配行尾,例如:'^、dog$'匹配以字符串 dog 为结尾的行(注意:awk 指令中,'$'则是匹配字符串的结尾)

$

$

$

$

 

^$

 

 

匹配空行

 

^$

^$

^$

^$

^string$

匹配行,例如:'^dog$'匹配只含一个字符串 dog 的行

^string$

^string$

^string$

^string$

\<

匹配单词,例如:'\<frog' (等价于'\bfrog'),匹配以 frog 开头的单词

\<

\<

不支持

不支持(但可以使用\b来匹配单词,例如:'\bfrog')

 

\>

 

匹配单词,例如:'frog\>'(等价于'frog\b '),匹配以 frog 结尾的单词

\>

\>

不支持

不支持(但可以使用\b来匹配单词,例如:'frog\b')

 

\<x\>

 

匹配一个单词或者一个特定字符,例如:'\<frog\>'(等价于'\bfrog\b')、'\<G\>'

\<x\>

\<x\>

不支持

不支持(但可以使用\b来匹配单词,例如:'\bfrog\b'

 

()

 

匹配表达式,例如:不支持'(frog)'

不支持,但可以使用\(\),如:\(dog\)

()

()

()

 

\(\)

 

匹配表达式,例如:不支持'(frog)'

\(\)

不支持(同())

不支持(同())

不支持(同())

 

 

匹配前面的子表达式 0 次或 1 次(等价于{0,1}),例如:where(is)?能匹配"where" 以及"whereis"

不支持(但可以使用\?)

\?

匹配前面的子表达式 0 次或 1 次(等价于'\{0,1\}'),例如:'where\(is\)\? '能匹配 "where"以及"whereis"

\?

不支持(同?)

不支持(同?)

不支持(同?)

?

当该字符紧跟在任何一个其他限制符(*, +, ?, {n},{n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串 "oooo",'o+?' 将匹配单个"o",而 'o+' 将匹配所有 'o'

不支持

不支持

不支持

不支持

.

匹配除换行符('\n')之外的任意单个字符(注意:awk 指令中的句点能匹配换行符)

.

.(如果要匹配包括“\n”在内的任何一个字符,请使用:'(^$)|(.)

.

.(如果要匹配包括“\n”在内的任何一个字符,请使用:' [.\n] '

*

匹配前面的子表达式 0 次或多次(等价于{0, }),例如:zo* 能匹配 "z"以及 "zoo"

*

*

*

*

\+

匹配前面的子表达式 1 次或多次(等价于'\{1, \}'),例如:'where\(is\)\+ '能匹配 "whereis"以及"whereisis"

\+

不支持(同+)

不支持(同+)

不支持(同+)

+

匹配前面的子表达式 1 次或多次(等价于{1, }),例如:zo+能匹配 "zo"以及 "zoo",但不能匹配 "z"

不支持(同\+)

+

+

+

 

{n}

 

n 必须是一个 0 或者正整数,匹配子表达式 n 次,例如:zo{2}能匹配

不支持(同\{n\})

{n}

{n}

{n}

{n,}

"zooz",但不能匹配 "Bob"n 必须是一个 0 或者正整数,匹配子表达式大于等于 n次,例如:go{2,}

不支持(同\{n,\})

{n,}

{n,}

{n,}

{n,m}

能匹配 "good",但不能匹配 godm 和 n 均为非负整数,其中 n <= m,最少匹配 n 次且最多匹配 m 次 ,例如:o{1,3}将配"fooooood" 中的前三个 o(请注意在逗号和两个数之间不能有空格)

不支持(同\{n,m\})

{n,m}

{n,m}

{n,m}

 

x|y

 

匹配 x 或 y,例如: 不支持'z|(food)' 能匹配 "z" 或"food";'(z|f)ood' 则匹配"zood" 或 "food"

不支持(同x\|y)

x|y

x|y

x|y

 

[0-9]

 

匹配从 0 到 9 中的任意一个数字字符(注意:要写成递增)

[0-9]

[0-9]

[0-9]

[0-9]

 

[xyz]

 

字符集合,匹配所包含的任意一个字符,例如:'[abc]'可以匹配"lay" 中的 'a'(注意:如果元字符,例如:. *等,它们被放在[ ]中,那么它们将变成一个普通字符)

[xyz]

[xyz]

[xyz]

[xyz]

 

[^xyz]

 

负值字符集合,匹配未包含的任意一个字符(注意:不包括换行符),例如:'[^abc]' 可以匹配 "Lay" 中的'L'(注意:[^xyz]在awk 指令中则是匹配未包含的任意一个字符+换行符)

[^xyz]

[^xyz]

[^xyz]

[^xyz]

[A-Za-z]

匹配大写字母或者小写字母中的任意一个字符(注意:要写成递增)

[A-Za-z]

[A-Za-z]

[A-Za-z]

[A-Za-z]

[^A-Za-z]

匹配除了大写与小写字母之外的任意一个字符(注意:写成递增)

[^A-Za-z]

[^A-Za-z]

[^A-Za-z]

[^A-Za-z]

 

\d

 

匹配从 0 到 9 中的任意一个数字字符(等价于 [0-9])

不支持

不支持

\d

\d

 

\D

 

匹配非数字字符(等价于 [^0-9])

不支持

不支持

\D

\D

\S

匹配任何非空白字符(等价于[^\f\n\r\t\v])

不支持

不支持

\S

\S

\s

匹配任何空白字符,包括空格、制表符、换页符等等(等价于[ \f\n\r\t\v])

不支持

不支持

\s

\s

\W

 

匹配任何非单词字符 (等价于[^A-Za-z0-9_])

 

\W

\W

\W

\W

\w

匹配包括下划线的任何单词字符(等价于[A-Za-z0-9_])

\w

\w

\w

\w

\B

匹配非单词边界,例如:'er\B' 能匹配 "verb" 中的'er',但不能匹配"never" 中的'er'

\B

\B

\B

\B

 

\b

 

匹配一个单词边界,也就是指单词和空格间的位置,例如: 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的'er'

\b

\b

\b

\b

\t

匹配一个横向制表符(等价于 \x09和 \cI)

不支持

不支持

\t

\t

\v

匹配一个垂直制表符(等价于 \x0b和 \cK)

不支持

不支持

\v

\v

\n

匹配一个换行符(等价于 \x0a 和\cJ)

不支持

不支持

\n

\n

\f

匹配一个换页符(等价于\x0c 和\cL)

不支持

不支持

\f

\f

\r

匹配一个回车符(等价于 \x0d 和\cM)

不支持

不支持

\r

\r

\\

匹配转义字符本身"\"

\\

\\

\\

\\

 

\cx

 

匹配由 x 指明的控制字符,例如:\cM匹配一个Control-M 或回车符,x 的值必须为A-Z 或 a-z 之一,否则,将 c 视为一个原义的 'c' 字符

不支持

不支持

 

\cx

 

\xn

 

匹配 n,其中 n 为十六进制转义值。十六进制转义值必须为确定的两个数字长,例如:'\x41' 匹配 "A"。'\x041' 则等价于'\x04' & "1"。正则表达式中可以使用 ASCII 编码

不支持

不支持

 

\xn

 

\num

 

匹配 num,其中 num是一个正整数。表示对所获取的匹配的引用

不支持

\num

\num

 

[:alnum:]

匹配任何一个字母或数字([A-Za-z0-9]),例如:'[[:alnum:]] '

[:alnum:]

[:alnum:]

[:alnum:]

[:alnum:]

[:alpha:]

匹配任何一个字母([A-Za-z]), 例如:' [[:alpha:]] '

[:alpha:]

[:alpha:]

[:alpha:]

[:alpha:]

[:digit:]

匹配任何一个数字([0-9]),例如:'[[:digit:]] '

[:digit:]

[:digit:]

[:digit:]

[:digit:]

[:lower:]

匹配任何一个小写字母([a-z]), 例如:' [[:lower:]] '

[:lower:]

[:lower:]

[:lower:]

[:lower:]

[:upper:]

匹配任何一个大写字母([A-Z])

[:upper:]

[:upper:]

[:upper:]

[:upper:]

[:space:]

任何一个空白字符: 支持制表符、空格,例如:' [[:space:]] '

[:space:]

[:space:]

[:space:]

[:space:]

[:blank:]

空格和制表符(横向和纵向),例如:'[[:blank:]]'ó'[\s\t\v]'

[:blank:]

[:blank:]

[:blank:]

[:blank:]

[:graph:]

任何一个可以看得见的且可以打印的字符(注意:不包括空格和换行符等),例如:'[[:graph:]] '

[:graph:]

[:graph:]

[:graph:]

[:graph:]

[:print:]

任何一个可以打印的字符(注意:不包括:[:cntrl:]、字符串结束符'\0'、EOF 文件结束符(-1), 但包括空格符号),例如:'[[:print:]] '

[:print:]

[:print:]

[:print:]

[:print:]

 

[:cntrl:]

 

任何一个控制字符(ASCII 字符集中的前 32 个字符,即:用十进制表示为从 0 到31,例如:换行符、制表符等等),例如:' [[:cntrl:]]'

 

[:cntrl:]

 

 

[:cntrl:]

 

 

[:cntrl:]

 

 

[:cntrl:]

 

[:punct:]

任何一个标点符号(不包括:[:alnum:]、[:cntrl:]、[:space:]这些字符集)

[:punct:]

[:punct:]

[:punct:]

[:punct:]

[:xdigit:]

任何一个十六进制数(即:0-9,a-f,A-F)

[:xdigit:]

[:xdigit:]

[:xdigit:]

[:xdigit:]

※,BRE模式

  • 正则表达式模式都区分大小写。
  • 在正则表达式中,空格和其他的字符并没有什么区别
  • 特殊字符有 .*[]^${}\+?|()。如果要用某个特殊字符作为文本字符,就必须转义。正斜杠虽然不是特殊字符,但由于sed编辑器或gawk程序的正则表达式边界就是用正斜杠界定的,所以正斜杠也需要转义。
  • 锚字符,即^$两个标记行首行尾的字符。如果你将脱字符^放到模式开头之外的其他位置,那么它就跟普通字符一样,不再是特殊字符了。而$符如果不放在模式结尾,则会表示变量。
  • 特殊字符点号用来匹配除换行符之外的任意单个字符。它必须匹配一个字符,如果在点号字符的位置没有字符,那么模式就不成立。
  • 字符组。字符组匹配一个范围内的字符。 可以在单个正则表达式中用多个字符组。
    • ·grep [Yy][Ee][Ss] file· // YES, YEs, YeS, Yes, yES, yEs, yeS, yes都可匹配。
    • ·echo "yes" | sed -n /[Yy][Ee][Ss]/p·  // 同上.
  • 排除型字符组。例如:[^ch]at,匹配前面不是c或h的at,如bat, lat等等会匹配,但即使是排除,字符组仍然必须匹配一个字符,所以以 at 开头的行仍然未能匹配模式。
  • 区间。可以在单个字符组指定多个不连续的区间,如·sed -n '/[a-ch-m]at/p' data6·该字符组允许区间a~c、h~m中的字母出现在 at 文本前,但不允许出现d~g的字母
  • 特殊的字符组。除了定义自己的字符组外,BRE还包含了一些特殊的字符组,可用来匹配特定类型的字符。
                BRE特殊字符组
        组                      描 述
    [[:alpha: ]]     匹配任意字母字符,不管是大写还是小写
    [[:alnum: ]]     匹配任意字母数字字符0~9、A~Z或a~z
    [[:blank: ]]     匹配空格或制表符
    [[:digit: ]]     匹配0~9之间的数字
    [[:lower: ]]     匹配小写字母字符a~z
    [[:print: ]]     匹配任意可打印字符
    [[:punct: ]]     匹配标点符号
    [[:space: ]]     匹配任意空白字符:空格、制表符、NL、FF、VT和CR
    [[:upper: ]]     匹配任意大写字母字符

    ·echo "abc123" | sed -n '/[[:digit:]]/p'· // 可以在正则表达式模式中将特殊字符组像普通字符组一样使用。

  • 星号。表示前面的字符出现0次或多次。可以和字符组一起使用,如/[a-c]*/

※,ERE模式(扩展正则表达式):POSIX ERE模式包括了一些可供Linux应用和工具使用的额外符号。gawk程序能够识别ERE模式,但sed编辑器不能,但正因为如此,gawk程序在处理数据流时通常才比较慢。

  • 问号。表明前面的字符可以出现0次或1次。
  • 加号。表明前面的字符可以出现1次或多次,但必须至少出现1次。
  • 花括号。{m}: m :正则表达式准确出现 m 次。{m,n}:正则表达式至少出现 m 次,至多 n 次。警告:默认情况下,gawk程序不会识别正则表达式间隔。必须指定gawk程序的 --re- interval命令行选项才能识别正则表达式间隔。例如:·echo "bt" | gawk --re-interval '/be{1}t/{print $0}'·
  • 逻辑或(|)。如果任何一个模式匹配了数据流文本,文本就通过测试。如
    • `echo "The cat is asleep" | gawk '/cat|dog/{print $0}'`
    • `echo "He has a hat." | gawk '/[ch]at|dog/{print $0}'`
  • 表达式分组(小括号)。正则表达式模式也可以用圆括号进行分组。当你将正则表达式模式分组时,该组会被视为一个标准字符。可以像对普通字符一样给该组使用特殊字符。如
    • ·echo "Sat" | gawk '/Sat(urday)?/{print $0}'· //
    • `echo "Saturday" | gawk '/Sat(urday)?/{print $0}'`
  •  

※,正则表达式实战

1. 统计 $PATH 下每个目录下的文件数量

#!/bin/bash
#IFS=: #IFS慎用! 定义之后都会以:作为分隔符,比如下面的ls命令也会以:分割,导致无法达到想要的效果.
myPATH=$(echo $PATH | sed  's/:/ /g')
echo $myPATH
for path in $myPATH;do
    count=0;
    files=$(ls $path)
    for file in $files;do
        # 以下两种方式都可以,[]内的变量可以加$符也可以不加.
        count=$[count + 1]
        # count=$[$count + 1]
    done
    echo $path--------$count
done

2. 解析邮件地址:username@hostname.domain

username 值可用字母数字字符以及以下特殊字符: 点号,单破折线,加号, 下划线。

hostname 只允许字母数字字符以及以下特殊字符:点号,下划线

domain 顶级域名只能是字母字符,必须不少于二个字符(国家或地区代码中使用),并且长度上不得超过五个字符。

满足以上条件的完整的邮件正则表达式如下:·^([a-zA-Z0-9_\-\.\+]+)@([a-zA-Z0-9_\-\.]+)\.([a-zA-Z]{2,5})$·

※,




第四部分,创建使用的脚本:

第24章:编写简单的脚本使用工具

※,归档

※,管理用户账户

※,检测磁盘空间

第25章:创建与数据库、Web及电子邮件相关的脚本

※,MySQL数据库

※,使用web: 通常在考虑shell脚本编程时,最不可能考虑到的就是互联网了。命令行世界看起来往往跟丰富多彩的互联网世界格格不入。但你可以在shell脚本中非常方便的利用一些工具访问Web以及其他网络设备中的数据内容。作为一款于1992年由堪萨斯大学的学生编写的基于文本的浏览器,Lynx程序的历史几乎和互联网一样悠久。因为该浏览器是基于文本的,所以它允许你直接从终端会话中访问网站,只不过Web页面上的那些漂亮图片被替换成了HTML文本标签。这样你就可以在几乎所有类型的Linux终端上浏览互联网了。Lynx使用标准键盘按键浏览网页。链接会在Web页面上以高亮文本的形式出现。使用向右方向键可以跟随一个链接到下一个Web页面。

1, 安装lynx: sudo aptitude install lynx

2, lynx -dump www.eiduo.com

※,使用电子邮件: sudo aptitude install mailutils

第26章:一些有意思的小脚本

※,终端间发送消息

技术栈:

  • ·who· // who命令可以告诉你当前系统中所有的登录用户,包括用户名,用户登录的终端名,登录时间
  • `who -T` // -T查看某用户是否开启消息功能,+号表示开启,-号表示关闭
  • ·whoami· // 
  • ·mesg· // 查看当前登录用户是否开启了消息功能, y开启,n未开启
  • ·mesg y· //为当前用户开启消息功能
  • ·write <user> <terminal>· // 向登录在终端<terminal>上的用户<user>发送消息,enter后输入消息

完整脚本如下:

#!/bin/bash
#
#mu.sh - Send a Message to a particular user
#############################################
#
# Save the username parameter
#
muser=$1
#
# Determine if user is logged on:
#
logged_on=$(who | grep -i -m 1 $muser | gawk '{print $1}')
#
if [ -z $logged_on ]
then
echo "$muser is not logged on."
echo "Exiting script..."
exit
fi
#
# Determine if user allows messaging:
#
allowed=$(who -T | grep -i -m 1 $muser | gawk '{print $2}')
#
if [ $allowed != "+" ]
then
echo "$muser does not allowing messaging."
echo "Exiting script..."
exit
fi
#
# Determine if a message was included:
#
if [ -z $2 ]
then
echo "No message parameter included."
echo "Exiting script..."
exit
fi
#
# Determine if there is more to the message:
#
shift
#
while [ -n "$1" ]
do
whole_message=$whole_message' '$1
shift
done
#
# Send message to user:
#
uterminal=$(who | grep -i -m 1 $muser | gawk '{print $2}')
#
echo $whole_message | write $logged_on $uterminal
#
exit
View Code

※,获取格言:

技术栈:

  • ·wget <url>· // 下载url网页至本地
  • ·wget -o <file> <url>· // -o 将日志信息写入 file
  • `wget -O <file> <url>` // -O 将文档写入 file
  • `wget --spider <url>` // 测试地址的有效性
  • `wget -nv --spider <url>` //  -nv (代表non-verbose)选项来精简输出信息。行尾的 OK 并不是说Web地址是有效的,而是表明返回的Web地址和发送的地址是一样的。输入无效的URL后,输出的最后仍然是 OK 。但是Web地址的结尾是 error404.html 。这才表示Web地址是无效的。
  • `wget --content-disposition <url>` 可以直接将远程URL中的文件名作为下载到本地的文件名

完整脚本如下:

#!/bin/bash
#
# Get a Daily Inspirational Quote
#####################################
#
# Script Variables ####
#
quote_url=www.quotationspage.com/qotd.html
#
# Check url validity ###
#
check_url=$(wget -nv --spider $quote_url 2>&1)
#
if [[ $check_url == *error404* ]]
then
echo "Bad web address"
echo "$quote_url invalid"
echo "Exiting script..."
exit
fi
#
# Download Web Site's Information
#
wget -o /tmp/quote.log -O /tmp/quote.html $quote_url
#
# Extract the Desired Data
#
sed 's/<[^>]*//g' /tmp/quote.html |
grep "$(date +%B' '%-d,' '%Y)" -A2 |
sed 's/>//g' |
sed '/&nbsp;/{n ; d}' |
gawk 'BEGIN{FS="&nbsp;"} {print $1}' |
tee /tmp/daily_quote.txt > /dev/null
#
exit
View Code

※,发送手机消息: 使用Linux shell脚本编写短信,然后在特定时间把这条短信发送到你的手机上。

在命令行中发送短信的方法有好几种。其中一种方法是通过系统的电子邮件使用手机运营商的SMS服务。另一种方法是使用 curl 工具。

1,  使用curl:  与wget 类似, curl 工具允许你从特定的Web服务器中接收数据。与 wget 不同之处在于,你还可以用它向Web服务器发送数据。除了 curl 工具,你还需要一个能够提供免费SMS消息发送服务的网站。在本节脚本中用到的是http://textbelt.com/text。这个网站允许你每天免费发送最多75条短信。(非美国用户不管用...)

$ curl http://textbelt.com/text \
-d number=317AAABBBB \
-d "message=Test from curl"
{
"success": false,
"message": "Invalid phone number."
}$
$

2, 使用电子邮件发送短信:是否能够使用电子邮件作为替代方案要取决于你的手机运营商。如果运营商使用了SMS网关,那算你运气好。联系你的手机运营商,拿到网关的名字。网关名通常类似于txt.att.net或vtext.com。你 通 常 可 以 使 用 因 特 网 找 出 手 机 运 营 商 的 SMS 网 关 。 有 一 个 很 棒 的 网 站 ,http://martinfitzpatrick.name/list-of-email-to-sms-gateways/,上面列出了各种SMS网关以及使用技巧。如果在上面没有找到你的运营商,那就使用搜索引擎搜索吧。通过电子邮件发送短信的基本语法如下。

mail -s "your text message" your_phone_number@your_sms_gateway,// 交互式,输入消息后按ctrl + D结束

mail -s "Test from email" 3173334444@vtext.com < message.txt  // 从文件中读取消息




附录1:bash命令快速指南

附录2:sed和gawk快速指南

 



结束标记

posted on 2021-03-19 19:50  everest33  阅读(299)  评论(0)    收藏  举报