# shell

本文围绕Linux Shell脚本展开,介绍了Shell解释器,包括交互式与非交互式使用方式。详细阐述了脚本编写、执行方法,以及利用脚本搭建YUM、ftp、Web等服务。还讲解了变量、运算、条件测试、循环结构、函数等知识,同时介绍了正则表达式、sed、awk等工具的使用。

shell脚本

解释器Shell

  • Shell是Linux内核与用户之间的解释器程序,负责向内核翻译及传达用户或程序的指令

  • Shell解释器通常默认是指bash解释器/bin/bash

    • /etc/shells:存储系统中解释器的路径。
    • bash解释器支持:Tab键、快捷键、历史命令、别名、管道、重定向、标准输入输出。
  • 解释器的使用方式:

    • **交互式:**需要人工干预,执行效率低。
    • **非交互式:**安静地在后台执行,执行效率高,方便写脚本。
  • 不同解释器执行ls命令:

    • 使用sh解释器之前:账户登录(root)——> bash解释器——> ls命令:bash解释器是有颜色的。root默认通过bash解释器执行ls命令。

    • 使用sh解释器之后:账户登录(root)——> bash解释器——> sh命令——>sh解释器——> ls命令:sh解释器是黑白的。root默认通过bash解释器来执行sh命令,sh命令进入sh解释器,通过sh解释器执行ls命令。

  • usermod -s 解释器 用户名:更改该用户的登录解释器。

// 切换解释器
]# cat /etc/shells 		// 查看系统中的解释器
    /bin/sh			// sh的快捷方式,多数Unix的默认解释器
    /bin/bash		// bash的快捷方式,多数Linux的默认解释器
    /usr/bin/sh		// sh的快捷方式
    /usr/bin/bash	// bash的快捷方式
]# ls -l /bin/{sh,bash} /usr/bin/{sh,bash}
    -rwxr-xr-x. 1 root root 1150568 412 2022 /bin/bash
    lrwxrwxrwx. 1 root root       4 412 2022 /bin/sh -> bash		// 在该设备中,/bin/sh是bash的快捷方式
    -rwxr-xr-x. 1 root root 1150568 412 2022 /usr/bin/bash
    lrwxrwxrwx. 1 root root       4 412 2022 /usr/bin/sh -> bash
]# sh                   // 使用sh解释器
sh-4.2# ls              // 返回内容黑白
sh-4.2# exit  
]# yum -y install ksh   // 安装ksh解释器
]# ksh                  // 使用ksh解释器,ksh解释器不支持快捷键、tab、方向键
# ls					// 返回内容黑白

shell脚本与重定向输出

shell 脚本

  • 脚本是提前写好可执行语句,能够完成特定任务的文件。shell脚本是通过bash解释器运行的脚本。

  • 编写shell脚本(见名知意,扩展名用.sh):

    • 声明解释器:#!/bin/bash
    • 注释:#注释内容 ,注释内容有脚本功能、作者信息、变量作用等等。
    • 命令序列:系统依次执行若干条命令(非交互式命令)。
  • 执行shell脚本

    • 方式1:赋予脚本执行权限xchmod +x 脚本路径,后直接运行:脚本路径
    • 方式2:以子进程bash解释器执行脚本(首选)bash 脚本路径
      • bash命令会新建一个bash的子进程,后台静默执行脚本,bash子进程执行完脚本后会自杀:
        账户登录(root)——> bash解释器——> bash命令——> bash子进程——> 脚本
    • 方式3:以当前的解释器执行该脚本:source 脚本路径,也可以写为. 脚本路径
      • 账户登录(root)——> bash解释器——> 脚本。
  • bash -x 脚本路径调试脚本,会显示脚本执行的每个步骤。

// 执行脚本文件的三种方式
~]# echo '
    > #!/bin/bash					// 声明解释器
    > # This is a test shell.		// 注释
    > echo hello world
    > mkdir /opt/abc
    > cd /opt/abc
    > touch xyz 					// 相对路径创建/opt/abc/xyz
    > ' > /opt/test01.sh			// 编写脚本/opt/test01.sh
// 方式1:赋予脚本执行权限后直接以脚本路径运行
~]# chmod +x /opt/test01.sh	
~]# /opt/test01.sh
	hello world
~]# ls /opt/abc
	xyz								// 成功执行
// 方式2:bash
~]# chmod -x /opt/test01.sh
~]# rm -rf /opt/abc
~]# bash /opt/test01.sh		
	hello world
~]# ls /opt/abc				// 开一个子进程bash执行脚本后自杀,因此当前不进入/opt/abc
	xyz								// 成功执行
// 方式3:source
~]# rm -rf /opt/abc
~]# . /opt/test01.sh				// 也可以写为"source /opt/test01.sh"
	hello world				
abc]# 						// 以当前的bash执行脚本,因此当前进入了/opt/abc
abc]# ls /opt/abc
	xyz								// 成功执行

脚本搭建YUM仓库

// 脚本搭建本地YUM仓库
]# cat /opt/yumrepo1.sh
    rm -rf /etc/yum.repos.d
	mkdir /etc/yum.repos.d
    echo '
    [app]
    name=appstream
    baseurl=file:///mydvd/AppStream
    enabled=1
    gpgcheck=0
    #gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-rockyofficial
    [base]
    name=baseos
    baseurl=file:///mydvd/BaseOS
    gpgcheck=0
    ' > /etc/yum.repos.d/mydvd.repo
    mkdir /mydvd
    echo ' 
    /dev/sr0 /mydvd iso9660 defaults 0 0
    ' >> /etc/fstab	
    mount -a  
]# bash /opt/yumrepo1.sh
// 脚本搭建网络YUM仓库
// 需要192.168.88.240开启ftp匿名访问,共享目录/var/ftp/dvd下存在YUM仓库,还需要处于同一个网段本机才能直接使用该网络YUM仓库
]# cat /opt/yumrepo2.sh
    rm -rf /etc/yum.repos.d
	mkdir /etc/yum.repos.d
    echo '
    [app]
    name=appstream
    baseurl=ftp://192.168.88.240/dvd/AppStream
    gpgcheck=0
    [base]
    name=baseos
    baseurl=ftp://192.168.88.240/dvd/BaseOS
    gpgcheck=0
    ' > /etc/yum.repos.d/mydvd.repo
]# bash /opt/yumrepo2.sh

脚本搭建ftp服务

// 脚本搭建ftp服务
// 开启ftp服务,并将hosts共享文件放入ftp共享目录pub
虚拟机(192.168.166.2):
]# cat /opt/ftp.sh
	yum install -y vsftpd
	systemctl restart vsftpd.service
	cp /etc/hosts /var/ftp/pub
]# bash /opt/ftp.sh
]# vim /etc/vsftpd/vsftpd.conf 			// 修改配置文件
	anonymous_enable=YES				// 修改该行
]# systemctl restart vsftpd.service
]# curl ftp://192.168.166.2
	drwxr-xr-x    2 0        0              19 Jun 28 03:25 pub

重定向输出

  • 重定向标准输出>>>):只重定向标准输出。
  • 重定向错误输出2>2>>):只重定向错误输出。
  • 重定向所有输出&>&>>):重定向标准输出和错误输出。
  • /dev/null:该文件能无限容纳字符。
// 重定向:标准输出、错误输出、所有输出
]# ls /mnt/abc.txt
    /mnt/abc.txt
]# ls /mnt/xyz.txt
	ls: 无法访问'/mnt/xyz.txt': 没有那个文件或目录
]# ls /mnt/abc.txt > /opt/test.txt					// 重定向标准输出
]# ls /mnt/xyz.txt 2> /opt/test.txt					// 重定向错误输出
]# ls /mnt/abc.txt /mnt/xyz.txt > /opt/test.txt		// 重定向标准输出
	ls: 无法访问'/mnt/xyz.txt': 没有那个文件或目录 // 错误输出
]# ls /mnt/abc.txt /mnt/xyz.txt 2> /opt/test.txt	// 重定向错误输出
	/mnt/abc.txt								 // 标准输出
]# ls /mnt/abc.txt /mnt/xyz.txt &> /opt/test.txt	// 重定向所有输出。不返回输出语句
]# yum install -y vsftpd &> /dev/null				// 重定向所有输出。不返回输出语句

脚本搭建Web服务

// 脚本搭建网站服务
// 开启服务并设置开机自启,并定义默认页面内容为"httpd-test~~~",脚本执行过程要静默
虚拟机(192.168.166.2):
]# cat /opt/httpd.sh
    #!/bin/bash
    yum install -y httpd > /dev/null
    systemctl restart httpd > /dev/null
    systemctl enable httpd > /dev/null
    echo 'httpd-test~~~' > /var/www/html/index.html
]# bash /opt/httpd.sh
]# curl 192.168.166.2
	httpd-test~~~
]# systemctl is-enabled httpd
	enabled

变量与定界符

变量与常量

  • 变量(variable):以固定的名称存放可能会变化的内容。

    • 定义变量变量名=变量值

      • 等号两边不能有空格。
      • 变量名只能由字母、数字、下划线组成,区分大小写。变量名不能以数字开头
      • 定义变量是临时的,定义在脚本中只在脚本内有效,定义在终端只在当前解释器进程有效(在新终端失效,在子进程解释器也失效)
      • 定义永久变量:在配置文件/etc/profile 定义变量,重启机器或者 source /etc/profile 生效。
    • 调用变量$变量名${变量名}

    • 取消变量unset 变量名变量名=

  • 常量(constant):固定不变的内容。

// 变量
]# a=10					// 定义变量并赋值
]# echo $a
	10
]# a=100				// 重新赋值
]# echo $a
	100
]# echo $aRMB.abc		// 没有定义变量aRMB,变量aRMB值为空。因为变量名you
	.abc
]# echo $a.RMB			// 变量a为100
	100.RMB 
]# echo ${a}RMB         // 变量a为100
	100RMB
]# a=					// 重新赋值为空(没有空格),相当于"unset a" 

环境变量

  • 环境变量:系统定义的变量。
    • USER:当前用户名。
      UID:当前用户UID。
      HOME:当前用户家目录。
      SHELL:当前用户解释器。
      PWD:当前位置。
      HOSTNAME:主机名。
      LOGNAME:登录的用户名。
    • RANDOM:产生0到几万的随机整数。
    • PS1(Prompt String):一级提示符。
    • PS2:二级提示符,使用 \ 强制换行时出现二级提示符 >
    • PATH:存储系统命令的路径。系统依靠这里的路径来查找使用命令功能。
    • TZ:时区。空则默认为零时区。
  • env(environment):显示所有环境变量
  • set显示所有变量
// 一级提示符
[root@hostname /]# echo $PS1   // 查看默认的一级提示符
	[\u@\h \W]\$
[root@hostname /]# PS1='hehe#' // 修改一级提示符
hehe#                          // 显示效果
hehe#PS1='[\u@\h \W]\$ '      // 恢复原有设置
[root@hostname /]# 
// 二级提示符
]# echo $PS2       	// 查看默认的二级提示符
	>
]# ls \         	// "\"强制换行,观察提示符"> "
> -ld \				// "\"强制换行
> /opt
	drwxr-xr-x. 3 root root 85 628 11:59 /opt
]# PS2='=> '        // 修改二级提示符为"=> "
]# ls \             // "\"强制换行,观察提示符
=> -ld \
=> /opt
	drwxr-xr-x. 3 root root 85 628 11:59 /opt
]# PS2='> ' 		// 恢复原有设置
// 环境变量PATH
]# echo $PATH
	/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin		// 系统命令的路径
]# PATH=
	bash: sed: 没有那个目录
]# ls				// 系统根据PATH的路径来寻找系统命令,现在找不到ls命令
	bash: ls: 没有那个文件或目录
	bash: sed: 没有那个目录

位置变量与预定义变量

  • $1$2$3、……:位置变量,代表脚本后面的第1、2、3、……个参数。

  • $*:所有位置变量的值。

    • $*:将所有参数作为单个字符串返回,并使用第一个 IFS(Internal Field Separator)变量的值作为参数之间的分隔符。通常情况下,IFS 的默认值是空格字符。因此,$* 展开为一个单独的字符串,参数之间用空格分隔。
    • $@:将每个参数作为独立的字符串返回,并保留参数之间的空格。当使用 $@ 时,每个参数都被视为一个独立的元素。
  • $#:位置变量的个数。

  • $0:当前运行的进程名或脚本名。

  • $?:上一个命令的执行结果(退出码)。

    • 执行成功为0,执行失败为非0。
    • 查看退出码的实际含义:perror 数字
  • $$:当前运行的进程的PID。

  • $!:后台最后一个进程的PID。

// 位置变量与预定义变量
~]# vim /opt/test05.sh
	#!/bin/bash
    echo $0              // 脚本的名称(当前位置),source运行脚本显示解释器的名字
    echo $1              // 第一个参数
    echo $2              // 第二个参数
    echo $*              // 所有的参数
    echo $#              // 所有参数的个数
    echo $$              // 当前进程的进程号PID
    echo $?              // 上一个程序执行结果,成功为0,失败非0
~]# bash /opt/test05.sh a b c	// 绝对路径执行脚本
    /opt/test05.sh
    a
    b
    a b c
    3
    38544
    0			// 0代表上一个命令"echo $$"执行成功
~]# cd /opt
opt]# bash test05.sh a b c		// 相对路径执行脚本
    test05.sh
    a
    b
    a b c
    3
    38553
    0
opt]# vim test06.sh
	#!/bin/bash
	echo $0	
	ls sdfaasdf
    echo $? 
opt]# source test06.sh
	bash		// source运行脚本时,$0是解释器进程名称
	ls: 无法访问'sdfaasdf': 没有那个文件或目录
	2			// 非0代表上一个命令"ls sdfaasdf"执行失败
