SheLL#Typora-SheLL笔记

SheLL-1认识bash

1 bash 的登录主机欢迎信息: /etc/issue, /etc/motd

当你登录到系统之前,想了解这个系统的一些简单的硬件及软件版本信息等可以做如下设置:

/etc/issue //修改这个文件的内容即可
SheLL#Typora-SheLL笔记_第1张图片

/etc/motd //编辑这个文件,会在用户登录之后看到一些欢迎信息

2 Bash 快捷键

Ctrl + a :移到命令行首
Ctrl + e :移到命令行尾
Ctrl + f :按字符前移(右向)
Ctrl + b :按字符后移(左向)

Ctrl + d :删除光标处的字符
Ctrl + h :删除光标前的字符

Ctrl + t :交换光标处和之前的字符

Ctrl + u :从光标处删除至命令行首
Ctrl + k :从光标处删除至命令行尾

Ctrl+c   :终止目前在进行的命令

Ctrl+d   :输入结束(EOF),例如写邮件结束的时候



BASH 的通配符

通配符如下表:

SheLL#Typora-SheLL笔记_第2张图片

Linux 系统的语系会影响字符的排序和匹配
查看本系统的语系使用命令 locale
SheLL#Typora-SheLL笔记_第3张图片

[dmtsai@study ~]$ LANG=C              >==由于与编码有关,先设置语系一下

范例一:找出 /etc/ 下面以 cron 为开头的文件名
[dmtsai@study ~]$ ll -d /etc/cron*    >==加上 -d 是为了仅显示目录而已

范例二:找出 /etc/ 下面文件名“刚好是五个字母”的文件名
[dmtsai@study ~]$ ll -d /etc/?????    >==由于 ? 一定有一个,所以五个 "?"  就对了

