linux_shell编程

SHELL编程

编程语言
机器语言 01010101
低级语言(汇编语言) 助记词(转义器 将助记词编译成机器语言)
高级语言 跨平台
编译型(编译器) 将标准文档转成对应平台的汇编
C / C++

        解析型(解析器)      将文档实时转义成平台指令
            Shell PHP Python JavaScript, GO

        编译解析型(编译器/虚拟机)
            Java C# PHP Python JS, 易, OC, swift
编程入口
    编辑文档, 文档的第一行开始,逐行解析

SHELL解析器     /bin/bash
    #!/bin/bash
    快捷键
        快捷键模式
            emacs       默认
                ctrl + a
                ctrl + e
                ctrl + d
                ...
            vi

            临时更换模式: set -o vi

        tab键补全

特殊符号

        *           通配符
        \           转义符, 去除后面字符的特殊意义
        ""          批量转义符, 去除引号引起来的内容大部分特殊字符的特殊意义
        ''          批量完成转义符, 去除引号引起来的内容所有特殊字符的特殊意义
        ``          引起来内容当作shell命令去执行并且将执行标准输出替换, 尽量使用$()
                    $()可以嵌套, 反引号不能嵌套
                    ``和$()都是开新进程去执行

        {}          展开,代表多个的意思
            字符串截取
            数组的增. 删. 改. 查
            变量的默认值
            正则表达式匹配
            awk
        ()          子程序
            $()
        &           将程序放入后台执行(多进程)
        !           命令历史特殊符号
        ~           当前登录用户的家目录
        /           根
        #           注释符
        -           选项(标准输入)
        $           组合使用, 访问变量
        |           管道
        < > >>..    重定向
        .           当前目录
        ..          上一级目录
        [  ]        判断表达式
        :           永真(if while)
        ;           一条命令的结束
        &&          逻辑与
            前面的是"真"的才是会执行后面的
        ||          逻辑或
            前面的是"假"的才是会执行后面的

    命令历史
        查看命令历史
            history
            history 10

        存储命令历史
            history -w /tmp/history.txt

        读取命令历史
            history -r /tmp/history.txt

        删除命令历史
            history -c

        执行命令执行
            执行最后一条命令历史
                !!

            执行109的命令
                !109

            执行root开头的命令
                !root

            获取最后一条命令历史的最后一个参数
                !$

        命令历史的环境变量
            HISTFILE        控制命令历史默认写入的文件(~/.bash_history)
                echo $HISTFILE  打印环境变量的值
            HISTFILESIZE    控制命令历史写入文件的最大条目
                echo $HISTFILESIZE  打印环境变量的值
            HISTSIZE        控制命令历史最大条目数
            HISTTIMEFORMAT  控制命令历史时间显示格式
                export HISTTIMEFORMAT="%F %T "

命令别名 alias

        简化命令的输入, 只影响当前终端

        创建命令别名
            格式: alias 别名='执行命令名字及参数'
            例:
                alias i='ifconfig enp0s25'
                alias p='ping -c 3 192.168.2.1'
                alias pb='ping -c 3 www.baidu.com'

        删除命令别名
            格式: unalias 别名

        查看命令别名
            alias


**环境变量**
​ 提升程序运行效率, 将系统环境相关的信息存储到变量里,减少与内核交互
​ 会影响子进程(将自己的环境变量复制给子进程)

​ 输出当前shell所有环境变量
​ env

​ 访问环境变量的值
​ echo $环境变量名

​ 定义和修改环境变量的值(PPP0)
​ export 环境变量名=值

​ 删除环境变量
​ unset 环境变量名

​ 配置文件
​ bash启动时会执行配置文件(shell脚本)
/.bashrc

​ 当bash结束时会自动将命令历史写入
/.bash_history


学习编程语言的流程:
了解准备学习的语言概述
编程入口
变量的使用(数据类型)
操作符(操作变量) -> 语句
流程控制语句(分支 循环)
函数(代码的打包)
数组
字符串处理
I/O文件操作


变量
变量默认没有数据类型之分, 一切皆于字符串

变量 : 以一组文字或符号来替换一些比较复杂或者容易变动的数据

变量的用途

    简单的用途就是为了方便搜索
    常用于编程里,对经常使用的值使用变量,方便引用

变量的分类

    自定义变量                  不影响子进程

    环境变量                    影响子进程

自定义变量转环境变量

    export  自定义变量名

变量命名规则:
    严格区分大小写,同名称但大小写不同的变量名是不同的变量
    变量名可以是数字 字母特殊字符进行组合,
        但不能以数字和特殊字符开头, 最好别用下划线开头更不能以纯数字为变量名
    定义变量时两边不能有空格,有空格的字符串要用引号引起来
    单引号和双引号的区别,单引号会转义所有特殊字符
    尽可能描述变量存储的值(见名知意)

定义变量
    变量名=值
        =两边不能有空格
        例:
            a=123
            a="hello world"
            a=34.6778
        特殊的变量定义方式:
            var=`ls -l`     把ls -l的标准输出结果赋给var变量
            a=$(ls -l)      将命令执行标准输出赋值给a变量

更新变量值
    变量名=新值
        num1=2333
        num2=$(($num1 + 1000))  结果是3333
        num2=$[num1 + 1000]
        let num2=($num1 + 1000)   结果是3333

        str1="hello world "
        str2=$str1"hello shell"  hello world hello shell

    变量名+=追加值


获取变量值(PPP1)
    $变量名
        或
    ${变量名}

删除变量
    unset 变量名