// 应用:创建用户并设置密码
]# vim /opt/useradd.sh
	#!/bin/bash
    useradd $1
    echo $2 | passwd --stdin $1	
]# bash /opt/useradd.sh zhangsan 123
    更改用户 zhangsan 的密码 。
    passwd:所有的身份验证令牌已经成功更新。

定界符 “”、‘’、$()

  • 三种定界符
    • 双引号 " ":其内为整体,允许扩展,其内可以使用$引用变量。echo后省略引号默认是双引号
    • 单引号 ’ ':其内为整体,禁用扩展,不支持变量和正则,其内使用 $ 视为普通字符。
    • 反撇号 ``,也可以写作**$():可以将命令的输出内容**作为变量值。
      • 例如:rpm -e $(rpm -qa | grep mariadb):卸载包含mariadb字符串的软件
      • 例如:rpm -ql mysql > /opt/1.txtrm -rf $(cat /opt/1.txt):删除mysql相关的文件
// 引号的作用:其内为整体
]# cd /mnt
mnt]# touch a b		// 创建两个文件
mnt]# ls -l
    总用量 0
    -rw-r--r--. 1 root root 0 628 16:42 a
    -rw-r--r--. 1 root root 0 628 16:42 b
mnt]# touch "a b"	// 创建一个文件,这里也可以使用单引号'a b' 
mnt]# ls -l
    总用量 0
    -rw-r--r--. 1 root root 0 628 16:42  a
    -rw-r--r--. 1 root root 0 628 16:42 'a b'
    -rw-r--r--. 1 root root 0 628 16:42  b
]# xx=a b c	
	-bash: b: command not found    // 未界定时赋值失败
]# xx="a b c"   	// 赋值成功,这里也可以写作 xx='a b c'  

// 双引号和单引号的区别
]# echo "$xx"		// 双引号内允许扩展,可以使用$引用变量
	a b c		
]# echo '$xx'		// 单引号内禁用扩展,$是普通字符
	$xx

// 反撇号的作用
]# date
	20230628日 星期三 16:26:35 CST
]# a=date		// 将date看成字符串
]# echo $a
	date
]# a=`date`		// 可以保留date命令的功能,也可以写作 a=$(date)
]# echo $a
	20230628日 星期三 16:26:35 CST

// 应用:创建一个文件,名称"abc-年-月-日.txt"
]# touch /mnt/abc-`date +%Y-%m-%d`.txt
]# ls /mnt
	a		'a b'		abc-2023-06-28.txt		b

交互式赋值 read

  • read [-p "提示信息"] 变量名1 变量名n (prompt):从键盘读入变量值完成赋值(交互式)。
    • 可以给多个变量赋值,以空格作为区分。
  • stty -echo(set tty):屏蔽回显。(但不会屏蔽提示信息)
    stty echo:恢复回显。
// read,交互式创建用户并设置密码
]# vim /opt/read.sh
	#!/bin/bash
	read -p "please input user's name:" x
    useradd $x
	stty -echo								// 屏蔽回显,不会屏蔽提示信息
	read -p "please input password:" y		
	stty echo								// 恢复回显
	echo ''
    echo $y | passwd --stdin $x
]# bash /opt/read.sh						// 运行脚本
	please input user's name:lisi
	please input password:					// 屏蔽密码的显示
	更改用户 lisi 的密码 。	
	passwd:所有的身份验证令牌已经成功更新。

局部变量与全局变量 export

  • 局部变量:定义的变量默认仅在当前Shell环境中有效,无法在子Shell环境中使用
  • 全局变量:在当前Shell及子Shell环境中均有效(向下生效),但无法在父Shell环境中使用
  • export 变量名1[=变量值1] 变量名n[=变量值n]:可以将局部变量声明为全局变量,也可以直接创建全局变量。
    export -n 全局变量名1 全局变量名n:取消指定变量的全局属性。
// 局部变量与全局变量
]# pstree | grep bash										// 查看bash进程
        |-sshd---sshd---sshd---bash-+-grep					// 处于第一层的bash
]# a=101			// 第一层创建局部变量a,赋值为101
]# echo $a
	101
]# bash				// 创建子进程bash并进入		
]# pstree | grep bash
        |-sshd---sshd---sshd---bash---bash-+-grep			// 处于第二层的bash
]# b=202			// 第二层创建局部变量b,赋值为202
]# echo "$a+$b"
	+202
]# export b			// 将第二层局部变量b声明为全局变量(向下生效)
]# export B=200		// 第二层创建全局变量B(向下生效),赋值为200
]# echo "$a+$b+$B"	// 第一层的局部变量a在第二层不存在
	+202+200
]# bash
]# pstree | grep bash
        |-sshd---sshd---sshd---bash---bash---bash-+-grep	// 处于第三层的bash
]# c=303			// 第三层创建局部变量c,赋值为303
]# echo "$a+$b+$B+$c"
	+202+200+303	// 第一层的局部变量a在第三层不存在,第二层的全局变量b和B在第三层存在
]# exit														// 退出到第二层
	exit
]# pstree | grep bash
        |-sshd---sshd---sshd---bash---bash-+-grep			// 处于第二层的bash
]# echo "$a+$b+$B+$c"
	+202+200+ 		// 子进程的变量在父进程不存在:第三层的变量c在第二层不存在
]# exit														// 退出到第一层
	exit
]# pstree | grep bash
        |-sshd---sshd---sshd---bash-+-grep					// 处于第一层的bash
]# echo "$a+$b+$B+$c"
	101+++ 			// 子进程的变量在父进程不存在:第二层的变量b和B在第一层不存在

shift命令

shift 是一个 Bash 命令,用于将脚本或函数的参数列表中的参数向左移动。当你调用一个函数或脚本时,传入的参数会被存储在一个参数列表中,通过 $1$2$3 等方式进行访问。

当你使用 shift 命令时,第一个参数 $1 会被丢弃,而原本的 $2 被移动到 $1 的位置,原本的 $3 被移动到 $2 的位置,以此类推。这意味着所有的参数都向左移动一位。

]# cat test_shift.sh
#!/bin/bash
echo "The total number of arguments is: $#"
echo "First argument is: $1"
echo "Second argument is: $2"
echo "Third argument is: $3"
shift
echo "After shifting"
echo "First argument is now: $1"
echo "Second argument is now: $2"
echo "Third argument is now: $3"

]# bash ./test_shift.sh apple banana cherry
    The total number of arguments is: 3
    First argument is: apple
    Second argument is: banana
    Third argument is: cherry
    After shifting
    First argument is now: banana
    Second argument is now: cherry
    Third argument is now:

运算

  • 运算符:+-*/ 取整、% 取余

整数运算 expr、$[]、let

  • expr 整数1 运算符1 整数2 运算符n 整数n(expression):运算并输出值
    • 运算符两侧必须空格,特殊符号使用引号或转义字符,引用变量必须$
  • $[算术运算]$((算术运算)):运算但不输出值。
    • 运算符两侧可以不空格,特殊符号直接使用,引用变量可以不$
    • 注意:$()是反撇号,$[]是运算,${}是变量
  • let 变量名算术运算:运算但不输出值。可以直接用运算给变量赋值;支持变量自运算
    • 自运算:++--+=-=*=/=%=
// 整数运算
// expr
]# expr 6 + 4
	10
]# expr 6 - 4
	2
]# expr 6 '*' 4		// 通过引号使用特殊符号*本身
	24
]# expr 6 \* 4		// 也可以通过转义字符\使用特殊符号
	24
]# expr 6 / 4		// 除法取整
	1
]# expr 6 % 4		// 除法取余
	2
]# a=6
]# b=4
]# expr $a \* $b	// 引用变量必须$
	24

// $[]、$(())
]# echo $[6*4]		// 直接使用特殊符号*
	24
]# echo $((6*4))	
	24
]# echo $[$a*$b]
	24
]# echo $[a*b]		// 引用变量可以不$
	24
]# x=$[6*4]			// 不直接输出值,可以给变量赋值
]# echo $x
	24
]# echo $[RANDOM]	// 随机返回一个非0整数
	9594

// let
]# y=5+9			// 字符串给变量赋值
]# echo $y			
	5+9
]# let y=5+9		// 用运算给变量赋值
]# echo $y
	14
]# let y++			// 支持变量自增减
]# echo $y
	15

小数运算 bc

  • echo "1.1+1" | bc:返回2.1。
  • echo "10/3" | bc:返回3。
    echo "scale=3;5/3" | bc :截取小数点后三位,返回1.666,多个表达式用 ; 隔开。
  • 可以直接使用命令bc进入交互式运算界面,quit退出,scale=n约束小数位。
// 小数运算
// bc交互式运算
]# bc
12.34*56.789
700.776				// 根据两位小数与三位小数运算来截取三位小数,注意不是四舍五入
scale=4				// 结果截取四位小数
12.34*56.789
700.7762
3*1
3.0000
quit

// bc非交互式
]# A=12.34
]# echo "$A*56.789;5/3" | bc
	700.776
    1
]# echo "scale=4;$A*56.789;5/3" | bc
    700.7762
    1.6666

字符串拼接

  • 变量名1=$变量名2$变量名3:将变量2和变量3的字符串合并后赋予给变量1。例如:a=$m$n
  • 变量名1+=$变量名2:将变量1和变量2的字符串合并后赋予给变量1。例如:a+=$m
// 字符串拼接
]# m=1
]# n=2
]# l=$m$n		// 方式1
]# echo $l
	12
]# l+=$m		// 方式2
]# echo $l
	121

数值比较

  • 比较符:>>=<<===!=
  • $[整数1 比较符 整数2]:true为1,false为0,比较符两侧可不空格。只能比较整数,不输出内容。
  • echo "数字1 比较符 数字2" | bc:true为1,false为0,比较符两侧可不空格。
// 数值比较
]# echo $[1<2] 
	1
]# echo $[1.1<2] 
	-bash: 1.1<2: 语法错误: 无效的算术运算符 (错误符号是 ".1<2")
]# echo "1.1<2" | bc 
	1
]# echo "1.10==1.1" | bc
	1

条件测试 []

  • 条件测试的书写格式(测试内容也叫表达式):test 表达式[ 表达式 ] ,不输出内容。
    • 注意:方括号与表达式之间要有空格

字符串比较

  • test 表达式[ 表达式 ] ,不输出内容。
    • [ 字符串1 == 字符串2 ]:比较两个字符串是否相等,相等则true。
    • [ 字符串1 != 字符串2 ]:比较两个字符串是否不相等,不相等则true。
    • [ -z 调用变量 ]:判断该变量是否为空,空则true。
    • [ ! -z 调用变量 ] :判断该变量是否为非空,非空则true。
    • [ -n "调用变量" ] :判断该变量是否为非空,非空则true。注意需要使用双引号
  • 注意:当调用变量为空时会报错,可以使用双引号"调用变量",这样调用的变量为空时也不会报错
// 字符串比较
// ==与!=
]# test a == a	// 也可以写为[ a == a ],注意空格
]# echo $?
	0			// 0为true
]# test a == b
]# echo $?
	1			// 非0为flase
]# [ a != a ]
]# echo $?
	1			// 非0为flase
]# [ a != b ]
]# echo $?
	0			// 0为true

// 报错:"调用变量",使用双引号表示有输入内容,当调用的变量为空时也不会报语法错误。
]# m=abc
]# n=				// 没有空格为空
]# [ $m == $n ]		// 变量n为空报错
	-bash: [: abc: 需要一元表达式
]# [ $m == "$n" ]	// 变量n为空不报错
]# echo $?
	1

// -z与! -z
]# [ -z $n ]		// 变量n为空则true
]# echo $?
	0
]# [ ! -z $n ]		// 也可以写为[ -n "$n" ]
]# echo $?
	1

逻辑与或

  • &&:逻辑与,使用同Java的短路与,前者成功才执行后者,全真整体为真。
  • ||:逻辑或,使用同Java的短路或,前者失败才执行后者,一真整体为真。
  • 组合:
    • A && B && CA && B的结果来逻辑与C
    • A || B && CA || B的结果来逻辑与C
    • A && B || CA && B的结果来逻辑或C
    • A || B || CA || B的结果来逻辑或C
// &&与||的组合
mnt]# touch a c					// 创建文件用"ls 文件"查看,创建目录用"ls -d 目录"查看
mnt]# ls
	a  c
mnt]# ls a && ls b && ls c		// a成功后继续执行b,b失败,a&&b失败,逻辑与不执行c
	a
	ls: 无法访问'b': 没有那个文件或目录
mnt]# ls a && ls b || ls c		// a成功后继续执行b,b失败,a&&b失败,逻辑或执行c
    a
    ls: 无法访问'b': 没有那个文件或目录
    c
mnt]# ls a || ls b && ls c		// a成功后不执行b,a||b成功,逻辑与执行c
    a
    c
mnt]# ls a || ls b || ls c		// a成功后不执行b,a||b成功,逻辑或不执行c
	a

// 应用:非root用户运行脚本就退出
]# vim /etc/yuhuo.sh
    #!/bin/bash
    [ $USER != root ] && echo "unsuccessfully" && exit		// 用户非root就输出语句,前两者均成功后退出
    echo "execute successfully"
]# bash /etc/yuhuo.sh 
	execute successfully
]# su - zhangsan 
]$ bash /etc/yuhuo.sh 
	unsuccessfully

整数值比较

  • [ 整数1 操作符 整数2 ],不输出内容。
    • 操作符:
      • -eq(equal):等于。

      • -ne(not equal):不等于。

      • -ge(greater equal):大于等于。

      • -le(lesser equal):小于等于。

      • -gt(greater than):大于。

      • -lt(lesser than):小于。