范例三:找出 /etc/ 下面文件名含有数字的文件名
[dmtsai@study ~]$ ll -d /etc/*[0-9]*  >==记得中括号左右两边均需 *

范例四:找出 /etc/ 下面,文件名开头非为小写字母的文件名:
[dmtsai@study ~]$ ll -d /etc/[^a-z]*  >==注意中括号左边没有 *

范例五:将范例四找到的文件复制到 /tmp/upper 中
[dmtsai@study ~]$ mkdir /tmp/upper; cp -a /etc/[^a-z]* /tmp/upper

BASH 特殊符号

特殊符如下:

#   注释
\   转移符号,可以将具有特殊意义的符号变为一般字符
;   连续下达命令的分隔符
&   将一个工作置于后台

``   其内的命令可以先执行,建议用$( )取代
()  在中间为子shell的起始与结束
{}  在中间为命令区块的组合

SheLL-2变量

一、什么是变量

简单说就是让一个特定的字符串代表不固定的内容

yy=123
yy是变量的名字,123是变量的值

echo $yy //查看变量的值

就是用一个简单的好记的字符串,来取代比较复杂或者容易变动的数据

二、变量的设定

设定规则:

  1. 变量名与变量内容以一个等号 = 的连结,且等号两边不能有空格。如下所示:
myname=XiguaTian
  1. 变量名称只能是英文字母与数字,但是开头字符不能是数字,如下为错误:
2myname=xiguatian
  1. 变量内容若有空格符可使用双引号 " 或单引号' 将变量内容组合起来,但

• 双引号内的特殊字符如 $! 等,可以保有原本的特性,如下所示:

var="lang is $LANG"

#则
echo $var

#可得
lang is en_US

感叹号 ! 不可以在shell命令行里直接在双引号里使用,但是可以在脚本中使用。

• 单引号内的特殊字符则仅为一般字符 (纯文本),如下所示:

var='lang is $LANG' 

# 则
echo $var

#可得
lang is $LANG 
  1. 可用转意符 \ 将特殊符号(如 [Enter], $, \, 空格符, 等)变成一般字符;
  2. 在一串命令中,还需要藉由其他的命令提供的信息,可以使用反单引号 `命令` 或$(命 令)(推荐这种)。特别注意,那个反引号 是键盘上方数字键1` 左边那个按键,而不是单引号!

例如想要取得
核心版本的内容:

version=$(uname -r)
# 再
echo $version

#可得
4.9.125-linuxkit
  1. 增加变量的内容时,则可用 "$变量名称"字符串${变量}字符串 (推荐)累加内容,如下所示:
PATH="$PATH":/home/bin

PATH=${PATH}:/home/bin
  1. 若该变量需要在其他子程序执行,则需要以 export 来使变量变成环境变量:
export PATH
  1. 通常大写字符为系统默认变量,自定义变量可以使用小写字符,方便判断 (纯粹个人习惯) ;

  2. 取消变量的方法为使用 unset :unset 变量名称 例如取消 myname 的设定:

unset myname

三、 环境变量

这种变量是会影响bash环境操作的,会在真正进入系统前由一个bash程序读入到系统中。通常都环境变量的名字以大写字符命名。

常见环境变量

PATH HOME MAIL SHELL PWD USER UID ID 等。

RANDOM 随机数

echo  $RANDOM

PS1:(提示字符的设置)

\d :可显示出“星期 月 日”的日期格式,如:“Mon Feb 2”
\H :完整的主机名称。如 www.sharkyun.com
\h :仅取主机名称在第一个小数点之前的名字, www
\t :显示时间,为 24 小时格式的“HH:MM:SS”
\T :显示时间,为 12 小时格式的“HH:MM:SS”
\A :显示时间,为 24 小时格式的“HH:MM”
@ :显示时间,为 12 小时格式的“am/pm”样式
\u :目前使用者的帐号名称,如“dmtsai”;
\v :BASH 的版本信息
\w :完整的工作目录名称,由根目录写起的目录名称。但主文件夹会以 ~ 取代;
\W :利用 basename 函数取得工作目录名称,所以仅会列出最后一个目录名。
# :下达的第几个指令。
$ :提示字符,如果是 root 时,提示字符为 # ,否则就是 $

读取环境变量的值

echo $HOME

列出shell环境下的所有环境变量及其内容

  • env // env 是 environment (环境) 的简写,所有的环境变量(包含自定义的环境表里)

  • set // 列出系统中所有的变量,包括自定义的变量

  • export 变量名 // 使自定义的变量 成为 环境变量,环境变量可以被向下继承

子进程 仅会继承父 shell 的环境变量, 不会继承父 shell 的自定义变量

name=shark
export  name

bash
echo $name

并且 export 声明的环境变量只能被其子 shell 继承使用,不能被 父 shell 继承使用

[root@kube-master py3]# bash           # 打开一个子 shell
[root@kube-master py3]# export a=hello # 在 子 shell 声明一个环境变量
[root@kube-master py3]# bash           # 在子 shell 中再打开一个 子 shell
[root@kube-master py3]# echo $a        # 变量可以生效
hello
[root@kube-master py3]# exit           # 退出 子 shell 的 子 shell
exit
[root@kube-master py3]# exit           # 退出 子 shell
exit
[root@kube-master py3]# echo $a        # 在 当前 shell 中, 其子 shell 声明的环境变量是无效的

[root@kube-master py3]# 

bash 的环境变量文件

  • longin shell
    取得shell时需要完整的登入流程;特点是登入时需要用户帐号和密码

  • non-login shell
    取得shell时不需要再次输入帐号和密码的情况下,所得到的 shell

longin shell 会读取以下两个文件:

  1. ·/etc/profile· :这是系统整体设定,最好不要修改
  2. ·~/.bash_profile~/.bash_login~/.profile` :属于个人的配置文件

/etc/profile 会主动依序调用以下脚本文件:

  • /etc/inputrc :定义快捷键
  • /etc/profile.d/*sh :定义bash操作接口颜色、语系、命令别名等
  • etc/locale.conf :定义系统的默认语系

bash 在读完 /etc/profile 后,接下来会读取以下3个文件,且只会读去一个,会按照以下顺序优先读取

~/.bash_profile //会调用 ~/.bashrc
也会有新的环境变量在下面的文件中被添加
~/.bash_login
~/.profile

最终,~/.bashrc 才是最后被读入到系统环境中的文件

让这些环境变量文件中的变量等设置及时在当前 shell 终端中生效,有下两种方式

source ~/.bashrc 
或者
.    ~/.bashrc

no-longin shell 当取得 no-longin shell 时,该 shell 仅会读取 ~/.bashrc 文件 而~/.bashrc 最后又会调用 /etc/bashrc

/etc/bashrc 的作用:

• 依据不同的UID定义出 umask
• 依据不同的UID定义出提示符(就是PS1变量)
• 呼叫 /etc/profile.d/*.sh 的设定

其他的相关配置文件

/etc/man.config
这个文件最重要的就是定义了MANPATH 这个变量,它定义了man page 的路径;在以tarball的方式安装软件时有用
~/.bash_history
历史命令记录文件;记录的数量与HISTFILESIZE变量有关。在/etc/profile
~/.bash_logout
记录了当我注销bash后,系统再帮我做完什么动作后才离开的。

四、预定义变量

预定义的特殊变量有着特殊的含义,用户不可以更改,所有的预定义变量都由 $ 符号和另外一个符号组成,常用的预定义特殊变量如下:

$!   上一个后台命令对应的进程号
$?   上一个命令的退出状态,为十进制数字,如果返回为0,则代表执行成功,则否为不成功。
$$   当前的进程号PID

以上变量请配合 echo 使用,例如:

echo $! 
echo $$ 
echo $?

五、从键盘的输入给变量赋值: read

read

[root@www ~]# read [-pt] variable
选项不参数:
-p :后面可以接提示字符!
-t :后面可以接等待的『秒数!』,比如 -t 5 提示用户输入信息时间是 5 秒钟
       超过 5 秒钟,程序就继续向下运行

例如:

#vi read.sh 
read -p “请输入你的姓名” name
echo "你的姓名是: $name"

六、变量内容的删除、取代与替换

准备工作,防止误操作导致变量失效。

先让小写的 path 自订变量设置的与 PATH 内容相同
[dmtsai@study ~]$ path=${PATH}
[dmtsai@study ~]$ echo ${path}
/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/shark/bin

• 删除

  1. 删除最后一个
echo ${path%:*}

% 从字符串的后面开始进行匹配删除,但匹配到第一个时就结束匹配。

:* 就是需要进行匹配的字符串 , : 是普通字符串 * 是通配符,代表任何数量的任意字符串。

所以下面的红色字体的字符串将会匹配后进行删除

/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/shark/bin

  1. 删除第一个

接着上例的变量进行操作

echo ${path#*:}

# 是从前面开始匹配删除,匹配到道理和上面的 % 同样的道理

${path#*:} 就是 从变量值的最前面开始匹配,直到找到第一个符匹配合格的字符就结束

所以下面的红色字体会被匹配后删除

/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/shark/bin

  • 取代和替换

SheLL#Typora-SheLL笔记_第4张图片

以上不需要都掌握,需要记住下面这个

var=${str:-expr}

strvar 可以是相同的字符串。比如 inpu_date=${inpu_date:-expr}

expr 可以是任意合法的表达式,比如一个字符串,一个子 shell 的命令 $(date +%F)

示例,生产中会遇到经常查数据库的操作,一般查询最多的就是查当天的数据,但也有别的日期的数据。现在就希望当用户没有输入时,就给一个默认值,这里给的是当天的日期,假如输入的具体的日期,就按照用户输入的日期查询。

  • 初始版本

假设今天是 20200202

read  -p  "输入日期>:"   input_date

input_date=${input_date:-20200202}

echo "当前的日期是:${input_date}"

执行示例效果

[root@sharkyun script]# sh read-defaut-value.sh
输入日期>:
当前的日期是:20200202
[root@sharkyun script]# sh read-defaut-value.sh
输入日期>:20200201
当前的日期是:20200201
[root@sharkyun script]#
  • 进化版本
read  -p  "输入日期>:"   input_date

input_date=${input_date:-$(date +%F)}

echo "当前的日期是:${input_date}"

七、时间运算

// 计算 3 小时之后是几点几分
date +%T -d '3 hours'

// 任意日期的前 N 天,后 N 天的具体日期
date +%F -d "20190910 1 day"
date +%F -d "20190910 -1 day"

// 计算两个日期相差天数, 比如计算生日距离现在还有多少天
d1=$(date +%s -d 20180728)
d2=$(date +%s -d 20180726)
echo $((   (d1-d2)     /   86400          ))

# 输出 
2

SheLL-3shell脚本

一、什么是 shell script

shell script 是利用 shell 的功能所写的一个“程序 (program)”,这个程序是使用纯文本文件,将一些 shell 的语法与指令(含外部指令)写在里面, 搭配正则表达式、管道命令与数据流重导向等功能,以达到我们所想要的处理目的。

简单的说,也就是可以使用一个普通的文本,写上多条 shell 命令,一起执行这些命令。
但是,在这个文件中可以添加一些逻辑判断什么的。

二、shell script 规范

  • script 的功能;
    script 的版本信息;
    script 的作者与联络方式;
    script 的版权宣告方式;
    script 的 History (历史纪录);
    script 内较特殊的指令,使用“绝对路径”的方式来下达;
    script 运行时需要的环境变量预先宣告与设置。

三、简单脚本练习

vim show-name.sh

#!/bin/bash
# Program:
#    User inputs 2 integer numbers; program will cross these two numbers.
# History:
# 2015/07/16    VBird    First release
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
export PATH
echo -e "You SHOULD input 2 numbers, I will multiplying them! \n"
read -p "first number:  " firstnu
read -p "second number: " secnu
total=$((   ${firstnu}   *    ${secnu}  ))
echo -e "\nThe result of ${firstnu} x ${secnu} is ==> ${total}"

四、执行脚本方式 (source script, sh script, ./script)

source script

父进程中执行脚本中代码

sh script

子进程中执行脚本中的代码,相当于打开了一个子 shell ,一个全新的环境。

五、script 的默认变量

位置变量

位置变量也属于预定义的变量
位置变量真对于脚本来说
位置变量是根据命令出现在命令行上的位置来确定的变量,在shell调用过程中,会按参数所在的位置进行调用。

命令(shell脚本名) 参数1 参数2 参数3

$0    $1     $2     $3  ...$9

例如:

wc /etc/passwd /etc/group

例如:

编辑一个任意文件(这里是 position-arg.sh),写入如下内容

#!/bin/bash
echo "脚本名称是 $0"
echo "第一个参数是 $1"
echo "第二个参数是 $2" 
echo "第三个参数是 $3"

此脚本地执行时,使用sh执行即可

sh    position-arg.sh  a b c  d  e

假如脚本中使用了一个位置变量,但是执行脚本的时候,传入了多个参数,程序并不会报错。

但是执行的时候只传入了一个参数,同样不会报错,并且 $2 不会被赋值

SheLL#Typora-SheLL笔记_第5张图片

特殊变量(重要)

$# :代表后接的参数“个数”
$@ :代表 "$1" "$2" "$3" "$4" 之意,每个变量是独立的(用双引号括起来);
$* :代表"$1c$2c$3c$4",其中 c 为分隔字符,这个分为空白键, 所以本例中代表 "$1 $2 $3 $4"之意。

#!/bin/bash
echo "脚本名称$0"
echo "第一个参数是 $1"
echo "第一个参数是 $2"
echo "第一个参数是 $3"
echo '所有的参数分别是 $@'  "$@"
echo '所有的参数分别是 $*'  "$*"
echo "一共有参数 $# 个"

六、set

set -u

当脚本中遇到未定义的变量时,默认是忽略。

有时候这并不是开发者想要的。

假如有未定义的变量,应该报错,并且终止脚本继续运行。

set -u 就可以办到

示例

#!/usr/bin/env bash
set -u

echo $a
echo shark

执行报如下错误

$ bash set.sh
set.sh: line 13: a: unbound variable

分析

SheLL#Typora-SheLL笔记_第6张图片

注意

set -u

a=`ls |grep 'asfdasdf'`
echo "|$a|"

# -u  只针对未定义的变量有效
# 这种情况,变量的值会是一个空字符串
# 所以脚本会继续执行

set -e

set -e 的作用就是:
在脚本执行中,有任何命令的返回值是非 0 的情况,则正在运行的脚本则会退出。

设置 -e 之前

[root@kube-master set]# cat set-e-before.sh
#!/bin/sh

foo
echo "继续执行"

执行结果

[root@kube-master set]# sh set-e-before.sh
set-e-before.sh: line 3: foo: command not found
继续执行

设置 -e 之后

解决办法是在脚本中设置 set -e

[root@kube-master set]# cat set-e-after.sh
#!/bin/sh

set -e

foo
echo "继续执行"

执行结果

[root@kube-master set]# sh set-e-after.sh
set-e-after.sh: line 5: foo: command not found

但是,对于一组含有管道的命令无效,比如:

#!/usr/bin/env bash
set -e
foo | echo "shark"    # 注意这里有管道符
echo  "程序会继续运行"

执行结果

[root@kube-master set]# sh set-e.sh
shark
set-e.sh: line 2: foo: command not found   # 这是报错信息
程序会继续运行

foo 不是 shell 中的命令,执行会报错,但是其后面有个管道,最终管道后的 echo 命令执行成功了,这种情况下 脚本会继续执行。

解决办法是使用下面的 set -o pipefail
set -o pipefail

需要和 set -e 配合使用。
如果设置,如果管道中的所有命令都成功退出,整条命令的返回值才是 0。否则返回非 0
默认情况下禁用此选项

示例脚本内容

# 设置 set -o pipefail 后,此时脚本就会终止运行
set -e
set -o pipefail
foo |echo ''
echo shark

执行效果

[root@kube-master set]# sh set-e-pipefail.sh

set-e-pipefail.sh: line 4: foo: command not found

SheLL-4判断表达式

test

SheLL#Typora-SheLL笔记_第7张图片

示例:

$ touch a.txt
$ test -e a.txt;echo $?
0                             # 测试成功,命令返回值为 0

$ test -e s.txt;echo $?
1                             # 测试失败,命令返回值为 非 0

$ test -f a.txt;echo $?
0

$ test -d a.txt;echo $?
1


SheLL#Typora-SheLL笔记_第8张图片

示例:

$ test -r a.txt; echo $?
0

$ test -x a.txt; echo $?
1

$ test -w a.txt; echo $?
0

$ test -u a.txt; echo $?     # 判断 a.txt 文件是否具有 SUID 属性
1

$ cat a.txt                        # 查看 a.txt ,此文件内容为空

$ test -s a.txt; echo $?    # 判断 a.txt 文件中有内容
1                                      # 命令返回值为 1 ,说明文件中没有内容

$ echo "123" > a.txt

$ test -s a.txt; echo $?
0


SheLL#Typora-SheLL笔记_第9张图片

示例:

$ touch b.txt

$ ls -l a.txt
-rw-r--r--  1 shark  staff  4 12 17 22:59 a.txt

$ ls -l b.txt
-rw-r--r--  1 shark  staff  0 12 17 23:05 b.txt

$ test a.txt -nt b.txt; echo $?  # 判断 a.txt 是否比 b.txt 新
1                                            # 返回 1, 表示判断表达式不成立

$ test b.txt -nt a.txt; echo $?
0

SheLL#Typora-SheLL笔记_第10张图片

$ test a.txt -ef a-hard.txt; echo $?
0


SheLL#Typora-SheLL笔记_第11张图片

示例:

$ test 10 -eq 20; echo $?
1

$ n1=10

$ n2=20

$ test $n1 -eq $n2; echo $?
1

$ test $n1 -lt $n2; echo $?
0

$ test $n1 -ne  $n2; echo $?
0


SheLL#Typora-SheLL笔记_第12张图片

注意:
这里的 string 可以是实际的字符串,也可以是一个变量
这里说的字符串是否为 0 的意思是 字符串的长度是否为 0

示例

$ test   -z  ''; echo $?      # 空字符串
0

$ test  -z  ' '; echo $?      # 含有一个空格的字符串
1

$ test  !  -z ' '; echo $?   # 判断含有一个空格的字符串,其长度为非 0 的字符串, 空格也算是字符串。
0

$ test -z ${name}; echo $?   # 变量未定义,shell 中认为其长度为 0
0

$ name=shark

$ test -z ${name}; echo $?
1

$ age=''                               # 定义变量,并且赋值为空字符串

$ test  -z  ${age}; echo $?    # shell 中,被赋值为空字符串的变量长度也为 0
0

注意:

再次强调一下, 在 shell 中,以下两种情况,变量的长度均视为 0

  • 1.变量未定义
    1. 变量定义了,但赋值为空字符串,比如 a='' , b=""
[root@kube-master script]# name=shark
[root@kube-master script]# age=shark
[root@kube-master script]# test $name == $age ;echo $?
0
[root@kube-master script]# test $name != $age ;echo $?
1
[root@kube-master script]#

SheLL#Typora-SheLL笔记_第13张图片

示例

SheLL#Typora-SheLL笔记_第14张图片


判断符号 []

[ -z "${HOME}" ] ; echo $?

必须要注意中括号的两端需要有空白字符来分隔! 假设我空白键使用“□”符号来表示,那么,在这些地方你都需要有空白键:

SheLL#Typora-SheLL笔记_第15张图片

  • 在中括号 [] 内的每个元素之间都需要用空格来分隔;
  • 在中括号内的变量,最好都以双引号括号起来;

错误示范

# 定义变量
name="shark ops"

# 开始测试值是否相等
[ ${name} == "xiguatian" ]

会报如下错误信息:

bash: [: too many arguments

之前的错误写法 [ ${name} == "xiguatian" ] 的,会变成这样 [ shark ops == "xiguatian" ]

正确写法应该写成这样 [ "${name}" == "xiguatian" ] 的, 会变成这样 [ "shark ops" == "xiguatian" ]

有没有引号很重要

[root@shark ~]# n=""
[root@shark ~]# [ $n ] && echo "为真" || echo "为假"
为假
[root@shark ~]# n=" "
[root@shark ~]# [ $n ] && echo "为真" || echo "为假"
为假
[root@shark ~]# [ "$n" ] && echo "为真" || echo "为假"
为真

也可以使用 ! 进行取反

[root@shark ~]# [ ! "$n" ] && echo "为假" || echo "为真"
为真
[root@shark ~]# n=
[root@shark ~]# [ ! "$n" ] && echo "为假" || echo "为真"
为假
[root@shark ~]#

中括号中使用 多条件判断

[root@a0652462c802 ~]# [ "${name}" == "xi"  -o  10 -eq 10 ];echo $?
0
[root@a0652462c802 ~]# [ "${name}" == "xi" ] || [ 10 -eq 10 ] ;echo $?
0
[root@a0652462c802 ~]# [ "${name}" == "xi" ] && [ 10 -eq 10 ] ;echo $?
1
[root@a0652462c802 ~]# [ "${name}" == "xi"  -a  10 -eq 10 ];echo $?
1
[root@a0652462c802 ~]#

判断一个变量的值 的长度是否为零

[root@a0652462c802 ~]# name=""
[root@a0652462c802 ~]# [ "$name" ];echo $?  # 变量值的长度不为0
1
[root@a0652462c802 ~]# [ ! "$name" ];echo $?  # 变量值的长度为0
1
[root@a0652462c802 ~]# name="dd"
[root@a0652462c802 ~]# [ "$name" ];echo $?  # 变量值的长度不为0
0
[root@a0652462c802 ~]# name="     "
[root@a0652462c802 ~]# [ "$name" ];echo $?  # 变量值的长度不为0
0
[root@a0652462c802 ~]# [ ! -z "$name" ];echo $?  # 变量值的长度不为0
0
[root@a0652462c802 ~]#
[root@a0652462c802 ~]# echo ${#name}   # 计算变量值的长度
5
[root@a0652462c802 ~]#

SheLL-5逻辑结构

一、if 判断

if … then … fi

单层
如果 是真的 那么 …

if [ 条件判断式 ];then
   当条件判断式成立时,可以进行的命令。
fi   # 结束这个判断语句结构

示例:
提示输入一个整数,程序判断输入的值是否大于 18

风格 1

#!/usr/bin/env sh

read -p "请输入一个整数:" num

if [ "$num" -gt 18 ];then
   echo "你输入的数值大于 18"
fi

echo "hello..."

风格2

#!/usr/bin/env sh

read -p "请输入一个整数:" num

if [ "$num" -gt 18 ]
then
    echo "你输入的数值大于 18"
fi

echo "hello..."

if … then … else…fi

多重、复杂条件判断式
如果 是真的 那么 … 否则 …

一颗红心,两手准备

if [ 条件判断式 ]; then
    当条件判断式成立时,可以进行的指令工作内容;
else
    当条件判断式不成立时,可以进行的指令工作内容;
fi

示例:

判断用户输出的字符串
输入 y
就输出文本内容 “script is running…”
否则输出文本内容 “STOP”

SheLL#Typora-SheLL笔记_第16张图片


if … elif … elif … else … fi

多个条件判断 (if … elif … elif … else) 分多种不同情况执行

if [ 条件判断式一 ]; then
    当条件判断式一成立时,可以进行的指令工作内容;
elif [ 条件判断式二 ]; then
    当条件判断式二成立时,可以进行的指令工作内容;
else
    当条件判断式一与二均不成立时,可以进行的指令工作内容;
fi

注意
elif 也是个判断式,因此出现 elif 后面都要接 then 来处理!但是 else 已经是最后的没有成立的结果了, 所以 else 后面并没有 then

示例

通过判断输入的整数,判断是什么常用服务

#!/usr/bin/env sh
read -p "请输入一个常用的服务默认端口号:"  port

if [ "$port" -eq 80 ];then
    echo "HTTP 服务"
elif [ "$port" -eq 3306 ];then
    echo "Mysql 服务"
elif [ "$port"  -eq  21 ] || [ "$port"  -eq  20 ];then
    echo "FTP 服务"
elif [ "$port" -eq 22 ];then
    echo "SSHD 服务"
elif [ "$port"  -eq 23 ];then
    echo "Telnet 服务"
else
   echo "未知服务"
fi

在判断中支持正则

使用双中括号 [[ ]]

  • 匹配 使用 =~

比如: [[ "$val" =~ [a-z]+ ]]

=~ 表示 匹配
[a-z]+ 是正表达式,不需要用任何引号,用了引号就会被识别为普通字符串

  • 不匹配 使用 !

比如: [[ ! "$val" =~ [0-9]+ ]]

示例

#!/usr/bin/env   sh
name=shark

if [[ "$name"  =~  [a-z]+ ]];then
   echo "ok"
fi


if [[ ! "$name" =~ [0-9]+ ]];then
   echo  "good"
fi

嵌套

#!/usr/bin/env sh
read -p "请输入一个常用的服务默认端口号:"  port

if [[ "$port" =~ [0-9]+ ]];then

    if [ "$port" -eq 80 ];then
        echo "HTTP 服务"
    elif [ "$port" -eq 3306 ];then
        echo "Mysql 服务"
    elif [ "$port"  -eq  21 ] || [ "$port"  -eq  20 ];then
        echo "FTP 服务"
    elif [ "$port" -eq 22 ];then
        echo "SSHD 服务"
    elif [ "$port"  -eq 23 ];then
        echo "Telnet 服务"
    else
       echo "未知服务"
    fi
else
    echo "nmber"
fi

二、 利用 case … esac 判断

基本语法

case  $变量名称 in    <==关键字为 case ,还有变量前有钱字号
  "第一个变量的值")     <==每个变量内容建议用双引号括起来,关键字则为小括号 )
	程序段
	;;               <==每个类别结尾使用两个连续的分号来处理!
  "第二个变量的值")
	程序段
	;;
  *)                 <==最后一个变量内容都会用 * 来代表所有其他值
	不包含第一个变量内容与第二个变量内容的其他程序运行段
	exit 1
	;;
esac                 <==反过来写,结束当前语句结构!

运维工具箱

case

========================================================

模式还可以是多个

#!/bin/bash
cat << EOF
m|M) show memory usages;
d|D) show disk usages;
q|Q) quit
EOF
read -p "Your choice" choice
case $choice in
m|M)
        free -m
        ;;
d|D)
        df -h
        ;;
q|Q)
        exit
        ;;
*)
        echo "Invalid input"
        ;;
esac

三、while … do … done (不确定的循环)

while [ condition ]  ==>中括号内的状态就是判断式
do                   ==> do 是循环的开始!
    命令或者代码
    命令或者其他逻辑语句
done                 ==> done 是循环的结束

计数

每次循环体执行完,while 都会检查条件是否为真,为真继续循环,否则终止循环。

n=0
while [ "$n" -lt 5 ]
do
   let n++
   echo "$n"
done

读文件

while read line
do
    echo $line
done < /etc/passwd

每次循环, 都会把 /etc/passwd 中的每一个行内容 赋值给变量 line

read

[root@sharkyun ~]# read id name age
1 shark 18
[root@sharkyun ~]# echo $age
18
[root@sharkyun ~]# echo $id  $name
1 shark

[root@sharkyun ~]# old_ifs=$IFS; IFS=','
[root@sharkyun ~]# read id name age
2, xiguatian, 20
[root@sharkyun ~]# echo "$id| $name| $age"
2|  xiguatian|  20
[root@sharkyun ~]# export IFS=$old_ifs
[root@sharkyun ~]# read id name age
2, xiguatian, 20
[root@sharkyun ~]# echo "$id| $name| $age"
2,| xiguatian,| 20
[root@sharkyun ~]# read id name age
2,xiguatian,20
[root@sharkyun ~]# echo "$id| $name| $age"
2,xiguatian,20| |

while read

$ cat db.sql
1 shark 18
2 xiguatian 20

$ cat  while-read-m.sh
while read id name age
do
    echo "$id | $name | $age"
done < db.sql

$ sh while-read-m.sh

1 | shark | 18
2 | xiguatian | 20

五、 for do done (固定的循环)

SheLL#Typora-SheLL笔记_第17张图片

var 是变量名
con1 con2 con3 是常量,就是具体的数据
也可以是一个已经被赋值的变量, 写法 ${v1} ${v2}

以上面的例子来说,这个 $var 的变量内容在循环工作时会动态的改变:

  1. 第一次循环时, $var 的内容为 con1 ;
  2. 第二次循环时, $var 的内容为 con2 ;
  3. 第三次循环时, $var 的内容为 con3 ;

for 循环中 变量的取值方式

a. 从一组字符串中取值

for  var     in    one    two    three   four   five 
do
    echo    "****************************"
    echo   '$var   is '    $var
done

b. 从位置变量中取值

for    var
do
    echo  '-----------------------------'
    echo   '$var   is '    $var
done

按照如下方式执行:
SheLL#Typora-SheLL笔记_第18张图片

c. 从累计变化的格式中取值

#!/bin/bash 

for    ((var=1;var<=10;var++))
do
    echo   "------------------------" 
    echo   '$var   is '    $var
done

d. 从命令结果中取值

#!/bin/bash
for   var   in   $(cat ip.txt)
do
    echo  " ------------------------" 
    echo   '$var   is '    $var
done

IFS

修改 for 循环中的分界符,默认是 空格

示例 a.txt 文件的内容

hello world name age
hello world name age
# 先保存原来的值
old_ifs=$IFS

# 设置分界符为 回车
IFS=$'\n'
for line in $(cat a.txt)
do
  echo $line
done

# 把变量的值回复成原来的状态
export IFS=$old_ifs

嵌套循环

for n in {1..3}
do
    for i in {a..e}
    do
        echo "外层循环的值$n--内层循环的值$i"
    done
    echo "----------------"
done

执行结果

SheLL#Typora-SheLL笔记_第19张图片

补充:数值运算

➜  ~ n=1
➜  ~ let n++
➜  ~ echo $n
2
➜  ~ (( n++ ))
➜  ~ echo $n
3
➜  ~ a=2
➜  ~ b=3
➜  ~ let f=a+b
➜  ~ echo $n
3
➜  ~ echo $f
5
➜  ~ let f = a + b     ==> 错误
zsh: bad math expression: operand expected at `='
➜  ~ let "f = a + b"
➜  ~ echo $f
5

六、break 和 continue

  • break 就是退出循环,循环不再继续了。假如是嵌套的循环,就是退出当前层级的循环。

  • continue 当执行 contniue 的时候,循环体内,continue 之后的代码不再执行,继续进行下一次循环

SheLL-6正则表达式

什么是正则表达式

简单的说,正则表达式就是处理字串的方法,他是以行为单位来进行字串的处理行为, 正则表达式通过一些特殊符号的辅助,可以让使用者轻易的达到“搜寻/删除/取代”某特定字串的处理程序!

正则表达式基本上是一种“表达式”, 只要工具程序支持这种表达式,那么该工具程序就可以用来作为正则表达式的字串处理之用。 例如 vi, grep, awk ,sed 等等工具,因为她们有支持正则表达式, 所以,这些工具就可以使用正则表达式的特殊字符来进行字串的处理。但例如 cp, ls 等指令并未支持正则表达式, 所以就只能使用 Bash 自己本身的通配符而已。

是 Linux 基础当中的基础,如果学成了之后,一定是“大大的有帮助”的!这就好像是金庸小说里面的学武难关:任督二脉! 打通任督二脉之后,武功立刻成倍成长!

关于语系

在英文大小写的编码顺序中,zh_TW.big5 及 C 这两种语系的输出结果分别如下:

LANG=C 时:0 1 2 3 4 ... A B C D ... Z a b c d ...z
LANG=zh_TW 时:0 1 2 3 4 ... a A b B c C d D ... z Z

SheLL#Typora-SheLL笔记_第20张图片

尤其要记住:

[:alnum:] 代表所有的大小写英文字符和数字 0-9 A—Z a-z
[:alpha:] 代表任意英文大小写字符  A-Z a-z
[:lower:] 代表小写字符       a-z
[:upper:] 代表大写字符        A-Z
[:digit:] 代表数字         0-9

练习示例文件

数据来源于鸟哥私房菜

"Open Source" is a good mechanism to develop programs.
apple is my favorite food.
Football game is not use feet only.
this dress doesn't fit me.
However, this dress is about $ 3183 dollars.
GNU is free air not free beer.
Her hair is very beauty.
I can't finish the test.
Oh! The soup taste good.
motorcycle is cheap than car.
This window is clear.
the symbol '*' is represented as start.
Oh!	My god!
The gd software is a library for drafting programs.
You are the best is mean you are the no. 1.
The world  is the same with "glad".
I like dog.
google is the best tools for search keyword.
goooooogle yes!
go! go! Let's go.
# I am VBird

匹配示例

SheLL#Typora-SheLL笔记_第21张图片

image.png


SheLL#Typora-SheLL笔记_第22张图片
输出:
查找 # 号开头的行


输出:
SheLL#Typora-SheLL笔记_第23张图片


SheLL#Typora-SheLL笔记_第24张图片

输出:
image.png


输出:

image.png

匹配英文句点 ·
SheLL#Typora-SheLL笔记_第25张图片


SheLL#Typora-SheLL笔记_第26张图片

输出:
SheLL#Typora-SheLL笔记_第27张图片


SheLL#Typora-SheLL笔记_第28张图片

输出:
image.png


SheLL#Typora-SheLL笔记_第29张图片

输出:

image.png


SheLL#Typora-SheLL笔记_第30张图片

输出:

SheLL#Typora-SheLL笔记_第31张图片


SheLL#Typora-SheLL笔记_第32张图片

输出:

image.png

匹配 2 个连续的 a 字符

image.png

匹配 2个以上连续的 a 字符

image.png

匹配 3 个以下连续的字符 a

image.png


进阶  grep

-A   n  把匹配成功行之后的n行也同时列出。 A 就是 after 的首字母
        就是 之后 的意思

-B   n  把匹配成功行之前的n行也同时列出。B 就是 before 的首字母
        就是 之前 的意思

范例:

显示 /etc/passwd 含有 mail 的行及其前2行和后 3 行

[root@e9818e4ea8b3 ~]# grep mail -B 2 -A3 /etc/passwd
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin

显示 目标行(这里是含有 mail 字符)的 各 3 行

grep mail -C  3 /etc/passwd

SheLL#Typora-SheLL笔记_第33张图片

只显示匹配到的字符

grep -o 'nologin' /etc/passwd

加上统计数量,匹配到的行数,而不是匹配到的个数

[root@shark ~]# grep root /etc/passwd
root:x:0:0:root:/root:/bin/bash
operator:x:11:0:operator:/root:/sbin/nologin
[root@shark ~]# grep root /etc/passwd -c
2
[root@shark ~]#

只要文件名

[root@shark ~]# grep -l 'nologin' /etc/*  2>> /dev/null
/etc/passwd
/etc/passwd-
[root@shark ~]#

递归查找,就是在一个目录下查找

[root@shark ~]# grep -l -r 'nologin' /etc/*  2>> /dev/null
/etc/pam.d/sshd
/etc/pam.d/login
/etc/pam.d/remote
/etc/passwd
/etc/passwd-
/etc/selinux/targeted/active/policy.linked
/etc/selinux/targeted/active/file_contexts
/etc/selinux/targeted/active/policy.kern
/etc/selinux/targeted/contexts/files/file_contexts.pre
/etc/selinux/targeted/contexts/files/file_contexts
/etc/selinux/targeted/contexts/files/file_contexts.bin
/etc/selinux/targeted/tmp/policy.linked
/etc/selinux/targeted/tmp/file_contexts
/etc/selinux/targeted/tmp/policy.kern
/etc/selinux/targeted/policy/policy.31
[root@shark ~]#

搜索 oo 但其前面不要有g

grep -n '[^g]oo'  regular_express.txt

注意:当搜索的行内含有符合搜索条件时后,此行就会忽略 明确不要的条件(就是明确不要的条件会无效),比如以上的例子就可能会搜索到下面的内容

3:tool is a good tool
8:goooooogle 

显示开头不是 英文字符的行

grep -n  ‘^[^[:alpha]]’ regular_repress.txt

符号 ^ 在 [] 内时是取反的意思,在 [] 之外是行首的意思
显示行首不是#和;的行

grep '^[^#;]' regular_repress.txt

找到以 . 结尾的行

grep -n  '\.$' regular_repress.txt

需要用 \ 进行转意

查找 开头是 g 和结尾也是 g ,中间的字符可有可无

grep -n   'g.*g' regular_repress.txt

. 代表一个任意字符
* 代表重复零到多个在 其前面的一个字符
.* 代表零个或多个任意字符

查找以a为开头的任意文件名
方法一:

通配符

ls -l a*

方法二:

ls |grep -n '^a.*'

列出 /etc 目录下的链接文件

ls -l /etc |grep '^l'

再统计一下多少个

ls -l /etc |grep -c '^l'

扩展正则

SheLL#Typora-SheLL笔记_第34张图片

关于 分组 小括号 () 的深入理解

写出匹配日期格式 YYYY-MM-DD 的正则表达式

[root@sharkyun ~]# echo "2019-12-30" |grep -E '[1-9][0-9]{3}-((0[1-9])|(1[0-2]))-((0[1-9])|([12][0-9])|(3[01]))'
2019-12-30
[root@sharkyun ~]# echo "1919-12-30" |grep -E '[1-9][0-9]{3}-((0[1-9])|(1[0-2]))-((0[1-9])|([12][0-9])|(3[01]))'
1919-12-30

支持扩展正则的工具

  • grep -E

  • egrep

  • sed

  • awk

正则高级部分: 贪婪|非贪婪(扩展)

贪婪 就是尽可能的多匹配

非贪婪 就是尽可能的少匹配,只需要在一些表示量词(就是次数)的后面加上 ?, 比如: .*? +?

grep 实现非贪婪

grep 或者 egrep 默认都是贪婪模式,不支持非贪婪模式。
要想实现非贪婪需要使用 -P 参数,这会使用 Perl 语言环境的正则

SheLL#Typora-SheLL笔记_第35张图片

SheLL#Typora-SheLL笔记_第36张图片

image.png

Perl 语言中:

  • \w 表示任意 一个 大小写字母 [a-zA-Z] 、下划线 _ 和数字 [0-9]
  • \d 表示任意 一个 数字 [0-9]
    当然这些规则适用于大部分的 编程语言,比如 python java javascript go php 等
    SheLL#Typora-SheLL笔记_第37张图片

SheLL-7数组

数组 (array) 变量类型

定义和取值

数组中的值: wukong  bajie  shaseng
值的索引号: 0      1     2

数组的索引只能是 整数

# 定义一个数组
var=(wukong bajie shaseng)

echo ${var[2]} //显示数组中索引号为 2 的值,索引号从 0 开始
输出 shaseng

echo ${var[*]}  //显示数组中所有的值
输出 wukong bajie shaseng

定义数组,并且其值从命令的结果中获取

# 把文件中的每一行作为数组中的一个值
line=(`cat /etc/passwd`)

切片

普通字符串切片

# 获取到 CPU 型号信息,作为基础数据进行处理
[root@shark ~]# cpu=$(grep 'model name' /proc/cpuinfo |uniq|cut -d: -f2)
[root@shark ~]# echo "|$cpu|"
| Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz|
[root@shark ~]# echo ${cpu:1}  # 从索引号 1 开始向后取值,取到最后
Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz
[root@shark ~]# cpu=${cpu:1}  
[root@shark ~]# echo "|$cpu|"
|Intel(R) Xeon(R) Platinum 8163 CPU @ 2.50GHz|

数组切片

[root@shark ~]# nums=(a b c d e)
[root@shark ~]# echo ${nums[*]:2}  # 从索引号 2 开始向后取值,取到最后
c d e
[root@shark ~]# echo ${nums[*]:2:2} # # 从索引号 2 开始向后取值,取 2 个
c d
[root@shark ~]#

循环数组

[root@shark ~]# nums=(a b c d e)
[root@shark ~]# for i in ${nums[*]}
> do
>     echo $i
> done
a
b
c
d
e

declare 声明关联数组

数组的索引可以是 普通字符串

声明关联数组使用 A 选项

declare -A  数组名称
  1. 声明
# 声明关联数组,数组名称为 info
[shark@sharkyun ~]$ declare -A   info
  1. 添加值
  • 每次添加一个值,可以追加

示例:

[shark@sharkyun ~]$ info["name"]="shark"
[shark@sharkyun ~]$ info["age"]=18
[shark@sharkyun ~]$ echo ${info["name"]}  # 显示索引对应的值
shark
[shark@sharkyun ~]$ echo ${info["age"]}
18
  • 一次添加所以的值,不可以追加,每次都会覆盖上次的值

每个值之间使用 空格 隔开

var=([key1]="value1" [key2]="value2")

示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iQAbTXd6-1600593675664)(assets/image-20200910101909271.png)]

删除

[root@kube-master arry]# unset info[name]
[root@kube-master arry]# echo ${!info[*]}
age

循环关联数组

[root@shark ~]# echo ${info[@]}
shark 18
[root@shark ~]# for i in ${info[@]}
> do
>   echo $i
> done
shark
18
[root@shark ~]#

SheLL-8AWK

一、awk简介

awk 是一种编程语言,用于在linux/unix下对文本和数据进行处理。

数据可以来自标准输入、一个或多个文件,或其它命令的输出。

支持用户自定义函数和动态正则表达式等先进功能,是linux/unix
下的一个强大编程工具。

在命令行中使用,但更多是作为脚本来使用。

awk的处理文本和数据的方式是这样的,它逐行扫描文件,从第一行到最后一行,寻找匹配的特定模式的行,并在这些行上进行你想要的操作。如果没有指定处理动作,则把匹配的行显示到标准输出(屏幕),如果没有指定模式,则所有被操作所指定的行都被处理。

awk分别代表其作者姓氏的第一个字母。因为它的作者是三个人,分别是Alfred Aho、Brian Kernighan、Peter Weinberger。

gawk是awk的GNU版本,它提供了Bell实验室和GNU的一些扩展。

二、awk的两种形式语法格式

awk [options] 'commands’ file1 file2

awk [options] -f awk-script-file filenames

options:

-F 对于每次处理的内容,可以指定一个子定义的分隔符,默认的分隔符是空白字符(空格或 tab 键 )

command:

BEGIN{}                        {}               END{}

处理所有内容之前的动作       处理内容中的动作   处理所有内容之后的动作

示例

 awk 'BEGIN{print "----开始处理了---"} {print "ok"} END{print "----都处理完毕---"}' /etc/hosts
----开始处理了---
ok
ok
ok
----都处理完毕---

BEGIN{} 通常用于定义一些变量,例如 BEGIN{FS=":";OFS="---"}

三、awk工作原理

[root@5e4b448b73e5 ~]# awk -F:  '{print $1,$3}' /etc/passwd
root 0
bin 1
daemon 2
adm 3
...略...

(1)awk,会处理文件的每一个行,每次处理时,使用一行作为输入,并将这一行赋给内部变量$0,每一行也可称为一个记录,以换行符结束

(2)然后,行被***(默认为空格或制表符)分解成字段(或称为域),每个字段存储在已编号的变量中,从$1开始,
最多达100个字段

(3)awk如何知道用空白字符来分隔字段的呢? 因为有一个内部变量FS来确定字段分隔符。初始时,FS赋为空白字符

(4)awk打印字段时,将以内置的方法使用 print 函数打印,awk 在打印出的字段间加上空格。这个空格是内部的一个变量 OFS 输出字段的分隔符, 逗号 , 会和 OFS 进行映射,通过 OFS 可以控制这个输出分隔符的值。

(5)awk输出之后,将从文件中获取另一行,并将其存储在$0中,覆盖原来的内容,然后将新的字符串分隔成字段并进行处理。该过程将持续到所有行处理完毕

四、记录与字段相关内部变量:

查看帮助:

man awk

$0 : awk变量 $0 保存当前正在处理的行内容
NR : 当前正在处理的行是 awk 总共处理的行号。
FNR: 当前正在处理的行在其文件中的行号。
NF :每行被处理时的总字段数
$NF: 当前处理行的分隔后的最后一个字段的值
FS : 输入行时的字段分隔符,默认空白字符(空格 tab键)

OFS : 输出字段分隔符,默认是一个 空格

awk 'BEGIN{FS=":"; OFS="+++"} /^root/{print $1,$2,$3,$4}' /etc/passwd

ORS 输出记录分隔符, 默认是换行符.

示例

将文件每一行合并为一行

ORS默认输出一条记录应该回车,但是这里是加了一个空格

awk 'BEGIN{ORS="  "} {print $0}' /etc/passwd 

输出:

root:x:0:0:root:/root:/bin/bash  bin:x:1:1:bin:/bin:/sbin/nologin  daemon:x:2:2:daemon:/sbin:/sbin/nologin  adm:x:3:4:adm:/var/adm:/sbin/nologin  lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin  sync:x:5:0:sync:/sbin:/bin/sync  shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown  halt:x:7:0:halt:/sbin:/sbin/halt  mail:x:8:12:mail:/var/spool/mail:/sbin/nologin  operator:x:11:0:operator:/root:/sbin/nologin  games:x:12:100:games:/usr/games:/sbin/nologin  ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin  nobody:x:99:99:Nobody:/:/sbin/nologin  systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin  dbus:x:81:81:System message bus:/:/sbin/nologin  tss:x:59:59:Account used by the trousers package to sandbox the tcsd daemon:/dev/null:/sbin/nologin  [root@5e4b448b73e5 ~]#

五、格式化输出:

printf 函数

awk -F: '{printf "%-15s %-10s %-15s\n", $1,$2,$3}' /etc/passwd
awk -F: '{printf "|%-15s| %-10s| %-15s|\n", $1,$2,$3}' /etc/passwd
  • %s 字符类型
  • %d 十进制整数
  • %f 浮点类型 ,保留小数点后 2 位printf "%.2f\n" 10
  • %-15s占15字符 - 表示左对齐,默认是右对齐
  • printf 默认不会在行尾自动换行,加 \n

六、awk模式和动作

任何 awk 语句都由 模式动作 组成。

模式部分 决定动作语句何时被触发。
如果省略模式部分,命令中的动作讲作用于每一行。

模式可以是

  • 正则表达式
  • 逻辑表达式
  • 包含正则表达式和逻辑表达式的复合语句

1 正则表达式:

  • 将整行进行正则匹配(包含):

就是当前处理的行有没有包含 指定的模式(书写的正则表达式)
/正则/ 正则需要写在双斜线内

! 用于取反,就是找到不匹配正则模式的行

AWK默认的动作就是打印出整行,所以当打印整行内容的时候,打印的动作命令可以省略。

awk '/^root/' /etc/passwd
awk '!/^root/' /tec/ passwd
  • 将某一字段进行正则匹配:

可以使用的匹配操作符(~!~
字段 ~ /正则/

awk -F: '$3 ~ /^1/' /etc/passwd
awk -F: '$NF !~ /bash$/' /etc/passwd
  • 匹配开头是 bin 的或者开头是 root 的行
awk -F: '/^(bin|root)/' /etc/passwd
# 输出
root:x:0:0:root:/root:/bin/zsh
bin:x:1:1:bin:/bin:/sbin/nologin

2 逻辑表达式

逻辑表达式采用对文本进行比较,只有当条件为真,才执行指定的动作。
逻辑表达式使用***关系运算符***进行两个值的比较,可以用于比较数字与字符串。

实现 字符串的完全相等需要使用 ==!=

其中字符串需要使用双引号引起来

awk -F: '$NF == "/bin/bash"' /etc/passwd
awk -F: '$1 == "root"' /etc/passwd

其他的运算符号:

关系运算符有
< 小于 例如 x
> 大于 例如 x>y
<= 小于或等于 例如 x<=y
== 等于 例如 x==y
!= 不等于 例如 x!=y
>= 大于等于 例如x>=y`

示例

awk -F: '$3 == 0' /etc/passwd
awk -F: '$3 < 10' /etc/passwd

df -P | grep '/' |awk '$4 > 25000 {print $0}'
  • 算术运算:+, -, *, /, %(模: 取余), ^(幂:2^3)

可以在逻辑表达式中执行计算,awk都将按浮点数方式执行算术运算

awk -F: '$3 * 10 > 5000{print $0}' /etc/passwd

3 复合模式

符合模式中,通常会使用逻辑运算符号

&& 逻辑与, 相当于 并且
||逻辑或,相当于 或者
! 逻辑非 , 取反

awk -F: '$1~/root/ && $3<=15' /etc/passwd
awk -F: '$1~/root/ || $3<=15' /etc/passwd
awk -F: '!($1~/root/ || $3<=15)' /etc/passwd
  • 范围模式, 模式之间用逗号 , 隔开

使用语法是: 起始表达式, 终止表达式

下面的意思是: 从开头是 bin 的行开始匹配成功一直到含有 adm 的行结束匹配
也就是 开头是 bin 的行到含有 adm 的行 的所有内容都符合匹配条件。

awk -F: '/^bin/,/adm/ {print $0 }' /etc/passwd
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

综合练习

机构号  机构名称        省别    尾箱号  尾箱状态   领用柜员号   领用柜员姓名    币种    余额
11007eee        北京东城区街支行   北京    03      未领用           156     19001.68
11007fff        北京东城区街支行   北京    03      未领用           840     2672.00
11007aaa        北京东城区街支行   北京    04      未领用           156     7261.31
11007ccc        北京朝阳区路支行   北京    02      未领用           156     161490.08
110088ee        北京朝阳区路支行   北京    03      未领用           840     19711.00
34009eff         山西煤矿区路支行   山西    03      未领用           156     282370.23
11007eee        山西煤矿区路支行   山西    03      未领用           156     282370.23
11007eee        山西煤矿区路支行   山西    03      未领用           156     282370.23
11007264        山东平阴县支行     山东    02      未领用          156     304516.23
11007889        山东济阳县支行   北京    04      未领用           840     24551.00
11007264        北京朝阳区支行     北京    02      未领用           156     304516.23
11007284        北京朝阳区支行     北京    02      领用     1002  巫妖王           156     304516.23
11007194        北京朝阳区行       北京    02      未领用           156     304516.23
11007264        河南中原区支行     河南    02      未领用          156     304516.23
11007284        河南二七支行     河南    03      领用     1003 钟馗     156     9046.23
  1. 找到 河南省的 尾箱未领用的,机构号,和余额 #
  2. 找到 所有北京的 结构号,
  3. 找到 北京的机构并且尾箱没有领用的
  4. 找到县级支行 #
  5. 找到平阴县支行
  6. 找到余额大于 8000 的北京支行 #
  7. 找到哪些柜员领用了尾箱,打印出柜员号和柜员姓名
  8. 都要求格式化输出 #

七、awk 脚本编程

1 if语句

格式 { if (表达式) {语句; 语句; ...}}

awk -F: '{ if ($3==0) {print $1 " is administrator."}}' /etc/passwd
输出: root is administrator.

# 统计系统级别用户的数量
awk -F: '{ if ($3>0 && $3<1000) {count++}  } END{print count}' /etc/passwd 
输出: 22

2 if…else语句

格式 { if (表达式){语句;语句;...}else {语句;语句;...}}

awk -F: '{ if ($3==0){print $1} else {print $7} }' /etc/passwd

awk -F: '{ if ($3==0){count++} else{i++} } END{print "管理员个数: "count "系统用户数: "i}' /etc/passwd
输入:
管理员个数: 1系统用户数: 24

awk -F: '{ if($3==0){count++} else{i++} } END{print "管理员个数: "count ; print  "系统用户数: "i}' /etc/passwd
输出:
管理员个数: 1
系统用户数: 24

3 if…else if…else语句

格式
{ if(表达式1) {语句;语句;...} else if (表达式2) {语句;语句;...} else if(表达式3){语句;语句;...} else {语句;语句;...} }

awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print i; print k; print j}' /etc/passwd

输出:
1
2
22
awk -F: '{if($3==0){i++} else if($3>999){k++} else{j++}} END{print "管理员个数: "i; print "普通用个数: "k; print "系统用户: "j}' /etc/passwd

输出:
管理员个数: 1
普通用个数: 2
系统用户: 22


八、 awk使用外部变量:

1 使用自定义的 shell 变量

方法一:awk参数-v(推荐使用,易读)

[root@shark ~]# read -p ">>:" user
>>:root
[root@shark ~]# awk -F: -v awk_name=$user '$1==awk_name {print "用户存在"}' /etc/passwd
用户存在
[root@shark ~]#

2 使用 shell 的环境变量

[root@shark ~]# read -p ">>:" user
>>:root
[root@shark ~]# export user
[root@shark ~]# awk -F: '$1==ENVIRON["user"] {print "用户存在"}' /etc/passwd
用户存在
[root@shark ~]# unset user

九、指定多个分隔符:[]

echo "a b|c d| ||||e | |" |awk -F'[ |]' '{print $10}'
e
echo "a b|c d| ||||e | |" |awk -F'[ |]+' '{print $5}'
e
[root@shark ~]# echo "110.183.58.144 - - [10/May/2018:23:49:27 +0800] GET http://app." |awk -F'[][ ]'  '{print $5 }'
10/May/2018:23:49:27
[root@shark ~]# echo "110.183.58.144 - - [10/May/2018:23:49:27 +0800] GET http://app." |awk -F'[][ ]+'  '{print $4 }'
10/May/2018:23:49:27

注意: 中括号内的任意字符均视为普通字符, 比如 . * 都被看做是 普通字符。
例如:

$ echo "a.b*c" |awk -F'[.*]' '{print $1, $2,$3}'
a b c

作业:
1. 取得网卡IP(除ipv6以外的所有IP)
2. 获得内存使用情况
3. 获得磁盘使用情况
5. 打印出/etc/hosts文件的最后一个字段(按空格分隔)
6. 打印指定目录下的目录名

十、生产实例:

统计日志中某个时间范围的 IP 访问量,并进行排序

部分日志

110.183.58.144 - - [10/May/2018:23:49:27 +0800] "GET http://app.znds.com/html/20180504/y222sks_2.2.3_dangbei.dangbei HTTP/1.1" 200 14306614 "-" "okhttp/3.4.1"
1.69.17.127 - - [10/May/2018:23:49:31 +0800] "GET http://app.znds.com/down/20180205/ttjs_3.0.0.1_dangbei.apk HTTP/1.1" 200 13819375 "-" "okhttp/3.4.1"
1.69.17.127 - - [10/May/2018:23:49:40 +0800] "GET http://app.znds.com/down/20180416/ttyj_1.1.6.0_dangbei.apk HTTP/1.1" 200 16597231 "-" "okhttp/3.4.1"
1.69.17.127 - - [10/May/2018:23:50:00 +0800] "GET http://app.znds.com/down/20170927/jydp_1.06.00_dangbei.apk HTTP/1.1" 200 36659203 "-" "okhttp/3.4.1"

具体实现

日志文件名:app.log

$ start_dt='10/May/2018:23:47:43
$ end_dt='10/May/2018:23:49:05'
$ awk -v st=${start_dt} -v ent=${end_dt} -F'[][ ]' '$5 == st,$5 == ent  {print $1}' app.log  |sort |uniq -c |sort -nr |head -n 10
     66 223.13.142.15
      6 110.183.13.212
      4 1.69.17.127
      1 113.25.94.69
      1 110.183.58.144

时间转换工具

[root@shark ~]# unset months_array
[root@shark ~]# declare -A month_array
[root@shark ~]# month_array=([01]="Jan" [02]="Feb" [03]="Mar" [04]="Apr" [05]="May" [06]="Jun" [07]="Jul" [08]="Aug" [09]="Sept" [10]="Oct" [11]="Nov" [12]="Dec")
[root@shark ~]# echo ${month_array[01]}
[root@shark ~]# Jan
[root@shark ~]# m=10
[root@shark ~]# echo ${month_array[$m]}
[root@shark ~]# Oct

字符串切片

语法: ${var:从这个索引号开始:取出多少个字符:}

索引号从 0 开始

[root@shark ~]# st="20180510234931"
[root@shark ~]# m=${st:4:2}
[root@shark ~]# echo $m
[root@shark ~]# 05

常用日志分析语句

# 访问TOP 20 的IP
16348 58.16.183.52
awk  '$9==200 {print $1}'  2018-05-10-0000-2330_app.log | sort |uniq -c |sort -r |head -20


# 访问状态码为20X的  TOP 10的IP
2097 125.70.184.99
2000 183.225.69.158
awk  '$9 > 200 && $9 < 300{print $1}' 2018-05-10-0000-2330_app.log  | sort |uniq -c |sort -r |head


# 访问TOP 20 的url
250563 http://app.xxx.com/update/2018-04-04/dangbeimarket_4.0.9_162_znds.apk
awk  '$9 == 200{print $7,$9}' 2018-05-10-0000-2330_app.log | sort |uniq -c |sort -r |head -20

# 访问状态码为20X的  TOP 10的url
248786 http://app.znds.com/update/2018-04-04/dangbeimarket_4.0.9_162_znds.apk
awk  '$9 > 200 && $9 < 300{print $7,$9}' 2018-05-10-0000-2330_app.log | sort |uniq -c |sort -r |head


# 访问次数超过1W的IP
58.16.184.247
58.16.183.52
awk  '{print $1}' 2018-05-10-0000-2330_app.log| sort |uniq -c |sort -r |awk '$1 > 10000 {print $1}'


# 访问状态码为404的  TOP 10的url
1017 http://app.xxx.com/update/fixedaddress/kuaisou_qcast.apk.md5
awk  '$9 == 404 {print $1}' 2018-05-10-0000-2330_app.log | sort |uniq -c |sort -r |head

十一、提高篇

1 拆分信息到文件中

awk拆分文件很简单,使用重定向就好了。

下面这个例子,是按第 3 例分隔文件,相当的简单(其中的NR!=1表示不处理表头)。

$ awk 'NR!=1{print > $3}' gy.txt
$ ls
gy.txt  北京     山西     山东  河南

你也可以把指定的列输出到文件:

awk 'NR!=1{print $2,$4,$5 > $3}' gy.txt

再复杂一点:(注意其中的if-else-if语句,可见awk其实是个脚本解释器)

$ awk 'NR!=1 {if($3 ~ /北京|山东/) print > "1.txt";
else if($3 ~ /山西/) print > "2.txt";
else print > "3.txt" }' gy.txt
 
$ ls ?.txt
1.txt  2.txt  3.txt

2 AWK 数组

语法: array_name[index]=value

  • array_name 数组名称
  • index 索引
  • value

awk 中的数组和 shell 中的关联数组是一样的,索引都是可以是任意的字符串,索引也可以使用对应有效的变量。

[root@shark ~]# awk 'BEGIN{
arr["a"]=1;
arr["b"]=2+3;
print arr["a"];
print arr["b"]
}'
1
5
[root@shark ~]# echo "a b" |awk 'BEGIN{
arr["a"]=1;
arr["b"]=2+3;
print arr["a"];
print arr["b"]
}'
1
5

示例文件

虚空行者 数学 68
虚空行者 英语 88
黑暗之女 语文 98
黑暗之女 数学 68
无极剑圣 语文 78
无极剑圣  数学 48
琴瑟仙女 语文 90
琴瑟仙女 数学 68
琴瑟仙女 英语 61
影流之主  语文 68
影流之主  数学 88
影流之主  英语 98
[root@shark ~]# awk '{arr[$1]++;print $1,arr[$1]}' hero
虚空行者 1
虚空行者 2
黑暗之女 1
黑暗之女 2
无极剑圣 1
无极剑圣 2
琴瑟仙女 1
琴瑟仙女 2
琴瑟仙女 3
影流之主 1
影流之主 2
影流之主 3
[root@shark ~]# awk '{arr[$1]++} END{print $1,arr[$1]}' hero
影流之主 3
[root@shark ~]# awk '{arr[$1]++} END{for (i in arr){print i, arr[i]}}' hero
黑暗之女 2
虚空行者 2
琴瑟仙女 3
影流之主 3
无极剑圣 2
[root@shark ~]#

我们再来看一个统计各个各省份网点数量的用法:

$ awk 'NR!=1 {a[$3]++;} END {for (i in a) print i ", " a[i];}' gy.txt
北京, 69
山西, 20
江苏, 10
山东, 16

a 是数组名称
[$3] 是数组的索引号,这个索引号可以是普通字符
ifor 循环 数组 得到的 索引号
a[i] 是获取到索引对应的值

3 内置函数示例

#从file文件中找出长度大于80的行
awk 'length>80' file

# length 是 awk 的内置函数,作业是统计每行的字符串长度

4 随便玩玩

下面的命令计算所有的 txt 文件的文件大小总和。

$ ls -l  *.txt | awk '{sum+=$5} END {print sum}'
2511401

其他

# 再来看看统计每个用户的进程的占了多少内存(注:sum的RSS那一列)

$ ps aux | awk 'NR!=1 {a[$1]+=$6;} END { for(i in a) print i ", " a[i]"KB";}'

#按连接数查看客户端IP
netstat -ntu | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr
 
#打印99乘法表
seq 9 | sed 'H;g' | awk -v RS='' '{for(i=1;i<=NF;i++)printf("%dx%d=%d%s", i, NR, i*NR, i==NR?"\n":"\t")}'
#!/bin/bash

declare -A month_array

month_array=([01]="Jan" [02]="Feb" [03]="Mar" [04]="Apr" [05]="May" [06]="Jun" [07]="Jul" [08]="Aug" [09]="Sept" [10]="Oct" [11]="Nov" [12]="Dec")

st=$1
end=$2
logfile=$3

start_yer=${st:0:4}
start_day=${st:6:2}
start_month=${st:4:2}
start_h=${st:8:2}
start_m=${st:10:2}
start_s=${st:12:2}

# 获取到映射月份
start_month=${month_array[$start_month]}

# 把用户输入的日期时间替换日志中的格式: 10/May/2018:23:49:31
start_dt="$start_day/$start_month/${start_yer}:${start_h}:${start_m}:$start_s"

echo $start_dt

end_yer=${end:0:4}
end_day=${end:6:2}
end_month=${end:4:2}
end_h=${end:8:2}
end_m=${end:10:2}
end_s=${end:12:2}

end_month=${month_array[$end_month]}
end_dt="$end_day/$end_month/${end_yer}:${end_h}:${end_m}:$end_s"

echo $end_dt
echo $logfile

export start_dt end_dt

awk -F '[][ ]+'  '$4==ENVIRON["start_dt"], $4==ENVIRON["end_dt"] ' $logfile

unset start_dt
unset end_dt

十二、参考资料

内建变量,参看:http://www.gnu.org/software/gawk/manual/gawk.html#Built_002din-Variables

流控方面,参看:http://www.gnu.org/software/gawk/manual/gawk.html#Statements

内建函数,参看:http://www.gnu.org/software/gawk/manual/gawk.html#Built_002din

正则表达式,参看:[http://www.gnu.org/software/gawk/manual/gawk.html#Regexp

SheLL-9SED

一、sed工作流程

SheLL#Typora-SheLL笔记_第38张图片

sed 是一种在线的、非交互式的编辑器,它一次处理一行内容。
处理时,先把当前处理的行内容存储在临时缓冲区中,称为“模式空间”(pattern space),
之后再用sed命令处理缓冲区中的内容,处理完成后,把缓冲区的内容打印到屏幕。
接着处理下一行,这样不断重复,直到文件末尾。
注意:模式空间的内容和 AWK 中的 $0 是一样的,处理每行的时候,都会被重新赋值为当前行的内容
文件内容并没有改变,除非你使用重定向存储输出。
Sed主要用来自动编辑一个或多个文件;简化对文件的反复操作;编写转换程序等。

二、命令格式

先准备一个文件

[root@e5ac44e88027 ~]# head /etc/passwd |grep -n ''   > mypasswd
[root@e5ac44e88027 ~]# cat mypasswd
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
6:sync:x:5:0:sync:/sbin:/bin/sync
7:shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8:halt:x:7:0:halt:/sbin:/sbin/halt
9:mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10:operator:x:11:0:operator:/root:/sbin/nologin

处理单个文件的命令格式

sed     [options]   '[匹配模式]     sed 的内部命令'       file1

处理多个文件的命令格式

sed     [options]   '[匹配模式]     [sed 的内部命令]'       file1  file2

options 选项是可选的,意思就是没有也行
匹配模式 是可选的用于在文件中每一行进行匹配到模式,模式可以是正则,也可以是文件的行号
内部的命令也是可选的,没想到吧,但是两个单引号是必须的

[root@kube-master sed]# sed '' mypasswd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
...以下输出省略...

注:
sed和grep不一样,不管是否找到指定的模式,它的退出状态都是0
只有当命令存在语法错误时,sed的退出状态才是非0

三、支持正则表达式

与grep一样,sed在文件中查找模式时也可以使用正则表达式(RE)和各种元字符。正则表达式是
括在斜杠间的模式,用于查找和替换,以下是sed支持的元字符。
使用基本元字符集 ^, $, ., *, [], [^], \< \>,\(\),\{\}
使用扩展元字符集 ?, +, { }, |, ( )使用扩展元字符的方式:

sed   -r

在实际使用的时候,都会加上 -r 参数,即使没有用的扩展正则也不会有任何影响。

四、sed基本用法

打印

sed 默认会输出文件的每一行,无论这行内容是否能匹配上匹配模式,假如被匹配到的则会再输出一次。

sed   -r   ''     mypasswd
sed   -r   'p'    mypasswd

p 是 sed 的内部命令,是 打印(输出) 的作用

屏蔽默认输出使用 -n 选项

sed -rn 'p'   mypasswd

sed -rn '/root/p'   mypasswd 显示root的行 ^ 匹配root 开头 ^root

搜索替换 – 这是重点 实际中用的最多

sed会自动打印文件的每一行,同时查找模式匹配的行,找到后执行后面的命令,默认是 p 打印(不加 -n 的情况下)

> 搜索每一行,找到有 root 的,把第一个替换为 shark
sed  -r  's/root/shark/'   mypasswd

> 搜索每一行,找到所有的 root 字符,进行全局替换为 `shark`
sed  -r  's/root/shark/g'    mypasswd

> i  是同时忽略大小写
sed  -r  's/root/shark/gi'    mypasswd 

> 找到含有 root 的进行删除
sed  -r  '/root/  d'    mypasswd

> 可以使用不同的 字符 作为界定符号,注意进行转义
sed  -r  '\#root#d'   mypasswd

注意:
当在模式匹配中使用其他界定符号时,需要对其进行转义。
其他界定符用在 s 搜索替换时不必转义。例如:

sed  -r  's#root#shark#'      mypasswd
sed  -r  's%root%shark%'      mypasswd
sed  -r  's|root|shark|'      mypasswd

五、sed扩展

地址(定址)
地址用于决定对哪些 进行编辑。地址形式可以是数字、正则表达式或二者的结合。如果没有指定地址,sed将处理输入文件中的所有行。

> 全部分删除
sed  -r  'd'        mypasswd

> 第 3 行删除
sed  -r  '3  d'     mypasswd

> 第 1 行到第 3 行都删除
sed  -r  '1,3  d'             mypasswd

> 含有 root 字符串的行删除
sed  -r  '/root/  d'          mypasswd

> 从含有 root 字符串的行开始匹配,一直删除到 第 5 行
sed  -r  '/root/,5  d'        mypasswd

> 从含有 halt 的行开始删除,并删除此行之后的 2 行,就是总共删除 3 行
sed  -r  '/halt/,+2  d'      mypasswd

> 含有 root 的行不删除,其他都删除
sed  -r  '/root/  !d'         mypasswd

> 使用行号除以 2 ,余数是 1 的行删除
sed  -r  '1~2  d'             mypasswd

> 使用行号除以 2, 余数 是 0 的 打印出来
sed  -rn  '0~2  p'            mypasswd


> 试试下面这个, 就是 每次处理的行号是被除数,第二个数是除数,第一数是 余数
sed  -rn   '0~3  p'           mypasswd 


六、sed命令

sed命令告诉 sed 对匹配到的行进行何种操作,包括打印、删除、修改等。

sed 部分命令示例

替换命令:s

sed -r 's/[0-9][0-9]/&.5/' mypasswd //&代表在查找串中匹配到的所有内容
sed -r 's/(no)login/\1不可登录/' mypasswd

部分输出为
bin:x:1:1:bin:/bin:/sbin/no不可登录

追加命令:a

sed -r '$a 1.1.1.1 www.qfedu.com' /etc/hosts
# $ 符号在这里标识一个文件的最后一行,
# a 是 sed 追加的命令
# a 命令后面的内容均视为 要追加的内容
# 整体意思是向文件的末尾追加一行内容

插入命令:i

# sed -r '2i\1111111111111' /etc/hosts
# sed -r '2i111111111\
> 2222222222\
> 3333333333' /etc/hosts

修改(替换)命令:c

# sed -r '2c\1111111111111' /etc/hosts
# sed -r '2c\111111111111\
> 22222222222\
> 33333333333' /etc/hosts

七、sed常见操作

删除配置文件中 # 号注释的行
sed -ri '/^#/d' file.conf

删除开头的一个或者多个空格或者 Tab 键 加上 '#' 或者开头是 '#' 的行
sed -ri '/^[ \t]*#/d' file.conf

YUM 源修改
sudo sed -ri s/^#baseurl/baseurl/g /etc/yum.repos.d/CentOS-Base.repo
sudo sed -ri s/^mirrorlist/#mirrorlist/g /etc/yum.repos.d/CentOSBase.repo

删除配置文件中//号注释行

sed -ri '\#^[ \t]*//#d' file.conf

