bash 与 shell脚本编写指南

bash 与 shell脚本编写指南

  • bash 与 shell脚本编写指南
  • bash基本命令
    • man与info命令
    • 遍历目录命令
    • 文件与目录列表命令
    • 处理文件相关命令
    • 处理目录命令
    • 查看文件内容
    • 通过root权限执行命令
  • bash命令进阶
    • 探查进程,检测程序
    • 2. 检测磁盘空间
    • 3. 处理数据文件
  • 3. shell基本用法
    • 1. shell基本操作
    • shell内建命令
  • 使用环境变量
    • 环境变量含义
    • 2. 设置用户自定义变量
    • 3. 环境变量持久化
    • 4. 数组变量
  • 5. 账户与文件权限
    • 添加新用户
    • 删除用户
    • 修改账户
    • 用户组操作
    • 理解文件权限
    • 更改文件权限
  • 管理文件系统
    • TODO
  • 安装程序软件
    • TODO
  • shell脚本编程基础
    • 构建基本脚本
    • 重定向输入输出与管道
    • 执行数学运算
    • 退出脚本
  • 使用条件控制命令
    • 使用if条件句
    • test命令
      • ```test```与```[]```的数值比较功能表
      • ```test```与```[]```的字符串比较功能表
      • ```test```与```[]```的文件比较功能表
    • 使用复合条件
    • if-then的高级特性
      • 使用双括号
      • 使用双方框括号
    • 使用case命令
  • 使用循环控制语句
    • for循环
      • 更改字段分隔符
      • C风格的for命令
    • while循环
      • 使用过个测试命令
    • until循环
    • 控制循环
      • break命令
      • continue命令
    • 处理循环输出
  • 处理输入输出
    • 命令行参数
      • 读取参数
      • 参数个数
      • 抓取所有变量
      • 移动变量 ```shift```命令
    • 数据的呈现
      • 标准文件描述符号
      • 重定向标准错误
      • 在脚本中使用重定向
      • 列出当前打开的文件描述符
      • 阻止命令的输出
      • 创建临时文件
      • tee命令
  • 控制脚本编写
    • 处理信号
      • linux信号
      • 生成linux信号
      • 捕获信号
    • 后台模式运行脚本
    • 作业控制
      • 查看作业
      • 重启停止的作业
      • 调整谦让度
      • 定时作业
  • 高级shell脚本编写
    • 创建与使用函数
    • 创建和使用库
  • sed命令初级
    • sed编辑器基础
      • 使用替换
      • 使用地址
      • 删除行
      • 插入和添加文本
      • 修改行
      • 转换命令
      • 打印命令
      • 使用sed处理文件
    • sed编辑器进阶
      • 多行命令
        • next命令
        • 多行删除命令
        • 打印多行命令
      • 保持空间
      • 排除命令
      • 改变流
        • 分支
        • 测试
      • 模式替换
        • &符号
        • 替代单独的单词
  • gawk程序
    • gawk基础
      • 从命令行读取脚本
      • 使用数据字段变量
      • 程序中使用多个命令
      • 从文件中读取命令
      • 在数据处理前后运行脚本
    • gawk进阶
      • 使用变量
        • 内建变量
        • 自定义变量
      • 处理数组
        • 定义数组变量
        • 遍历数组变量
        • 删除数组变量
      • 使用模式
        • 使用正则表达式
        • 匹配操作符
        • 数学表达式
      • 结构化命令
        • if语句
        • while语句
        • do-while语句
        • for语句
      • 格式化打印
      • 内建函数
        • 数学函数
        • 字符串函数
        • 时间函数
      • 自定义函数
        • 使用自定义函数
      • 创建函数库
  • 正则表达式
    • BRE模式
      • 纯文本
      • 特殊字符
      • 锚字符
      • 点号字符
      • 字符组
      • 排除型字符组
      • 区间
      • 特殊字符组
      • 星号
    • ERE模式
      • 问号
      • 加号
      • 花括号
      • 管道
      • 表达式分组
  • SSH
    • SSH端口转发:SSH隧道
      • 本地转发
      • 远程转发
      • 转发的扩展

bash 与 shell脚本编写指南

作者:杨豪迈

bash基本命令

man与info命令


    man cmd
    info cmd
  • man与info命令可以用来查看命令或者函数的详细用法(其中info打印的是最详细用法)
  • 查找不同的涵盖内容可以通过不同的数字标示不同的内容区域,标识数字如下:
区域号 涵盖内容
1 可执行程序或者shell命令
2 系统调用
3 库调用
4 特殊文件
5 文件格式与约定
6 游戏
7 概览,约定与杂项
8 超级用户与系统管理员命令
9 内核历程

具体用法实例

    man 7 hostname
    man 1 intro

遍历目录命令


    cd dir
    pwd

cd命令用来选择并进入相应的工作目录

pwd从来打印当前的工作目录

文件与目录列表命令


    ls
    tree

ls命令用来显示当前目录的列表
tree命令用来显示当前的目录树

ls命令常用选项

选项 用途
-F 用来区分目录和文件
-a 同时打印隐藏文件
-R 递归列出子目录
-l 显示文件详细信息
-i 显示文件inode编号
something something为正则表达式,可以过滤输出结果

处理文件相关命令


    #用来创建文件
    touch file
    
    #用来拷贝文件
    cp src dest
    #可以使用正则表达式
    # -i选项用来添加提醒是否覆盖存在文件(默认不提醒)
    # -r -R 表示递归拷贝文件树
    
    #创建链接
    ln data_files sl_data_file
    #如果是软连接,有不同的inode编号,为不同文件
    ln -s data_file l_data_file
    #如果是硬链接,同一个inode编号,为同一个文件
    ln data_file sl_data_file

    #移动与重命名文件,不改变时间戳与inode
    mv src dest
    #也可以移动文件

    #删除文件 
    rm file
    # -i 提示你是否真的删除
    # -r 递归删除文件夹
    # -f 强制删除

处理目录命令


    #创建目录
    mkdir dir
    # -p 批量创建子目录

    #查看目录树
    tree

    #删除文件夹
    rmdir
    #如果目录有文件就无法删除目录,不如rm -rf方便

查看文件内容


    #查看文件类型
    file my_file

    #查看文件内容(一次性显示)
    cat file
    # -n 加上行号
    # -b 只给有文本的行加上行号
    # -T 忽略制表符

    #查看文件内容(分页显示)
    more file
    less file
    #more 和 less命令功能基本相同,其中less支持上下翻页

    #查看文件尾部
    tail file
    # -n 显示末尾的行数,默认10行,例如 -n 5
    
    #查看文件的头部
    head file
    # -n 需要显示的头部的行数,例如 -n 5

通过root权限执行命令


    sudo cmd

如果你当前不是root用户,可以通过在命令开始添加sudo来运用root权限执行命令

bash命令进阶

探查进程,检测程序

  1. 显示进程信息

    ps
  • 默认情况下,只显示当前控制台下属于当前用户的进程,输出进程ID,属于那个TTY和已使用的cpu时间
  • 下面是常用参数
参数 描述
-A 显示所有进程
-a 显示除控制进程(session leader)和无中断进程之外的所有进程
-d 显示除控制进程之外的所有进程
-e 同 -A
-l 显示长列表
-H 显示进程层次关系
-f 显示完整格式输出
–forest 显示更加清晰的层级关系

ps 命令详细介绍见man ps

  1. 实时探测进程

    top

top命令可以对系统当前进程进行cpu占用的排序,键入f可以选择排序字段,键入d可以修改轮询间隔,键入q可以退出top

常用字段介绍

字段 解释
PID 进程ID
USER 进程属主
PR 进程优先级
NI 进程谦让度
VIRT 进程占用虚拟内存总量
RES 进程占用物理内存总量
SHR 进程和其他进程共享内存总量
S 进程当前状态 D:可以中断的休眠 R:运行 S:休眠 T:跟踪或者停止 Z:僵化
%CPU 占用CPU比率
%MEM 占用内存比率
  1. 结束进程

    kill PID

通过kill -l查看所有的可以控制的信号,然后可以通过kill -s 信号名称 pid来向程序发出相关信号,例如kill -s HUP 3940

通常使用kill -9 pid无条件终止进程

    killall name

killall命令用来根据进程的名称杀死进程,支持通配符

2. 检测磁盘空间

  1. 挂载储存媒体

    mount

默认情况下,输入mount会显示系统的挂载的设备列表

挂载的通常用法:mount -t type device directory就可以把设备挂到对应的目录下

常用参数

参数 描述
-a 挂载/etc/fstab文件中指定的所有文件系统
-f 模拟挂载,但是不真正挂载
-v 详细模式,说明挂载每一步
-p 加密挂载时,从文件描述符num中获取密码
-r 挂载为只读
-w 挂载为可读写
-L 指定label挂载
-U 指定uuid挂载
    umount dir
    umount device

卸载一个移动设备

  1. 查看磁盘空间

    df

df可以轻松的查看当前你的设备剩余的磁盘空间,一般使用df -h来以用户易读的形式显示剩余空间

  1. 查看指定目录的文件空间占用
    #显示文件占用
    du
    # -c 显示列出文件的总大小
    # -h 按照用户易读的形式显示
    # -s 显示每个参数的总计

3. 处理数据文件

  1. 排序数据

    sort

sort可以对输入进行字符串排序,通过添加-n选项可以转换为数字排序。通常与管道相结合使用(管道后面会介绍)

详细的sort用法参考man sort

  1. 搜索数据

    grep [options] pattern [file]

grep 是一个常用的强大的搜索工具,也通常与管道相结合使用

如果包含file,就在file中搜索包含pattern正则模式匹配的字符,如果没有file,就通过管道获取数据,搜索字符串

常用选项

选项 描述
-r 递归在文件夹中搜索
-n 显示查找的行号
-v 表示搜索不包含pattern的行,也就是相反的匹配结果
-c 统计有多少行包含匹配的模式
-e 可以通过包含多个-e来制定多个模式,例如grep -e one_pattern -e another_pattern file
-a 不要忽略二进制数据
-w 只显示全字符合的列