// 整数值比较
]# X=100
]# [ $X -eq 100 ] && echo yes || echo no		// 等于
	yes
]# [ $X -ne 100 ] && echo yes || echo no		// 不等于
	no
]# [ $X -gt 100 ] && echo yes || echo no		// 大于
	no
]# [ $X -ge 100 ] && echo yes || echo no		// 大于等于
	yes
]# [ $X -le 100 ] && echo yes || echo no		// 小于等于
	yes
]# [ $X -lt 100 ] && echo yes || echo no		// 小于
	no

// 应用:运行脚本必须输入两个位置参数
]# vim /opt/useradd.sh
	#!/bin/bash
	[ $# -ne 2 ] && echo "必须有两个位置参数:用户 密码" && exit
	// 也可以写为 [ $# != 2 ] && echo "必须有两个位置参数:用户 密码" && exit
    useradd $1
    echo $2 | passwd --stdin $1
]# bash /opt/useradd.sh test
	必须有两个位置参数:用户 密码
]# id test
	id: “test”:无此用户

// 应用:编写脚本,root每2分钟检查服务器的用户数量,如果数量增加就给root发报警邮件
]# cat /etc/passwd | wc-l
	48
]# vim /opt/checkuser.sh
    #!/bin/bash
    a=`cat /etc/passwd | wc-l`							// 反撇号写法1
    b=$(echo "server invaded" | mail -s warning root)	// 反撇号写法2
    [ &a -gt 48 ] && b
]# echo "*/2 * * * *  bash /opt/checkuser.sh" > /var/spool/cron/root	// 给root添加计划任务
	// 也可以"crontab -e -u root",当前用户是root时可以直接"crontab -e"

// 停止计划任务
]# crontab -r -u root		// 当前用户是root时可以直接"crontab -r"
	// 也可以"echo > /var/spool/cron/root"
// 清空邮箱
]# echo > /var/spool/mail/root

文件状态测试

  • [ 操作符 目标 ],目标可以是文件或目录,不输出内容。如果目标是变量,则 "${变量名}"
    • 操作符:
      • -e(exist):判断目标是否存在。! -e 不存在。
      • -d(directory):判断目标是否是目录。
      • -f(file):判断目标是否是文件。
      • -r(read):判断当前用户对目标是否有可读权限r。对root无效。
      • -w(write):判断当前用户对目标是否有可写权限w。对root无效。
      • -x(execute):判断当前用户对目标是否有可执行权限x
  • 注意:root对一切目录和文件有rw权限。
// 文件状态测试
[root@166-2 ~]# chmod 044 /opt/test01.sh
[root@166-2 ~]# ls -l /opt/test01.sh
	----r--r--. 1 root root 93 628 10:28 /opt/test01.sh
[root@166-2 ~]# [ -e /opt/test01.sh ] && echo yes || echo no		// 是否存在
	yes
[root@166-2 ~]# [ -d /opt/test01.sh ] && echo yes || echo no		// 是否是目录
	no
[root@166-2 ~]# [ -f /opt/test01.sh ] && echo yes || echo no		// 是否是文件
	yes
[root@166-2 ~]# [ -r /opt/test01.sh ] && echo yes || echo no		// 是否可读,对root无效
	yes
[root@166-2 ~]# [ -w /opt/test01.sh ] && echo yes || echo no		// 是否可写,对root无效
	yes
[root@166-2 ~]# [ -x /opt/test01.sh ] && echo yes || echo no		// 是否可执行
	no
[root@166-2 ~]# su - zhangsan 
[zhangsan@166-2 ~]$ [ -r /opt/test01.sh ] && echo yes || echo no	// 是否可读
	yes
[zhangsan@166-2 ~]$ [ -w /opt/test01.sh ] && echo yes || echo no	// 是否可写
	no
[zhangsan@166-2 ~]$ [ -x /opt/test01.sh ] && echo yes || echo no	// 是否可执行
	no

[[ ]]

  • [[]][] 都是 Bash 中的条件判断结构,但它们之间有一些区别。

    1. 语法:[[]] 是 Bash 特有的条件判断结构,而 [] 则是 POSIX 标准的条件判断结构,也被称为 test 命令。
    2. 字符串处理能力:[[]] 结构支持更多的字符串处理能力,如正则表达式匹配,字符串拓展等。
    3. 高级逻辑运算符:[[]] 结构支持高级的逻辑运算符,例如 &&||,可以进行复杂的逻辑判断。而 [] 结构中需要使用 -a(与)、-o(或)等选项来完成逻辑操作。
    4. 引用变量:在 [[ ]] 结构中,变量引用不需要使用双引号,即 "$variable" 可以简化为 $variable。而在 [] 结构中,推荐对变量引用加上双引号,即 "$variable"
    5. 转义字符处理:[[]] 结构能够自动处理转义字符,而 [] 结构中需要对反斜杠进行转义,例如 \[

    综上所述,[[]] 结构通常比 [] 结构更强大和更易用,因此在大部分情况下,建议使用 [[ ... ]] 进行条件判断。但需要注意的是,[[]] 结构只能在 Bash 环境中使用,不适用于其他 POSIX 兼容的 shell。

  • [[ 字符串1 =~ 字符串2 ]]:比较字符串1是否匹配正则表达式字符串2,不匹配则true。

if选择结构 fi

if单分支结构

  • 书写格式:(没写 fi 会报错:语法错误:未预期的文件结尾
    • 第一行:if 条件测试 ,注意是条件测试不是条件。
      第二行:then 命令序列(then可以单独写一行)
      最后一行:fi
    • 第一行:if 条件测试;then
      第二行:命令序列
      最后一行:fi
    • 命令序列:依次执行的若干条命令。

if双分支结构

  • 书写格式:
    • 第一行:if 条件测试
      第二行:then 命令序列(then可以单独写一行)
      第三行:else 命令序列(else可以单独写一行)
      最后一行:fi
    • 第一行:if 条件测试;then
      第二行:命令序列
      第三行:else 命令序列(else可以单独写一行)
      最后一行:fi
// if选择结构
[root@166-2 ~]# vim /opt/ifelse.sh
    #!/bin/bash
    if [ $UID == 0 ]						// 可以写为if [ $UID == 0 ];then
    then    echo "I am administrator"		// then可以单独写一行
            echo ok
    else    echo "I am not administrator"	// else可以单独写一行
            echo no
    fi
[root@166-2 ~]# bash /opt/ifelse.sh
    I am administrator
    ok
[root@166-2 ~]# su - zhangsan
[zhangsan@166-2 ~]$ bash /opt/ifelse.sh 
    I am not administrator
    no
  • ping命令选项:
    • -c(count):ping的次数。
    • -i(interval):每次ping间隔几秒。
    • -W:等待反馈的超时秒数,即ping不通后几秒返回信息。
// ping
]# ping -c 3 -i 0.2 -W 1 192.168.88.11		// ping3次,每次间隔0.2秒,ping不通1秒后返回信息
    PING 192.168.88.11 (192.168.88.11) 56(84) bytes of data.

    --- 192.168.88.11 ping statistics ---
    3 packets transmitted, 0 received, 100% packet loss, time 410ms

// 脚本
]# cat /opt/ping.sh
    #!/bin/bash
    read -p "please input destination ip address:" ip
    ping -c 3 -i 0.2 -W 1 $ip &> /dev/null
    if [ $? == 0 ]
    then
            echo 'successfully'
    else
            echo 'unsuccessfully'
    fi
]# bash /opt/ping.sh
    please input destination ip address:192.168.88.11
    unsuccessfully

if多分支结构

  • 书写格式:
    • 第一行:if 条件测试1
      第二行:then 命令序列1
      第三行:elif 条件测试2
      第四行:then 命令序列2
      第五行:elif 条件测试n
      第六行:then 命令序列n
      第七行:else 命令序列*
      最后一行:fi
// if多分支结构
// 根据输入的分数判断等级
# vim /opt/test04.sh
    #!/bin/bash
    read -p "exam score:" x
    if [ $x -ge 90 ] && [ $x -le 100 ] ;then
            echo 'A'
    elif [ $x -ge 80 ] && [ $x -lt 90 ];then
            echo 'B'
    elif [ $x -ge 60 ] && [ $x -lt 80 ];then
            echo 'C'
    elif [ $x -ge 0 ] && [ $x -lt 60 ];then
            echo 'D'
    else
            echo 'Error.Please input a number between 0 and 100'
    fi
]# bash /opt/test04.sh
    exam score:90
    A
]# bash /opt/test04.sh
    exam score:77
    C
]# bash /opt/test04.sh
    exam score:111
    Error.Please input a number between 0 and 100

循环结构 for、while

for循环

  • 书写格式:
    • 第一行:for 变量名 in 变量值列表
      第二行:do
      第三行:命令序列
      最后一行:done
    • 变量值列表
      • 情况1,直接列出每个变量:for i in a b c ,也可以写为for i in {a,b,c}
      • 情况2,连续整数:for i in {1..4},相当于for i in {1,2,3,4}
      • 情况3,调用变量:for i in $(seq $a)
        • seq 尾数seq 首数 尾数seq 首数 增量 尾数
      • 情况4,调用文本文件的内容:for i in $(cat 文本文件路径)。每一行内容为一个变量值。
// for循环
// 情况1:变量值列表,单个列出
]# vim /opt/for01.sh
    #!/bin/bash
    for i in a b c					// 也可以写为"for i in {a,b,c}"
    do
		echo "abc$i"
    done
]# bash /opt/for01.sh
    abca
    abcb
    abcc

// 情况2:变量值列表,连续整数
]# vim /opt/for02.sh
    #!/bin/bash
    for i in {1..3}			
    do
		echo "abc$i"
    done
]# bash /opt/for02.sh
    abc1
    abc2
    abc3

// 情况3,变量值列表,调用变量
]# vim /opt/for03.sh
    #!/bin/bash
    a=3
    for i in {1..$a}				// {数1..数n},其内不支持变量
    do 
		echo "abc$i"
    done
]# bash /opt/for03.sh
	abc{1..3}
]# vim /opt/for03.sh
    #!/bin/bash
    a=3
    for i in `seq $a`				// seq支持变量,也可以写为"for i in $(seq $a)"
    do 
         echo "abc$i"
    done   
]# bash /opt/for03.sh
    abc1
    abc2
    abc3 

// 情况4,变量值列表,文本文件内容:脚本批量创建用户
]# vim /opt/name.txt 
	zhangsan
    lisi
	wangwu
	zhaoliu
]# vim /opt/for04.sh 
    #!/bin/bash
    for i in `cat /opt/name.txt`	// 也可以写为"for i in $(cat /opt/name.txt)"
    do
		useradd $i
    done
]# bash /opt/for04.sh 

// 情况5,ping某个网段的IP地址
]# vim /opt/for05.sh
    #!/bin/bash
	s=0
	u=0
    for ip in {1..5}
    do      
            ping -c 3 -i 0.2 -W 1 192.168.166.$ip &> /dev/null
            if [ $? == 0 ];then		// 上一条命令执行成功时
                    echo "ping 192.168.166.$ip successfully"
                	let s++
            else
                    echo "ping 192.168.166.$ip unsuccessfully"
					let u++
            fi
    done 
	echo "succeed $s times totally, fail $u times totally"  
]# bash /opt/for05.sh
    ping 192.168.166.1 successfully
    ping 192.168.166.2 successfully
    ping 192.168.166.3 successfully
    ping 192.168.166.4 unsuccessfully
    ping 192.168.166.5 unsuccessfully
	succeed 3 times totally, fail 2 times totally
  • 设置vim缩进
    • vim 用户家目录/.vimrcau filetype sh set ai ts=4:自动在扩展名.sh的文件设置自动缩进为4个空格。

C语言风格的for循环

  • 书写格式:
    • 第一行:for ((变量名=初始值;条件;步长控制)),这里的条件使用>=,不用条件测试的操作符-ge
      第二行:do
      第三行:命令序列
      最后一行:done
// C语言风格的for循环
]# vim /opt/cfor01.sh
    #!/bin/bash
    for ((i=1;i<=5;i+=2))
    do
        echo abc$i
    done
]# bash /opt/cfor01.sh 
    abc1
    abc3
    abc5
]# vim /opt/cfor02.sh
    #!/bin/bash
    i=0
    for ((a=1;a<=5;a++))
    do
        let i=i+$a		// 也可以写为"let i+=$a"
        echo $i
    done
]# bash /opt/cfor02.sh 
	1
    3
    6
    10
    15

while循环结构

  • 条件格式:
    • 第一行:while 条件测试,注意是条件测试不是条件。
      第二行:do
      第三行:命令序列
      最后一行:done
    • 说明:条件测试为真则执行命令序列,然后继续条件测试,直到条件测试为假,结束while循环。
  • for循环只用于有限次数的循环,while循环可用于无限次数的循环(死循环)
    • 死循环:条件测试恒为真,也可以写为 while :
    • 注意:死循环会占用大量的CPU(top命令动态查看%Cpu的空闲值id很低),给死循环的添加命令sleep 0.1可以降低CPU占用。
// while循环
// 死循环
]# vim /opt/while01.sh
    #!/bin/bash
    while [ 1 -eq 1 ]		// 也可以写为"while :",这里的":"表示正确。 
    do
        echo abc
        sleep 0.1			// 可以在另一终端使用top查看进程的动态。没有sleep时死循环占用大量的CPU,%CPU的id值很低
    done
]# vim /opt/while02.sh
	#!adf/bin/bash
    i=1
    while [ $i -le 5 ]
    do
        echo abc$i
        sleep 0.1
        let i++
    done
]# bash /opt/while02.sh 
    abc1
    abc2
    abc3
    abc4
    abc5