${}的特殊访问
    解决变量与其它字符串连接混乱问题

    按位置进行截取(PPP2)
        ${变量名:起始值:截取个数}
        ${变量名:起始值:-从后面定位结束值}
        ${a:7}          从第8个开始到结束
        ${var:0-7:3}    从倒数第7个开始, 截取3个
        ${var:0-7}      从倒数第7个开始, 截取到最后

    按关键词进行截取(PPP3)(PPP4)
        ${变量名#*关键词}       截取第一个关键词后面内容
        ${变量名##*关键词}      截取最后一个关键词后面内容
        ${变量名%关键词*}       截取最后一个关键词前面内容
        ${变量名%%关键词*}      截取第一个关键词前面内容

    获取字符串长度
        ${#变量名}

    替换(PPP5)
        ${变量名/查找关键词/替换关键词}     替换第一个关键词
        ${变量名//查找关键词/替换关键词}    替换全部关键词

    变量默认值(PPP6)
        echo ${变量名-默认值}
            此变量名值为空或不存在,默认值才会使用
        num=${1-$0}

source或 . 内部命令, 使用本进程执行脚本, (默认情况下执行程序都开新进程)

内置变量
$$ 代表本脚本的PID(进程标识符) (PPPz)
终端的pid是这个终端运行的脚本的PPID,
这个脚本中运行的sleep命令会开启子进程去运行,
脚本退出之后,里面sleep进程还是会继续运行, 只是变成了孤儿进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$0          代表本脚本名
$1 代表脚本的第一个参数
$2 代表脚本的第二个参数
$3 代表脚本的第三个参数
...
${10} 代表脚本的第十个参数
...
$n 代表脚本的第n个参数

$* 代表脚本的所有参数
$@ 代表脚本的所有参数
假设在脚本运行时写了三个参数 1、2、3,
则 " * " 等价于 "1 2 3"(传递了一个参数),
而 "@" 等价于 "1" "2" "3"(传递了三个参数).

$? 代表上一条命令执行返回码
(0代表正确,非0代表错误)(非0退出就是上条命令有问题)
赋值没有返回码
命令结果赋值的时候, 会把命令返回码的值作为$?的值

输入/输出语句(PPP7)

echo/printf

echo(见练习echo)
    -n 不换行输出
    -e 启用反斜杠转义解释

    \c 最后不加上换行符号


printf "%d%s\n" 34 "hello"
    "%10d"      按10个空位右对齐, 整个字符占10个位置, 不足补空格
    "%-10d"     按10个空位左对齐, 整个字符占10个位置, 不足补空格
    "%010d"     按10 个空位右对齐, 前面补0
    "%10.2f"    按10个空位右对齐, 保留两位小数点

read
    -p      指定输入提示信息
    -s      关闭回显
    -n      指定输入最大字符个数
    -t      指定超时时间(秒)
    如果在read命令行中不指定变量的话,read命令会将它接收到的所有参数放到特殊环境变量REPLY中。

    read var                    等待用户给var变量赋值

    read -p "input your name:" var  提示信息等待用户给var变量赋值

    read -p "input your name:" -t 10 var  提示信息等待用户给var变量赋值,10秒不输入的话直接跳过

    read -s -p "input your password:" passwd    提示信息等待用户给passwd变量赋值并且隐藏显示

一行多条命令执行
; 以分号分隔多条命令顺序执行
&& 前面命令执行成功才会执行后面命令
|| 前面命令执行失败才会执行后面命令


代码规范(可读性)

加空行      使代码具有段落感
    解析器和第一行代码之间要加空行
    假如有连续定义多行变量, 在这之后要加空行
    if/while/for/until/case 之间加空行
    函数之间加空行
    按代码功能分段加空行

缩进对齐    使代码具有层次感
    缩进量为4个空格, 遇到if/while/for/until/case语句,里面内容进行缩进对齐
    函数内部要缩进

加空格      使代码更清晰
    双目操作符两边加空格(有左值右值的操作符)

命名规则    尽量能描述存储数据或功能
    首字母小写驼峰法    gameStatus      inputNum
    首字母大写驼峰法    GameStatus      InputNum
    下划线连接          game_status     input_num

    强烈不建议使用中文和拼音

折行        一行不要超过80个字符(包含空白字符)
    使用 \ 进行连接行, \后不能跟任何字符

流程控制语句

分支(PPP8)
    格式:
        if 命令; then
            命令成功要执行的代码块
        fi

        if 命令; then
            命令成功要执行的代码块
        else
            命令失败要执行的代码块
        fi

        if 命令1; then
            命令1成功要执行的代码块
        elif 命令2; then
            命令2成功要执行的代码块
        elif 命令3; then
            命令3成功要执行的代码块
        ...
        else
            命令失败要执行的代码块
        fi

    test命令(判断 条件表达式)
        test "$a" = "$b"
            或
        [ "$a" = "$b" ]
            或
        (("$a" == "$b"))
        [[ "$a" == "$b" ]]

        得到条件表达式所有帮助
            man test

        整型数据比较: -le -lt -ge -gt -ne -eq
            -eq  等于,如:if [ "$a" -eq "$b" ]
                equal

            -ne  不等于,如:if [ "$a" -ne "$b" ]
                no equal

            -gt  大于,如:if [ "$a" -gt "$b" ]
                greater than

            -ge  大于等于,如:if [ "$a" -ge "$b" ]
                greater equal

           -lt   小于,如:if [ "$a" -lt "$b" ]
                less than

           -le  小于等于,如:if [ "$a" -le "$b" ]
                less equal
            <   小于(需要双括号),如:(("$a" < "$b"))
            <=  小于等于(需要双括号),如:(("$a" <= "$b"))
            >   大于(需要双括号),如:(("$a" > "$b"))
            >=  大于等于(需要双括号),如:(("$a" >= "$b"))

        字符串比较: = != ==  (演练)
            = 等于,如:if [ "$a" = "$b" ]
            == 等于,如:if [ "$a" == "$b" ],与=等价
            != 不等于, 如: if [ "$a" != "$b" ]


        逻辑比较: -a -o !
            !   非,后面要跟空格
            -o  或, 条件只要达成1个就是真(or)
            -a  且,条件必须全部达成才是真(and)

一些特殊的参数:

             -a     文件存在。
             -e     文件存在(与-a相同)。
             -n     字符串长度不是零。
             -z     字符串长度为零。

             -r     文件存在并且可由当前进程读取。
             -w     文件存在并且可由当前进程写入。
             -x     文件存在并且可由当前进程执行。

             -d     文件存在并且是一个目录。
             -f     文件存在并且是一个常规文件。
             -s     文件存在且大小大于零。
             -b     文件存在,是一个块特殊文件。
             -c     文件存在,是一个字符特殊文件。
             -G     文件存在,并具有与此进程相同的组ID。
             -O     文件存在,并由该进程的用户ID拥有。
             -l     文件存在,是一个符号链接。
             -p     文件存在,是一个先入先出(FIFO)的特殊文件或命名管道。
             -S     文件存在,是一个套接字。

            if test -d "/tmp"; then
                echo "判断/tmp是否存在且是个目录"
            fi

            if [ -d "/tmp"]; then
                echo "判断/tmp是否存在且是个目录"
            fi


        对变量最好使用双引号引起来, 保护起来, 以免报语法错误

case(处理菜单使用, 多路匹配)(PPP9)

        格式:
            简略版:
            case "$num" in
                A|a) echo "A";;
                B|b) echo "B";;
                C|c) echo "C";;
                *) echo "Other";;
            esac

            完整版:
            case "$num" in
                A|a)
                    echo "A"
                    ;;
                B|b)
                    echo "B"
                    ;;
                C|c)
                    echo "C"
                    ;;
                *)
                    echo "Other"
                    ;;
            esac