file也可以使用正则表达式匹配

  1. 压缩工具

工具 扩展名
bzip2 .bz2
compress .Z (基本过时)
gzip .gz
zip .zip

.gz可以通过gunzip解压

.zip可以通过unzip解压

示例:

    zip -r ret.zip dir
    unzip ret.zip -d dir
  1. 归档数据

linux标准归档数据命令是tar命令

    tar function [options] object1 object2 ...
选项 描述
-A 将已有归档追加到另一个已有的归档文件
-c 创建一个新的归档文件
-d 检查归档文件和文件系统中文件的不同
–delete 从归档文件中删除
-r 追加到归档文件末尾
-u 追加新的同名文件
-x 从已有的归档文件中提取文件
-C dir 切换到制定目录
-f file 指定归档的文件名,一般为最后一个参数,指定文件名
-p 保留文件权限
-v 处理文件时显示文件
-z 有gzip属性的 gz
-j 有bz2属性的 bz2
-J -J :有xz属性的 xz

后缀名有.gz属性的添加 -z

后缀名有.bz2属性的添加 -j

后缀名有.xz属性的添加 -J

使用示例:

    #创建一个归档文件
    tar -cvf test.tar test/ test2/
    #查看归档文件内容
    tar -tf test.tar
    #提取归档文件内容 
    tar -xvf test.tar

3. shell基本用法

1. shell基本操作

  1. 创建,退出子shell

    bash
    exit

通过输入bash创建子shell,通过输入exit退出当前子shell

通过输入命令ps --forest查看当前终端中shell的层级关系

  1. 进程列表

进程列表是通过小括号括起来的一组命令,形如:

    #下面是进程列表
    (pwd;cd test;pwd;echo $BASH_SUBSHELL)

    #下面不是进程列表
    pwd;cd test;pwd;echo $BASH_SUBSHELL
  • 执行进程列表与非进程列表的区别:
    • 进程列表将会创建一个子bash并且执行当中的命令。命令执行在子bash中,不会对当前bash造成影响(例如第一个列表执行结束了仍在当前目录;第二个命令就会进如test文件夹)
    • 进程列表中环境变量$BASH_SUBSHELL会置位1)
  1. 后台命令

    cmd &

可以通过在命令后面添加&来使命令进入后台运行,前台仍可以输入命令执行。

例如:

    sleep 1000 &
    #通过jobs命令可以查看当前终端运行的后台命令
    jobs

同样,对于前台执行的命令,可以先通过Ctrl-z中断当前控制台执行的命令,然后输入bg命令把当前中断执行的命令放进后台执行,可以达到与直接放入后台执行相同的结果

也可以使用fg pid命令把后台命令重新放到前台来执行

  1. 创建协程

协程的创建不经常使用,协程命令会创建一个子bash,并将命令放进子bash中运行

    coproc cmd
    #例如
    coproc sleep 100

输入ps --forest可以查看bash与命令的层级关系

  1. ctrl-d ctrl-z ctrl-c

ctrl-d命令会在命令终端中输入EOF字符(文件结束符),相当于输入了exit,可以用来退出当前终端

ctrl-z会暂停当前前台的命令执行(信号SIGSTP),并不是停止当期命令执行。暂停之后可以通过输入bg将命令放到后台运行,也可以通过fg回复前台运行

ctrl-c会中断当前执行的命令。相当于发送SIGINT信号

shell内建命令


  1. 外部命令

外部命令是存在于bash之外的系统程序,不属于shell的一部分,通常位于/bin,/usr/bin,/sbin,/usr/sbin当中

    #打印外部命令的位置
    which cmd

    #打印命令的类型
    type cmd
    #如果输出是buildin,就是shell的内建命令,如果输出位置,就是外部命令
  1. 内建命令

例如cd exit echo pwd history等都是内建命令,命令的功能直接储存在shell当中

常用内建命令:

  • histroy用来打印最近使用的命令列表,列表储存在.bash_history当中。

  • !num你可以通过!num来获取前面执行的第num条命令

  • alias cmd='origin cmd'可以通过alias给命令添加别名,也可以通过alias -p来显示当前的所有别名

使用环境变量

环境变量含义

环境变量就是在shell中用来在内存中储存信息的一种特性

  1. 全局环境变量

全局变量对于shell会话和所有的子shell都是可见的

printenv命令可以查看全局变量的值,例如printenv HOME

也可以通过echo $HOME打印环境变量的值

  1. 局部环境变量

局部环境变量只能在定义它的进程中可见。

linux没有专门显示局部变量的命令,但是可以使用set命令显示为某个特定进程设置的包括全局变量,局部变量,和用户自定义变量在内的所有变量

2. 设置用户自定义变量

  1. 设置用户自定义局部环境变狼

    my_var=hello
    my_var="Hello world"
    #注意,等号两边不要有空格,不然会被误当成命令执行
    #如果环境变量之间有空格,就需要使用“”将变量包裹

    echo $my_var

这样定义的环境变量在子bash和父bash都不可用,只能用于当前的bash进程

  1. 设置用户自定义全局环境变量

    g_var=hello
    export g_var
    (echo $var)
    echo $var

通过使用export到出环境变量到全局,该环境变量在子shell中仍可见,但是子shell对父shell中定义的全局环境变量更改不会影响父shell中的全局变量的值,即使子shell导出全局变量也不会对父shell有任何影响

  1. 删除环境变量

    unset my_var

删除环境变量同样不会对父shell产生影响

  1. 设置PATH环境变量

在PATH环境变量中储存这用户命令的位置,使用:进行分割,如果添加路径,可以使用:

    PATH=$PATH:/the/path/to/add

程序员通常喜欢直接将单点符号加入PATH当中(代表当前目录)

    PATH=$PATH:.
  1. 单引号双引号区别

在shell脚本当中,变量可以通过双引号或者单引号包围。单引号会将所有字符视为普通字符,而双引号会保留特殊字符的含义。例如:

a=test
#输出$a
echo '$a'
#输出test
echo "$a"

3. 环境变量持久化

  1. 给所有用户使用

如果你想在每个用户打开bash是都创建一个环境变量,那么可以将环境变量的声明放在/etc/profile.d当中,创建一个以.sh结尾的文件,将修改的环境变量放置在这个文件当中

  1. 给个人使用

当你每次打开一个交互式shell时,都会执行一遍$HOME/.bashrc当中的命令。所以把环境变量的定义放在该文件当中,就可以持久化环境变量。同样,alias命令效果也是不能持久的,可以通过将alias的声明放置在$HOME/.bashrc当中使其持久化

4. 数组变量

    mytest=(one two three four five)
    echo ${mytest[2]} #打印three
    echo mytest #打印one。如果直接echo数组会打印第一个元素

    #打印所有元素
    echo ${mytest[*]}

    #改变索引
    mytest[2]=test

5. 账户与文件权限

/etc/passwd文件中储存了用户的相关信息

/etc/shadow储存了用户的密码管理控制

/etc/group保留了用户组信息

添加新用户

使用useradd命令添加新用户

    #查看创建用户时linux系统对于用户属性的默认值
    useradd -D
    #输出分析
    #GROUP=100 新用户被添加到GID为100的公共组
    #HOME=/home 新用户的目录将在/home下面
    #INACTIVE=-1 新用户账户密码过期后不会被禁用
    #EXPIRE= 新用户没有设置账户过期时间
    #SHELL=/bin/bash 设置默认shell
    #SKEL=/etc/skel 该目录文件会被复制到用户的HOME目录下
    #CREATE_MAIL_SPOOL=no 在mail目录下是否创建用户接收邮件的文件

    useradd -m test
    #根据默认设置创建用户,默认不会创建用户HOME目录,通过添加-m选项使其创建HOME目录
    # -c 给用户添加备注
    # -d 指定主目录名称
    # -e 指定账户过期日期
    # -f 指定过期多少天被禁用
    # -g 指定登录GID
    # -G 指定除登录组外的一个或者多个附加组
    # -k 和-m一同使用,复制/etc/skel到用户HOME目录
    # -m 创建用户HOME目录
    # -n 创建一个与用户同名的新组
    # -r 创建系统用户(区别与个人用户)
    # -p 指定默认密码
    # -s 指定shell种类,默认bash
    # -u 指定UID

删除用户

    userdel test

    #删除账户同时删除用户HOME文件
    userdel -r test

修改账户

    #修改账户信息
    usermod [options] username
    # -l 修改用户名
    # -L 锁定账户,无法登录
    # -U 接触锁定,使用户可以登录
    # -p 修改账户密码
    # -d 修改账户HOME目录

    #修改用户密码
    passwd username

    #修改用户备注
    chfn

    #修改账户默认shell
    chsh

    #修改密码过期日期
    chage

用户组操作

用户组操作不常使用,请大家自行查阅资料

理解文件权限

  1. 文件权限字段说明

例如:

-rw-rw-r--   1 ted  ted    1369 Mar 12 23:35 .emacs.bak
drwx------  20 ted  ted    4096 Aug  9 19:30 .emacs.d/
drwxrwxr-x   5 ted  ted    4096 Apr 27 14:09 .eric6/

如图是三个文件在ls -l命令的显示结果

文件权限描述字段一共由10个字符组成

  • 第一个字符含义:-代表文件,d代表目录,l代表链接,c代表字符设备,b代表块设备,n代表网路设备
  • 后面三组字符定义了三种访问权限:r:可读 w:可写 x:可执行
  • 三组权限分别对应:对象的属主,对象的属组,系统其他用户
  1. 默认文件权限
    umask 

通过umask可以显示默认的文件权限掩码。rwx分别代表4 2 1,从文件夹默认的权限777或者文件默认权限666减去umask文件掩码就就是生成文件的权限

更改文件权限

使用chmod改变文件权限,通过chown改变文件所属

    #通过八进制代码改变文件权限
    chmod 760 file

    #通过+-改变文件权限
    chmod u+x file
    #意为给file的属主用户增加执行权限
    chmod g-x file
    #意为去掉属组的执行权限
    #u 代表用户 g代表组 o代表其他 a代表上述所有
    # r w x分别是读写执行

    #改变文件的属主
    chown owner file