循环的中断与退出

  • break:结束所在循环结构。
  • continue:结束本次循环。
  • exit 0或1:0代表正常运行程序并退出程序,1代表非正常运行程序并退出程序,省略默认是0。不输出内容。
    • exit 0或1 写在末行,在程序退出后,用户可以 echo $? 来查看是0或1来判断程序是否正常退出。
// break、continue
]# vim /opt/end.sh 
    #!/bin/bash
    for i in {1..4}
    do
        echo "第${i}次外部循环"
        [ $i -eq 3 ] && continue		// 当i=3时跳过本次循环,继续第4次外部循环
        for j in {1..4}
        do
            echo "内部循环$j"
            [ $j -eq 3 ] && break		// 当j=3时跳出所在循环结构,不继续内部循环4
            echo "正常结束内部循环$j"
        done
        echo "正常结束第${i}次外部循环"
        echo ""
    done
]# bash /opt/end.sh 
    第1次外部循环
    内部循环1
    正常结束内部循环1
    内部循环2
    正常结束内部循环2
    内部循环3				// break没有"正常结束内部循环3",也没有开启"内部循环4"
    正常结束第1次外部循环

    第2次外部循环
    内部循环1
    正常结束内部循环1
    内部循环2
    正常结束内部循环2
    内部循环3
    正常结束第2次外部循环

    第3次外部循环				// continue没有开启"内部循环1",但继续开启"第4次外部循环"4次外部循环
    内部循环1
    正常结束内部循环1
    内部循环2
    正常结束内部循环2
    内部循环3
    正常结束第4次外部循环

// 应用:写一个求和的脚本,每次输入一个参数,当用户输入0时结束脚本,没有输入时不报错。
]# vim /opt/qiuhe.sh
	#!/bin/bash
    i=0
    while :
    do
        read -p "please input a number for their sum (input 0 to end):" n
		[ -z $n ] && continue		// 当n为空时跳过本次循环,继续下一次循环,也可以写为[ -n "$n" ] && continue
        [ $n -eq 0 ] && break		// 当n为0时结束所在循环结构,这里结束脚本
        let i+=n
    done
    echo "their sum is $i"      
]# bash /opt/qiuhe.sh
	please input a number for their sum (input 0 to end):			// 参数为空时继续下一次循环
    please input a number for their sum (input 0 to end):1
    please input a number for their sum (input 0 to end):23
    please input a number for their sum (input 0 to end):561
    please input a number for their sum (input 0 to end):231
    please input a number for their sum (input 0 to end):485
    please input a number for their sum (input 0 to end):0			// 参数为0时结束循环
    their sum is 1301

case分支结构 esac

case分支结构

  • 书写格式:
    • 第一行:case 调用的变量名 in
      第二行:变量值1)
      第三行:命令序列1;;
      第四行:变量值2)
      第五行:命令序列2;;
      第六行:变量值n)
      第七行:命令序列n;;
      第八行:*)
      第九行:命令序列;;
      最后一行:esac
    • 说明:
      • 当调用的变量成功匹配变量值1时,会执行命令序列1。
        当调用的变量匹配变量值1失败时,会继续匹配变量值2,成功匹配则执行命令序列2,匹配失败则继续匹配下一个变量值。
        所有设定的变量值均匹配失败后,会成功匹配通配符 * (相当于 if选择结构的else),执行命令序列。
      • 命令序列的最后一条命令以;;结尾则跳转esac结束case分支,不以;;结尾则继续匹配下一个变量值(会报错)
// case分支结构
]# vim /opt/case.sh
    #!/bin/bash
    case $1 in
    abc)
        echo ok;;
    xyz)
        echo no;;
    *)
        echo "please print $0 with abc or xyz"
    esac
]# bash /opt/case.sh			// 参数为空
	please print /opt/case.sh with abc or xyz
]# bash /opt/case.sh aasdfa		// 参数为aasdfa
	please print /opt/case.sh with abc or xyz
]# bash /opt/case.sh abc		// 参数为abc
	ok
]# bash /opt/case.sh xyz		// 参数为xyz
	no

脚本安装源码包

// case分支结构应用:nginx软件不支持systmectl命令,nginx的命令路径太长,通过case分支结构来便捷使用nginx
// 安装软件nginx-1.22(源码包)
真机(192.168.166.1):
]# scp /linux-soft/s2/wk/lnmp_soft.tar.gz root@192.168.166.3:		// 传输到root的家目录

虚拟机(192.168.166.3):
]# cp /root/lnmp_soft/nginx-1.22.1.tar.gz /opt
]# vim /opt/sourceinstall.sh				// 安装源码包的脚本
    #!/bin/bash
    yum install -y gcc make openssl-devel pcre-devel > /dev/null	
	// 安装源码包需要gcc、make,安装nginx额外需要openssl-devel(支持搭建https加密网站)、pcre-devel(支持正则表达式),不显示标准输出" > /dev/null"
    tar -xf nginx-1.22.1.tar.gz > /dev/null	// 将该压缩包解压到当前目录
    cd nginx-1.22.1							// 进入当前目录下的解压后的目录
    ./configure > /dev/null					// 运行脚本文件"configure",以生成大纲文件"Makefile"
    echo make********************			// 用以分割显示内容
    make > /dev/null
    echo makeinstall*************			// 用以分割显示内容
    make install > /dev/null
]# cd /opt 
opt]# bash sourceinstall.sh 

// 开启nginx服务以检测是否安装成功,nginx不支持systemctl命令
opt]# systemctl stop httpd 					// 关掉httpd服务,避免浏览器访问Web
opt]# systemctl stop firewalld				// 关掉防火墙
opt]# less nginx-1.22.1/Makefile  // 查看nginx软件主程序nginx的路径"/usr/local/nginx/sbin/nginx",或者通过查看README
opt]# /usr/local/nginx/sbin/nginx			// 开启nginx,打开虚拟机的浏览器输入"192.168.166.3",看到nginx的欢迎页面
opt]# /usr/local/nginx/sbin/nginx			// 重复开启会报错
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address already in use)
    nginx: [emerg] still could not bind()
opt]# /usr/local/nginx/sbin/nginx -s stop	// 关闭nginx,打开虚拟机的浏览器输入"192.168.166.3",报错无法连接
opt]# /usr/local/nginx/sbin/nginx -s stop	// 重复关闭会报错
	nginx: [error] open() "/usr/local/nginx/logs/nginx.pid" failed (2: No such file or directory)

// 脚本使用nginx
]# vim /opt/usenginx.sh
    #!/bin/bash
    case $1 in
    start|kai)		// start或kai
            /usr/local/nginx/sbin/nginx;;
    stop|guan)
            /usr/local/nginx/sbin/nginx -s stop;;
    restart|cq)
            /usr/local/nginx/sbin/nginx -s stop	2> /dev/null	// 不显示重复关闭的错误输出
            /usr/local/nginx/sbin/nginx;;			// 命令序列的最后一条命令以";;"结尾
    *)
            echo "print start or stop or restart";;
    esac
]# bash /opt/usenginx.sh start		// 打开虚拟机的浏览器输入"192.168.166.3",看到nginx的欢迎页面
]# bash /opt/usenginx.sh stop		// 打开虚拟机的浏览器输入"192.168.166.3",报错无法连接
]# bash /opt/usenginx.sh restart	// 打开虚拟机的浏览器输入"192.168.166.3",看到nginx的欢迎页面

ss命令

  • ss命令(Socket Statistics):程序正在运行会有一行信息
  • ss 选项:查看联网的程序的信息(包括进程名、端口号、传输协议等)
    • -n(numeric):以数字格式显示端口号。例如:不使用时显示http,使用时显示80。
    • -t(tcp):显示TCP连接的端口。
    • -u(udp):显示UDP连接的端口。
    • -l(listening):显示所有服务正在监听的端口信息。例如:httpd启动后,会一直监听80端口。
      -a(all):既显示服务端的端口,又显示客户端访问的临时端口。
    • -p(processes):显示端口的服务名称,也就是程序名称。
// ss -ntulp
]# ss -ntulp | head -3
/* 同时使用选项-tu,才有"Netid"以区分tcp和udp。
 * 不使用选项-l,"State"只显示ESTAB。
 * 使用选项-p,"Process"才有内容。
 */
 // 协议   运行状态  接收    发送            本地IP:端口号     可连接的IP:端口号 进程信息
    Netid State  Recv-Q Send-Q  Local Address:Port  Peer Address:Port  Process            
    udp   UNCONN 0      0             0.0.0.0:35856      0.0.0.0:*    users:(("avahi-daemon",pid=898,fd=17))  
    udp   UNCONN 0      0       192.168.122.1:53         0.0.0.0:*    users:(("dnsmasq",pid=1789,fd=5))  
	
]# ss -tulp | head -3	// -n的作用:以数字格式显示端口号,注意"Port"
    Netid State  Recv-Q Send-Q  Local Address:Port   Peer Address:Port Process
    udp   UNCONN 0      0             0.0.0.0:35856       0.0.0.0:*    users:(("avahi-daemon",pid=898,fd=17)) 
    udp   UNCONN 0      0       192.168.122.1:domain      0.0.0.0:*    users:(("dnsmasq",pid=1789,fd=5)) 

]# ss -nulp | head -3	// -t的作用:显示TCP连接的端口。以下都是UDP协议的
    State  Recv-Q Send-Q  Local Address:Port  Peer Address:Port Process 
    UNCONN 0      0             0.0.0.0:35856      0.0.0.0:*    users:(("avahi-daemon",pid=898,fd=17))  
    UNCONN 0      0       192.168.122.1:53         0.0.0.0:*    users:(("dnsmasq",pid=1789,fd=5))   

]# ss -ntlp | head -3	// -u的作用:显示UDP连接的端口。以下都是TCP协议的
    State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process        
    LISTEN 0      128          0.0.0.0:111       0.0.0.0:*    users:(("rpcbind",pid=854,fd=4),("systemd",pid=1,fd=37))
    LISTEN 0      128          0.0.0.0:80        0.0.0.0:*    users:(("nginx",pid=3008,fd=8),("nginx",pid=3007,fd=8)) 

]# ss -ntup | head -3	// -l的作用:显示所有服务正在监听的端口信息。没有-l只显示已开启的服务,注意"State"的ESTAB
    Netid State Recv-Q Send-Q Local Address:Port  Peer Address:Port Process
    tcp   ESTAB 0      0      192.168.166.3:22   192.168.166.1:48804 users:(("sshd",pid=2258,fd=5),("sshd",pid=2225,fd=5))

]# ss -ntul | head -3	// -p的作用:显示端口的服务名称,也就是程序名称。注意"Process"
    Netid State  Recv-Q Send-Q  Local Address:Port  Peer Address:Port Process
    udp   UNCONN 0      0             0.0.0.0:35856      0.0.0.0:*          
    udp   UNCONN 0      0       192.168.122.1:53         0.0.0.0:*         

// ss命令应用:查看nginx的运行状态
]# /usr/local/nginx/sbin/nginx -s stop
]# ss -ntulp | grep nginx				// nginx处于关闭状态没有输出信息
]# echo $?
	1
]# /usr/local/nginx/sbin/nginx
]# ss -ntulp | grep nginx				// nginx处于运行状态有输出信息 
	tcp   LISTEN 0      128           0.0.0.0:80         0.0.0.0:*    users:(("nginx",pid=22560,fd=8),("nginx",pid=22559,fd=8))
]# echo $?
	0

// 脚本:判断软件是否运行
]# vim /opt/isrun.sh
    #!/bin/bash
    ss -ntulp | grep -q $1				// 选项-q(quiet)不显示输出。
	// 也可以写为 ss -ntulp | grep $1 > /dev/null
    [ $? == 0 ] && echo "$1 is runnning." || echo "$1 is stopped."
	// 也可以写为 [ $? -eq 0 ] && echo "$1 is runnning." || echo "$1 is stopped."
]# /usr/local/nginx/sbin/nginx -s stop
]# bash /opt/isrun.sh nginx
	nginx is stopped.
]# /usr/local/nginx/sbin/nginx 
]# bash /opt/isrun.sh nginx
	nginx is runnning.

函数

  • 在Shell环境中,可以将一些重复操作的命令序列,定义为公共的语句块,称为函数
    • 可以在终端定义函数,也可以在脚本定义函数。
    • 函数的作用:避免代码重复,将大的工程分割为若干小的功能模块,代码可读性增强。
  • 定义函数:
    • 第一行:function 函数名 {
      第二行:命令序列
      最后一行:}
    • 第一行:函数名(){
      第二行:命令序列
      最后一行:}
  • 调用函数:函数名 参数1 参数2 参数n

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

// 颜色
// echo命令,选项-e使用某种功能,\033更改颜色,[数字m为颜色,[0m为默认色,30多是普通色,40多是背景色,90多是高亮色
]# echo -e "\033[31mABCD\033[0m"	// 将颜色改为红色,输出ABCD,再将颜色改为默认色	
	ABCD		// 显示为红色
]# echo -e "\033[32mABCD\033[0m"
	ABCD		// 显示为绿色
]# echo -e "\033[33mABCD\033[0m"
	ABCD		// 显示为黄色
]# echo -e "\033[34mABCD\033[0m"
	ABCD		// 显示为蓝色
]# echo -e "\033[35mABCD\033[0m"
	ABCD		// 显示为紫色
]# echo -e "\033[35mABCD"		// 因为结尾没有"\033[0m"来将颜色还原为默认色,以下所有内容会变为紫色
	ABCD
]# 				// 全部显示为紫色
]# echo -e "\033[0m"