删除无内容空行
- 开头和结尾之间什么都没有的行
- 开头和结尾之间有多个空格的行
- 开头和结尾之间有多个  Tab 键的行
sed -ri '/^[ \t]*$/d' file.conf

删除注释行及空行:
以下 3 中效果一样,挑一个自己喜欢的
sed -ri '/^[ \t]*#/d; /^[ \t]*$/d' /etc/vsftpd/vsftpd.conf
sed -ri '/^[ \t]*#|^[ \t]*$/d' /etc/vsftpd/vsftpd.conf
sed -ri '/^[ \t]*($|#)/d' /etc/vsftpd/vsftpd.conf

修改文件:
sed -ri '/^SELINUX=/cSELINUX=disabled' /etc/selinux/config
sed -ri '/UseDNS/cUseDNS no' /etc/ssh/sshd_config
sed -ri '/GSSAPIAuthentication/cGSSAPIAuthentication no' /etc/ssh/sshd_config

给文件行添加注释:
sed -r '2,6s/^/#/' a.txt

使用小括号进行分组,可以有多个分组, 后面可以使用 \1 获取到第一个分组的内容  
sed -r '2,6s/(.*)/#\1/' a.txt
sed -r '2,6s/.*/#&/' a.txt &匹配前面查找的内容

sed -r '3,$ s/^#*/#/' a.txt 将行首零个或多个#换成一个#