循环(PPP11 PPP10)

    循环控制语句
        break           退出循环
        continue        跳过本次循环
    while(演练目录)
        格式:
            while 命令(条件表达式); do
                循环代码块
            done

            命令成功或条件表达式为真才会执行循环体

    until
        格式:
            until 命令(条件表达式); do
                循环代码块
            done

            命令失败或条件表达式为假才会执行循环体

    for(用for循环去做PPP5)
        格式:
            for 迭代变量 in 迭代对象 ; do
                循环代码块
            done

            迭代对象是以空格为分隔符序列

            for i in 1 2 3 4 5 6 7 8 9 10; do
                echo $i
            done

            for i in 1 2.334 "hello" "1.txt" 5 678.45 7 8 9 10; do
                echo $i
            done

            for ((i = 0; i < 5; i++)); do
                echo $i
            done
    seq
        产生一个序列, 可进行迭代
        count=18
        for i in $(seq 0 $count); do
            echo $i
        done

函数 打包代码(PPP23 PPP24)

函数定义格式:
[function] 函数名 () {
函数代码块
}

函数调用格式:
函数名 [参数]…

函数就是命令,与命令用法一样

exit和return后面接的必须是0-255的数字
小于0会报错, 且最终的值是一个1-255的随机数字, 大多数是2
大于255, 最终的值是一个1-255的随机数字, 大多数是1
返回的不是数字, 会报错, 且返回的状态码是1-255之间的一个随机数字
函数中出现了return, 执行到这一行的时候就会结束,这个函数下面的代码不会执行了
在脚本中, 碰到exit会直接退出脚本, 下面的代码不会再执行

注意点:
    1. shell是面向过程的语言, 从上到下依次执行(类似C语言)
    2. 定义的变量要在使用之前,不然值是空的
    3. 定义函数要在使用之前, 不然会报错
    4. 函数外和函数内通信可以以传参的形式, 改变共同使用的变量
    5. 函数内和函数外通信可以使用的标准输出的形式(echo/printf)和return/exit
    6. 函数名要和变量名的起名规则是一样的, 做到见名知意
    7. 函数不调用不执行
    8. 假如函数名和linux的命令起冲突的时候, 会先执行自定义的函数

数组
与变量一样是存储数据
批量操作同类型的数据
SHELL只支持一维数组
数组不能直接作为函数参数进行传递

定义
1. 索引数组
declare -a 数组名
数组名=(值1 值2 值3…)
2. 键值对数组
declare -A 数组名
数组名=([“name1”]=soul [“name2”]=shell)
数组名[键]=值

    3. 索引键值对混合数组(强烈不建议使用)
        只是shell的语法允许这样做,但是正常使用的时候不要这样用
        数组名[键]=值
        数组名=(值1 值2 值3...)