// 函数应用1
]# vim /opt/color.sh
    #!/bin/bash 	
    color(){			// 定义函数color
            echo -e "\033[$1m$2\033[0m"		// $1为函数的参数1,$2为函数的参数2
    }
    color 31 red		// 调用函数color,函数参数1为31,函数参数2为red
    color 32 green
    color 33 yellow
    color 34 blue
    color 35 purple
	color 41 ABC41
    color 42 ABC42
    color 91 ABS91
    color 92 ABC92
]# bash /opt/color.sh 
    red			// 普通红色
    green		// 普通绿色	
    yellow		// 普通黄色
    blue		// 普通蓝色
    purple		// 普通紫色
	ABC41		// 红底
    ABC42		// 绿底
    ABS91		// 高亮红色
    ABC92		// 高亮绿色

// 函数应用2
]# vim /opt/isrun.sh
    #!/bin/bash
	color(){
			echo -e "\033[$1m$2\033[0m"
    }
    ss -ntulp | grep -q $1
    [ $? == 0 ] && color 32 "$1 is runnning." || color 31 "$1 is stopped."
	/* 脚本参数1如果在运行中输出绿字"$1 is runnning.",没有运行输出红字"$1 is stopped."
     * 调用函数color,函数参数1是32,参数2是"$1 is runnning.",注意这里的$1是脚本参数。
     */
]# /usr/local/nginx/sbin/nginx -s stop
]# bash /opt/isrun.sh nginx
	nginx is stopped.			// 显示为红色
]# /usr/local/nginx/sbin/nginx 
]# bash /opt/isrun.sh nginx		
	nginx is runnning.			// 显示为绿色

退出关键字

  • 三个退出的关键字:
    • exit 数字:退出所在脚本。定义脚本的 $?,数字缺省为0。
    • break:退出所在循环。
    • return 数字:退出所在函数。定义函数的 $?,数字缺省为0。
    • 注意:函数的输出语句使用 echo 完成。

字符串处理 ${处理}

  • 字符串处理不会改变变量本身的值,只是得到一串字符。没有输出内容。

字符串的截取 ${::}

  • ${变量名:起始位置:长度}:从起始位置截取该长度的字符串。起始位置(索引号)从0起计
    • 字符串截取不会改变变量本身的值,只是得到一串字符
    • ${#变量名}:返回变量的长度
// 字符串截取
]# a=123456789
]# echo ${a:2:3}	// 索引号从0起计,截取从第3位开始总共3个字符。
	345
]# echo $a
	123456789
// 应用1:随机取值
]# x=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
]# echo ${x:61:1}	// 验证索引号26+26+10-1=61的字符,大括号${变量}
	9
]# n=$[RANDOM%62]	// 环境变量RANDOM随机返回0到几万的正整数,中括号$[运算],${#x}返回变量x的长度
]# echo ${x:n:1}	// 每次取一个字符,n随机取值[0,61]
// 应用2:随机生成8位密码的脚本
]# vim /opt/passgen.sh
    #!/bin/bash
    x=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
	pass=					// 变量pass可以不赋予起始值,但使用source方式运行脚本时,会累加变量pass的字符串。
    for i in {1..8}
    do
		n=$[RANDOM%62]	// n随机取值[0,61]
        p=${x:n:1}		// 每次随机取x的一位字符
        pass+=$p		// 也可以写为"pass=$pass$p"
    done
    echo "password: $pass"
]# bash /opt/passgen.sh	
	password: nf81KqQ6
]# bash /opt/passgen.sh
	password: zob8CO6G

字符串的替换 ${/ /}

  • ${变量名/旧字符串/新字符串}:替换第一个旧字符串为新字符串。
    ${变量名//旧字符串/新字符串}:替换所有旧字符串为新字符串。
    • 新字符串为空则删除旧字符串。
    • 字符串替换不会改变变量本身的值,只是得到一串字符
// 字符串替换
]# a=12341234
]# echo ${a/3/78}
	127841234
]# echo ${a//3/78}
	1278412784
]# echo ${a/3/}
	1241234
]# echo ${a//3/}
	124124
]# b=				// 定义变量b为空
]# echo ${a//3/$b}
	124124
]# echo $a			// 字符串替换不会改变变量本身的值。
    12341234

字符串的删除 ${#}${%}

  • ${变量名#字符串}:掐头,从第一个字符开始匹配,(最短匹配)。
    ${变量名%字符串}:去尾,从最后一个字符开始匹配,(最短匹配)。
  • ${变量名##字符串}:掐头,从第一个字符开始匹配,(最长匹配)。
    ${变量名%%字符串}:去尾,从最后一个字符开始匹配,(最长匹配)。
// 字符串的删除,最短匹配
]# b=abcdefg
]# echo ${b#abc}	// 掐头abc
	defg
]# echo ${b%efg}	// 去尾efg
	abcd
]# echo ${b%e}		// 无效,去尾一定要从最后一个字符开始匹配
	abcdefg
]# echo ${b%e*}		
	abcd
]# echo ${b#c}		// 无效,掐头一定要从第一个字符开始匹配
	abcdefg
]# echo ${b#*c}		
	defg

// 最短匹配和最长匹配的区别
]# c=1234512345
]# echo ${c#*3}		// 匹配最短的"*3"
	4512345
]# echo ${c##*3}	// 匹配最长的"*3"
	45
]# echo ${c%4*}		// 匹配最短的"4*"
	12345123
]# echo ${c%%4*}	// 匹配最长的"4*"
	123
]# echo ${c#*2*}	// 掐头最末的"*"无效
	34512345
]# echo ${c%*2*}	// 掐头最初的"*"无效
	123451

// 应用:给文件改后缀名
]# touch /mnt/abc{01..10}.txt
]# cd /mnt
mnt]# ls
    abc10.txt  abc2.txt  abc4.txt  abc6.txt  abc8.txt
    abc1.txt   abc3.txt  abc5.txt  abc7.txt  abc9.txt
mnt]# vim /opt/change.sh 
    #!/bin/bash
    for i in $(ls *.txt)
    do
		mv $i ${i%txt}doc		// 删除字符串txt后拼接字符串doc
    done
mnt]# bash /opt/change.sh 
mnt]# ls
	abc10.doc  abc2.doc  abc4.doc  abc6.doc  abc8.doc
	abc1.doc   abc3.doc  abc5.doc  abc7.doc  abc9.doc

变量初始值的处理 ${:-}

  • ${变量名:-字符串}:当该变量存在且非空时返回变量本身的值,否则返回该字符串。不输出内容。
// 变量初始值的处理
]# a=123456
]# echo ${a:-test}
	123456
]# unset a
]# echo ${a:-test}
	test
// 应用:创建用户默认密码123456
]# vim /opt/moren.sh
	#!/bin/bash
	read -p "请输入用户名:" user
    [ -z $user ] && exit
    read -p "请输入密码(初始密码123456):" pass
    useradd $user
    echo "${pass:-123456}" | passwd --stdin $user

正则表达式 BRE、ERE

  • 正则表达式(Regular Expression):使用一串符号来描述有共同属性的数据

基本正则 BRE

  • 基本正则(BRE,Basic Regular Expressions)的使用:grepegrep、……

  • 基本正则表达式(写在双引号内)

    • ^:匹配行首。例如:^a 以a开头。
    • $:匹配行尾。例如:a$ 以a结尾。
    • ^$:匹配空行(空格行非空行)。
    • []:集合,匹配集合中的任意单个字符。例如:[abc]匹配 a 或 b 或 c 。
      [a-z]:匹配a到z的任意单个字符。
      [0-9a-zA-Z] :匹配所有数字字母的任意单个字符。
    • [^]:匹配集合外的任意单个字符。例如:[^abc]匹配 a、b、c之外的任意单个字符 。
      [^0-9a-zA-Z] :匹配所有数字字母之外的任意单个字符。
    • .:匹配任意单个字符,包括空格。例如:r..t 匹配r与t之间有任意两个字符的字符串。
    • *:匹配前一个字符任意次数(不允许单独使用)。例如:ro*t 匹配r与t之间有任意个o的字符串,可匹配rt。
    • .*:匹配任意内容,包括空格行和空行
    • \{n,m\}:匹配前一个字符n到m次。例如:ro\{1,3\}t 匹配r与t之间有1到3个o的字符串,即只匹配rot、root、rooot。
    • \{n,\}:匹配前一个字符n次及以上。例如:ro\{1,\}t 匹配r与t之间有1个及以上o的字符串,可匹配rot。
    • \{n\}:匹配前一个字符n次。例如:ro\{1\}t 匹配r与t之间有1个o的字符串,即只匹配rot。
    • \(字符串\):将字符串组合为整体;捕获并保留。例如:\(ro\)\{2\} 只匹配roro。
      • \1调用第一个保留项,\2调用第二个保留项,……
// 基本正则,grep 或 egrep
]# cat /opt/BRE.txt 
				// 空行,不含空格
	 			// 空格行,在终端中选择空行和空格行可看出区别
    a
    b
    c
    rt
    rot
    root
    rooot
    rotro
    roro
]# grep ^a /opt/BRE.txt				// 以a开头的行
	a
]# grep o$ /opt/BRE.txt				// 以o结尾的行
    rotro
    roro
]# grep ^$ /opt/BRE.txt				// 空行
				// 空行,不含空格
]# grep [abc] /opt/BRE.txt			// 含a或b或c的行
    a
    b
    c	
]# grep [a-c] /opt/BRE.txt			// 含a或b或c的行
    a
    b
    c
]# grep [^a-crt] /opt/BRE.txt		// 含abcrt之外字符的行,包括空格
	 			// 含有空格字符
    rot
    root
    rooot
    rotro
    roro
]# grep "r..o" /opt/BRE.txt			// r与o之间有任意两个字符的行
    rooot
    roro
]# grep "ro*r" /opt/BRE.txt			// r与r之间有任意个o的行
	roro
]# grep "r.*r" /opt/BRE.txt 		// r与r之间有任意内容的行
    rotro
    roro
]# grep "ro\{1,2\}t" /opt/BRE.txt	// r与t之间有1到2个o的行
    rot
    root
    rotro
]# grep "ro\{1,\}t" /opt/BRE.txt	// r与t之间有至少1个o的行
    rot
    root
    rooot
    rotro
]# grep "ro\{1\}t" /opt/BRE.txt		// 含rot的行
    rot
    rotro
]# grep "\(ro\)\{2\}" /opt/BRE.txt	// 含roro的行
	roro
]# grep "." /opt/BRE.txt			// 含任意单个字符(包括空格)的行
 				// 空格行
    a
    b
    c
    rt
    rot
    root
    rooot
    rotro
    roro
]# grep ".*" /opt/BRE.txt			// 含任意内容的行,包括空行和空格行
				// 空行
 				// 空格行
    a
    b
    c
    rt
    rot
    root
    rooot
    rotro
    roro

扩展正则 ERE

  • 扩展正则(ERE,Extended Regular Expressions)的使用:grep -E(ERE)、egrep、……

  • 扩展正则表达式(写在双引号内)

    • (字符串):将字符串组合为整体;捕获并保留\1调用第一个保留项,\2调用第二个保留项,……
    • {n,m}:匹配前一个字符n到m次。
    • {n,}:匹配前一个字符n次及以上。
    • {n}:匹配前一个字符n次。
    • +:匹配前一个字符最少一次。相当于{1,}
    • ?:匹配前一个字符最多一次。相当于{0,1}
    • |:或者。例如:root|sh$表示字符串root或以sh结尾。
    • \b(boundary):单词边界,不能有数字字母下划线。例如:the\bthe\> 表示e是单词右边界,\bthe\<the 表示t是单词左边界,\bthe\b\<the\> 表示单词the。
      • 区别:$是行结尾,\>是单词右边界。
    • \w(word):匹配数字、字母、下划线
    • \s(space):匹配空格和制表符
    • \d(digit):匹配数字。相当于[0-9],但\d兼容性不好,grep -P "\d" 内容
    • \t(tabulation character):表示一个制表符。
// 扩展正则,grep -E 或 egrep
]# cat /opt/EXT.txt
    rotrot
    rt
    rot
    root		// 两个o
    rooot		// 三个o
	roooot		// 四个o
    bash
]# egrep "ro{2,3}t" /opt/EXT.txt	// r与t之间有2到3个o的行
    root
    rooot
]# egrep "ro{2,}t" /opt/EXT.txt		// r与t之间至少2个o的行
    root
    rooot
    roooot
]# egrep "ro{2}t" /opt/EXT.txt		// 含root的行
	root
]# egrep "(rot){2}" /opt/EXT.txt	// 包含rotrot的行
	rotrot
]# egrep "ro+t" /opt/EXT.txt 		// r与t之间至少1个o的行,可写为ro{1,}t
    rotrot
    rot
    root
    rooot
    roooot
]# egrep "ro?t" /opt/EXT.txt 		// r与t之间至多1个o的行,可写为ro{0,1}t
    rotrot
    rt
    rot
]# egrep "root|sh$" /opt/EXT.txt 	// 含root或以sh结尾的行
    root
    bash

]# cat /opt/THE.txt
    atheb
    the_
    _the
    :the
    the:
    the
]# egrep "the" /opt/ext.txt
    atheb
    the_
    _the
    :the
    the:
    the
]# egrep "\bthe" /opt/ext.txt		// t左边不含数字字母下划线
	// 也可以写作 egrep "\<the" /opt/ext.txt
    the_
    :the
    the:
    the
]# egrep "the\b" /opt/ext.txt		// e右边不含数字字母下划线
	// 也可以写作 egrep "the\>" /opt/ext.txt
    _the
    :the
    the:
    the
]# egrep "\bthe\b" /opt/ext.txt
	// 也可以写作 egrep "\<the\>" /opt/ext.txt
	:the
    the:
    the

匹配IP地址的正则

// 匹配IP地址的正则表达式
// 250-255
25[0-5]
// 200-249
2[0-4][0-9]
// 0-199
1?[0-9]?[0-9]

// 0-255
25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9]

// 前24位
(25[0-5]\.|2[0-4][0-9]\.|1?[0-9]?[0-9]\.){3}

