Linux shell 编程

1、简介

​    Shell 是一个用 C 语言编写的程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务。 Shell 脚本(shell script),是一种为 shell 编写的脚本程序。 业界所说的 shell 通常都是指 shell 脚本,但shell 和 shell script 是两个不同的概念。

2、变量

  • 等号两边不能有空格。
  • 变量名只能使用英文字母,数字和下划线,首个字符不能以数字开头 。
  • 变量名中间不能有空格,可以使用下划线(_)。
  • 变量名不能使用标点符号 。
  • 变量名不能使用bash里的关键字
2.1、定义变量
name='franky'
#除了显式地直接赋值,还可以用语句给变量赋值
for file in `ls /etc`for file in $(ls /etc)
2.2、使用变量

使用一个定义过的变量,只要在变量名前面加美元符号即可,如:

your_name="frank"
echo $your_name
echo ${your_name}
#变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界。推荐给所有变量加上花括号,这是个好的编程习惯。
2.3、只读变量
#!/bin/bash
myUrl="http://www.google.com"
readonly myUrl
#使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
2.4、删除变量
#!/bin/sh
myUrl="http://www.runoob.com"
unset myUrl
echo $myUrl #没有任何输出
2.5、变量类型
  • 局部变量: 局部变量在脚本或命令中定义,仅在当前shell实例中有效,其他shell启动的程序不能访问局部变量。
  • 环境变量: 所有的程序,包括shell启动的程序,都能访问环境变量,有些程序需要环境变量来保证其正常运行。必要的时候shell脚本也可以定义环境变量,例如:父进程定义的变量在子进程中,通常是无法使用的,但通过export将变量变成环境变量后,就可以使用了。
  • shell变量: shell变量是由shell程序设置的特殊变量。shell变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了shell的正常运行
2.6、变量的声明(declare)

默认情况下,变量的赋值内容都是字符类型的。 declare命令的使用形式如下:

declare [-aixrfF] variable_name
  参数a:将variable定义为数组
  参数i:将variable定义为整型(integer)
  参数x:将variable设置成环境变量,类似于export的作用
  参数r:variable为readonly类型,值不能被更改,不能使用unset。
  参数f/F:函数