访问(演练)
    访问指定元素
        echo ${数组名[下标]}
            下标是正整数, 从0开始描述数组的第几个元素
            下标是负整数, 倒数第几个, 从1开始
            下标是-0就相当于是0
            下标是浮点数会报错
        echo ${数组名[键名]}

    访问所有元素
        echo ${数组名[*]}
        echo ${数组名[@]}

    访问数组长度
        ${#数组名[*]}
        ${#数组名[@]}

    访问数组所有键名
        ${!数组名[*]}
        ${!数组名[@]}
        索引数组的键名类似于{0..n}

更新
    更新数组的指定元素
        数组名[下标]=新值

        数组名+=(追加值1 追加值2...)
        给数组追加一个变量
        a=(1 2 3 4)
        num=5
        a[${#a[*]}]=$num

删除(PPP12)
    删除数组指定元素
        unset 数组名[下标]

        只是清空对应下标元素的值, 并不会改变数组其它元素的位置
        a=(1 2 3 4 5)
        len1=${#a[*]}   <-- 5
        unset a[3]      <-- 删除下标为3的元素
        len2=${#a[*]}   <-- 4
        for i in "${a[*]}"; do
            echo "$i"               <-- 这种算法不能把a[3]的实际值打出来
                                        这个时候打印的第4个数字只是第4个数字
                                        而不是a[3]的值
        done

        x=0
        for i in "${a[*]}"; do
            echo "下标为$x""的值是: ${a[$x]}"   <-- 这个时候打印的第4个数字才是a[3]的值
            let x+=1
        done


    删除整个数组
        unset 数组名

declare

定义只读变量(常量)
    declare -r 只读变量名=值

定义索引数组
    declare -a 数组名

定义键值对数组

    declare -A 数组名

定义自动转化大写的变量
    declare -u 变量名
        给此变量名赋值,会自动转成大写

定义自动转化小写的变量
    declare -l 变量名
        给此变量名赋值,会自动转成小写

定义环境变量
    declare -x 环境变量名

变量作用域
$* 和 $@的区别: 它们的区别在于使用双引号, $*代表一个元素, $@代表多个元素

函数直接可以访问和修改函数外的变量
shell里的变量都是全局变量, 在函数里定义的变量在函数调用后也可以直接访问

在函数内可以使用local关键词定义局部变量 (只能在函数中使用)

查找命令

which 查找$PATH变量指定的目录里的命令

    which ifconfig              查出ifconfig命令的路径

whereis 查找命令,显示命令更多的信息

    whereis ifconfig            命令路径,帮助文档等

locate 文件查找命令

    速度快,通过系统自带的一个数据库去查找

        /var/lib/mlocate/mlocate.db

    locate hello                查找带hello的文件,如果hello是刚刚新建的就找不到,因为数据库还没有没保存现在的信息

    updatedb                    手动更新查找数据库,然后再查找就会很快找到刚刚新建的hello文件

find

文件查找命令,功能最强大,速度慢,因为会扫描整个查找的范围

    find 范围 参数 关键字

    find /etc -name grub.conf   查找/etc目录下的grub.conf文件

    find / -name "*.conf"       查找/下所有.conf文件

    find / -iname grub.conf    查找/目录下的grub.conf文件,忽略大小写

    find / -maxdepth 2 -name grub.conf     可以使用-maxdepath参数来控制查找的层次,就是说只查当前目录和子目录,最多查2级目录
    find / -mindepth 2 -name grub.conf     最少查二级目录

    find /etc -type d           查找/etc/下所有的目录

    find /etc -type f           查找/etc/下的所有普通文件

    find /etc -type l -name "*.conf"      查找/etc/下软链接文件是.conf结尾的文件

    find /etc -type s           查找/etc/下所有socket文件

    find /etc -type c           查找/etc/下的所有字符设备文件

    find /etc -type p           查找/etc/下所有管道文件

    find /etc -user root        查找/etc/所属用户是root的文件

    find /etc -group root       查找/etc/所属用户组是root的文件

    find /etc -uid 500          查找/etc/下uid是500的文件,和-user类似

    find /etc -gid 500          查找/etc/下gid是500的文件,和-group类似

    find /etc -nouser           查找没有所属用户的文件

    find /etc -nogroup          查找没有所属用户组的文件

    find /etc -perm 777 -type d    查找/etc/下权限为777的目录

    find . -perm  111           查找权限是111的文件

    find . -size +10M           查找当前目录下大于10M的文件,单位可以有K,M,G,b等

    find / -size -2M            查找根目录下少于2M的文件
    find / -size +10M -size -100M   查找跟目录下面大于10M且小于100M的文件

    find / -mtime 1             查找根目录下1小时以前修改的所有文件

    find / -mtime +2            查找根目录下2个多小时以前修改的所有文件

    find / -mtime -3            查找根目录下最近3小时内修改的所有文件

    find / -atime 1             查找根目录下1天以前访问或读过的所有文件

    find / -atime -1            查找根目录下最近1天内读过或访问的文件

    find / -ctime -3            查找根目录下最近3天内状态发生改变的文件

    find / -cmin -3             查找根目录下最近3分钟内状态发生改变的文件

    find / -empty               查找根目录下所有空白文件或者空目录

    find / -false               查找根目录下总是错误的文件

    find / -false -exec ls -l {} \;   查找根目录下总是错误的文件并且用ls -l查看

    exec
        把前面命令的输出作为后面命令的参数
        对find出来的文件进行进一步的操作
        用ls -hl格式化输出/root下面属于root的大于10M的普通文件
        find / -size +10M -user root -type f 2> /dev/null -exec ls -hl "{}" \;

    PPP13

文本处理工具

wc 统计命令

wc -c /etc/passwd                   统计/etc/passwd文件里有多少个字符

wc -w /etc/passwd                   统计/etc/passwd文件里有多少个单词

wc -l /etc/passwd                   统计/etc/passwd文件里有多少行

sort 排序命令

sort -f /etc/passwd                 忽略大小写排序

sort -b /etc/passwd                 忽略最前面的空格符部分

sort -n /etc/passwd                 按数字大小排序

sort -u /etc/passwd                 去除重复行显示

sort -r /etc/passwd                 反向排序

sort -n -k5 /etc/passwd             指定第五列进行按数字大小排序

sort -t: -n -k5 /etc/passwd         以:为分隔符指定第五列按数字大小排序

cut 字段截取

cut -d: -f1 /etc/passwd             以:为分隔符, 只显示第一列数据

cut -d: -f1,2,5 /etc/passwd         只显示第一列,第二列,第五列的数据

cut -d: -f1,2 /etc/passwd           以:为分隔符只显示第一列,第二列的数据

uniq 去除文件中相邻的重复行

uniq -u /etc/passwd                 只显示没有被重复过的行
                                        重复行不显示

uniq -d /etc/passwd                 只显示被重复过的行
                                        重复的行中间只显示一行

uniq -i /etc/passwd                 忽略大小写去除文件中相邻的重复行
                                        重复的行中间只显示一行

uniq -c /etc/passwd                 统计相邻重复行数

diff 比较文件差异

diff -B /etc/passwd passwd          忽略空行造成的不同

3a4,6    表示4行和6行文件内容不一样,
> i     表示不一样的字符内容
> 
> 9  

   -c  显示全部内文,并标出不同之处,用!号标出
diff -c /etc/passwd passwd

说明:

"|"表示前后2个文件内容有不同
"<"表示后面文件比前面文件少了1行内容
">"表示后面文件比前面文件多了1行内容

tr 替换字符

cat /etc/passwd | tr a b            查看/etc/passwd文件并把里面的a替换成b

tr a-z A-Z < /etc/passwd            把/etc/passwd文件里的小写全转成大写
不会修改源文件,输出到标准输出

grep 文本查找(查找到结果打印对应行)

格式: grep 参数 关键词 [文件路径]...

    当有参数r的时候, 没有写文件路径的话就是在当前文件
参数
    -r      递归查找文件
    -n      输出行号
    -i      忽略大小写
    -l      只输出路径
    -w      完全匹配
    -q      查找的结果不输出
    -A      输出结果后面的行(包括查找结果行)
    -B      输出结果前面的行(包括查找结果行)
    -v      输出没有关键词的行
    -o      代表的是只输出匹配的选项。
在passwd文件中查找root输出对应行内容
    grep root /etc/passwd

在passwd文件中查找root输出对应行内容并且带行号
    grep -n root /etc/passwd

在passwd文件中查找nfs输出结果后3行(包含查找结果行)
    grep -A 3 nfs /etc/passwd

在passwd文件中查找nfs输出结果前3行
    grep -B 3 nfs /etc/passwd

在passwd文件中查找nfs输出结果前3行后2行
    grep -A 2 -B 3 nfs /etc/passwd

在多个文件中查找root关键词并且带颜色输出结果(文件名:行号:行内容)
    grep -rn root ./

在一个目录下所有文件查找root关键词
    grep -r root ./

在一个目录下的所有文件查找root关键词,只输出有结果的文件路径
    grep -rl root ./

查找没有root关键词的行  小写-v
    grep -v root /etc/passwd

查找多个关键词(逻辑或的关系)
    grep --color=auto -e 'bin' -e 'nologin' /etc/passwd

查找单词(关键词)
    grep -w root /etc/passwd

忽略大小写查找
    grep -i root /etc/passwd

关闭输出查找(用于shell判断使用)
    grep -q root /etc/passwd

使用扩展正则表达式(egrep)
    grep -E 'a+' /etc/passwd
        或
    egrep 'a+' /etc/passwd

 2 : egrep = grep -E 可以使用基本的正则表达外, 还可以用扩展表达式. 注意区别.
扩展表达式:
+ 匹配一个或者多个先前的字符, 至少一个先前字符.
? 匹配0个或者多个先前字符.
a|b|c 匹配a或b或c
() 字符组, 如: "love(able|ers)" 匹配loveable或lovers.
(..)(..)\1\2 模板匹配. \1代表前面第一个模板, \2代第二个括弧里面的模板.
x{m,n} =x\{m,n\} x的字符数量在m到n个之间.

basename $(pwd) 取得当前目录名
pwd | awk -F ‘/‘ ‘{print $NF}’

dirname $(pwd) 取得当前路径,不包含当前目录名
pwd | sed -nr ‘s/(.)(/)(.)$/\1\2/p’

script_dir=$( cd $(dirname “$0”) && pwd ) 表示脚本所在目录的

tac 倒转文件里的内容

rev 倒转文件里的单词顺序


正则表达式 (字符串匹配 模糊匹配)

名字叫正规表示法

.           代表任意一个字符
    r...    代表r开头的任意四个字符

\<root      代表root开头的单词
root\>      代表root结尾的单词
\<root\>    代表完全匹配root单词
\b          <-- 单词边界
\B          <-- 非单词边界

^           代表行开头
    ^root   代表以root开头的行
$           代表行结尾
    root$   代表以root结尾的行

*           匹配前一个字符0次或n次
    a*      匹配0个a到多个a
    .*      匹配0个到n个任意字符
+           匹配前一个字符1次或n次(至少要有一个)
    a+      匹配至少一个a
?           匹配前一个字符0次或1次
    a?      匹配一个a或没有a

        默认情况下,数量表示符只作用于前面的【一个】字符,如果需要作用于
            前面的多个字符,可以使用(...)把前面的字符括起来
        ab+         匹配ab, abb, abbb, abbbb...
        (ab)+       匹配ab, abab, ababab, abababab...
{}          匹配前一个字符指定次数
    a{1}    匹配一个a
    a{0,1}  与a?功能相同
    a{0,}   与a*功能相同
    a{1,}   与a+功能相同
    a{1,3}  匹配一个a 或 二个a 或三个a
        这些数字可以是任意的正整数

[]   字符类       匹配一个字符
    [abc]           匹配这个字符必须是a或b或c
    [^abc]          匹配这个字符不是abc的其它任意字符
    [a-z]           匹配26个小写字母
    [0-9]           匹配0到9的字符
    [a-zA-Z0-9_]    匹配数字 大小写字母和下划线
    [a-zA-Z]+       匹配单词
    [^a-zA-Z]+      匹配单词分隔符

    预定义字符类
        [[:alnum:]]     字母和数字的组合,相当于[a-zA-Z0-9]
        [[:digit:]]     十进制数字 [0-9]
        [[:alpha:]]     字母组合 [a-zA-Z]
        [[:blank:]]     空格和制表符
        [[:punct:]]     特殊字符集合
        ...

\           转义
    \.      取消.的特殊意义
    \n      代表换行

    \d      任意一个十进制数字 [0-9]
    \D      任意一个不是数字的字符 [^0-9]
    \s      任意一个空白字符 [ \f\n\r\t]
    \w      任意一个单词字符 [a-zA-Z0-9_]
    \W      [^a-zA-Z0-9_]

|           代表或
    a|b     匹配a或b

()          子表达式
    (root)|(kyo)    匹配root或kyo

默认情况下,数量表示符是最大匹配,好的正则表达式引擎支持用问号 ? 来启用最小匹配
.*b   匹配 aaabababa      <-- 最大匹配
           ^^^^^^^^
.*?b  匹配 aaabababa      <-- 最小匹配
           ^^^^

引用表示法
--------------------------------------------------------------
从左边开始数左小括号(openning brace),数字从1开始,被第一对括号匹配的字符
    可以用\1 来引用,第二对可以用\2 来引用,以此类推。
echo abcabcabcaabb | grep -E '(a(bc)){2}\1' --color
    abcabcabcaabb
echo abcabcabcaabb | grep -E '(a(bc)){2}a\2' --color
    abcabcabcaabb
echo "hello world, hello world, hello beautiful world"      \
    | grep -E --color '((hello) (world)), \1, \2 .* \3'

    hello world, hello world, hello beautiful world

PPP14

sed 流编辑器

格式: sed 参数 "表达式" [操作文件]...

基本格式包含以下部分:
1. 指定范围,不明确指定的话,默认是所有的行,可以是行号,或者正则表达式
2. 指定动作,常用的有d, s, p, i, a
    d: delete, 删除
    s: substitute, 替换
    p: print, 打印
    i: insert, 在前面插入
    a: append, 在后面添加

选项:
    -e          表达式
    -n          取消非匹配行的文件输出
    -i          操作原文件(慎用)
    -r          支持更多正则表达式

    ()      域, 匹配子表达式,用于替换引用
        第一个小括号对应\1来引用
        第二个小括号对应\2来引用
        ...
        以此类推

        有-r参数, 小括号不需要转义, 没有-r参数需要转义 \(...\)

输出    p(PPP15)

    输出第3行内容
        sed -n '3p' /etc/passwd

    输出第3行和第7行内容,中间为;表示单独的操作
        sed -n '3p;7p' /etc/passwd

    输出第3行到第5行和第7行内容,中间为逗号,表示范围
        sed -n '3,5p;7p' /etc/passwd

    输出有root关键词的行
        sed -n '/root/p' /etc/passwd

    输出以root开头的行
        sed -n '/^root/p' /etc/passwd

    输出有数字的行
        sed -n '/[0-9]/p' /etc/passwd

    打印非空格开头的行
        sed -n '/^[^[:blank:]]/p' passwd
            或
        sed -n '/^[^\ ]/p' passwd

增加 i a (PPP16)
    在第2行的上面, 也就是第2行加上一行内容
        sed -i '2i sfasdfdsf' passwd

    在第2行的下面, 也就是在第3行加一行内容
        sed -i '2a sfasdfdsf' passwd

    i和a的区别:
        i是指定几行就在几行加,
        a是指定几行就在几行的下一行加

删除    d

    删除第3行
        sed '3d' mypasswd

    删除第10行到第30行
        cat -n /etc/passwd | sed '10, 30d'

    删除第3行, 影响原文件
        sed -i '3d' mypasswd

    删除第二行到第三行
        cat -n passwd | sed -e '2,3d'

    删除第一行和第五行
        cat -n passwd | sed -e '1d;5d'

    删除空行
        cat passwd | sed -e '/^$/d'

    删除空格开始的行
        cat /etc/passwd |sed -e '/^[[:blank:]]/d' --删除以空格开头的
        cat /etc/passwd |sed -e '/^ /d'    --同上
        cat /etc/passwd |sed -e '/^\ /d'    --同上

    删除/etc/passwd的空行和注释
        cat passwd |sed -e '/^#/d;/^$/d'
    ...

    删除一个字符串中指定位置的字符(PPP17)
    删除第3至第5的字符
 echo 123456789ABCDEF | sed -r 's/^(..)...(.*)$/\1\2/'  保留第1小括号和第2个的内容
    删除第3个字符
  echo 123456789ABCDEF | sed -r 's/^(.{2}).(.*)$/\1\2/'
    删除从3个字符开始的10个字符
    echo 123456789ABCDEF | sed -r 's/^(.{2}).{10}(.*)$/\1\2/'

替换 s(PPP18)(PPP19)

    将每行的第一个root替换成soul
        sed -e 's/root/soul/' mypasswd

    将每行的第二个root替换成soul
        sed -e 's/root/soul/2' mypasswd

    将每行的所有root替换成soul
        sed -e 's/root/soul/g' mypasswd

    将每行的开头的空格删掉
        cat -n /etc/passwd | sed -e 's/^[ ]*//g'

    将1到10行的所有root替换成soul
        sed -e '1,10s/root/soul/g' mypasswd

    将10到最后行的所有root替换成soul
        sed -e '10,$s/root/soul/g' mypasswd

    将10到最后行的所有小写字母替换K
        sed -e '10,$s/[a-z]/K/g' mypasswd

    删除每行中第一个字符
        sed -e "s/^.//" mypasswd

    将每行中第一个字符替换成A
        sed -e "s/^./A/" mypasswd

    删除每行中第二个字符
        sed -r -e "s/^(.)./\1/" mypasswd

    删除每行中第一个有效字符(非空白字符)
        sed -e "s/^([[:blank:]]*)./\1/" mypasswd
            或者
        sed -r -e 's/^([ ]*)./\1/' mypasswd

    删除每行最后一个字符
        sed -e 's/.$//' mypasswd

    从左边开始数左小括号(openning brace),数字从1开始,被第一对括号匹配的字符
        可以用\1 来引用,第二对可以用\2 来引用,以此类推。

    删除每行倒数第二个字符
        sed -r -e 's/.(.)$/\1/' mypasswd
            把倒数第一个和倒数二个换成了倒数第一个

    删除每行的第二个数字
        sed -r -e 's/[0-9]+//2' passwd

    练习:
        2018-9-24
        2018-12-1
        2018-1-4
        2018-10-1

        把上面的日期格式替换成2018-10-01的形式


    删除每行第二个单词([a-zA-Z]+)
        sed -re 's/^([a-Z]+)([^a-Z])([a-Z]+)/\1\2/' 1_passwd.txt
            或者
        sed -re 's/[a-Z]+//2' 1_passwd.txt

    删除每行第3个非单词
        sed -re 's/[^a-Z]+//3' 1_passwd.txt

    PPP20
    PPP21


查找有线网卡设备名
    ifconfig | sed -rn 's/^(e[0-9a-zA-Z]+).*$/\1/p'
截取IP地址(一行命令)
    输出结果格式: 192.168.0.183/255.255.255.0
    #!/bin/bash

    # 再脚本运行的进程里,把语言环境改成英文,就不会受到语言环境的影响
    export LANG=en_US.UTF-8
    export LANGUAGE=en_US:en

    # 截取出正在上网的网卡名称
    link=$(route -n  | sed -n '/UG/p' | sed -re 's/(.*)(\b[a-Z0-9]+$)/\2/')
            或者
    link=$(route -n | sed -n '/UG/p' | awk '{print $NF}')
    # 截取出ip和子网掩码, 用/分开
    ip=$(ifconfig $link | sed -n '/Mask/p' | sed -r 's/^([^0-9]+)([0-9.]+)(.*)([^0-9.]+)([0-9.]+$)/\2\/\5/')
    echo $ip

Awk

Awk是一门编程语言,有很多版本,我们用的是GNU的gawk

    以:为分隔符打印第三列

        awk -F ":" '{print $3}' /etc/passwd

awk 命令的基本格式为:

awk [选项] ‘脚本命令’ 文件名

此命令常用的选项以及各自的含义,如表 1 所示。

选项 含义
-F fs 指定以 fs 作为输入行的分隔符,awk 命令默认分隔符为空格或制表符。
-f file 从脚本文件中读取 awk 脚本指令,以取代直接在命令行中输入指令。
-v var=val 在执行处理过程之前,设置一个变量 var,并给其设备初始值为 val。

awk 的强大之处在于脚本命令,它由 2 部分组成,分别为匹配规则和执行命令,如下所示:

1
'匹配规则{执行命令}'

这里的匹配规则,和 sed 命令中的 address 部分作用相同,用来指定脚本命令可以作用到文本内容中的具体行,可以使用字符串(比如 /demo/,表示查看含有 demo 字符串的行)或者正则表达式指定。另外需要注意的是,整个脚本命令是用单引号(’’)括起,而其中的执行命令部分需要用大括号({})括起来。

在 awk 程序执行时,如果没有指定执行命令,则默认会把匹配的行输出;如果不指定匹配规则,则默认匹配文本中所有的行。

举个简单的例子:

1
awk '/^$/ {print "Blank line"}' test.txt

在此命令中,/^$/ 是一个正则表达式,功能是匹配文本中的空白行,同时可以看到,执行命令使用的是 print 命令,此命令经常会使用,它的作用很简单,就是将指定的文本进行输出。因此,整个命令的功能是,如果 test.txt 有 N 个空白行,那么执行此命令会输出 N 个 Blank line。

awk 使用数据字段变量

awk 的主要特性之一是其处理文本文件中数据的能力,它会自动给一行中的每个数据元素分配一个变量。

默认情况下,awk 会将如下变量分配给它在文本行中发现的数据字段:

  • $0 代表整个文本行;
  • $1 代表文本行中的第 1 个数据字段;
  • $2 代表文本行中的第 2 个数据字段;
  • $n 代表文本行中的第 n 个数据字段。

前面说过,在 awk 中,默认的字段分隔符是任意的空白字符(例如空格或制表符)。 在文本行中,每个数据字段都是通过字段分隔符划分的。awk 在读取一行文本时,会用预定义的字段分隔符划分每个数据字段。

所以在下面的例子中,awk 程序读取文本文件,只显示第 1 个数据字段的值:

1
2
3
4
5
6
7
8
[root@localhost ~]# cat data2.txt
One line of test text.
Two lines of test text.
Three lines of test text.
[root@localhost ~]# awk '{print $1}' data2.txt
One
Two
Three

该程序用 $1 字段变量来表示“仅显示每行文本的第 1 个数据字段”。当然,如果你要读取采用了其他字段分隔符的文件,可以用 -F 选项手动指定。

awk 脚本命令使用多个命令

awk 允许将多条命令组合成一个正常的程序。要在命令行上的程序脚本中使用多条命令,只要在命令之间放个分号即可,例如:

1
2
 echo "My name is luke" | awk '{$4="yichen"; print $0}'
My name is yichen

第一条命令会给字段变量 $4 赋值。第二条命令会打印整个数据字段。可以看到,awk 程序在输出中已经将原文本中的第四个数据字段替换成了新值。

除此之外,也可以一次一行地输入程序脚本命令,比如说:

1
2
3
4
5
[root@server1 ~]# awk '{
> $4="yichen"
> print $0}'
My name is luke
My name is yichen

在你用了表示起始的单引号后,bash shell 会使用 > 来提示输入更多数据,我们可以每次在每行加一条命令,直到输入了结尾的单引号。

注意,此例中因为没有在命令行中指定文件名,awk 程序需要用户输入获得数据,因此当运行这个程序的时候,它会一直等着用户输入文本,此时如果要退出程序,只需按下 Ctrl+D 组合键即可。

awk从文件中读取程序

跟 sed 一样,awk 允许将脚本命令存储到文件中,然后再在命令行中引用,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cat awk.sh
{print $1 "'s home directory is " $6}

awk -F: -f awk.sh /etc/passwd

root's home directory is /root
bin's home directory is /bin
daemon's home directory is /sbin
adm's home directory is /var/adm
lp's home directory is /var/spool/lpd
...
Christine's home directory is /home/Christine
Samantha's home directory is /home/Samantha
Timothy's home directory is /home/Timothy

awk.sh 脚本文件会使用 print 命令打印 /etc/passwd 文件的主目录数据字段(字段变量 $6),以及 userid 数据字段(字段变量 $1)。注意,在程序文件中,也可以指定多条命令,只要一条命令放一行即可,之间不需要用分号。

awk BEGIN关键字

awk 中还可以指定脚本命令的运行时机。默认情况下,awk 会从输入中读取一行文本,然后针对该行的数据执行程序脚本,但有时可能需要在处理数据前运行一些脚本命令,这就需要使用 BEGIN 关键字。

BEGIN 会强制 awk 在读取数据前执行该关键字后指定的脚本命令,例如:

1
2
3
4
5
6
7
8
9
10
[root@localhost ~]# cat data3.txt
Line 1
Line 2
Line 3
[root@localhost ~]# awk 'BEGIN {print "The data3 File Contents:"}
\> {print $0}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3

可以看到,这里的脚本命令中分为 2 部分,BEGIN 部分的脚本指令会在 awk 命令处理数据前运行,而真正用来处理数据的是第二段脚本命令。

awk END关键字

和 BEGIN 关键字相对应,END 关键字允许我们指定一些脚本命令,awk 会在读完数据后执行它们,例如:

1
2
3
4
5
6
7
8
[root@localhost ~]# awk 'BEGIN {print "The data3 File Contents:"}
\> {print $0}
\> END {print "End of File"}' data3.txt
The data3 File Contents:
Line 1
Line 2
Line 3
End of File

可以看到,当 awk 程序打印完文件内容后,才会执行 END 中的脚本命令。

    以:为分隔符统计/sbin/nologin有多少个

        awk 'BEGIN{FS=":" ;count=0}{if($7 = "/sbin/nologin")count+=1}END{print count}' /etc/passwd

            BEGIN      在读记录之前执行
            END        在读完记录后再执行
            $0         代表所有记录
            $1-n       代表一个记录的第n个字段

    使用awk脚本

        #!/bin/awk

        BEGIN{
            FS=:
            count=0         定义变量
        }

        {
            if($7 == "/sbin/nologin")
            {
                count += 1
                print $0
            }
        }

        END{
            print "count="count
        }

        awk -f 1.awk /etc/passwd        运行上面的脚本

        FNR         当前处理到第几个文件
        NF          当前多少个字段
        NR          多少行
        OFS         输入指定字段分隔符
        ORS         输出记录分隔符
        FS          指定分隔符


    tips:
        awk没有指定分隔符的话, 就是默认以"空格"为分隔符

    打印所有行, 以空格为分隔符

        awk '{ print $0 }' /etc/passwd

    打印第一列,以:为分隔符

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

    打印每行的最后一列

        awk -F : '{print $NF}' /etc/passwd

    打印第一列和第三列, 并且加上welcome

        awk -F ":" '{print $1 "\t" $3 "welcome"}' /etc/passwd

    打印第八行

        awk -F : '{if (NR == 8){print $0}}' /etc/passwd
        awk -F : 'NR == 8 {print $0}' /etc/passwd

    打印行总数

        awk -F : 'END{print NR}' /etc/passwd

    打印每行的字段数

        awk -F : '{print NF}' /etc/passwd

    打印最后一行的最后一列

        awk -F : 'END{print $NF}' /etc/passwd

    打印字段数大于4的行

        awk -F : 'NF > 4 {print $0}' /etc/passwd

    打印文件里所有字段总数

        awk -F : 'BEGIN{c = 0}{c=c+NF}END{print c}' /etc/passwd

    打印uid在30~40范围内的用户名

        awk -F : '$3 >= 30 && $3 <= 40{print $1}' /etc/passwd

    打印5到56行

        awk -F : 'NR >= 5 && NR <= 56 {print $0}' /etc/passwd

    打印偶数行

        awk '{if (NR % 2 == 0)print $0}' /etc/passwd

    打印偶基数行

        awk '{if (NR % 2 == 1)print $0}' /etc/passwd

    打印每行的第一单词

        awk 'BEGIN{FS="[^A-Za-z]+"}{print $1}' /etc/passwd

    打印每行的第一个和第三个单词

        awk 'BEGIN{FS="[^a-zA-Z]+"}{print $1,$3}' /etc/passwd

    打印字段数大于5个的行总数

        awk -F ":" 'BEGIN {count=0 }(NF > 5){count+=1} END{print count}' /etc/passwd

评论


:D 一言句子获取中...

加载中,最新评论有1分钟缓存...