// 32位
(25[0-5]\.|2[0-4][0-9]\.|1?[0-9]?[0-9]\.){3}(25[0-5]|2[0-4][0-9]|1?[0-9]?[0-9])

sed

  • 流式编辑器(Stream Editor,sed命令):非交互式对文档进行增删改查等操作。逐行处理,并将处理结果输出到屏幕

sed命令

  • 有输出内容的前置命令 | sed [选项] '条件指令'sed [选项] '条件指令' 文件路径
    • 选项

      • -n:屏蔽默认输出。sed命令默认输出读取的内容
      • -i:修改源文件,没有输出。sed命令默认不修改源文件。与选项-E连用时只写作-Ei
      • -r-E(ERE):支持扩展正则。sed命令默认只支持基本正则
      • -e(expression):指定要执行的编辑命令,一条sed命令指定多个编辑命令可以使用-e
        • sed [选项] -e '条件指令1' -e '条件指令n' 文件路径
    • 条件(也叫定址符,写在单引号内,指令前面):指定符合条件的行,省略默认所有行

      • /正则表达式/:查找内容。
      • $:最后一行。
      • 行号:直接写在指令前面。
        行号!:除该行之外的所有行。
        • m:第m行。
          m!:第m行之外的所有行。
        • m,n:第m到n行。
          m,n!:第m到n行之外的所有行。
        • m,+n:第m行以及其后的n行。共n+1行。
          m,+n!:第m行以及其后的n行之外的所有行。
        • m~n:第m行以及每次步进n行。
          m~n!:第m行以及每次步进n行之外的所有行。
        • mp;np:输出第m行和第n行。
          md;nd:删除第m行和第n行,也就是输出第m行和第n行之外的所有行。
    • 指令(写在单引号内,同指令多指令用 ; 隔开)

      • =:输出行号

        • '条件=',选项-n-E
      • p(print):输出整行(可理解为查找)。

        • '条件p',选项-n-E
      • d(delete):删除整行,会输出其余所有行,可看作p的相反指令。

        • '条件d',选项-E-i
      • s(substitute):替换部分,替换为空是删除旧字符串。

      • '条件s/旧字符串/新字符串/flags标记',选项-n-E-i

        • flags标记项:数字(行内第几个匹配项)、g(行内所有匹配项)、p(输出匹配行)
      • a(append):行下追加整行内容

        • '条件a 内容',选项-E-i
      • i(insert):行上添加整行内容

        • '条件i 内容',选项-E-i
      • c(replace):替换整行内容

        • '条件c 内容',选项-E-i
p输出
  • p(print):输出整行(可理解为查找)。
    • '条件p',选项-n-E
// sed p输出。'条件p'
]# head -5 /etc/passwd | cat -n > /opt/user.txt 
]# cat /opt/user.txt
         1  root:x:0:0:root:/root:/bin/bash
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed 'p' /opt/user.txt							// 指令'p'默认输出所有行
         1  root:x:0:0:root:/root:/bin/bash			// 指令处理结果
         1  root:x:0:0:root:/root:/bin/bash			// 默认输出,输出读取的内容
         2  bin:x:1:1:bin:/bin:/sbin/nologin		// 逐行处理
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
             
]# sed -n '1p' /opt/user.txt			// 选项-n屏蔽sed默认输出,条件指令'1p'是输出第1行
		 1  root:x:0:0:root:/root:/bin/bash
]# sed -n '1!p' /opt/user.txt 			// 输出第1行之外的所有行
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
             
]# sed -n '2p;4p' /opt/user.txt			// 输出第2行和第4行
		 2  bin:x:1:1:bin:/bin:/sbin/nologin
		 4  adm:x:3:4:adm:/var/adm:/sbin/nologin
             
]# sed -n '2,4p' /opt/user.txt			// 输出第2到4行
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
]# sed -n '2,4!p' /opt/user.txt 		// 输出第2到4行之外的所有行
         1  root:x:0:0:root:/root:/bin/bash
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
             
]# sed -n '2,+3p' /opt/user.txt			// 输出第2行以及其后3行,即2345行
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed -n '2,+3!p' /opt/user.txt 		// 输出第2行以及其后3行之外的所有行,即1行
	     1  root:x:0:0:root:/root:/bin/bash
             
]# sed -n '1~2p' /opt/user.txt			// 输出第1行以及每次步进2行,即第135行
         1  root:x:0:0:root:/root:/bin/bash
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed -n '1~2!p' /opt/user.txt 		// 输出第1行以及每次步进2行之外的所有行,即24行
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
             
]# sed -n '/root/p' /opt/user.txt		// 条件指令'/root/p'是输出含有root字符串的行(输出结果没有颜色标记)
	     1  root:x:0:0:root:/root:/bin/bash
??
]# sed -n '!/root/p' /opt/user.txt
]# sed -nE '/bash$|dae/p' /opt/user.txt	// 选项-E是支持扩展正则
         1  root:x:0:0:root:/root:/bin/bash
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
]# sed -n '$p' /opt/user.txt			// 条件指令'$p'是输出最后一行
	     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
=输出行号
  • =:输出行号
    • '条件=',选项-n-E
// sed =输出行号。'条件='
]# head -5 /etc/passwd | cat -n > /opt/user.txt 
]# cat /opt/user.txt
         1  root:x:0:0:root:/root:/bin/bash
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed -n '=' /opt/user.txt 			// 指令'='默认输出所有行的行号
    1
    2
    3
    4
    5
]# sed -n '2,+3=' /opt/user.txt 		// 输出第二行以及其后三行的行号
    2
    3
    4
    5
]# sed -n '1~2=' /opt/user.txt 			// 输出第一行以及每次步进两行的行号
    1
    3
    5
]# sed -n '$=' /opt/user.txt 			// 条件指令'$='是输出最后一行的行号
	5
]# sed -n '/bash$/=' /opt/user.txt		// 条件指令'/bash$/='是输出以bash字符串结尾的行的行号
	1
]# sed -nE '/bash$|dae/=' /opt/user.txt	// 选项-E支持扩展正则
    1
    3
d删除
  • d(delete):删除整行。
    • '条件d',选项-E-i
// sed d删除。'条件d'
]# head -5 /etc/passwd | cat -n > /opt/user.txt 
]# cat /opt/user.txt
         1  root:x:0:0:root:/root:/bin/bash
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed '1d' /opt/user.txt				// 条件指令'1d'是删除第1行
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed '1!d' /opt/user.txt				// 删除第1行之外的所有行
    	 1  root:x:0:0:root:/root:/bin/bash

]# sed '2d;4d' /opt/user.txt			// 删除第2行和第4行
         1  root:x:0:0:root:/root:/bin/bash
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed -n '2p;4p' /opt/user.txt			// 输出第2行和第4行
		 2  bin:x:1:1:bin:/bin:/sbin/nologin
		 4  adm:x:3:4:adm:/var/adm:/sbin/nologin

]# sed '2,4d' /opt/user.txt				// 删除第2到4行
         1  root:x:0:0:root:/root:/bin/bash
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed '2,4!d' /opt/user.txt			// 删除第2到4之外的所有行
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin

]# sed '2,+3d' /opt/user.txt			// 删除第2行以及其后3行,即2345行
     	 1  root:x:0:0:root:/root:/bin/bash
]# sed '2,+3!d' /opt/user.txt			// 删除第2行以及其后3行之外的所有行,即1行
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

]# sed '1~2d' /opt/user.txt				// 删除第1行以及每次步进2行,即第135行
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
]# sed '1~2!d' /opt/user.txt			// 删除第1行以及每次步进2行之外的所有行,即第246行
         1  root:x:0:0:root:/root:/bin/bash
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

]# sed '/root/d' /opt/user.txt			// 删除含有root字符串的行
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed -E '/bash$|dae/d' /opt/user.txt	// 选项-E是支持扩展正则
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed '$d' /opt/user.txt				// 删除最后一行
         1  root:x:0:0:root:/root:/bin/bash
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
]# cat /opt/user.txt
         1  root:x:0:0:root:/root:/bin/bash
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed -Ei '/bash$|dae/d' /opt/user.txt	// 选项-i是修改源文件,与选项-E连用只能写作-Ei
]# cat /opt/user.txt
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
s替换部分
  • s(substitute):替换部分,替换为空是删除旧字符串。
    • '条件s/旧字符串/新字符串/[flags标记]',选项-n-E-i
      • flags标记项:数字(行内第几个匹配项,省略默认第一个)、g(行内所有匹配项)、p(输出匹配行)。
      • 注意:旧字符串和新字符串均是正则表达式
      • 新字符串使用&代表旧字符串匹配到的全部内容。
      • 旧字符串使用(正则表达式)捕获并保留时,新字符串使用\数字来调用第数字个()的保留内容。
      • 注意:这里的 /// 也可以用其它符号或空格代替,只需要前后一致就行,例如:条件/s?旧字符串?新字符串?[flags标记]
// sed s替换。'条件s/旧字符串/新字符串/flags标记'
]# cat /opt/num.txt				
    2017 2011 2018
    2017 2017 2024
    2017 2017 2017
]# sed 's/2017/1009/' /opt/num.txt 				// 指令s默认替换所有行的第一个匹配项。替换所有行的第一个2017为1009
    1009 2011 2018
    1009 2017 2024
    1009 2017 2017
]# sed 's/2017/1009/2' /opt/num.txt 			// 最后数字2是指令s的标记项,代表行内第二个匹配项。替换所有行的第二个2017
    2017 2011 2018
    2017 1009 2024
    2017 1009 2017
]# sed '2s/2017/1009/' /opt/num.txt 			// 替换第二行的第一个2017
    2017 2011 2018
    1009 2017 2024
    2017 2017 2017
]# sed '2s/2017/1009/2' /opt/num.txt 			// 替换第二行的第二个2017
    2017 2011 2018
    2017 1009 2024
    2017 2017 2017
]# sed 's/2017/1009/g' /opt/num.txt 			// g是指令s的标记项,代表行内所有匹配项。替换所有行的所有2017
    1009 2011 2018
    1009 1009 2024
	1009 1009 1009
]# sed '/2024/s/2017/1009/g' /opt/num.txt   	// 替换含2024字符串的行的所有2017
    2017 2011 2018
    1009 1009 2024
    2017 2017 2017
]# sed -n '/2024/s/2017/1009/p' /opt/num.txt 	// 屏蔽默认输出,p是指令s的标记项,代表输出匹配行。
	1009 2017 2024
]# sed -n '/2024/s/2017/1009/gp' /opt/num.txt 	// 标记项pg连用。替换含2024字符串的行的所有2017并输出
	1009 1009 2024
]# cat /opt/num.txt 
    2017 2011 2018
    2017 2017 2024
    2017 2017 2017
]# sed -i 's/2017/1009/2' /opt/num.txt			// 选项-i表示修改源文件
]# cat /opt/num.txt 
    2017 2011 2018
    2017 1009 2024
    2017 1009 2017

// 将第一行"/bin/bash"替换为"/sbin/sh"
]# sed '1s/\/bin\/bash/\/sbin\/sh/' /opt/user.txt
	// 只要使用三个同样的符号就行:sed '1s#/bin/bash#/sbin/sh#' /opt/user.txt
    // 也可以用空格:sed '1s /bin/bash /sbin/sh ' /opt/user.txt 
     1  root:x:0:0:root:/root:/sbin/sh
     2  bin:x:1:1:bin:/bin:/sbin/nologin
     3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
     4  adm:x:3:4:adm:/var/adm:/sbin/nologin
     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
s替换与()保留
  • (内容1)(内容2):捕获并保留内容1、内容2。内容支持正则表达式
    \数字:调用第数字个()的保留内容。
// s替换与()保留
// 原内容"成绩 人名",现需要"人名 成绩"
]# cat /opt/score.txt
    100 laowang
    98 gangge
    59 laoniu
]# sed -Ei 's/([0-9]+)(\s+)([a-z]+)/\3\2\1/' > /opt/score.txt
]# cat /opt/score.txt
    laowang 100
    gangge 98
    laoniu 59
a、i、c 整行内容
  • a(append):行下追加整行内容
    • '条件a 内容',选项-E-i
  • i(insert):行上添加整行内容
    • '条件i 内容',选项-E-i
  • c(replace):替换整行内容
    • '条件c 内容',选项-E-i
// sed a行下追加,i行上添加、c替换整行
]# head -5 /etc/passwd | cat -n > /opt/user.txt
]# cat /opt/user.txt
         1  root:x:0:0:root:/root:/bin/bash
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed 'a 1009' /opt/user.txt			// 指令a默认所有行下追加整行内容
         1  root:x:0:0:root:/root:/bin/bash
    1009
         2  bin:x:1:1:bin:/bin:/sbin/nologin
    1009
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
    1009
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
    1009
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
    1009
]# sed '2a 1009' /opt/user.txt			// 条件指令'2a 1009'表示第二行下追加整行内容1009
         1  root:x:0:0:root:/root:/bin/bash
         2  bin:x:1:1:bin:/bin:/sbin/nologin
    1009
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed '/sh$/a 1009' /opt/user.txt		// 条件指令'/sh$/a 1009'表示以sh结尾的行下追加整行内容1009
         1  root:x:0:0:root:/root:/bin/bash
    1009
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed '/sh$/i 1009' /opt/user.txt		// 条件指令'/sh$/i 1009'表示以sh结尾的行上添加整行内容1009
    1009
         1  root:x:0:0:root:/root:/bin/bash
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
         5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed '$i 1009' /opt/user.txt			// 条件指令'$i 1009'表示末行上添加整行内容1009
         1  root:x:0:0:root:/root:/bin/bash
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
    1009
	     5  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# sed '$c 1009' /opt/user.txt			// 条件指令'$c 1009'表示末行上替换整行内容1009
         1  root:x:0:0:root:/root:/bin/bash
         2  bin:x:1:1:bin:/bin:/sbin/nologin
         3  daemon:x:2:2:daemon:/sbin:/sbin/nologin
         4  adm:x:3:4:adm:/var/adm:/sbin/nologin
    1009