sed -r '30,50s/^[ \t]*#*/#/' /etc/nginx.conf
sed -r '2,8s/^[ \t#]*/#/' /etc/nginx.conf

sed中使用外部变量
var1=11111

# 无效
sed -r '3a$var1' /etc/hosts

# 正确
sed -r "3a$var1" /etc/hosts

# 有效
sed -r 3a$var1 /etc/hosts

# 报错
sed -r "$a$var1" /etc/hosts

# 有效,但是中间不能有空格
sed -r '$a'"$var1" /etc/hosts

# 有效, 将第一个 $ 进行转义
sed -r "\$a  $var1" /etc/hosts

多重编辑选项:-e

[root@kube-master sed]# sed -e '1,3 d' -e 's/root/shark/' mypasswd
4:adm:x:3:4:adm:/var/adm:/sbin/nologin
5:lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
6:sync:x:5:0:sync:/sbin:/bin/sync
7:shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
8:halt:x:7:0:halt:/sbin:/sbin/halt
9:mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
10:operator:x:11:0:operator:/shark:/sbin/nologin
[root@kube-master sed]#

sed -e '1,3 d' -e 's/root/shark/' mypasswd
等同于
sed '1,3 d; s/root/shark/' mypasswd

SheLL-10函数

函数

就是对代码的封装,通常会完成一个功能,而出现的一种组织和代码的方式。