2.7、变量中切割字符串
  • 利用操作符进行字符串切割
    1、#操作符。从左边开始删除第一次出现子字符串即其左边字符,保留右边字符。
    [franky@localhost ~]$ str='http://www.franky.com/cut-string.html'
    [franky@localhost ~]$ echo ${str#*//}
    www.franky.com/cut-string.html
    
    2、##操作符。从左边开始删除最后一次出现子字符串即其左边字符,保留右边字符。
    [franky@localhost ~]$ str='http://www.franky.com/cut-string.html'
    [franky@localhost ~]$ echo ${str##*/}
    cut-string.html
    
    3、%操作符。从右边开始删除第一次出现子字符串即其右边字符,保留左边字符。
    [franky@localhost ~]$ str='http://www.franky.com/cut-string.html'
    [franky@localhost ~]$ echo ${str%/*}
    http://www.franky.com
    
    4、%%操作符。从右边开始删除最后一次出现子字符串即其右边字符,保留左边字符。
    [franky@localhost ~]$ str='http://www.franky.com/cut-string.html'
    [franky@localhost ~]$ echo ${str%%/*}
    http:
    
  • 通过字符的位置切割字符串
    1、从左边第几个字符开始以及字符的个数,用法为:start:len,例如:
    [franky@localhost ~]$ str='http://www.franky.com/cut-string.html'
    [franky@localhost ~]$ echo ${str:0:5}
    http:
    
    2、从左边第几个字符开始一直到结束,用法为:start,例如:
    [franky@localhost ~]$ str='http://www.franky.com/cut-string.html'
    [franky@localhost ~]$ echo ${str:7}  # 其中的 7 表示左边第8个字符开始
    www.franky.com/cut-string.html
    
    3、从右边第几个字符开始以及字符的个数,用法:0-start:len,例如:
    [franky@localhost ~]$ str='http://www.franky.com/cut-string.html'
    [franky@localhost ~]$ echo ${str:0-15:10} #0-15表示右边算起第15个字符开始,10表示截取字符的个数。
    cut-string
    
    4、从右边第几个字符开始一直到结束,用法:0-start,例如:
    [franky@localhost ~]$ str='http://www.franky.com/cut-string.html'
    [franky@localhost ~]$ echo ${str:0-15} #右边算起第15个字符开始,到本字符串结尾
    cut-string.html
    
    注意: 左边的第一个字符是用 0 表示,右边的第一个字符用 0-1 表示

3、传递参数

参数含义
$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。
$0命令行脚本名称
$n命令行脚本后接第n个参数
$#命令行参数个数
$*以"$1 $2 … $n"的形式输出所有参数
$@以"$1" “ 2 " … " 2" … " 2""n” 的形式输出所有参数
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$_显示Shell使用的当前选项,与set命令功能相同。

4、数组

  • Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小 。

  • 数组元素的下标由0开始。

  • Shell 数组用括号来表示,元素用"空格"符号分割开,语法格式如下:

    array_name=(value1 value2 value3... valuen)
    
4.1、定义数组
#!/bin/bash
my_array=(A B "C" D)

#也可以使用下标来定义数组:
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
4.2、读取数组
  • 一个一读取

    echo "第一个元素为: ${my_array[0]}"
    echo "第二个元素为: ${my_array[1]}"
    echo "第三个元素为: ${my_array[2]}"
    echo "第四个元素为: ${my_array[3]}"
    
  • 获取所有元素

    echo "数组的元素为: ${my_array[*]}"
    echo "数组的元素为: ${my_array[@]}"
    
  • 获取数组长度

    echo "数组元素个数为: ${#my_array[*]}"
    echo "数组元素个数为: ${#my_array[@]}"
    

5、运算符

原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 、let和 expr。expr 是一款表达式计算工具,使用它能完成表达式的求值操作。

例如,两个数相加(注意使用的是反引号` 而不是单引号 '):

#!/bin/bash

val=`expr 2 + 2`
echo "两数之和为 : $val"
  • 表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2 。
  • 完整的表达式要被``包含,注意这个字符不是常用的单引号,在 Esc 键下边。
5.1、算数运算符

下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:

运算符说明举例
+加法`expr $a + $b` 结果为 30。
-减法`expr $a - $b` 结果为 -10。
*乘法`expr $a \* $b` 结果为 200。
/除法`expr $b / $a` 结果为 2。
%取余`expr $b % $a` 结果为 0。
=赋值a=$b 将把变量 b 的值赋给 a。
==相等。用于比较两个数字,相同则返回 true。[ $a == $b ] 返回 false。
!=不相等。用于比较两个数字,不相同则返回 true。[ $a != $b ] 返回 true。

注意:

  • 条件表达式要放在方括号之间,并且要有空格 。
  • 乘号(*)前边必须加反斜杠(\)才能实现乘法运算;在 MAC 中 shell 的 expr 语法是:$((表达式)),此处表达式中的 “*” 不需要转义符号 “\” 。
5.2、关系运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。 下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20。

运算符说明举例
-eq检测两个数是否相等,相等返回 true。[ $a -eq $b ] 返回 false。
-ne检测两个数是否不相等,不相等返回 true。[ $a -ne $b ] 返回 true。
-gt检测左边的数是否大于右边的,如果是,则返回 true。[ $a -gt $b ] 返回 false。
-lt检测左边的数是否小于右边的,如果是,则返回 true。[ $a -lt $b ] 返回 true。
-ge检测左边的数是否大于等于右边的,如果是,则返回 true。[ $a -ge $b ] 返回 false。
-le检测左边的数是否小于等于右边的,如果是,则返回 true。[ $a -le $b ] 返回 true。
5.3、布尔运算符
下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20: 
运算符说明举例
!非运算,表达式为 true 则返回 false,否则返回 true。[ ! false ] 返回 true。
-o或运算,有一个表达式为 true 则返回 true。[ $a -lt 20 -o $b -gt 100 ] 返回 true。
-a与运算,两个表达式都为 true 才返回 true。[ $a -lt 20 -a $b -gt 100 ] 返回 false。
5.4、字符串运算符

下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”:

运算符说明举例
=检测两个字符串是否相等,相等返回 true。[ $a = $b ] 返回 false。
!=检测两个字符串是否相等,不相等返回 true。[ $a != $b ] 返回 true。
-z检测字符串长度是否为0,为0返回 true。[ -z $a ] 返回 false。
-n检测字符串长度是否为0,不为0返回 true。[ -n “$a” ] 返回 true。
str检测字符串是否为空,不为空返回 true。[ $a ] 返回 true。
5.5、文件测试运算符
运算符说明举例
-b检测文件是否是块设备文件,如果是,则返回 true。[ -b $file ] 返回 false。
-c检测文件是否是字符设备文件,如果是,则返回 true。[ -c $file ] 返回 false。
-d检测文件是否是目录,如果是,则返回 true。[ -d $file ] 返回 false。
-S检测文件是否存在且为一个 Socket 档案?
-L检测文件是否存在且为一个连结档?
-f检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true。[ -f $file ] 返回 true。
-g检测文件是否设置了 SGID 位,如果是,则返回 true。[ -g $file ] 返回 false。
-k检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true。[ -k $file ] 返回 false。
-p检测文件是否是有名管道,如果是,则返回 true。[ -p $file ] 返回 false。
-u检测文件是否设置了 SUID 位,如果是,则返回 true。[ -u $file ] 返回 false。
-r检测文件是否可读,如果是,则返回 true。[ -r $file ] 返回 true。
-w检测文件是否可写,如果是,则返回 true。[ -w $file ] 返回 true。
-x检测文件是否可执行,如果是,则返回 true。[ -x $file ] 返回 true。
-s检测文件是否为空(文件大小是否大于0),不为空返回 true。[ -s $file ] 返回 true。
-e检测文件(包括目录)是否存在,如果是,则返回 true。[ -e $file ] 返回 true。
5.6、两文件之间的比较

如: test file1 -nt file2

参数含义
-ntnewer than)判断file1 是否比 file2 新
-ot(older than)判断 file1 是否比 file2 旧
-ef判断 file1 不 file2 是否为同一文件,可用在hard link 的判定上。 主要意义在判定,两个档案是否均指向同一个 inode 。
5.7、整数二元比较操作符
[](()) or [[]]释义
-eq==equal
-ne!=not equal
-gt>greater than
-ge>=greater equal
-lt<less than
-le<=less equal
5.8、逻辑操作符
[](()) or [[]]释义
-a&&and
-o||or
!!not
5.9、test命令

Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。

#!/bin/sh
num1=100
num2=100
if test $num1 -eq $num2; then
    echo '两个数相等!'
else
    echo '两个数不相等!'
fi
=================================  等价于  =================================
# test $num1 -eq $num2 <==> [ $num1 -eq $num2 ]  <==> [[ $num1==$num2 ]] <==> (($num1==$num2)) 
# 注意:[[]]和[]两端必须有空格,而(())可以没有。比较符两端可以没有空格,但为了书写美观加上空格。
============================================================================
#!/bin/sh
if test -e /etc/hosts -o -e /etc/resolv.conf;then
    echo '至少有一个文件存在!'
else
    echo '两个文件都不存在'
fi
============================================================================
#!/bin/sh
if [ -e /etc/hosts -o -e /etc/resolv.conf ];then
    echo '至少有一个文件存在!'
else
    echo '两个文件都不存在'
fi

6、流程控制

6.1、分支
  • if

    if test $(ps -ef | grep -c "ssh") -le 10 ; then
        echo 'num <= 10'
    elif [ $(ps -ef | grep -c "ssh") -gt 10 -a $(ps -ef | grep -c "ssh") -le 100 ]; then
    	echo '10 < num <= 100'
    else
        echo 'num > 100'
    fi
    
    # 这些代码也可以写成一行,不影响执行,注意用分号将每一句隔开。
    
  • case

    #!/bin/sh
    function start_func() {
        echo 'start service'
    }
    function stop_func() {
        echo 'stop service'
    }
    case $1 in
      start)
        start_func
        ;;
      stop)
        stop_func
        ;;
       restart)
         stop_func
         start_func
         ;;
    esac
    
6.2、循环
  • for

    for var in item1 item2 ... itemN
    do
        command1
        command2
        ...
        commandN
    done
    
  • while

    while condition
    do
        command
    done
    # condition 一般为条件表达式,如果返回值为false,则跳出循环。
    
  • until

    • until 循环执行一系列命令直至条件为 true 时停止。
    • until 循环与 while 循环在处理方式上刚好相反 。
    • 一般 while 循环优于 until 循环,但在某些时候—也只是极少数情况下,until 循环更加有用。
    until condition
    do
        command
    done
    # condition 一般为条件表达式,如果返回值为true,则跳出循环。
    
  • 无限循环

    while :
    do
        command
    done
    ##########################
    while true
    do
        command
    done
    ##########################
    for (( ; ; ))
    do
        command
    done
    
  • 跳出循环
    break 跳出所有循环(终止执行后面的所有循环)
    continue 仅仅跳出本次循环

  • 例子

    #!/bin/sh
    
    # 定义变量
    declare -a arr
    declare -i i
    
    # 构造数组
    i=0
    while ((i<10));do
      arr[i]=num0$i
      ((i++))
    done
    
    # 打印数组中各元素
    for n in ${arr[@]};do
      echo $n
    done
    
    # 打印数组中各元素
    len=${#arr[@]}
    for ((i=0; i<len; i++));do
      echo ${arr[$i]}
    done
    

7、函数

Linux shell 可以用户定义函数,然后在shell脚本中可以随便调用。 shell中函数的定义格式如下:

#!/bin/sh
function funname(){
    echo "第一个参数为 $1 !"
    echo "第二个参数为 $2 !"
    echo "第十个参数为 $10 !"
    echo "第十个参数为 ${10} !"
    echo "第十一个参数为 ${11} !"
    echo "参数总数有 $# 个!"
    echo "作为一个字符串输出所有参数 $* !"
    return $(($3+$2-$5)) #返回一个0-255的整数
}
funname #执行函数,不传递参。
funname arg1 arg2 arg3 arg4 arg5 #执行函数并传参
  • 可以带function fun() 定义,也可以直接fun() 定义, 不带任何参数。
  • 参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)

8、输入/输出

8.1、命令
  • read

    • -p 指定读取值时的提示符
    • -t 指定读取值时等待的时间(秒)
    #!/bin/sh
    read -t 5 -p "What's your name?" name
    echo "hello $name,welcome to my script."
    
  • echo - display a line of text

    echo会将输入的字符串送往标准输出。输出的字符串间以空白字符隔开, 并在最后加上换行号。

    • -n 不要在最后自动换行
    • -e 启用下列反斜杠转义字符的功能
    • \a 发出警告声
    • \b 删除前一个字符
    • \c 最后不加上换行符号
    • \f 换行但光标仍旧停留在原来的位置
    • \n 换行且光标移至行首
    • \r 光标移至行首,但不换行
    • \t 插入tab
    • \v 与\f相同
    • \ 插入\字符
    • \nnn 插入nnn(八进制)所代表的ASCII字符
  • printf

    printf 由 POSIX 标准所定义,因此使用 printf 的脚本比使用 echo 移植性好。 printf 使用引用文本或空格分隔的参数,外面可以在 printf 中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。默认 printf 不会像 echo 自动添加换行符,我们可以手动添加 \n。

    • printf 命令的语法

      #!/bin/bash
      printf  format-string  [arguments...]
      
      # format-string: 为格式控制字符串
      # arguments: 为参数列表。
      
    • 实例

      #!/bin/bash
      printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg  
      printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234 
      printf "%-10s %-8s %-4.2f\n" 杨过 男 48.6543 
      printf "%-10s %-8s %-4.2f\n" 郭芙 女 47.9876 
      
      # %s %c %d %f都是格式替代符
      # %-10s 指一个宽度为10个字符(-表示左对齐,没有则表示右对齐),任何字符都会被显示在10个字符宽的字符内,如果不足则自动以空格填充,超过也会将内容全部显示出来。
      # %-4.2f 指格式化为小数,其中.2指保留2位小数。
      
      # %d %s %c %f 格式替代符详解:
      # d: Decimal 十进制整数 -- 对应位置参数必须是十进制整数,否则报错!
      # s: String 字符串 -- 对应位置参数必须是字符串或者字符型,否则报错!
      # c: Char 字符 -- 对应位置参数必须是字符串或者字符型,否则报错!
      # f: Float 浮点 -- 对应位置参数必须是数字型,否则报错!
      
8.2、重定向
命令说明
command > file将输出重定向到 file。
command < file将输入重定向到 file。
command >> file将输出以追加的方式重定向到 file。
n > file将文件描述符为 n 的文件重定向到 file。
n >> file将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m将输出文件 m 和 n 合并。
n <& m将输入文件 m 和 n 合并。
<< tag将开始标记 tag 和结束标记 tag 之间的内容作为输入。

注意:文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

  • 执行命令,屏幕不显示输出结果

    $ command > /dev/null
    
  • 执行命令,屏幕不显示标准和错误输出

    $ command > /dev/null 2>&1
    

9、通配符

通配符是shell的内置功能,用于匹配文件名。

符号作用
*匹配任何字符串/文本,包括空字符串;*代表任意字符(0个或多个) ls file *
?匹配任何一个字符(不在括号内时)?代表任意1个字符 ls file 0
[abcd]匹配abcd中任何一个字符
[a-z]表示范围a到z,表示范围的意思 []匹配中括号中任意一个字符 ls file 0
{…}表示生成序列. 以逗号分隔,且不能有空格
{}利用 {} 来备份
[!abcd]或[^abcd]表示非,表示不匹配括号里面的任何一个字符
  • 利用 {} 来备份

    [root@localhost test]# touch {ab,ac,ad} && ls
    ab  ac  ad
    [root@localhost test]# cp a{c,fff} && ls
    ab  ac  ad  afff
    
  • []与{}区别

    [] - 只能用来找文件

    {} - 用来找文件,或创造文件,生成序列

10、正则表达式

Linux正则表达式不同于通配符, Linux正则表达式为Linux三剑客而生,其它大多数命令不支持Linux正则表达式。

  • 基础正则表达式

    符号意义
    *0个或多个在*字符之前的那个普通字符
    .匹配任意一个字符(任意 && 一个)
    .*任意个任意字符
    ^匹配行首,或后面字符的非 。
    $匹配行尾
    ^$空行(匹配行首,又匹配行尾,中间无任何字符,故为空行
    []匹配字符集合;[a-z]:包含a到z中任意;[ab]:包含a或b
    [^]取反
    \转义符,屏蔽一个元字符的特殊意义
    <>精确匹配符
    {n}匹配前面字符出现n次
    {n,}匹配前面字符至少出现n次
    {n,m}匹配前面字符出现n-m次
  • 扩展正则表达式

    符合意义
    ?匹配0个或1个在其之前的那个普通字符
    +匹配1个或多个在其之前的那个普通字符
    ()表示一个字符集合或用在expr中
    |表示“或”,匹配一组可选的字符
  • 预定义的字符类(不属于正则,但正则匹配会用到)

    符号字符表示
    [:digit:]任意数字,相当于0-9
    [:lower:]任意小写字母
    [:upper:]任意大写字母
    [:alpha:]任意大小写字母
    [:alnum:]任意数字或字母
    [:blank:]水平空白字符
    [:space:]水平或垂直空白字符
    [:punct:]标点符号
    [:print:]可打印字符
    [:cntrl:]控制(非打印)字符
    [:graph:]图形字符
    [:xdigit:]十六进制字符

11、Linux三剑客

11.1、grep(文本过滤器)

Linux系统中grep命令是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹 配的行打印出来。grep全称是Global Regular Expression Print,表示全局正则表达式版本,它的使用权限是所有用户。

参数意义
-v排除匹配内容行
-n显示匹配行及 行号
-i不区分大小写
-c只输出匹配行的计数
-h查询多文件时不显示文件名
-l查询多文件时只输出包含匹配字符的文件名
-s不显示不存在或无匹配文本的错误信息
-o只输出匹配内容
-E扩展正则表达式(ERE)
-B n匹配行及匹配行前n行
-A n匹配行及匹配行后n行
-C n匹配行及匹配行前、后n行
[root@localhost scripts]# grep --color=auto -B 2 'nobody' /etc/passwd 
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
11.2、sed(文本流编辑器)

sed是一个很好的文件处理工具,本身是一个管道命令,主要是以行为单位进行处理,可以编辑一个或多个文件、简化对文件的反复操作、编写转换程序等。

11.2.1、语法

sed [-hnV][-e<script>][-f<script文件>][文本文件]
参数说明

  • -e <script>或–expression=<script> :以选项中指定的script来处理输入的文本文件。
  • -f <script文件>或–file=<script文件> :以选项中指定的script文件来处理输入的文本文件。
  • -n或–quiet或–silent:使用安静模式,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。
  • -i:直接修改读取的档案内容,而不是由屏幕输出。
  • -h或–help:显示帮助。
  • -V或–version: 显示版本信息。

动作说明

  • a :新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)
  • c :取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行
  • d :删除,因为是删除啊,所以 d 后面通常不接任何咚咚
  • i :插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行)
  • p :打印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行
  • s :取代。s/old/new/g,g代表全部;s/old/new/只将每行中的第一个old改为new,s/old/new/g将每行中所有old改为new。
11.2.2、实例
  • 打印/etc/passwd文件中的3~5行

    [franky@localhost ~]$ sed -n '3,5p' /etc/passwd
    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
    
  • /etc/passwd文件中以root开头的行后新增hello world行

    [franky@localhost ~]$ sed  '/^root/ahello world' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    hello world
    bin:x:1:1:bin:/bin:/sbin/nologin
    ......
    
    #若修改文件而非屏幕输出,需要加-i参数
    [franky@localhost ~]$ sed -i '/^root/ahello world' /etc/passwd 
    
  • /etc/passwd文件中以root开头的行前插入hello world行

    [franky@localhost ~]$ sed  '/^root/ihello world' /etc/passwd 
    hello world
    root:x:0:0:root:/root:/bin/bash
    bin:x:1:1:bin:/bin:/sbin/nologin
    ......
    
    #若修改文件而非屏幕输出,需要加-i参数
    [franky@localhost ~]$ sed -i '/^root/ihello world' /etc/passwd 
    
  • /etc/passwd文件中的root修改为franky

    [franky@localhost ~]$ sed -n 's/root/franky/gp' /etc/passwd
    franky:x:0:0:root:/root:/bin/bash
    operator:x:11:0:operator:/franky:/sbin/nologin
    
  • 删除/etc/passwd文件中的第二行到最后一行

    [franky@localhost ~]$ sed '2,$d' /etc/passwd
    root:x:0:0:root:/root:/bin/bash
    
    #若修改文件而非屏幕输出,需要加-i参数
    [franky@localhost ~]$ sed -i '2,$d' /etc/passwd
    
  • 多点编辑

    # 一条sed命令,删除/etc/passwd第三行到末尾的数据,并把bash替换为sh
    [franky@localhost ~]$ sed  -e '3,$d' -e 's/bash/sh/' /etc/passwd
    root:x:0:0:root:/root:/bin/sh
    bin:x:1:1:bin:/bin:/sbin/nologin
    
11.3、awk(文本报告生成器)

​awk是一个非常棒的数字处理工具。相比于sed常常作用于一整行的处理,awk则比较倾向于将一行分为数个“字段”来处理。运行效率高,而且代码简单,对格式化的文本处理能力超强。

​awk的一般语法格式为:awk [-参数 变量] 'BEGIN{初始化}条件类型1{动作1}条件类型2{动作2}…END{后处理}';其中BEGIN后{}中的语句在开始读文件之前执行,而END{}中的语句在结束读文件之后执行,awk是一行一行读取文件。

11.3.1、参数
参数意义
-F指定字段分隔符
-v用于多变量赋值。
-f允许awk调用并执行符合awk语法的程序文件
11.3.2、变量
  • 变量命名规则:以字母或下划线开头,剩下的部分可以是:字母、数字、下划线。

    1. 以字母开头
    2. 使用中划线或者下划线做单词的连接
    3. 同类型的用数字区分
    4. 对于文件最好加上拓展名
  • 内置变量

    参数意义
    ARGC命令行参数的个数
    ARGV命令行参数数组
    ARGIND当前被处理文件的ARGV标志符
    NR已经读出的记录数
    FNR当前文件的记录数
    FS输入字段分隔符(默认空格),相当于-F选项
    OFS输出字段分隔符(默认空格)
    NF当前记录中的字段个数,结尾变量
    RS输入记录分隔符,默认为"\n"
    ORS输出记录分隔符,默认为换行符,控制每个print语句后的输出符号
    [root@CentOS6 ~]$ cat test.txt 
    this is a test.
    [root@CentOS6 ~]$ awk 'BEGIN{OFS=":"}END{print $1,$2,$3,$4}' test.txt    
    this:is:a:test.
    
  • 自定义变量

    [root@CentOS6 ~]$ awk -v test="hello world" 'BEGIN{print test}' # 等号两边不能有空格
    hello world
    [root@CentOS6 ~]$ awk 'BEGIN{test="hello world";print test}' # 等号两边可以有空格   
    hello world
    
11.3.3、打印
  • print - print的使用格式:print item1, item2, …
    1、各项目之间使用逗号隔开,而输出时则以空白字符分隔;
    2、输出的item可以为字符串或数值、当前记录的字段(如$1)、变量或awk的表达式;数值会先转换为字符串,而后再输出;
    3、print命令后面的item可以省略,此时其功能相当于print $0

    awk 'BEGIN{FS=":"}{print}' /etc/passwd
    awk -F':' '{print $0}' /etc/passwd
    

    4、如果想输出空白行,则需要使用print "

     awk '{print ""}' /etc/passwd
    
  • printf - printf命令的使用格式:printf format, item1, item2, …
    要点:

    • 其与print命令的最大不同是,printf需要指定format;
    • format用于指定后面的每个item的输出格式;
    • printf语句不会自动打印换行符,需要加\n;

    format格式:

    • %c: 显示字符的ASCII码;
    • %d, %i:十进制整数;
    • %e, %E:科学计数法显示数值;
    • %f: 显示浮点数;
    • %g, %G: 以科学计数法的格式或浮点数的格式显示数值;
    • %s: 显示字符串;
    • %u: 无符号整数;
    • %%: 显示%自身;

    修饰符:

    • n: 显示宽度,n为正整数;
    • -: 左对齐(默认右对齐);
    • +:显示数值符号;

    实例:

    [franky@localhost ~]$ awk 'BEGIN{FS=":"}{printf "%-10s%-10s\n",$1,$3}' /etc/passwd
    root      0  
    bin       1         
    daemon    2         
    adm       3         
    lp        4         
    sync      5 
    ...
    [root@CentOS6 ~]$ awk -F: '{printf "%-15s %i\n",$1,$3 > "/dev/stderr" }' /etc/passwd
    root            0
    saslauth        499
    ntp             38
    mysql           500
    [root@CentOS6 ~]$ awk -F: '$3+1>=500{printf "%-15s %i\n",$1,$3 > "/dev/stderr" }' /etc/passwd
    saslauth        499
    mysql           500
    ########### 特殊文件描述符 ###########
    # /dev/stdin:标准输入
    # /dev/sdtout: 标准输出
    # /dev/stderr: 错误输出
    # /dev/fd/N: 某特定文件描述符,如/dev/stdin就相当于/dev/fd/0;
    
11.3.4、操作符
  • 算术操作符

    运算符名称描述实例
    x+yx 和 y 的和awk ‘BEGIN{print 3+2}’
    x-yx 和 y 的差awk ‘BEGIN{print 3-2}’
    x*yx 和 y 的积awk ‘BEGIN{print 3*2}’
    x/yx 和 y 的商awk ‘BEGIN{print 3/2}’
    x%yx 除以 y 的余数awk ‘BEGIN{print 3%2}’
    x**yx的y次方awk ‘BEGIN{print 3**2}’
    x^yx的y次方awk ‘BEGIN{print 3^2}’
    -x取反x的相反数awk ‘BEGIN{print -(-2)}’
    +x转换为数值
  • 赋值操作符

=,+=,-=,*=,/=,%=,^=, **=,++,–

需要注意的是,如果某模式为=号,此时使用/=/可能会有语法错误,应以/[=]/替代;

  • 模式匹配符

    ~ - 是否匹配, !~ - 是否不匹配

  • 逻辑关系符

    && - 并且, || - 或者, ! - 非

  • 字符串操作符
    只有一个,而且不用写出来,用于实现字符串连接;

  • 布尔值

    awk中,任何非0值或非空字符串都为真,反之就为假;

  • 比较操作符

    <, <=, >, >=, ==, !=

  • 条件表达式(三元运算符)

    语法:selector?if-true-exp:if-false-exp

    awk -F: '{print ($3>500?$1:$2)}' /etc/passwd
    
  • 函数调用

    function_name (para1,para2)

11.3.5、模式
  • /regular expression/:仅处理被模式匹配到的行

    awk '/root/{print}' /etc/passwd
    
  • relational expression:关系表达式,为"真"时处理 (“真”:结果是非0值或非空字符串)

    awk -F ":" '$3>500{print}' /etc/passwd 
    
  • line ranges:行范围 (startline, endline 或 /pat1/, /pat2/)

    注意: 此处行范围不支持直接给出数字的格式

    awk -F: '(NR>=2&&NR<=10){print $1}' /etc/passwd
    awk -F: '/^h/,/^s/{print $1}' /etc/passwd
    
  • BEGIN/END模式

    • BEGIN{}: 仅在开始处理文件中的文本之前执行一次
    • END{}:仅在文本处理完成之后执行一次
11.3.6、流程控制
  • 分支

    • if

      awk  'BEGIN{FS=":"}{if($3>500)print $1}' /etc/passwd
      
    • if…else

      awk  'BEGIN{FS=":"}{if($3>500)print $1;else print $2}' /etc/passwd
      
    • if…elseif…else

      awk -F ':' '{if ($1 == "root") print $1;else if ($1 == "seker") print $6;else if ($1 == "zorro") print $7;else print NR}' /etc/passwd
      
    • switch…case
      语法:switch(expression) {case VALUE1 or /REGEXP/: statement; case VALUE2 or /REGEXP2/: statement; ...; default: statement}

  • 循环
    对一行内的多个字段逐一处理时使用;对数组中的各元素逐一处理时使用

    • while

      • 语法 :while(condition) statement (条件"真"时进入循环;条件"假"时退出循环)
      awk '/^[[:space:]]*linux16/{i=1;while(i<=NF) {print $i,length($i); i++}}' /etc/grub2.cfg
      
    • do-while

      • 语法:do statement while(condition) (至少执行一次循环体)
      awk '/^[[:space:]]*linux16/{i=1;do{print $i,length($i);i++} while(i<=NF)}' /etc/grub2.cfg 
      
    • for

      • 语法:for(expr1;expr2;expr3) statement ,

        for(variable assignment;condition;iteration process) {for-body}

      awk '/^[[:space:]]*linux16/{for(i=1;i<=NF;i++) {print $i,length($i)}}' /etc/grub2.cfg
      
      # 迭代循环
      awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print weekdays[i]}}'
      
    • break 和 continue
      break:终止循环
      continue:中断本次循环继续下一轮

    • next
      提前结束对本行的处理而直接进入下一行

      awk -F: '{if($3%2!=0) next; print $1,$3}' /etc/passwd
      
11.3.7、数组
  1. 关联数组
    array[index-expression]
    index-expression:
    ​ ① 可使用任意字符串;字符串要使用双引号;
    ​ ② 如果某数组元素事先不存在,在引用时,awk会自动创建此元素,并将其值初始化为"空串"

  2. 若要判断数组中是否存在某元素,要使用"index in array"格式进行

  3. 若要遍历数组中的每个元素,要使用for循环:for(var in array) {for-body}

awk 'BEGIN{weekdays["mon"]="Monday";weekdays["tue"]="Tuesday";for(i in weekdays) {print weekdays[i]}}'
netstat -tan | awk '/^tcp\>/{state[$NF]++}END{for(i in state){ print i,state[i]}}'
awk '{ip[$1]++}END{for(i in ip){print i,ip[i]}}' /var/log/httpd/access_log
awk '/^UUID/{fs[$3]++}END{for(i in fs){print i,fs[i]}}' /etc/fstab
awk '{for(i=1;i<=NF;i++){count[$i]++}}END{for(i in count){print i,count[i]}}' /etc/fstab
netstat -tan | awk '/^tcp\>/{state[$NF]++}END{for(i in state){print i,state[i]}}'
11.3.8、函数
  • 内置函数

    函数功能
    rand()返回0和1之间一个随机数
    length([s])返回指定字符串的长度
    sub(r,s,[t])以r表示的模式来查找t所表示的字符中的匹配的内容,并将其第一次出现替换为s所表示的内容
    gsub(r,s,[t])以r表示的模式来查找t所表示的字符中的匹配的内容,并将其所有出现均替换为s所表示的内容
    split(s,a,[r])以r为分隔符切割字符s,并将切割后的结果保存至a所表示的数组中
  • 自定义函数

    一个程序包含有多个功能,每个功能我们可以独立一个函数。

    函数可以提高代码的复用性。

    用户自定义函数的语法格式为:

    function function_name(argument1, argument2, ...) {
        function body
    }
    

    解析:

    • function_name 是用户自定义函数的名称。函数名称应该以字母开头,其后可以是数字、字母或下划线的自由组合。AWK 保留的关键字不能作为用户自定义函数的名称。
    • 自定义函数可以接受多个输入参数,这些参数之间通过逗号分隔。参数并不是必须的。我们也可以定义没有任何输入参数的函数。
    • function body 是函数体部分,它包含 AWK 程序代码。

    以下实例我们实现了两个简单函数,它们分别返回两个数值中的最小值和最大值。我们在主函数 main 中调用了这两个函数。 文件 functions.awk 代码如下:

    # 返回最小值
    function find_min(num1, num2)
    {
      if (num1 < num2)
        return num1
      return num2
    }
    
    # 返回最大值
    function find_max(num1, num2)
    {
      if (num1 > num2)
        return num1
      return num2
    }
    
    # 主函数
    function main(num1, num2)
    {
      # 查找最小值
      result = find_min(10, 20)
      print "Minimum =", result
    
      # 查找最大值
      result = find_max(10, 20)
      print "Maximum =", result
    }
    
    # 脚本从这里开始执行
    BEGIN {
      main(10, 20)
    }  
    

    执行 functions.awk 文件,可以得到如下的结果:

    $ awk -f functions.awk 
    Minimum = 10
    Maximum = 20
    
11.3.9、经典案例
  • 统计TCP连接各状态数

    netstat -n|awk '/^tcp/{++S[$NF]}END{for(n in S)print n,S[n]}'
    
  • 查看IP地址

    ip addr|awk -F '[/ ]+' '/inet /{print $3}'
    
  • 将访问量大的IP加入黑名单

    #!/bin/sh
    
    while true
    do
        awk '{print $1}' access.log|grep -v "^$"|sort|uniq -c >/tmp/tmp.log
    
        # 读取/tmp/tmp.log文件,按行循环处理
        exec </tmp/tmp.log
        while read line
        do
            ip=`echo $line|awk '{print $2}'`
            count=`echo $line|awk '{print $1}'`
            if [ $count -gt 30000 -a `iptables -L -n|grep "$ip"|wc -l` -lt 1 ];then
                iptables -I INPUT -s $ip -j DROP
                echo "$line is dropped" >>/tmp/droplist.log
            fi
        done
    
        sleep 5
    
    done
    
  • 统计文件text.awk的第一列中是浮点数的平均值

    cat text.awk 
    1.5 33
    1s.11	44
    34.5 6
    ss	7
    8
    
    awk 'BEGIN{total = 0;cnt = 0} {if($1~/^[\d]+\.[\d]+$/){total += $1; cnt++}} END{print total/len}' text.awk
    
    # 分析:$1~/^[\d]+\.[\d]+$/表示$1与“/ /”里面的正则表达式进行匹配,
    # 若匹配,则total加上$1,且len自增,即数目加1.“^[\d]+\.[\d]+$”是个正则表达式,
    # “^[\d]”表示以数字开头,“\.”是转义的意思,表示“.”为小数点的意思。“[0-9]*”表示0个或多个数字.
    
  • $前使用了反斜杠

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值