脚本搭建httpd服务(82端口)

// 脚本搭建httpd服务并监听82端口
/* 如果搭建过httpd网站,停止服务、卸载软件并删除所有配置
 * 搭建httpd网站服务:装包、配置、起服务(selinux宽松0)
 */
虚拟机(192.168.166.2):
]# vim /opt/httpd82.sh
    #!/bin/bash
    systemctl stop httpd.service &> /dev/null					// 停止服务
    yum -y remove httpd &> /dev/null							// 卸载软件
    rm -rf /etc/httpd											// 删除配置文件父目录
    yum install -y httpd > /dev/null							// 装包
    sed -i '/^Listen 80$/s/80/82/' /etc/httpd/conf/httpd.conf	// 配置,修改端口号
    setenforce 0												// 更改selinux为宽松0
    systemctl restart httpd > /dev/null							// 启服务
    systemctl enable httpd > /dev/null							// 开机自启服务
    echo "sed-test~~" > /var/www/html/index.html				// 编写默认网页文件
]# bash /opt/httpd82.sh
]# curl 192.168.166.2
	sed-test~~
]# ss -ntulp | grep httpd		// 查看httpd服务端口号
]# systemctl stop firewalld		// 如果其它机器访问本机httpd服务,本机需要关闭防火墙

脚本提取用户名和密码串

// 提取用户名和密码串
// 找出使用bash的所有用户名,并找到对应的加密密码串,最后输出"用户名 --> 加密密码串"
]# vim /opt/userpass.sh
    #!/bin/bash
/* 找出使用bash的所有用户名
 * 写法一:
 * grep "/bash" /etc/passwd | sed 's/:.*//'		// :.*代表冒号:加上任意内容
 * 写法二:
 * sed -n '/bash$/s/:.*//p' /etc/passwd  		// 选项-n屏蔽默认输出,标记项p输出匹配行 
 */
	user=$(sed -n '/bash$/s/:.*//p' /etc/passwd)
    for name in $user
    do   
        pass=$(grep $name /etc/shadow)
        pass=${pass#*:}
        pass=${pass%%:*}
		echo "$i --> $pass" >> /opt/userpass.txt
	done
]# bash /opt/userpass.sh
]# cat /opt/userpass.txt
    root --> $6$raxwu9kly0mSJFAz$pIAfYLjLE/wyeCnmxBD/hts/pCFEOgUw582IlB3WOoEO9.IxfEgGPHBjwqfY813nONvn9z0kM7N7eWliVzHkH0
    test166-2 --> $6$xy2FCve.Z.jv5m4s$UcX0Yz1gntkzvD.roeQv2f0NRHhvG/vkcODWB.VpIm9.1HeBNnEEZSw.sZlyvnHy5GCLf2SamcmPaEliBEsRb/
    zhangsan --> $6$uTVKaSFA90adc35J$XK15WTIlDGu5HZq1ySlHb.VwslvXok2VSXnPr2D6PDzHohGJkYgl653KRlHfRou3ZBOw1/v7EWjkHm1nxiuCV.
    wangwu --> $6$4hM6Xp7ReRctbL/4$.baJp758Gvi64B9yyt8IoLNwzVIQFRYRRXZGsUoMrpiO0OBvmZNuHwghKP/Y/FYRWEZQc.r4m95cal0did1hU0

awk

  • awk是创造者Aho Weinberger Kernaighan三个人的首字母缩写。
  • 相比grep和sed,awk是精确搜索
  • awk同sed一样也是逐行处理,并将处理结果输出到屏幕
  • Shell编程三剑客:grep、sed、awk

awk命令

  • 有输出内容的前置命令 | awk [选项] '条件{指令 内置变量}'awk [选项] '条件{指令 内置变量}' 文件路径
    • 选项
      • -F:定义分隔符。支持扩展正则。
        • awk命令默认分隔符为空格或制表符,按照分隔符分列(字段)
        • -F分隔符-F[分隔符1分隔符2](分隔符1或分隔符2)
    • 条件用法同sed命令的条件(定址符):指定符合条件的行,省略默认所有行。awk命令默认支持扩展正则
    • 指令(写在{}内,多指令用 ; 隔开)
      • print:输出。
      • 当有条件且纯print指令即 条件{print} 时,可以简写为条件
    • 内置变量(写在{}内)
      • $数字(第几列/字段)、$0(所有列/字段,整行)、NR(Number of Records,行号)、NF(Number of Fields,列数/字段数)、$NF(最后一列/字段)
      • , :表示一个空格字符。
      • "字符串" :该字符串是常量。
      • \t(tabulation character):表示一个制表符,写在""内。
    • 注意:awk [选项] '条件{指令 内置变量}' 文件路径 :这里使用单引号'内容'时,内容可以支持内置变量,但不支持调用awk语句外的变量var,解决方法使用三个单引号 ''' $var'''
// awk命令
]# cat /opt/awktest.txt
    hello world
    welcome to Beijing
]# awk '{print}' /opt/awktest.txt				// 指令print表示输出,无条件时默认所有行
	// 无条件时不可省略{print}
    hello world
    welcome to Beijing
]# awk '/to/{print}' /opt/awktest.txt			// 含to的行
	// 有条件且纯{print}时,可以简写为:awk '/to/' /opt/awktest.txt
	welcome to Beijing
]# awk '/to/{print $0}' /opt/awktest.txt		// 含to的行的所有列
	welcome to Beijing
]# awk '/to/{print $1,$3}' /opt/awktest.txt		// 含to的行的第1列和第3列
	welcome Beijing
]# awk '/to/{print NR}' /opt/awktest.txt		// 含to的行的行号
	2
]# awk '/to/{print NF}' /opt/awktest.txt		// 含to的行的列数
	3
// 选项-F定义分隔符,默认分隔符是空格
]# awk '/^root/{print}' /etc/passwd				// 以root开头的行
	root:x:0:0:root:/root:/bin/bash
]# awk '/^root/{print NF}' /etc/passwd			// 该行默认分隔符为空格或制表符的列数
	1
]# awk -F: '/^root/{print NF}' /etc/passwd		// 该行分隔符为":"的列数
	7
]# awk -F/ '/^root/{print NF}' /etc/passwd		// 该行分隔符为"/"的列数
	4
]# awk -F[:/] '/^root/{print NF}' /etc/passwd	// 该行分隔符为":"或"/"的列数
	10
]# awk '/^root/{print $1}' /etc/passwd			// 该行分隔符为空格或制表符的第一列
	root:x:0:0:root:/root:/bin/bash
]# awk -F: '/^root/{print $1}' /etc/passwd		// 该行分隔符为":"的第一列
	root
# awk -F: '/^root/{print $1,$6}' /etc/passwd	// ","表示空格字符
	root /root
]# awk -F: '/^root/{print $1$6}' /etc/passwd	// 不使用","的效果
	root/root
]# awk -F: '/^root/{print $1"的家目录是:"$6}' /etc/passwd	// "字符串"
	root的家目录是:/root

// awk命令单引号的使用'条件{指令 内置变量}'
]# a="root"
]# echo $a
	root
]# awk -F: '/^$a/{print $1"的家目录是:"$6}' /etc/passwd		  // $a识别为字符串$a
]# awk -F: '/^'''$a'''/{print $1"的家目录是:"$6}' /etc/passwd  // '''$a'''识别为变量a,字符串root
	root的家目录是:/root

监控网卡的流量信息

// 监控网卡eht0的流量信息
]# ifconfig eth0
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 192.168.166.2  netmask 255.255.255.0  broadcast 192.168.166.255
        inet6 fe80::3dbe:e761:bedf:b1a8  prefixlen 64  scopeid 0x20<link>
        ether 52:54:00:81:4a:5a  txqueuelen 1000  (Ethernet)
        RX packets 11263  bytes 834102 (814.5 KiB)			// 实时接收的数据流量
        RX errors 0  dropped 34  overruns 0  frame 0
        TX packets 2893  bytes 643396 (628.3 KiB)			// 实时发送的数据流量
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

]# ifconfig eth0 | awk '/RX p/{print "eth0网卡接收数据流量"$5"字节"}'
	eth0网卡接收数据流量891169字节
]# ifconfig eth0 | awk '/TX p/{print "eth0网卡接收数据流量"$5"字节"}'
	eth0网卡接收数据流量682077字节
]# ifconfig eth0 | awk '/TX p/{print "eth0网卡接收数据流量"$5"字节"}'	
	eth0网卡接收数据流量682645字节

awk处理的时机

  • awk会逐行处理文本,支持在处理第一行之前做一些准备工作,以及在处理完最后一行之后做一些总结性质的工作。
    • BEGIN{}:读取文件前执行,在所有行之前处理,指令执行1次,叫做行前处理任务
    • {}:读取文件时执行,逐行处理,指令执行n次,叫做逐行处理任务
    • END{}:读取文件后执行,在所有行之后处理,指令执行1次,叫做行后处理任务
      • 三者可单独使用,也可以一起使用。在{}中定义的变量可以传递使用。
// awk处理的时机??
]# cat /opt/awktest.txt			// 准备四行内容
    a
    b
    c
    d
]# awk 'BEGIN{print "ok"}{print "abc"}END{print "end"}' /opt/awktest.txt	// 一起使用
    ok		// 读取文件前输出ok
    abc		// 逐行读取第一行输出abc
    abc		// 逐行读取第二行输出abc
    abc		// 逐行读取第三行输出abc
    abc		// 逐行读取第四行输出abc
    end		// 读取文件后输出end
]# awk 'BEGIN{print NR}{print NR}END{print NR}' /opt/awktest.txt	
    0		// 读取文件前,当前行号为0
    1		// 逐行读取第一行时的行号
    2		// 逐行读取第二行时的行号
    3		// 逐行读取第三行时的行号
    4		// 逐行读取第四行时的行号
    4		// 读取文件后,当前行号为最后一次读取的行的行号

统计用户信息

// 制作表格:用户名 UID 家目录,最后统计用户数。 
]# head -5 /etc/passwd > /opt/awkuser.txt
]# cat /opt/awkuser.txt 
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    adm:x:3:4:adm:/var/adm:/sbin/nologin
    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
]# awk -F: 'BEGIN{print "User\tUID\tHome"}{print $1"\t"$3"\t"$6}END{print "All",NR,"records."}' /opt/awkuser.txt
    User    UID     Home
    root    0       /root
    bin     1       /bin
    daemon  2       /sbin
    adm     3       /var/adm
    lp      4       /var/spool/lpd
    All 5 records.

// 统计使用bash解释器的用户数
]# awk 'BEGIN{x=0}/\/bash$/{x++}END{print "总共有"x"个用户使用bash解释器。"}' /etc/passwd
	总共有3个用户使用bash解释器。

awk处理的条件

  • $数字~/正则/:该数字列满足该正则的行。
    $数字!~/正则/:该数字列不满足该正则的行。

  • 数值1比较符数值2:符合该数值比较的行。

    • 比较符:==!=>>=<<=
  • 条件1&&条件2:同时满足条件1和条件2的行。注意:同条件测试逻辑与不一样。
    条件1||条件2:满足条件1或条件2的行。注意:同条件测试逻辑或不一样。

  • 运算比较符数值:运算结果符合数值比较的行。

// awk处理的条件
]# head -5 /etc/passwd > /opt/awkuser.txt
]# cat /opt/awkuser.txt 
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    adm:x:3:4:adm:/var/adm:/sbin/nologin
    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

// 第一类,~与!~
]# awk -F: '$6~/root/{print}' /opt/awkuser.txt			// 输出第6列满足包含root的行
	root:x:0:0:root:/root:/bin/bash
]# awk -F: '$6!~/root/{print}' /opt/awkuser.txt			// 输出第6列不满足包含root的行
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
    adm:x:3:4:adm:/var/adm:/sbin/nologin
    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

// 第二类,数值比较
]# awk -F: '$3==2{print}' /opt/awkuser.txt				// 输出第3列数值等于2的行
	daemon:x:2:2:daemon:/sbin:/sbin/nologin
]# awk -F: '$3<=2{print}' /opt/awkuser.txt				// 输出第3列数值小于等于2的行
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
    daemon:x:2:2:daemon:/sbin:/sbin/nologin
]# awk -F: 'NR<=2{print}' /opt/awkuser.txt				// 输出行号小于等于2的行
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin

// 第三类,&&与||
]# awk -F: 'NR<=2&&$7~/bash/{print}' /opt/awkuser.txt	// 输出行号小于等于2并且第7列包含bash的行
	root:x:0:0:root:/root:/bin/bash
]# awk -F: 'NR<=2||$7~/bash/{print}' /opt/awkuser.txt	// 输出行号小于等于2或第7列包含bash的行
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
]# awk -F: 'NR>4&&$7~/bash/{print}' /opt/awkuser.txt	// 输出行号大于4并且第7列包含bash的行
]# awk -F: 'NR>4||$7~/bash/{print}' /opt/awkuser.txt	// 输出行号大于4或第7列包含bash的行
    root:x:0:0:root:/root:/bin/bash
    lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin

// 第四类,数学运算
]# awk -F: 'NR%2==0{print NR,$0}' /opt/awkuser.txt		// 输出偶数行的行号和所有列
    2 bin:x:1:1:bin:/bin:/sbin/nologin
    4 adm:x:3:4:adm:/var/adm:/sbin/nologin