函数式编程

  1. 减少代码重复编写,从而也提高了代码的可复用率。
  2. 程序逻辑结构清晰。
  3. 可以使程序代码更易读,便于管理维护。
  4. 是模块化编程思想的基础。

函数必须先定义才可以使用

一、定义函数

方法一:

函数名() {
	函数要实现的功能代码
}

方法二:

function 函数名 () {
	函数要实现的功能代码
}

例如:

say_you_say_me(){
    echo "我看过很多书,但都没有你好看^_^"
    echo "我看过很多书,但都没有你好看^_^"
}

二、调用函数

  1. 无参函数调用方法

函数名

say_you_say_me
  1. 有参函数调用方法
    函数传参时和脚本的传参一样。

函数名 参数1 参数2

# 定义函数
say_you_say_any(){
    echo "我看过很多书,但都没有 "$1" 好看^_^"
}

# 调用函数
say_you_say_any  xinyi

执行效果

[root@kube-master function]# sh say.sh
我看过很多书,但都没有 xinyi 好看^_^

作业:

  1. 编写系统初始化脚本
    配置YUM
    安装 bash-completion epel-release lsof
    关闭 selinux
    关闭 firewalld
    配置静态 IP
    按q键退出程序

  2. 编写系统管理工具箱
    查看内存的使用情况
    查看磁盘的使用情况
    查看系统的负载
    查看目前登录的用户数量
    按q键退出程序