管理文件系统

TODO

有时间补充

安装程序软件

TODO

有时间补充

shell脚本编程基础

构建基本脚本

  1. 创建 shell脚本文件

创建bash时最好在第一行指定使用的shell,格式为

#!/bin/bash

然后就可以通过chmod u+x file.sh赋予脚本运行权限,然后输入脚本文件的位置即可运行脚本

  1. 显示消息

    #显示this is a test
    echo this is a test

    #将echo输出与下一个命令显示在同一行
    echo -n "the time and date are: "
    date
    #通过-n参数使得echo与date命令的输出显示在同一行

  1. 显示变量

    echo $HOME
    #通过在环境变量前面加$使用环境变量,如果需要显示$,需要转义$为\$
  1. 使用用户变量

    test1=hello
    test2=hey
    echo "$test1 and $test2"
  1. 命令替换

shell脚本可以从命令中提取信息,并且将信息赋给变量。然后就可以在脚本中随意使用变量了。

  • 第一种方法:反引号字符 ` (该字符位于TAB按钮上方)
  • 第二种方法:$()格式
    #反引号字符:
    testing=`date`
    #$()格式
    testing=$(date)
    echo "the date and a time are :" $testing

    #使用示例:
    today=$(date +%y%m%d)
    ls /usr/bin -al > log.$today

重定向输入输出与管道

  1. 输出重定向

    command > output

将命令的输出重定向到output文件,并且覆盖原有的output文件

    command >> output

将命令的输出重定向到output文件,并将输出追加到output文件后面

  1. 输入重定向

    command < inputfile

将inputfile作为标准输入重定向到command命令

例如:

    wc < test
    #wc统计文本的行数,词数和字节数。这样命令就会输出test文件的总行数,单词数和字节数
    #内联输入重定向
    command << marker
    data
    marker
    #内联输入重定向是输入多个数据,以marker(一个自己设定的标记开始和结束的字符)开头,以marker结尾
  1. 管道

管道就是可以将一个命令的输出重定向到另一个命令的输入当中

    command1 | command2

例如,寻找名为qq的进程

    ps -A | grep qq

这样就可以通过管道,将ps命令的输出作为grep命令的输入,从而打印出名为qq的进程名称。

执行数学运算

  1. expr命令

expr只支持整数运算,数字与符号之间有空格,需要考虑特殊字符的转义(例如*)

    expr 1 + 5
    #6
    expr 5 \* 2
    #需要对*号进行转义,输出10

    示例:
    var1=10
    var2=20
    var3=$(expr $var2 / $var1)
    echo the result is $var3
  1. 使用方括号

方括号同样只支持整数运算,不需要考虑转义

    var1=$[1 + 5]
    var4=$[$var1 * ($var2 - $var3)]
    echo $var4
  1. 浮点数解决方案

使用内建的bc计算器

    bc

就可以进入bc计算器

如何在脚本中使用bc

    方法一:管道法
    var1=$(echo "scale=4; 3.44 / 5" | bc)
    echo $var1
    通过scale指定小数点位数,也就是保留四位小数,最终输出是.6880

    方法二:内联输入重定向
    var=$(bc<

退出脚本

  1. 退出状态码

linux提供了$?变量来记录上一个命令退出的状态码

    echo $?

查看上一个命令退出状态码。如果是0就是正常退出

一般bash设置以一些内置linux退出状态码

状态码 描述
0 命令成功结束
1 未知错误
3 不适合的shell命令
126 命令不可执行
127 没找到命令
128 无效退出参数
128+x 与linux信号x相关的错误
130 ctrl-c中断
255 正常范围外的状态码

同时命令可以通过exit num来指定退出状态码

最终状态码是num%255

使用条件控制命令

使用if条件句

基本结构:

if command
then
    command
else
    command
fi
  • 条件判断:在bash中的if条件句并不是对bool类型的判断,而是对语句执行的退出状态码的判断,也就是如果command返回值是0(也就是执行成功),那么then后面的语句就会执行,否者不执行,执行else语句

  • if也可以有多条else语句

  • if也可以镶套执行

举例应用:

if grep ${testusername} /etc/passwd
then
    echo found ${testusername}
else
    echo ${testusername} is not exist
fi

如果用户 t e s t u s e r n a m e 存 在 , 就 显 示 第 一 个 e c h o , 否 则 显 示 第 二 个 e c h o ( 可 以 通 过 ‘ ‘ ‘ testusername存在,就显示第一个echo,否则显示第二个echo(可以通过``` testusernameecho,echo?```查看命令退出状态码)

test命令

上述的if条件句只能通过命令状态码判断运行。如果测试其他条件可以通过test命令实现

命令格式:

test condition

当test条件成立是,返回状态码0,否则返回非0状态码

举例:

测试变量是否存在
A="";test $A;echo $?
输出1

A=0;test $A;echo $?
输出0

在bash中,提供了[ ]可以像test命令一样方便的测试条件

  • []之间与内部命令之间通过空格间隔开

test[]的数值比较功能表


比较 描述
n1 -eq n2 ==
n1 -ge n2 >=
n1 -gt n2 >
n1 -le n2 <=
n1 -lt n2 <
n1 -ne n2 !=

这些数值比较功能可以用在数值和变量上面

举例:

if [ $value -gt 5 ]
then
    do something
fi
  • 但是记住,bash中数值比较不能使用浮点数,只能是整数

test[]的字符串比较功能表

比较 描述
str1 = str2 ==
str1 != str2 !=
str1 \< str2 <(记住转义小于符号,不然会被当成重定向)
str1 \> str2 >(记住转义大于符号,不然会被当成重定向)
-n str1 len(str1)!=0
-z str1 len(str1)==0
  • 在bash当中,大写字母被认为是小于小写字母的,这一点和sort命令正好相反,因为bash比较测试是用ascii的字符顺序进行的,而sort命令是使用本地语言设置中定义的排序顺序

在比较字符串时候,通常需要加额外字符保证不报错:

if test x$arg = xyes
then
    ...
fi

这是因为如果arg变量不存在,就会产生if test x = xyes但是如果没有x,就会变成if test = yes这样bash就会报错。所以添加x就是为了防止这种情况下的bash报错

test[]的文件比较功能表


比较 描述
-d file file是否存在并且是一个目录
-e file file是否存在
-f file file是否存在并且是一个文件
-r file file存在并且可读
-s file file存在并且非空
-w file file存在并且可写
-x file file存在并且可执行
-O file file存在并且属于当前用户
-G file file存在并且默认组与当前用户相同
file1 -nt file2 file1比file2新
file1 -ot file2 file1比file2老

使用复合条件

#两种形式
#AND
if [ condition1 ] && [ condition2 ]
#OR
if [ condition1 ] || [ condition2 ]

if-then的高级特性

使用双括号


在bash中,双括号命令允许你使用高级数学表达式。

列出除了test命令中列举的其他数学操作

符号 描述
! 逻辑反
var++ 后增
var– 后减
++var 前增
–var 前减
~ 位反
** 幂运算
>> << 右移与左移
& | 布尔和与或
&& || 逻辑和与或

举例:

var1=10
if (( var1 ** 2 > 90 ))
then
    (( var1 = var1 * 10 ))
    echo var1 is ${var1}
fi

使用双方框括号


双方括号提供了字符串高级比较特性

使用格式:

[[ expression ]]

其中expression的写法和test中定义的用法相同,但是额外提供了一个test命令当中没有提供的特性,那就是正则表达式

举例:

if [[ $USER == r* ]]
then 
    echo Hello
else
    echo i do not know you 
fi

[[]]当中就可以灵活的使用正则表达式进行各种字符串匹配操作

使用case命令

case使用效果可以达到多个if-else的效果

下面是case的基本使用模板:

case $var in
#通过 | 分割代表多种可能形式
thing1 | thing2 )
    dosomething
    ;;
thing3 )
    dosomething
    ;;
# * 代表默认所有可能
* )
    default_something
    ;;
esac

使用循环控制语句

for循环

形式:

for var in list
do 
    commands
done

举例:

#遍历自身定义的值(如果列表中出现特殊字符,如单引号,需要转义)
for test in A "C D"  hh\'
do 
    echo i am $test
done

#输出:
i am A
i am C D
i am hh'

#从命令中取值(参考命令替换章节)
dir=.
for f in $(ls $dir)
do 
    echo "there is file $f"
done
#会枚举出当前文件夹所有文件

更改字段分隔符


在bash当中,有一个叫做IFS(internal field separator)的环境变量,中文名是内部字段分隔符。默认情况下,bash shell会通过空格,制表符,换行符当做字段分隔符

如果想要更换字段分隔符,可以通过临时改变IFS环境变量来实现相应的效果

例如:

IFS=$'\n'
IFS=:

或者相应方式进行改变即可,改变过后可以通过IFS=来重新定义回默认分隔符

C风格的for命令


我们可以通过使用bash命令当中加入双括号还引入c风格的for循环

例如:

for (( i=1,j=0; i<10; i++,j++ ))
do
    echo i,j=$i,$j
done

在c风格for循环同样可以像上面一样使用多变量

while循环

while循环相对来说简单一些,格式如下:

while command
do 
    commands
done

其中command的用法和if中的用法完全一样,可以使用test命令或者[ ]

使用过个测试命令


while中可以含有多个条件命令,其中以最后一个命令的执行作为最终的结果

while command1
    command2
do 
    commands
done 

until循环

until命令和c语言当中的until命令基本相同,和while命令的用法很相似

until command
do 
    commands
done

command也可以像while命令一样加入很多的命令,并且以最后一个命令的退出码作为判断

控制循环

break命令


break n

break命令就像c语言当中的break命令一样,用来跳出循环。如果没有参数n,默认跳出当前循环,如果含有参数n,则表跳出多重循环,n==1表示跳出当前循环

continue命令


continue n 

continue命令和c语言中用法差不多,n的含义和上述break命令当中n含义相同

处理循环输出

shell中你可以对循环的输出结果进行管道或者重定向

重定向:

for i in list
do 
    commands 
done > test.txt

这样子循环所有的输出都被从定向到了test.txt当中

管道:

for i in list
do 
    commands
done | sort

这样子命令的最总结果会输出排序后的输出

处理输入输出

命令行参数

命令行参数通过下面模式输入:

./some.sh arg1 arg2

读取参数


$0代表脚本的名称,$1是第一个参数,以此类推,$9是第九个参数

示例:

echo the file name is $0
echo the first arg is $1

可以通过if [ -n "$1" ]来检查是否含有参数

参数个数


对于bash shell脚本$#会包含总参数个数的信息,值就是一共多少个参数

如果你想表示最后一个参数是什么,应该这么做:

echo the last arg is ${!#}

注:为什么使用感叹号而不是${$#},因为花括号里面当然不能使用$,可以在花括号里面使用!来达到代替$的目的

抓取所有变量


我们可以通过$*$@来抓取所有的参数

$*会把所有的命令行参数当成一个字符串,所以不能通过for遍历

$@会把命令分割成多个独立的单词,所以可以通过for循环遍历

移动变量 shift命令


命令使用方法:

shift

使用shift命令,默认情况下他会把每个参数都同时向左移动一个位置,这样子,$3的值就变成了$4的值,以此类推。$1的值就会被删除,但是$0的值并不会改变

我们可以通过shift 9来读取第10个和之后的参数,这也是脚本程序的通常做法

数据的呈现

标准文件描述符号


Linux中的标准文件描述符号

文件描述符 缩写 描述
0 STDIN 标准输入
1 STDOUT 标准输出
2 STDERR 标准错误
  • 使用输入重定向时,linux会用指定的文件去替换标准输入文件描述符

  • 使用输出重定向可以用来改变输出重定向的指向

  • 默认情况下,标准错误输出也是和标准输出一样,输出到屏幕上面,但是STDERR不会随着STDOUT的重定向而改变。

重定向标准错误


command 2> file

我们可以通过用数字指明你要重定向的文件描述符,这样子就可以重定向错误输出了

如果想要把输出和错误重定向到不同的文件当中,可以使用这种方法

command 2> err_file 1> out_file

如果想要把输出和错误重定向到相同的文件当中,可以使用&>符号

command &> file

如果通过&>重定向错误和输出,因为错误在bash当中具有较高的优先级,所以输出的顺序可能会和你的预想不太相同,错误会集中的显示在文件的开始部位,然后才是输出的信息的显示

在脚本中使用重定向

  1. 临时重定向


    我们在脚本当中,可以把单独的一行重定向输出到一个选定的文件描述符当中,例如:

    echo "this is an error" >&2
    

    这样子echo输出的数据就会当成标准错误显示出来

  2. 永久重定向


    重定向每个echo语句就会十分的繁琐,我们可以通过exec语句实现重定向某个特定的文件描述符

    exec 1>file
    在此执行的脚本的标准输出就会被重定向到file当中
    exec 2>another_file
    在次执行的语句的标准错误都会被重定向到another_file当中
    
  3. 重定向输入


    exec 0 < file
    

    这样就会从file当中获取STDIN的数据了

  4. 创建输出重定向


    可以通过exec命令文件描述符分配重定向的文件。例如如果文件描述符3可用。那么

    exec 3>testout
    

    这样就创建了一个输出的重定向,我们可以通过

    echo "some thing" >&3
    

    把命令的输出对应到某个文件描述符当中。当然命令还有很多的灵活操作,下面我们一个一个介绍

  5. 重定向输出文件描述符


    如果你可以分配一个文件描述符给标准文件描述符,同样,你也可以将标准文件描述符分配给其他人,见下面的例子:

    把文件描述符3的输出定向到1的输出
    exec 3>&1
    把文件描述符1的输出定向到test的输出
    exec 1>test
    此时 3 = 屏幕输出;1=test
    
    下面的语句将会输出到test文件句柄的写入
    echo "where am i"
    
    把1的输出重新定向到屏幕输出当中
    exec 1>&3
    
    下面语句重新显示在屏幕上面
    echo "i am on the screen "
    
  6. 创建输入文件描述符与重定向输入文件描述符


    类似上面的操作,我们同样可以重定向输入文件描述符

    把文件描述符6的输入定向到0的输入
    exec 6<&0
    把文件描述符0的写入定向到test的读取
    exec 0
  7. 创建读写文件描述句柄


    举例:

    exec 3<>test
    read line <&3
    echo "read line &line"
    echo "write line " >&3
    
  8. 关闭文件描述符


    shell结束会,会自动关闭所有的文件描述符,但是有时候你也同样需要手动关闭文件描述符

    exec 3>&-
    

    通过这样,就可以关闭句柄为3的文件描述符

列出当前打开的文件描述符


lsof

我们可以通过lsof命令列出所有打开的文件描述符

这个命令会产生大量的输出,会列出当前linux上所有打开的文件的文件的相关信息。

这里我只讲解一下最常用的两个选项,-p-d选项

lsof -a -p $$ -d 0,1,2

这条语句调用了lsof打印当前进程0,1,2三个句柄的信息。其中-p后面跟进程号,$$在shell当中代表当前进程号。-d表示选择当前要打印的进程文件描述符。-a代表将符合-p选项和符合-d选项的所有文件描述符取交集

lsof的默认输出

描述
COMMAND 正在运行的命令的前九个字符
PID 进程ID
USER 进程属主
FD 文件描述符号和访问类型
TYPE 文件的类型 CHR:字符型 BLK:块型 DIR:目录 REG:常规文件
DEVICE 设备的设备号
SIZE 表示文件的大小
NODE 本地文件的节点号
NAME 文件名

阻止命令的输出


有时候你可能不想显示脚本的输出,比如说你想将脚本当做后台程序运行,这时候你应该阻止文件的输出

linux下有一个特殊文件,就是/dev/null,null文件和他的文件名一样,文件里面什么都没有,输出到null文件里面到任何数据都不会保存,全部都丢掉了。

举例:

丢弃command的标准输出
command > /dev/null

丢弃command的错误
command 2> /dev/null

清空某文件
cat /dev/null > file

创建临时文件

linux 使用/tmp目录存放不需要永久保存的文件,大多数linux发行版配置了系统启动时自动删除/tmp目录下的文件

系统任何账户都有权限创建临时文件,不用担心清理工作。

  1. 创建本地临时文件

    默认情况下,mktemp会在本地目录创建一个文件,用mktemp命令创建一个临时文件只需指定一个文件名摸板就行了。模板可以指定任意文本文件名,在文件末尾添加六个X就行了(一般是六个,其实任意就行)

    tmpfilename=$(mktemp test.XXXXXX)
    

    命令返回创建的文件的文件名称,保证文件的唯一性,不重名。

  2. /tmp目录下创建临时文件

    -t选项会强制mktemp命令在系统临时目录下创建临时文件,这样mktemp命令返回的是文件的全路径名称而不是只有文件名。

  3. 创建临时目录

    -d选项会使得mktemp命令创建临时目录而不是临时文件,这样你可以针对目录进行一些任何操作。

tee命令

将输出同时发送到显示器和日志文件,这种做法有时候可以排上用场。

tee命令相当于管道的一个T型接头,将从STDIN过来的数据同时发送给STDOUT和tee命令指向的文件

date | tee filename

如果是追加到文件的尾部

date | tee -a filename

控制脚本编写

处理信号

linux信号

信号 描述
SIGHUP 1 挂起进程
SIGINT 2 终止进程
SIGQUIT 3 停止进程
SIGKILL 9 无条件终止进程
SIGTERM 15 尽可能的终止进程
SIGSTOP 17 无条件停止进程,但是不是终止
SIGTSTP 18 停止或者暂停进程,但不终止进程
SIGCONT 19 继续停止运行的进程

默认情况下,bash shell会忽略收到的SIGQUIT和SIGTERM信号,但是会处理SIGHUP和SIGINT信号。

生成linux信号

  1. SIGINT

    通过输入ctrl c组合键发送SIGINT信号停止shell当前运行的进程。

  2. SIGTSTP

    通过输入ctrl z组合键生成一个SIGTSTP信号,停止shell中运行的任何进程,但是停止的进程会继续保留在内存当中,可以通过fg命令恢复暂停的进程。可以通过ps -l查看当前暂停的作业

  3. SIGKILL

    可以通过kill -9 PID来给对应的进程发送SIGKILL信号杀死进程。

捕获信号

也可以不忽略信号,在信号出现时候及时的捕获他并执行其他命令。trap命令可以允许你指定shell脚本要监控那些shell中拦截的信号。

  1. 捕获信号

    trap commands signals
    
    trap "echo 'i have trapped ctrl c'" SIGINT
    
  2. 捕获脚本退出

    trap commands EXIT
    

    在脚本执行结束之后就可以执行对应commands

  3. 移除捕获

    trap -- signals
    

后台模式运行脚本

在命令后面添加一个&就可以放进后台运行。

command &

然后返回作业号(在中括号当中)

在终端程序当中使用后台进程一定要小心,在终端中运行后台进程时候,每一个后台进程都对应一个终端会话(pts),如果终端会话退出,后台进程也会退出。

如果想要在终端退出也继续运行后台进程,可以通过使用nohup命令来拦截所有的SIGHUP信号,同时切断进程和终端的联系,所有的输出都会追加到一个nohup.out文件当中。

nohup ./comm.sh &

作业控制

查看作业

jobs

jobs命令可以查看分配给shell的作业,想要查看作业的PID,只需要添加一个-l选项

参数 描述
-l 列出PID和作业号
-n 列出上次shell发出通知后改变状态的作业
-p 只列出PID
-r 只列出运行的作业
-s 只列出停止的作业

在jobs中带有+号的作业是默认作业,如果其他作业操作不指定作业号,则默认针对该作业。带有-号的作业是下一个成为默认作业的作业

重启停止的作业

通过bg命令加作业号可以重启停止的作业放在后台,如果没有作业号,就是默认的作业

如果想要在前台重启作业号,可以使用fg命令加作业号,否则是默认作业

调整谦让度

调度优先级就是内核分配给进程的CPU时间。在linux当中,有shell启动的所有进程的调度优先级都是相同的。调度优先级是整数,从-20(最高优先级)到19(最低优先级)。

nice -n 10 command

调整谦让度为10

如果提高优先级需要有root权限

renice -n -20 -p PID

同样renice命令不能提高优先级,除非有root权限

定时作业

  1. 使用at命令
    at会将作业提交到队列当中,指定shell何时运行该作业。atd是at的守护进程。atd守护进程会检查/var/spool/at没记录来获取at命令提交的作业。默认情况下会60s检测一次,如果时间匹配,就会运行作业。

    at [-f filename] time
    

    时间格式有很多类型,可以参考百度

    at的输出默认发送到用户的邮箱当中,如果添加-M选项就可以忽略命令的输出。所以建议在脚本当中使用重定向。

    可以使用atq命令查看作业信息,atm 作业号删除指定的作业

  2. 使用cron时间表
    对于需要定期执行的命令,可以使用cron时间表

    cron时间表采用如下格式min hour dayofmonth month dayofweek command指定作业何时执行。
    可以使用通配符。如果想在每周一10:15执行:15 10 * * 1 command

    可以通过crontab -l命令查看已有的时间表,使用crontab -e更改时间表

    如果你的脚本对时间精确性要求不高,可以通过在/etc/cron\.*ly目录中添加脚本达到定期执行脚本的作用。例如每天daily,每周weekly

    如果对应作业时间时系统处于关机状态,想要此时执行一遍没有执行的作业,可以使用anacron程序,这个详见百度。

高级shell脚本编写

创建与使用函数

  1. 基本的脚本函数

    有两种创建函数的方式:

    function name{
        commands
    }
    
    name(){
        commands
    }
    
  2. 使用函数

    函数的定义一定要放在函数使用的前面

    function fun1{
        echo "i am a function"
    }
    
    fun1
    
  3. 返回值

    如果不指定返回值,默认返回的是最后一个命令执行的状态码,这样子非常危险

    可以通过return返回函数的返回值

    db1{
        read -p "input a value " value
        return [ $value * 2 ]
    }
    

    但是返回值必须是0-255之间的值,而且需要立马获取$?,不然就会被覆盖

    有另一个方法接受函数返回值。那就是通过echo返回给获取的变量

    db2{
        read -p "input a value " value
        echo $[ value * 2 ]
    }
    result=$(db2)
    
  4. 向函数传递参数

    在脚本中向函数传递参数时候可以讲函数和参数写在一行,然后在函数中可以向脚本中一样通过$0-9获取函数参数

  5. 函数中的全局变量和局部变量

    在函数当中,如果直接使用变量,默认是使用全局的变量。所以是十分危险的。我们一般指定局部变量,通过local关键字

    db(){
        local i = 0
    }
    i=1
    db
    echo $i
    

    这样就不会改变全局变量的值

  6. 通过函数返回数组

    function arraybdlr{
        local origarray
        local newarray
        local elements
        local i
        origarray=($(echo "$@"))
        newarray=($(echo "$@))
        elements=$#
        for (( i = 0; i < elements; i++)){
            newarray[$i]=$[ ${origarray[$i]} * 2 ]
        }
        echo ${newarray[*]}
    }
    myarray=(1 2 3 4 5)
    echo "the origin array is ${myarray[*]}"
    arg1=$(echo ${myarray[*]})
    result=($(arraybdlr $arg1))
    echo "the new array is ${result[*]}"
    
  7. bash shell函数支持递归

创建和使用库

对于bash来说,如果使用bash运行一个脚本,相当于新建一个bash进程来运行脚本,所以在bash中作用域只是脚本内部。可以通过source命令达到使用当前bash执行命令的效果

source test.sh
. test.sh

这样子就可以调用脚本中定义的函数了

定义函数的最佳场所是.bashrc,因为这个文件每次启动一个新shell都会运行

sed命令初级

sed编辑器可以根据命令来处理数据流当中的数据。这些命令要要么储存在一个命令行文本文件当中,要么从命令行输入。

sed编辑器会执行一下操作

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

在流编辑器处理玩一行数据之后,他会读取下一行数据然后重复这个过程,直到处理完流当中的所有数据。

sed命令格式:

sed options script file

sed命令选项:

选项 描述
-e script 在处理输入时候,把script命令(script指单条sed命令)添加到已有命令当中(这个选项主要是用来指定多条处理指令,原本默认只有一条处理指令)
-f file 在处理输入时候,把file文件当中的命令添加到已有的命令当中
-n 不产生命令输出,使用print命令完成命令输出(通常和p选项一同使用)

例如:

#替换test为big test
echo "this is a test" | sed 's/test/big test/'
#同上
sed 's/test/big test/' data1.txt
sed -e 's/brown/green'; s/dog/cat/' data1.txt

sed -e '
> s/brown/green/
> s/fox/dog/' data1.txt

sed -f script1.sed data1.txt

当文件当中读取命令时候,不需要使用分号隔开,因为默认每行都是一条单独的命令

sed编辑器基础

使用替换

  1. 替换标记

    替换简单实例:

    sed 's/test/trial/' data.txt
    

    这样子设置之后data.txt中每一行的第一处test都会被转换成trial(默认情况下只替换每行出现的第一处)。可以使用替换标记替换一行当中不同位置的文本。

    s/pattern/replacement/flags
    

    flags有四种可以标记的形式:

    flag 说明
    数字 表明新文本将会替换第几处模式匹配的地方
    g 表明新文本将会替换所有匹配的文本
    p 表明将原先行的内容print出来(通常和-n选项一同使用)
    w file 讲替换的结果写到文件当中(只写替换的行,没有进行替换的行不写入)

    如果想要只输出替换过的行,可以通过p选项和-n结合使用。

    sed -n 's/test/trial/p' data.txt
    

    这里指定-n选项表示不把流处理的文本全部输出,只输出print的内容。然后p选项指定print所有替换和更改的行。这样子就可以只输出更改的行(更w选项效果类似)

  2. 替换标记

    如果在替换的过程中,发现了/字符,这种情况下,有两种解决方案:

    1. 使用转义字符,在/前面加上\变成\/
    2. 使用其他字符作为分隔符(sed可以自己检测)。例如:
      sed 's!/bin/bash!/bin/csh!' /etc/passwd
      

使用地址

默认情况下,sed使用的命令作用于文本数据的所有行。如果只想控制命令作用与某些特定的行,必须使用行寻址。

sed编辑器有两种形式的寻址:

  • 通过数字形式表示行区间
  • 通过文本模式过滤出来行

两种形式都通过相同格式指定地址:

[adderss]command

也可以将特定地址的多个命令分组:

address {
    command1 
    command2
    command3
}

sed编辑器会将命令作用到相应的行上面

  1. 数字方式寻址

    命令中可以指定单个行号或者使用起始行号,逗号和结尾行号进行标注

    sed '2s/dog/cat/' data.txt
    sed '2,3s/dog/cat/' data.txt
    

    如果想要将命令从第某一行可以使用美元符号。美元符号在sed中可以表示最后一行

    sed '2,$s/dog/cat/' data.txt
    
  2. 使用文本模式过滤

    sed允许指定文本模式来过滤出来命令需要的行,格式如下:

    /pattern/command
    

    必须使用正斜线把pattern封装起来。例如:

    sed '/ted/s/bash/csh/' /etc/passwd
    

    这里指定的ted跟cat /etc/passwd | grep ted效果相同,表示包含可以正则匹配到的行。

  3. 命令组合

    如果在单行指定多条命令,可以通过花括号隔开

    sed '2{
    > s/fox/ele/
    > s/dog/cat/
    > }' data.txt
    
    sed '2{s/fox/ele;s/dog/cat}' data.txt
    

删除行

删除命令d会删除所有命令当中匹配的行位置。使用命令时候应该格外小心,因为如果你忘记加入寻址模式时候,流当中的所有文本行都会被删除。例如:

sed '3d' data.txt

删除第三行。

sed '2,3d' data.txt

删除特定的区间

sed '/ted/d' /etc/passwd

删除模式匹配的行位置。(同上文当中的模式匹配方式)

同样也可以使用两个文本模式来指定某个删除的区间。例如一个文本test.txt如下:

test.txt

a
b
c
a
d
e

这种情况下如果使用sed '/b/,/a/d' test.txt,就会删除b-a的所有内容,然后输出:

a
e

但是如果是sed '/a/,/c/d' test.txt,结果就是输出一个空文件。因为匹配到第一个模式时候打开删除功能,匹配到c时候关闭删除功能。但是第二次匹配到a时候再次打开删除功能。然后一直删除到结尾。同样,如果对文本使用sed '/b/,/w/d' test.txt因为没有找到w字母,所以sed就直接删到结束位置,故最终输出只有a

插入和添加文本

sed允许用户插入和添加文本。

  • i命令会在指定行前面添加一个新行
  • a命令会在指定行后面添加一个新行

格式如下:

sed '[address]command\newline'

注意的是command后面是反斜杠,这一点和前面的几个不一样。

使用实例:

#第三行前面计入new line
sed '3i\new line' data.txt
#最后一行前面加入endline
sed '$i\endline' data.txt

修改行

使用c命令可以修改某一个特定的行。格式跟前者相同。

使用实例:

sed '/ted/c\this is the change of ted line' data.txt
sed '2,3c\this is the change line'

转换命令

转换命令是唯一一个可以处理单个字符的sed命令。格式如下:

[address]y/inchars/outchars/

转换命令会把inchars中的第一个字符变成outchars中的第一个字符,第二个字符转换成outchars中的第二个字符。这个映射过程一直持续到所有字符转换完成为止。如果inchars和outchars长度不想听,编辑器会进行一次报错。

例如:

echo "this 1 is 1" | sed 'y/123/456/'

输出为this 4 is 4

他会转换匹配的每一行,无法指定行中特定的位置

打印命令

之前简要介绍了如何在sed当中使用p和-n选项结合打印需要的信息。这里面介绍一共三个打印命令。

  • p命令打印文本行
  • =打印行号
  • l(小写的l)列出行。
  1. 打印行

    echo "this is test" | sed 'p'
    

    输出:

    this is test
    this is test
    

    常用做法是打印模式匹配的行:

    sed -n '/test/p' data.txt
    

    这样就会打印包含test的行(效果和grep test data.txt相同。

    如果你想在修改之前先显示当前行的内容,可以通过如下命令:

    echo -e "a\nb\nc" | sed -n '/b/{p;s/b/hhh/p}'
    

    这样输出:

    b
    hhh
    
  2. 打印行号

    使用=
    例如如果想要打印某一行的行号和内容:

    sed -n '/test/{=;p}' data.txt
    
  3. 列出行

    列出命令(list)可以用来打印数据流当中的不可打印的ASCII字符。任何的不可打印的字符要不然在其八进制值前面加上一个反斜杠,或者使用C风格的命令法(例如\t)。这个命令常用来打印不可打印的文件字符。

    用法:

    sed -n 'l' data.txt
    

使用sed处理文件

  1. 写入文件

    [address]w filename
    

    例如下面将文件的前两行写入test.txt当中

    sed '1,2w test.txt' data.txt
    

    当然如果你同时不想让它显示原本的文件内容可以指定-n选项。

  2. 从文件读取

    read命令可以是你将一个独立的文件的数据插入到数据流当中。sed会将文件插入到指定的地址后面。

    格式是:

    [address]r filename
    
    sed '3r data12.txt' data.txt
    sed '$r data12.txt' data.txt
    

    这样data12.txt就被插入到data.txt第三行后面了。

    读取命令一个很常用的用法就是占位符文本,例如一个文本内容是

    would the following people
    LIST
    please report to the ship's caption
    

    如果使用名单列表替换LIST内容:

    sed '/LIST/{r namelist.txt;d}' data.txt
    

sed编辑器进阶

多行命令

sed编辑器命令通常针对的都是单行命令。但是其实sed当中包含了三个用来处理多行文本的特殊命令。

  • N:将数据流的下一行加进来创建一个多行组(multiline group)来处理
  • D:删除多行组中的一行
  • P:打印多行组当中的一行
next命令
  1. 单行版本的next命令

    小写的n命令会告诉sed编辑器移动到数据流下一行文本当中,而不用重新回到命令最开始执行一遍。通常sed会移动到下一个数据流之前会在当前行执行完所有定义的命令。

    如果你想删掉包含header单词所在行的下一行,你可以使用命令:

    sed '/header/{ n ; d }' data.txt
    

    这样找到匹配行之后就会执行n变到下一行执行d

  2. 合并文本行

    单行版本n命令会将当前数据流的下一行移至sed工作空间(或者说是模式空间)。多行版本的next命令(大写N)回见下一行文本添加到模式空间已有的文本后面。

    文本行仍使用换行符分割。
    例如如果想要合并包含first的行和下一行,可以使用:

    sed '/first/{ N ; s/\n/ / }' data.txt   
    

    但是在使用N命令时要考虑一个特殊情况,那就是如果sed在执行最后一行时候,如果出现了N命令,sed编辑器就会停止执行(不执行N后面的命令了)。所以编写脚本同时也要注意解决这个问题。

多行删除命令

大写的D命令和d命令不相同。在使用N命令时候,一次加载了两行。这个时候如果使用d命令,就会同时删除两行。但是这个未必是期望的结果。

如果使用D命令,sed只会删除到第一个换行符为止(也就是删除最开始的第一行)

例如删除数据流当中出现在第一行前的空白行:

sed '/^$/{ N ; /header/D}' data.txt
打印多行命令

多行打印命令大写P和多行删除命令类似,只打印多行模式空间当中的第一行。包括模式空间当中的直到换行符的所有字符。这个选项也同样常和-n选项一同使用。

保持空间

模式空间(pattern space)是一个活跃的缓冲区,sed执行命令时会保存待检查的文本。保持空间(hold space)是用来执行某些行时可以用来保存一些行。有以下几条命令操作保持空间:

命令 描述
h 将模式空间复制到保持空间
H 将模式空间附加到保持空间
g 将保持空间复制到模式空间
G 将保持空间附加到模式空间
x 交换模式空间和保持空间的内容

例如如果你想要交换所有的包含first的行和下面一行(也就是反序输出这两行)可以使用下面命令:

sed -n '/first/ {h ; n ; p ; g ; p }' data.txt

排除命令

在sed命令使用当中,你可以配置命令使其不要作用到数据流当中的特定位置上面。感叹号命令可以用来排除命令。也就是原本起作用的命令不起作用。

sed -n '/header/!p' data.txt

这样子命令只作用到模式没有匹配的数据流上面。也就是包含header的行不会打印出来。

如果有了排除命令,如何实现将全文行反转的效果呢?下面我们介绍一下全文行反转的思路:

  1. 在模式空间当中放置一行
  2. 将模式空间的行放置到保持空间当中
  3. 在模式空间当中放入下一行
  4. 将保持空间内容追加到模式空间后面
  5. 将模式空间所有内容放进保持空间当中去
  6. 重复执行3-5步直到所有行都反序放置在保持空间当中

所以可以使用下面命令

sed -n '{1!G ; h ; $p}' data.txt

改变流

分支

sed编辑器提供了可以基于地址,地址模式或者地址区间排除一整块命令。这允许你在特定行执行一组命令

分支命令b格式如下:

[address]b [label]

address决定了哪些行会触发分支命令。label参数决定了要跳转的位置。如果没有label参数,跳转命令会跳转到脚本结尾

例如如果想要在数据流的2,3行跳过替换命令,可以使用以下脚本:

sed '{2,3b ; s/test/hhh/ }' test.dat

如果不想直接跳转到行尾,可以指定一个跳转的标签,标签是以冒号开始的,最多7个字符长度

sed '{/first/b jump1 ; s/a/b/ ; :jump1 ; s/c/d/ }' data.txt

这样就可以指定跳转位置了。如果跳转位置在跳转语句前面的话,就可以实现循环的功能

测试

测试命令t也可以用来改变sed脚本执行流程。测试命令会根据替换命令结果跳转到某个标签,而不是根据地址进行跳转。
如果替换命令成功匹配并且替换了一个模式,测试命令就不会执行跳转,否则跳转。

测试命令格式:

[address]t [label]

例如:

sed '{s/a/b/ ; t ;s/b/c/}' data.txt

如果第一个替换成功,就会跳过后面的命令。

模式替换

在sed执行替换命令时候,如果你想通过模式方法一次匹配替换多个模式对象,使用这种方法是行不通的。例如你想在所有的cat和hat外层添加一个括号:

echo "the cat is in a hat" | sed 's/.at/".at"/g'

这样子最终的输出结果是the ".at" is in a ".at"。为了解决这个问题,sed提出了模式替换。

&符号

&符号可以表示替换命令当中匹配的模式。不管模式匹配了什么文本,都可以在替代模式当中使用&来表示这段文本。
所以可以这样实现上述的目的:

echo "the cat is in a hat" | sed 's/.at/"&"/g'

这样子模式替换的最终输出就是the "cat" is in a "hat"

替代单独的单词

&符号会提取匹配替换命令中指定的模式的整个字符串。有时候你只是想提取这个字符串的一部分。

sed当中使用转移的圆括号表示每个子模式(这一点和平常转义的用法正好相反,因为必须使用转义符号表示分组字符。),同时在替换一方使用转义符号加数字表示第几个模式。例如用法如下:

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

这样最终输出就是that furry "cat" is pretty

  • 在sed当中使用圆括号和大括号等ERE模式时需要使用转义符号

gawk程序

相对于sed而言,gawk是一套更加高级的文本流处理工具,他可以提供一个类变成的环境来修改和重新组织文件当中的数据。

gawk编程语言当中,你可以:

  • 定义变量保存数据
  • 使用算术和字符串操作符处理数据
  • 使用结构化语句(例如if-else和循环)来添加处理逻辑
  • 提取数据元素,重新排列和格式化

gawk通常用来处理大日志文件,其格式如下:

gawk options program file

gawk通常使用选项:

选项 描述
-F fs 指定行中划分数据字段的分隔符
-f file 从指定文件当中读取程序
-v var=value 定义gawk当中一个变量和他的默认值
-mf N 指定要处理数据当中最大字段数
-mr N 指定数据文件当中的最大数据行数
-W keyword 指定gawk兼容模式和警告等级

gawk基础

从命令行读取脚本

将脚本放置在两个花括号当中。

gawk '{print "Hello world!"}' data.txt

gawk假定脚本是单个字符串,所以要放进单括号当中。

gawk和sed命令类似,也是对每一行进行处理。所以gawk程序会针对数据流每行执行脚本。

使用数据字段变量

gawk自动给一行每个数据元素分配一个变量。默认情况下,gawk会将如下变量分配给数据字段

  • $0表示整个文本行
  • $1表示文本行第一个数据字段
  • $2表示文本行第二个数据字段
  • $n表示文本行第n个数据字段

数据字段通过分隔符划分。默认分隔符是空白字符(例如空格和制表符)

gawk '{print $1}' data.txt

打印文本数据第一列

当然你可以指定其他分隔符,例如:

gawk -F: '{print $1}' /etc/passwd

打印出来密码文件的第一个数据字段

程序中使用多个命令

gawk程序允许通过分号分割语句,例如

echo  "i am rich" | gawk '{$4="Christ";print $0}'

输出:i am Christ

从文件中读取命令

下面有命令文件
scr.gawk

{print $1 "is" $6}

然后执行

gawk -f scr.gawk test.txt

这样就可以实现文件中储存命令。如果有多条命令,可以使用分好隔开,也可以使用每行一条命令(不需要分号)。

在数据处理前后运行脚本

也许你想在开始逐行处理前运行一些处理脚本,或者逐行处理后运行一些脚本,可以使用BEGIN和END关键词:

gawk 'BEGIN {print "begin the content"} ; {print $0} ; END {print "end of the content"}' test.txt

这样子就会输出内容当中,开始是begin the content,结尾时end of the content。达到先后处理的效果

gawk进阶

使用变量

内建变量
  1. 字段和记录分隔符变量

    数据字段可以让你使用$+数字的形式使用第n个字段。数据字段是通过字段分隔符划定的。默认情况下,字段分隔符是一个空白字符(也就是空格或者制表符)。之前说过可以通过-F选项指定使用的字段分隔符。也可以使用FS内建变量来分割字符。gawk有以下内建变量:

    变量 描述
    FLELDWIDTHS 通过空格分割的一列数字,定义了数据字段确切宽度
    FS 输入字段分隔符
    RS 输入记录分隔符
    OFS 输出字段分隔符
    ORS 输出记录分隔符

    如果数据文件data.txt内容如下

    data1,data2
    data3,data4
    

    如果数据是逗号分割,你可以通过这样:

    gawk 'BEGIN{FS=","} {print $1,$2}' data.txt
    

    打印读取打印data.txt内容。如果你想让输出数据通过横线分隔开,可以使用:

    gawk 'BEGIN{FS=",";OFS="-"} {print $1,$2}' data.txt
    

    如果你的数字文件是通过定长的格式存放的,例如data.txt123.4-5.67

    你可以使用gawk 'BEGIN{FIELEWIDTHS="4 4"}{print $1,$2}' data.txt这样每一个字段就会按照指定的长度读取,打印123.4 -5.67

    因为默认每一行都是一条gawk的记录数据流,所以RS和ORS默认都是换行符。如果你的记录是多行。常用的处理方法是多行记录之间通过一个空行分割开来。然后设置FS是换行符,RS是空白字符,就可以了

    #cat文件内容如下:
    #1\n2\n\n3\n4\n\n5\n6\n
    gawk 'BEGIN{FS="\n";RS=""} {print $1,$2}' data.txt
    #输出是:
    #1 2\n3 4\n5 6
    

    现在每一行都是一个字段,空白行是数据分隔符

  2. 数据变量

    变量 描述
    ARGC 命令行参数个数
    ARGIND 当前文件在ARGV中的位置
    ARGV 包含命令行参数的数组
    CONVFMT 数字转换格式,默认是%.6 g
    ENVIRON 当前shell环境变量及其值组成的关联数组
    ERRNO 读取或者关闭输入文件时发生的系统错误号
    FILENAME gawk输入数据的数据文件名
    FNR 当前数据文件数据行数
    IGNORECASE 如果这个值不是0,忽略字符串大小写
    NF 数据文件当中字段总数
    NR 已处理的数据记录个数
    OFMT 数字输出格式,默认是%.6g(也就是小数点后面最多6位)
    RLENGTH 由match函数匹配的字符串长度
    RSTART 由match函数匹配的子字符串起始位置

    使用ARGC和ARGV变量时候,ARGC表示参数个数(这个不是c语言里面的ARGV,这个是gawk命令后(也就是引号之后)的命令参数个数,例如:

    gawk -F: 'BEGIN{print ARGC,ARGV[0],ARGV[1],ARGV[2]} data1.txt data2.txt
    

    此时输出应该是3 gawk data1.txt data2.txt。这里0号参数表示的是命令名称,接下来参数表示的是gawk命令之后的参数(这不包含gawk参数和gawk命令)。同时,脚本中引用脚本变量不需要美元符。

    ENVIRON变量是一个关联数组。关联数组使用文本作为数组引索。例如打印HOME环境变量和PATH环境变量:

    gawk '{BEGIN{print ENVIRON["HOME] ; print ENVIRON["PATH]}}' test.txt
    

    如果你想打印第一列数据和最后一列数据,可以通过这样:

    gawk 'BEGIN{FS=":";OFS=":"} {print $1,$NF}' /etc/passwd
    

    这里因为NF表示的是字段总数,所以$NF就是最后一个字段了

    这里详解一个FNR和NR变量含义的不同:NR表示的是一共处理了几个记录(默认是一行是一个记录,可以通过RS指定记录分隔符)。即使你处理了多个文件的记录,这个NR的值也是累加的。但是FNR表示的是单个文件当中处理的记录个数。也就是每次处理其他文件时候FNR的值都会从1开始。

自定义变量
  1. 在脚本中给变量赋值

    gawk 'BEGIN{test="it is a test";print test}'
    

    这样就会输出it is a test。同样变量可以进行数学运算:

    gawk 'BEGIN{x=2;x=(x+3)*2;print x}'
    

    这样就会输出10。gawk支持标准算术操作符,包括求余(%)和幂运算(^或者**)

  2. 在命令行定义变量

    gawk可以通过使用-v选项在命令行上面指定程序需要用到的变量:

    gawk -v x=2 'BEGIN{x=x**2;print x}'
    

    最终输出是4

处理数组

定义数组变量

可以使用标准赋值语句来定义数组变量:

var[index] = element

其中index引索值可以是数字也可以是字符串:

gawk 'BEGIN{test["test"]="hhh";print test["test"]}'
gawk 'BEGIN{var[1]=25;var[2]=31;tot=var[1]+var[2];print tot}'
遍历数组变量

我们可以通过for循环遍历数组

for (var in array)
{
    statements
}

for循环会循环数组的引索值然后执行statement。你可以通过引索值取出数组内容。
例如:

gawk 'BEGIN{var["a"]="x";var["b"]="y";for (test in var){print "Index:",test," - Value:",var[test]}}'

这样最终是输出就是:

Index: a  - Value: x
Index: b  - Value: y
删除数组变量

以下命令从关联数组中删除一个元素:

delete array[index]

使用模式

使用正则表达式

在gawk当中,你可以使用BRE和ERE正则表达式。使用正则表达式时候,正则表达式必须是出现在花括号前面

gawk 'BEGIN{FS=","} /exp/{print $1}' data.txt

这样匹配了所有符合exp的记录,其中exp甚至可以包含字段分隔符

匹配操作符

匹配操作符允许把正则表达式作用做特定的字段上面,匹配操作符就是波浪线,例如:

$1 ~ /^data/

使用示例:

gawk '$2 ~ /exp/{print $0}' data.txt

这样他就会输出所有的第二个字段包含exp表达式的所有记录

一个更加实用的示例:

gawk -F: '$1 ~ /rich/{print $1 ,$NF}' /etc/passwd

这样就会输出rich所用的shell名称

如果你想使用排除,只需要在波浪号前面添加一个感叹号见就行了

gawk -F: '$1 !~ /rich/{print $1,$NF}' /etc/passwd

这样就会输出所有不匹配的记录

数学表达式

除了正则表达式之外,你也可以使用数学表达式。例如你如果想要显示所有属于root用户组的用户,你可以使用:

gawk -F: '$4 == 0 {print $1}' /etc/passwd

这样就会输出所有第四个字段是0的用户

可以使用的数学表达式:

x == y  x != y
x <= y  x >= y
x > y   x < y

跟正则表达式不相同的是,数学表达式必须完全匹配,而不是仅仅的正则匹配

结构化命令

if语句

if语句格式可以是:

if (condition)
    statement
if (condition) statement

如果statement需要时多条语句,就是用花括号括起来

当然if也支持else子句

例如:

gawk '{if ($1 > 20) print $1 * 2;else print $1 /2}' data.txt
while语句

while语句格式:

while (condition){

}

例如data5内容是:

130 120 135
160 113 140
145 170 215

执行

gawk '{total = 0;i = 1;while (i < 4){total += $i;i++;};avg = total / 3;print "Average:",avg;}' data5

这样就会输出每一行的平均值

当然gawk的循环当中也支持break和continue语句

do-while语句

用法和while类似,格式:

do
{
    statement
} while (condition)
for语句

格式:

for( variable assignment; condition; iteration process)

改写之前while循环的功能:

gawk '{
    total = 0
    for (i =1 ; i <4; i++)
    {
        total +=$i
    }
    avg = total / 3
    print "Average:" ,avg
}' data5

格式化打印

gawk除了print之外还有printf命令提供类似C语言的格式化打印功能

printf "format string",var1,var2,...

格式化占位符采用这种格式:

%[modifier]control-letter

其中control-letter是单字符指定显示什么类型数据,可以是:

控制字母 描述
c 将一个数作为ACSII显示
d 显示一个整数值
i 同d
e 科学计数法显示一个数
f 显示一个浮点数
g 用科学计数法或者浮点数显示(选择其中较短的一个)
o 显示一个八进制数
s 显示一个文本字符串
x 显示一个16进制数字
X 显示一个16进制数字,但是使用大写字母

除了控制字母之外,还有三种修饰符可以进一步控制输出

  • width: 指定字段最小宽度如果小于这个值,文本就会右对齐,并用空格填充。如果输出要大于这个值,就按照实际长度输出
  • prec: 指定小数点后面的位数或者字符串的最大字符数
  • -(减号):如果有减号,格式化时候采用左对齐而不是默认右对齐

例如如果想要输出左对齐的浮点数,最短字符个数是10(空格补齐),小数点后5位:%-10.5f

如果是字符串,最短15字符,左对齐:%-10s

内建函数

数学函数
函数 描述
atan2(x,y) x/y反正切,x,y单位是弧度
cos(x) x为弧度单位的余弦
sin(x) 同上
exp(x) x指数函数
int(x) x取整,取靠近0一侧
log(x) x自然对数
rand() 0-1随机浮点数
sqrt(x) x平方根
srand(x) 为随机数指定种子

除了标准数学函数之外,还提供了位操作函数 :

函数 描述
and(v1,v2) 按位与
compl(val) val补运算
lshift(val,count) 左移
rshift(val,count) 右移
or(v1,v2) 按位或
xor(v1,v2) 按位异或
字符串函数
函数 描述
asort(s [,d]) 根据数组s元素值排序。引索值会被替换成为新的排序顺序的连续数字。如果指定了d,排序后结果储存在d当中
asorti(s [,d]) 将数组按引索值排序。生成数组将引索值作为数据元素,按照连续数字引索表示顺序。如果指定了d,结构储存在d当中
gensub(r,s,h,[,t]) 查找变量$0或者参数t来匹配正则表达式r。如果h是g或者G,就用s替换掉匹配的文本。如果h是一个数字,替换掉第h个匹配r的地方
gsub(r,s [,t]) 相当于上式h=g
index(s,t) 返回字符串t在s当中的引索值
length([s]) 返回s长度,如果没有s,返回$0长度
match(s,r [,a]) 返回s中正则表达式r出现位置的引索。如果指定a,储存s中匹配表达式的那一部分
split(s,a [,r]) 将s用FS字符或者正则表达式r(如果指定)分开放到数组a当中。返回字段总数
sprint(format,variables) 用提供的format和variable返回一个类似printf输出的字符串
sub(r,s [,t]) 在$0或者t中寻找正则表达式r的匹配。如果找到了就用s替换掉第一处匹配
substr(s,i [,n]) 返回s中从引索i开始的n个字符组成的字符串。如果没有提供n,就返回剩下所有
tolower(s) 全部替换成小写
toupper(s) 全部替换成大写
时间函数

| 函数 | 描述 |
| mktime(datespec)| 把一个按照YYYY MM DD MM SS [DST]格式指定的日期转换称时间戳(格式同shell的data函数 |
| strftime(format [,timestamp])| 将当前时间戳(或者提供的timestamp)转换称格式化日期 |
| systime()| 返回当前时间戳 |

自定义函数

function name([variables])
{
    statement
}
使用自定义函数
gawk '
function myprint()
{
    printf "%-16 - %s\n" ,$1 ,$4
}
BEGIN{FS="\n";RS=""}
{myprint()}

创建函数库

可以通过-f参数指定加载的gawk命令文件

正则表达式

正则表达式通过正则表达式引擎实现。Linux中,有两种流行的正则表达式引擎:

  • POSIX基础正则表达式BRE引擎
  • POSIX扩展正则表达式ERE引擎

大多数linux工具都符合BRE标准。sed使用BRE标准,gawk使用ERE标准。

BRE模式

纯文本

grep -rn "test" .

这一类使用BRE当中的纯文本匹配。在文件流当中寻找包含test字符串的位置。空格也被视为普通字符。在纯文本模式下会区分大小写。

特殊字符

正则表达式识别的特殊字符包括:.*[]^${}\+?|()

如果想要把某一个特殊字符作为纯文本匹配当中的普通字符,需要使用反斜杠进行转义。这一点对于grep,sed,gawk等工具都适用。

锚字符

当指定正则表达式模式时,模式出现在任何地方都可以匹配。有两个特殊字符可以锁定模式在行尾或者行首。

  1. 锁定行首

    脱字符(^)定义数据流行首开始模式。如果模式出现在除了行首其他位置,正则表达式无法匹配。例如:

    echo "Books are great" | sed -n '/^Book/p'
    

    要使用脱字符,必须把脱字符放在模式的开头位置,如果不是开头位置,脱字符就自动变成了普通字符,例如:

    echo "hh^" | sed -n '/h^/p'
    

    这种情况BRE自动把它当成普通字符。这种情况甚至不需要进行转义。

  2. 锁定行尾

    美元符($)定义了行尾锚点。将这个特殊字符放在文本模式最后表示数据行必须是以该文本模式结尾。例如:

    echo "it is a good book" | sed -n '/book$/p'
    
  3. 组合锚点

    常见一些情况,行尾锚点和行首锚点同时使用,表示匹配符合某个文本模式的行:

    echo "this is a book" | sed -n '/^this is a book$/p'
    

    如果想要删掉所有的空白行,可以使用这种方式:

    sed -n '/^$/d' test.txt
    

点号字符

点号字符匹配处理换行符以外的任何一个单个字符。如果点号字符位置没有字符,模式就不会成立。空格也是算作一个字符

sed -n '/.at/p' data.txt

这样子就匹配到了at不是行首的所有行。

字符组

点号可以匹配任意字符,但是如果你想要限定匹配的具体字符,可以使用字符组。

方括号可以用来定义一个字符组。然后你可以在模式当中使用整个组,就像其他通配符一样。

sed -n '[ch]at/p' data.txt

这样子只能匹配cat和hat

字符组当中可以使用字母和数字和其他非特殊字符

排除型字符组

可以在字符组开始加入一个脱字符实现排除字符组

sed -n '/[^ch]at/p' data.txt

这样子会去掉所有的cat和hat

区间

在字符组当中字母和数字可以使用区间字符也就是单破折号字符表示一个字符区间,例如

sed -n '/[0-9]/p' data.txt

这样就可以表示所有包含数字的行

sed -n '/[a-ch-m]at/p' data.txt

这样子不允许出现d-g的字符

特殊字符组

BRE当中定义了特殊字符组可以匹配特定类型字符

描述
[[:alpha:]] 任意字母,无论大小写
[[:alnum:]] 任意数字和字母。0-9 a-z A-Z
[[:blank:]] 空格与制表符
[[:digit:]] 0-9
[[:lower:]] a-z
[[:print:]] 所有可以打印的字符
[[:punct:]] 匹配标点字符
[[:space:]] 空白字符:空格,制表符,NL,FF,VT,CR
[[:upper:]] A-Z

可以和普通字符组一同使用

星号

字符后面放置星号表明该字符必须匹配模式文本中出现0次或者多次

例如grep "test*" .表示t可以出现一次或者很多次。也就是可以匹配testtt,test,和tes。

另一个方便的特性就是点号和星号结合使用,这样子有匹配任意数量的任意普通字符。通常用于数据流当中两个可能相邻或者不相邻的文本字符串之间。

echo "this is a good man" | sed -n '/is.*man/p'

这也可以同样适用于字符组和字符区间

ERE模式

sed只使用BRE,gawk使用ERE

问号

问号类似于星号,只不过前面的字符只能出现零次或者1次。前面如果是字符组或者区间也是同样适用。

例如:

gawk '/be?t/{print $0}' test.txt

可以匹配bet,bt。但是不能匹配beet

加号

加号类似于问好,但是加号前面字符只能出现1次或者多次。也就是必须至少出现一次。如果没有出现,就不会字符匹配。

gawk '/be+t/{print $0}'

这样子可以匹配bet,beet,beeet,但是不能匹配bt

花括号

ERE当中的花括号允许你为可重复的正则表达式指定一个上限。这通常叫做interval。可以使用两种格式指定区间

  • m:正则表达式准确出现m次
  • m,n:正则表达式至少出现m次,至多出现n次

默认情况下,gawk不会识别正则表达式间隔,必须指定--re-interval

echo "bet" | gawk --re-interval '/be{1}t/{print $0}'
echo "bet" | gawk --re-interval '/b[ae]{1}t/{print $0}'

通过这种方式指定模式的出现次数

管道

管道允许你检查数据流时候,使用OR的方式指定正则表达式引擎要用的两个或者多个模式。如果任何一个模式匹配了数据流文本,文本就通过测试。如果没有模式匹配,则数据流文本匹配失败。

使用管道格式如下:

expr1|expr2|...

这是一个例子:

echo "The cat is asleep" | gawk ‘/cat|dog/{print $0}'

正则表达式和管道之间不能有空格,不然也是模式的一部分。

表达式分组

正则表达式也可以使用圆括号进行分组。分组之后,改组会被视为一个标准字符。可以像对普通字符一样使用这些特殊字符。例如:

echo -e "Sat\nSaturday" | gawk '/Sat(urday)?/{print $0}'

这种情况下,两行都可以被匹配成功。

分组和管道一起使用可以创建更多的模式匹配组:

echo "cat" | gawk '/(c|b)a(b|t)/print $0'

这样子的gawk可以自动的识别所有的cat,bat,cab,bab。

SSH

SSH端口转发:SSH隧道

本地转发

假设两个主机信息如下:

ServerA:10.1.0.1 不存在mysql服务

ServerB:10.1.0.2 安装mysql服务并且监听3306端口

现在,我们在serverA当中执行ssh -L 9906:10.1.0.2:3306 [email protected]就可以建立一个在ServerA和ServerB当中的隧道,下面解释一下命令的含意:

  • -L表示使用本地转发建立ssh隧道也就是本地端口的数据会被转发到目标主机的对应端口所以这个命令含义就是本地9906上面的数据会被转发到10.1.0.2的3306端口上面。

此时我们通过mysql命令访问serverA的本地回环的9906端口,就相当于访问ServerB的mysql服务。这样做同时也会对传输的数据进行加密,达到数据安全性的保障。

但是通常我们只想建立隧道,不想使用这个ssh连接(直接退出连接会导致ssh隧道消失),这时就需要配合-N,-f选项了。

  • -N会使得建立一个ssh隧道同时不打开ssh会话
  • -f会使得后台运行ssh隧道

所以只需要在建立ssh隧道同时添加-Nf就行了。

如果不想使用ServerA的回环地址而是其他网卡的地址可以使用以下命令指定监听的ip

ssh -fN -L 10.1.0.1:9906:10.1.0.2:3306 [email protected]

远程转发

和本地转发相对应,远程转发就是把本地的端口映射到远程(本地转发是吧远程端口映射到本地)

ssh -fN -R 9906:10.1.0.2:3306 [email protected]

之后10.1.0.1的9906端口就会被监听,可以通过该端口访问10.1.0.2的3306端口的服务。

转发的扩展

我们举一个现实的例子。我想通过dian.org.cn连接内网服务器192.168.0.66

ssh -Nf -L 12345:192.168.0.66:22022 [email protected] -p2222

这样192.168.0.66的22022端口就被映射到我的电脑本地的localhost:12345了

你可能感兴趣的:(linux,linux,服务器,ssh,bash,shell)