awk 'NR%2==0{print NR,$0}' user   #在条件中使用运算,找到将行号除以2余数等于0的行,然后输出该行的行号和所有列,相当于输出偶数行

awk数组、for、if

  • 数组是一个可以存储多个值的变量。

    • 定义数组:数组名[索引]=元素值 ,变量不赋值默认初始值为0。
      • 索引只能使用数字或"字符串",元素赋值也只能使用数字或"字符串"
    • 调用数组:数组名[索引]
  • awk命令中大括号内的 for语句:for(变量名 in 数组名){print 变量名,数组名[变量名]}

    • 注意:awk数组使用for语句,只能循环数组的索引
  • awk命令中大括号内的 if语句:if(数组名[变量名]>=3){print 变量名,数组名[变量名]}

    • 注意:if语句的变量名是数组的索引
// awk数组
// 索引只能使用数字或"字符串"
]# awk 'BEGIN{a=10;a=20;print a}'
	20
]# awk 'BEGIN{a["abc"]=10;a[1]="xyz";print a["abc"],a[1]}'	
	10 xyz
]# awk 'BEGIN{a[1]=10;a[a]=20;print a[1],a[a]}'				// 索引a识别为数组a
	awk: 命令行:1: 致命错误:试图在标量环境中使用数组“a”

// 元素赋值只能使用数字或"字符串"
]# awk 'BEGIN{a["*"]=10;a[1]=bs;print a["*"],a[1]}'			// 元素赋值bs识别无效
	10 
]# awk 'BEGIN{a["*"]=10;a[1]=a;print a["*"],a[1]}'			// 元素赋值a识别为数组a
	awk: 命令行:1: 致命错误:试图在标量环境中使用数组“a”
]# awk 'BEGIN{a["*"]=10;a[1]="a";print a["*"],a[1]}'		// 元素赋值"a"识别为字符串
	10 a

// 遍历数组,索引只能使用数字或"字符串",元素赋值也只能使用数字或"字符串"
]# awk 'BEGIN{a[1]=1;a[b]=b;a[_]=_;a["test"]=test;for(i in a){print i,a[i]}}'		// 索引字母下划线失效

    test 
    1 1
]# awk 'BEGIN{a[1]=1;a["b"]=b;a["_"]=_;a["test"]=test;for(i in a){print i,a[i]}}'	// 索引使用数字或"字符串"
    _ 
    b 
    test 
    1 1
]# awk 'BEGIN{a[1]=1;a["b"]="b";a["_"]="_";a["test"]="test";for(i in a){print i,a[i]}}' // 元素赋值使用数字或"字符串"
    _ _
    b b
    test test
    1 1

// 变量++,统计同行出现次数
]# cat /opt/shuzu.txt
    abc
    xyz
    abc
    opq
    xyz
    abc
/* 逐行处理任务{a[$1]++},变量不赋值默认初始值为0
 * 第一行:a["abc"]++,得到a["abc"]=1		
 * 第二行:a["xyz"]++,得到a["xyz"]=1
 * 第三行:a["abc"]++,得到a["abc"]=2
 * 第四行:a["opq"]++,得到a["opq"]=1
 * 第五行:a["xyz"]++,得到a["xyz"]=2
 * 第六行:a["abc"]++,得到a["abc"]=3
 */
]# awk  '{a[$1]++}END{print a["abc"]}' /opt/shuzu.txt
	3
]# awk  '{a[$1]++}END{print a["xyz"]}' /opt/shuzu.txt
	2
]# awk  '{a[$1]++}END{print a["opq"]}' /opt/shuzu.txt
	1
]# awk '{a[$1]++}END{for(i in a){print i,a[i]}}' /opt/shuzu.txt
    opq 1			// opq出现了1次
    abc 3			// abc出现了3次
    xyz 2			// xyz出现了2次
// 查找重复的行,即1次以上
]# awk '{a[$1]++}END{for(i in a){if(a[i]>1){print i,a[i]}}}' /opt/shuzu.txt
    abc 3
    xyz 2

二维数组


Arr[2,79]=78	
Arr[2,79]表示一个二维数组,第一个索引为2,第二个索引为79。这里是给Arr[2,79]赋值为78。这就意味着在数组Arr中的第2行、第79列的位置上的元素值为78

SUBSEP是awk中的一个特殊变量,用于分隔多维数组的不同维度。默认情况下,SUBSEP的值是`\034`。
因此Arr[2,79]可以写为Arr[2 SUBSEP 79]或Arr[2 \034 79]

统计web访问量??

  • /var/log/httpd/access_log:httpd服务的访问日志。

  • sort命令选项:

    • -n(numeric):按照数字排序。
    • -r(reverse):降序。
    • -k(key):指定按照第几字段排序(字段从1起计,多字段使用,分隔)。
    • t:指定字段分隔符。
// 统计web访问量
]# awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' /var/log/httpd/access_log
    192.168.166.1 3
    192.168.166.2 5
    192.168.166.3 2
    192.168.166.240 2
]# awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' /var/log/httpd/access_log | sort -nr -k 2
    192.168.166.2 5
    192.168.166.1 3
    192.168.166.3 2
    192.168.166.240 2

统计登陆系统失败的IP

  • /var/log/secure:系统安全日志。
// 统计以root登陆系统失败的IP
]# awk '/Failed password for root/{ip[$11]++}END{for(i in ip){print i,ip[i]}}' /var/log/secure
    192.168.166.2 2
    192.168.166.3 3
// 统计以root登陆系统失败3次以上的IP
]# awk '/Failed password for root/{ip[$11]++}END{for(i in ip){if(ip[i]>=3){print i,ip[i]}}}' /var/log/secure

监控系统信息

  • uptime:截取top动态的第一行。
  • free:查看内存(Mem物理内存和Swap虚拟内存)。
  • df -h:查看挂载点详细信息。
  • who:查看当前登录本机的用户数。
  • ??
// 监控系统信息
]# cat /opt/test.sh
    #!/bin/bash
    while:
    do
        clear
        uptime | awk '{print "CPU的15分钟平均负载量是:"$NF}'
        free -h | awk '/^Mem/{print "剩余内存容量是:"$4}'
        df -h | awk '/\/$/{print "根分区剩余容量是:"$4}'
        who | awk 'END{print "目前使用服务器人数是:"NR}'
        awk 'END{print "账户总数量是"NR"个"}' /etc/passwd
        rpm -qa | awk 'END{print "安装的rpm软件总数是"NR"个"}'
        sleep 3
    done

脚本文件 /etc/rc.d/rc.local

  • /etc/rc.d/rc.local(run command)脚本文件,会在Linux系统启动时自动执行。
    • 如果使用,需要赋予可执行权限chmod +x /etc/rc.d/rc.local

括号

命令

`cmd`$(cmd) 均可以用来优先执行命令并获取命令的标准输出,区别在于
	1.套用时``要加转义符:cmd1 `cmd2 \`cmd3\``,也可以 cmd1 $(cmd2 $(cmd3))
	2.``可以在多种unix shell中执行,$()可以在部分unix shell中执行

(cmd1;cmd2;cmd3){ cmd1;cmd2;cmd3;} 可以用于连接并执行多个命令,区别在于:
	1.()新开一个新shell环境执行命令,()内的变量和命令对()外的命令和变量没有影响。
	  {}在本shell环境中执行命令,{}内的变量和命令对{}外的命令和变量有影响。
	2.()内的最后一条命令的;可加可不加。
	  {}内的最后一条命令的;不可省略。
	3.{}的左括号和第一条命令之间必须有空格。

运算与判断

echo {a,b,c}.txt	# 不管存不存在,输出a.txt b.txt c.txt
echo {a..c}.txt		# 不管存不存在,输出a.txt b.txt c.txt
echo [a-c].txt		# 只输出存在的文件


$[exp]$((exp)) 				# 整数运算,进行不同进制运算时自动转为十进制,不会自动打印结果
	echo $((2#10*4))			# 8。2#10表示二进制数10
expr exp						# 整数运算,自动打印结果,但遇到特殊符号需要使用转义符
	expr 5 \* 2					# 10。特殊符号*需要转义
echo "exp" | bc					# 小数运算
	echo "scale=3;5/3" | bc		# 1.666

((exp)) 	# 也可以进行算术比较(不能进行字符串比较),括号内的变量可以省略$
((exp)) 	# 可以对变量进行定义或重新赋值,且之后脚本全部有效。

[ 条件表达式 ][[ 条件表达式 ]]
	1.重定向等字符在[]内是重定向,在[[]]内是比较符号。
    2.[[]]会返回一个退出状态码,真则`$?`为0,假则`$?`为1。`perror 数字`查看退出码含义
	3.[[]]支持&&||><
    4.[[]]支持算术扩展,[]不支持:可以[[ 1+2 -eq 3 ]]5.[[]]支持字符串的模式匹配,[]不支持,但注意不能加引号:[[ "hello" == hell? ]] 为真,[ "hello" == "hell?" ] 为假。
	6.[[]]支持正则:[[ exp =~ 正则表达式 ]]

正则

常见元字符:
	\d		# [0-9]
	\D		# \d取反,[^0-9]
	\w		# [A-Za-z0-9_]
	\W		# \w取反,[^A-Za-z0-9_]
	\s		# 空白字符
	\S		# \s取反
	\b		# 单词边界
	\B		# \b取反

(exp)			:普通捕获组,分配组号,sed或awk中使用`\组号`调用
(?<组名>exp)	  :命名捕获组,分配组名,sed或awk中使用`\k<组名>`调用
(?:exp)			:不捕获组。只是用于限定操作的范围
(?#注释)		  :注释

.*(?=exp)		:(零宽正向先行断言)后面是exp的内容,即匹配exp前面的内容
(?<=exp).*		:(零宽正向后行断言)前面是exp的内容,即匹配exp后面的内容
.*(?!exp)		:(零宽负向先行断言)后面不是exp的内容
(?<!exp).*		:(零宽负向后行断言)前面不是exp的内容

数组

数组名=(元素 元素 元素)				  # 定义数组,索引从0开始。例如 array=(a b c),则array[0]=a
    ${数组名[索引]}					# 调用数组元素
    特殊的,${数组名[0]} 可以简写为${数组名}
    ${#数组名[索引]}		 			# 该元素的长度
    特殊的,${#数组名[0]} 可以简写为${#数组名}
    ${!数组名[@]}${!数组名[*]}		# 列出所有索引
    ${数组名[*]}${数组名[@]}			# 遍历所有元素
    特殊地,"${数组名[*]}" 代表一个字符串
    
${数组名[*]}${数组名[@]} 均可以用于遍历数组的所有元素,但特殊地,"${数组名[*]}" 代表一个字符串,具体区别见下列两个示例
示例一:
]# a=(1 2 3)							# 定义一个数组
]# for i in "${a[*]}";do echo $i;done	# "${数组名[*]}" 代表一个字符串
	1 2 3
]# for i in ${a[*]};do echo $i;done		# ${数组名[*]} 代表一个数组
    1
    2
    3
]# for i in "${a[@]}";do echo $i;done	# "${数组名[@]}" 代表一个数组
    1
    2
    3
]# for i in ${a[@]};do echo $i;done		# ${数组名[@]} 代表一个数组
    1
    2
    3
示例二:
]# cat test.sh
#!/bin/bash
a=(1 2 3)
function test(){
	for i in $1
	do
		echo $i
	done
}
test "${a[*]}"		# 将"1 2 3"一个字符串作为一个位置参数传递给函数test()
echo "---"
test ${a[*]}		# 将1 2 3三个元素作为三个位置参数传递了函数test()
]# sh test.sh
	1
	2
	3
	---
	1

变量

${var}			# 直接引用变量
${!var}			# 间接引用变量
${#var}			# 变量的长度

${var:-string}			# 若变量var为空,则返回string,否则输出var值。不影响原值
${var:=string}			# 若变量var为空,则返回string并赋值给var,否则输出var值。影响原值。
${var:+string}			# 若变量var不为空,则返回string,否则输出空。不影响原值
${var:?string}			# 若变量var不为空,则把string输出到标准错误中并从脚本中退出,否则输出var值。

${var:起始索引0:长度}		# 切片,不影响原值

${var/old/new}${var//old/new}	# 替换(第一个,全部),不影响原值

${var#pattern}${var##pattern}	# 去头(最短,最长),不影响原值
${var%pattern}${var%%pattern}	# 去尾(最短,最长),不影响原值

注意:数组也可以进行切片和替换
${数组名[*]:2:2}
${数组名[*]/4/9}

${!var}${!数组名[@]}${!数组名[*]}

${!var}						# 间接引用变量
${!数组名[@]}${!数组名[*]} 	# 列出所有索引

# ${!var}
示例一:
]# fruit="apple"
]# apple="red"
]# echo ${fruit}		# apple
]# echo ${!fruit}		# red
示例二:
]# cat test.sh
#!/bin/bash
for i in `seq $#`
do
	echo $i
	echo ${!i}
done
]# sh test.sh a b
    1
    a
    2
    b

# ${!数组名[@]}、${!数组名[*]}
示例一:
]# test=(apple banana cherry)
]# echo ${!test[@]}
	0 1 2
]# echo ${!test[*]}
	0 1 2

]# cat test.sh
#!/bin/bash
aaa=111
bbb=222
ccc=333
test=(aaa bbb ccc)
for i in ${!test[@]}
do 
    echo ${test[i]}				# ${test[0]}、${test[1]}、${test[2]}
done
echo "---"
for i in ${!test[@]}
do 
    echo ${!test[i]}			
done
echo "---"
for i in ${!test[@]}
do 
    eval echo \$${test[i]}		# eval将参数当做一个命令来执行,而不仅仅是作为一个字符串
done
]# sh test.sh
    aaa
    bbb
    ccc
    ---
    111
    222
    333
    ---
    111
    222
    333

end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值