三、函数参数

在Shell中调用函数时可向其传递参数。在函数体内部通过 $n 的形式来获取参数的值,如:$1 表示第1个参数,$2 表示第2个参数…;当n>=10时,表示为 ${n},如:${10}、${11}

位置参数变量:是预定义变量中一种

位置参数变量 作用
$n 利用参数向程序中传递需要调用的值n为数字,n≤9直接用数字,n≥10都需要用{}包含:$0 表示命令本身 $1-$9 表示第1-9个参数 ${10} 表示第10个参数
$* 表示命令行中所有的参数,所有参数看作一个整体
$@ 表示命令行中的所有参数,每个参数区分对待
$# 表示命令行中所有参数的个数(不统计$0

传参示例

# 定义函数
show_args (){
    echo "函数的第一个参数$1"
    echo "函数的第二个参数$2"
    echo "函数的所有参数$@"
    echo '函数中 $0 还是' $0

}

# 调用函数
show_args hello world

输出

[root@kube-master function]# sh func-args.sh
函数的第一个参数hello
函数的第二个参数world
函数的所有参数hello world
函数中 $0 还是 func-args.sh

脚本的位置参数和函数的位置参数

[root@shark function]# cat func-arg.sh
#!/bin/bash

f1 (){
   echo "--->$1"

}

f1 $2
[root@shark function]# sh func-arg.sh a
--->
[root@shark function]# sh func-arg.sh a b
--->b
[root@shark function]#

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5ObK6Xf6-1600594004835)(assets/image-20200914165713151.png)]

五、函数中调用函数

f1(){
  echo "f1...."
}

function f2(){
  f1
  echo "f2..."
}

f2

输出

[root@kube-master function]# sh func.sh
f1....
f2...

六、函数的返回值

我们可以把函数中产生的数据,交给脚本中的其他代码使用,以便做进一个的处理,比如存入数据库,作为其他函数的参数,作为判断条件等。

f1(){
    ip="192.168.1.100"
    pwd="123"

    echo "$ip"   # 函数返回值1
    echo "$pwd"  # 函数返回值2
    # echo "$ip $pwd"   # 函数返回值
}

# 用变量接收函数的返回值, 函数需要先在子 shell 中执行
# 语法:
# 变量名=$(函数名) 或者 变量名=`函数名`
info=$(f1)

echo $info

输出

192.168.1.100 123

七、定义函数的局部变量: local

[root@shark function]# cat scirpt-arg.sh

ip="dd"

f1 (){
  # local 定义了一个局部变量,局部变量只能
  # 在函数内生效
  local ip="192.168.1.10"
  local pwd="123"
  echo "函数内:$ip"
}

f1

echo "函数外:$ip $pwd"
[root@shark function]# sh scirpt-arg.sh
函数内:192.168.1.10
函数外:dd
[root@shark fu

SheLL-11shell操作数据库

基本思路

shell 操作 MySQL 是通过给 mysql 这个客户端程序传递相应的参数实现的
mysql -u用户 -p'password' db_name -e "sql 语句"

#!/bin/bash
HOSTNAME="localhost" #数据库信息
PORT="3306"
USERNAME="root"
PASSWORD="QFedu123!"
DBNAME="d1" #数据库名称
TABLENAME="t1" #数据库中表的名称

exec_mysql="mysql -h${HOSTNAME} -P${PORT} -u${USERNAME} -p${PASSWORD}"

#创建数据库
create_db_sql="create database IF NOT EXISTS ${DBNAME}"
${exec_mysql} -e "${create_db_sql}"

${exec_mysql} -e "show databases;"

#创建表
create_table_sql="create table  IF NOT EXISTS ${TABLENAME} ( name varchar(20), id int(11) default 0 )"
${exec_mysql}   ${DBNAME}   -e   "${create_table_sql}"

#插入数据
insert_sql="insert into  ${TABLENAME}  values('billchen',2)"
${exec_mysql}   ${DBNAME} -e "${insert_sql}"

查询

查询时候可能需要避免不必要的输出

-N 不输出列名(字段名)
-B 不输出数据之间的边框竖线 (|)

输出格式可以是其他的,比如 -H 输出 HTML 格式

[root@shark ~]# cat exec-mysql.sh
#!/bin/bash

HOSTNAME="localhost" #数据库信息
PORT="3306"
MYSQLPWDFILE="./.mysqldb"
DBNAME="jspgou" #数据库名称
TABLENAME="jc_address" #数据库中表的名称

exec_mysql="mysql --defaults-file=${MYSQLPWDFILE}  -h${HOSTNAME} -P${PORT}"

city="北京市"
city2="上海市"
p_id=NULL
id=8
sql="select Id,parent_id,name from ${TABLENAME}
     where name='"$city"' or name='"$city2"' or Id=$id
      or parent_id is $p_id;"
echo $sql

${exec_mysql} ${DBNAME}  -e "$sql"

# 执行脚本
[root@shark ~]# sh exec-mysql.sh
select Id,parent_id,name from jc_address where name='北京市' or name='上海市' or Id=8 or parent_id is NULL;
+----+-----------+-----------+
| Id | parent_id | name      |
+----+-----------+-----------+
|  1 |      NULL | 江西省    |
|  8 |         7 | 西湖区    |
|  9 |      NULL | 安徽省    |
| 10 |      NULL | 北京市    |
| 11 |      NULL | 上海市    |
+----+-----------+-----------+
[root@shark ~]#

隐藏用户名和密码

[mysql]
user=root
password='QFedu123!'
mysql  --defaults-file=./mysql_pwd -h 172.17.0.2 -e  "show databases;"  -N -B

SheLL-12expect

expect 内部命令

首先需要安装

yum install -y expect

语法结构

spawn   shell 命令程序 

expect   "捕获到shell 命令程序执行之后输出的字符串"  
send  "发送给 shell 命令程序的字符串"

在命令行直接输入 expect 可以进入 expect 程序的解释器终端

[root@e5ac44e88027 ~]# expect
expect1.1> spawn echo "hello"   # 发送一条 shell 命令, 这里是指 echo "hello"
spawn echo hello
1490
expect1.2> expect "hello"     # 捕获这个字符串,只要包含这个字符串就可以
hello
expect1.3> send "yes\n"      # 发送一个字符串
expect1.4> expect off           # 结束这次捕获
yes
expect1.5>

在脚本中使用

# 开始 expect  解释器程序
/usr/bin/expect<

".ssh/id_rsa)" { send "\n"; exp_continue }
意思是 捕获到字符串 “.ssh/id_rsa)” 后 发送字符串 “\n” 就是相当于按下回车键
exp_continue 意思是继续进行捕获,不退出 expect 程序

实战案例

# 写个用于自动生成密钥对的函数
auto_keygen (){
    /usr/bin/expect<

你可能感兴趣的:(shell)