Bash Shell

【1】 Hello World!

几乎所有的讲解编程的书给读者的第一个例子都是 Hello World 程序,那么我们今天也就从这个例子出发,来逐步了解 BASH

用 vim 编辑器编辑一个 hello 文件如下:

#!/bin/bash 
# This is a very simple example
echo “Hello World”

一,第一行的 #! 是什么意思

#! 是说明 hello 这个文件的类型的,有点类似于 Windows 系统下用不同文件后缀来表示不同文件类型的意思(但不相同)。Linux 系统根据 "#!" 及该字串后面的信息确定该文件的类型,关于这一问题同学们回去以后可以通过 "man magic"命令 及 /usr/share/magic 文件来了解这方面的更多内容。


二,第一行的 /bin/bash 又是什么意思

在 BASH 中 第一行的 "#!" 及后面的 "/bin/bash" 就表明该文件是一个 BASH 程序,需要由 /bin 目录下的 bash 程序来解释执行。BASH 这个程序一般是存放在 /bin 目录下,如果你的 Linux 系统比较特别,bash 也有可能被存放在 /sbin /usr/local/bin /usr/bin /usr/sbin 或 /usr/local/sbin 这样的目录下;如果还找不到,你可以用 "locate bash" "find / -name bash 2> /dev/null" 或 "whereis bash" 这三个命令找出 bash 所在的位置;如果仍然找不到,那你可能需要自己动手安装一个 BASH 软件包了。

三,第二行是注释吗 

第二行的 "# This is a ..." 就是 BASH 程序的注释,在 BASH 程序中从“#”号(注意:后面紧接着是“!”号的除外)开始到行尾的多有部分均被看作是程序的注释。


四,echo 语句

三行的 echo 语句的功能是把 echo 后面的字符串输出到标准输出中去。由于 echo 后跟的是 "Hello World" 这个字符串,因此 "Hello World"这个字串就被显示在控制台终端的屏幕上了。需要注意的是 BASH 中的绝大多数语句结尾处都没有分号。


五,如何执行该程序

如何执行该程序呢?有两种方法:一种是显式制定 BASH 去执行:

$ bash hello.sh 或
$ sh hello.sh 

(这里 sh 是指向 bash 的一个链接,

“lrwxrwxrwx 1 root root 4 Aug 20 05:41 /bin/sh -> bash”

或者可以先将 hello 文件改为可以执行的文件,然后直接运行它,此时由于 hello 文件第一行的 "#! /bin/bash" 的作用,系统会自动用/bin/bash 程序去解释执行 hello 文件的:

$ chmod a+x hello.sh
$ ./hello.sh

此处没有直接 “$ hello.sh是因为当前目录不是当前用户可执行文件的默认目录,而将当前目录“.”设为默认目录是一个不安全的设置。

需要注意的是,BASH 程序被执行后,实际上 Linux 系统是另外开设了一个进程来运行的。

 关于输入、输出和错误输出

在字符终端环境中,标准输入/标准输出的概念很好理解。输入即指对一个应用程序 或命令的输入,无论是从键盘输入还是从别的文件输入;输出即指应用程序或命令产生的一些信息;与 Windows 系统下不同的是,Linux 系统下还有一个标准错误输出的概念,这个概念主要是为程序调试和系统维护目的而设置的,错误输出于标准输出分开可以让一些高级的错误信息不干扰正常的输出 信息,从而方便一般用户的使用。

在 Linux 系统中:标准输入(stdin)默认为键盘输入;标准输出(stdout)默认为屏幕输出;标准错误输出(stderr)默认也是输出到屏幕(上面的 std 表示 standard)。在 BASH 中使用这些概念时一般将标准输出表示为 1,将标准错误输出表示为 2。下面我们举例来说明如何使用他们,特别是标准输出和标准错误输出。

输入、输出及标准错误输出主要用于 I/O 的重定向,就是说需要改变他们的默认设置。先看这个例子:

$ ls > ls_result
$ ls -l >> ls_result

上面这两个命令分别将 ls 命令的结果输出重定向到 ls_result 文件中和追加到 ls_result 文件中,而不是输出到屏幕上。">"就是输出(标准输出和标准错误输出)重定向的代表符号,连续两个 ">" 符号,即 ">>" 则表示不清除原来的而追加输出。下面再来看一个稍微复杂的例子:

$ find /home -name lost* 2> err_result

这个命令在 ">" 符号之前多了一个 "2""2>" 表示将标准错误输出重定向。由于 /home 目录下有些目录由于权限限制不能访问,因此会产生一些标准错误输出被存放在 err_result 文件中。大家可以设想一下 find /home -name lost* 2>>err_result 命令会产生什么结果?

如果直接执行 find /home -name lost* > all_result ,其结果是只有标准输出被存入 all_result 文件中,要想让标准错误输出和标准输入一样都被存入到文件中,那该怎么办呢?看下面这个例子:

$ find /home -name lost* > all_result 2>& 1

上面这个例子中将首先将标准错误输出也重定向到标准输出中,再将标准输出重定向到 all_result 这个文件中。这样我们就可以将所有的输出都存储到文件中了。为实现上述功能,还有一种简便的写法如下:

$ find /home -name lost* >& all_result

如果那些出错信息并不重要,下面这个命令可以让你避开众多无用出错信息的干扰:

$ find /home -name lost* 2> /dev/null

同学们回去后还可以再试验一下如下几种重定向方式,看看会出什么结果,为什么?

$ find /home -name lost* > all_result 1>& 2 
$ find /home -name lost* 2> all_result 1>& 2
$ find /home -name lost* 2>& 1 > all_result

另外一个非常有用的重定向操作符是 "-",请看下面这个例子:

$ (cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xvfp -)

该命令表示把 /source/directory 目录下的所有文件通过压缩和解压,快速的全部移动到 /dest/directory 目录下去,这个命令在 /source/directory 和 /dest/directory 不处在同一个文件系统下时将显示出特别的优势。

下面还几种不常见的用法:

n<&- 表示将 号输入关闭 
<&- 表示关闭标准输入(键盘)
n>&- 表示将 号输出关闭
>&- 表示将标准输出关闭

六 shell特殊参数【命令行参数】

script 针对参数已经有设定好一些变量名称,对应如下: 

/path/to/scriptname  opt1  opt2  opt3  opt4  

         $0                   $1    $2    $3    $4 

执行的脚本档名为 $0 这个变量,第一个接的参数就是 $1  后面类推,

除了这些数字的变量外, 我们还有一些较为特殊的变量可以在 script 内使用来调用这些参数: 

·  $# :代表后接的参数『个数』,以上面为例这里显示为『 4 』; 

·  $@ :代表『 "$1" "$2" "$3" "$4" 』之意,每个变量是独立的(用双引号括起来) 

·  $* :代表『 "$1c$2c$3c$4" 』,其中 c 为分隔字符,默认为空格键, 所以本例中代表『 "$1 $2 $3 $4" 』之意。

七 随日期发化:利用 date 文档的建立

想象一个状况,假如我的服务器内有数据库,数据库每天的数据都不太一样,因此当我备份时, 希望将每天的资料都备份成不同的档名,这样才能够让旧的数据也能够保存下来不被覆盖。 哇!不同文件名呢!这真困扰啊?难道要我每天去修改 script ? 不需要啊!考虑每天的『日期』并不相同,所以我可以将文件名改成类似: backup.2013-12-15.data , 不就可以每天一个不同文件名了吗?呵呵!确实如此。那个 2013-12-15 怎举来的?那就是

重点啦!接下来出个相关的例子: 假如我想要建立三个空的文件 (透过 touch) ,文件名最开头由使用者输入决定,假如使用者输入 filename 好了,那今天的日期是 2013/12/15 , 我想要以前天、昨天、今天的日期来建立这些文件,亦即 filename_20131213, filename_20131214, filename_20131215 

该如何是好?

[root@www scripts]# vi sh03.sh 

#!/bin/bash 

# Program: 

#  Program creates three files, which named by user's input  

#  and date command. 

# History: 

# 2005/08/23  VBird  First release 

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin 

export PATH 

# 1. 让使用者输入文件名,并取得 fileuser 这个变量; 

echo -e "I will use 'touch' command to create 3 files." # 纯粹显示信息 

read -p "Please input your filename: " fileuser         # 提示使用者输入 

 

# 2. 为了避免使用者随意按 Enter ,利用变量功能分析文件名是否有设定? 

filename=${fileuser:-"filename"}           # 开始判断有否配置文件名 

 

# 3. 开始利用 date 指令来取得所需要的文件名了; 

date1=$(date --date='2 days ago' +%Y%m%d)  # 前两天的日期 

date2=$(date --date='1 days ago' +%Y%m%d)  # 前一天的日期 

date3=$(date +%Y%m%d)                      # 今天的日期 

file1=${filename}${date1}                  # 底下三行在配置文件名 

file2=${filename}${date2} 

file3=${filename}${date3} 

 

# 4. 将档名建立吧! 

touch "$file1"                             # 底下三行在建立档案 

touch "$file2" 

touch "$file3" 

关于filename=${fileuser:-"filename"}的用法,用来判断fileuser是否已经赋值。:-是一起的;fileuser 如果有值的话,就用所拥有的值赋予给filename变量;无值的话,就把filenname赋予给fileuser,再赋予给filename变量

【2】 变量赋值和引用

Shell编程中,使用变量无需事先声明,同时变量名的命名须遵循如下规则:

1. 首个字符必须为字母(a-z,A-Z) 或者_

2. 中间不能有空格,可以使用下划线(_)

3. 不能使用其他标点符号

需要给变量赋值时,可以这么写:

变量名=值 

要取用一个变量的值,只需在变量名前面加一个$ ( 注意给变量赋值的时候,不能在"="两边留空格 )

#!/bin/bash

对变量赋值:

a="hello world"  #等号两边均不能有空格存在

打印变量a的值:

echo "A is:" $a

有时候变量名可能会和其它文字混淆,比如:

num=2

echo "this is the $numnd"

上述脚本并不会输出"this is the 2nd"而是"this is the ";这是由于shell会去搜索变量numnd的值,而实际上这个变量此时并没有值。这时,我们可以用花括号来告诉shell要打印的是num变量:

num=2

echo "this is the ${num}nd"

其输出结果为:this is the 2nd

注意花括号的位置:

num=2

echo "this is the {$num}nd"

其输出结果为:this is the {2}nd

需要注意shell的默认赋值是字符串赋值。比如:

var=1

var=$var+1

echo $var

打印出来的不是2而是11。为了达到我们想要的效果有以下几种表达方式:

let "var+=1"

var="$[$var+1]"

((var++))

var=$(($var+1))

var="$(expr "$var" + 1)" #不建议使用

var="`expr "$var" + 1`" #强烈不建议使用,注意加号两边的空格,否则还是按照字符串的方式赋值,`Esc下方的`,而不是单引号'

注意:前2种方式在bash下有效,在sh下会出错。

let表示数学运算,expr用于整数值运算,每一项用空格隔开,$[]将中括号内的表达式作为数学运算先计算结果再输出。

Shell脚本中有许多变量是系统自动设定的,我们将在用到这些变量时再作说明。除了只在脚本内有效的普通shell变量外,还有环境变量,即那些由export关键字处理过的变量。本文不讨论环境变量,因为它们一般只在登录脚本中用到。

【3】 Shell里的流程控制

If语句

"if"表达式如果条件为真,则执行then后的部分:

if ....; then

  ....

elif ....; then

  ....

else

  ....

fi

大多数情况下,可以使用测试命令来对条件进行测试,比如可以比较字符串、判断文件是否存在及是否可读等等……通常用" [ ] "来表示条件测试,注意这里的空格很重要,要确保方括号前后的空格。

[ -f "somefile" ] :判断是否是一个文件

[-d “Directory”: 判断目录是否存在

[ -x "/bin/ls" ] :判断/bin/ls是否存在并有可执行权限

[ -n "$var" ] :判断$var变量是否有值

[ "$a" = "$b" ] :判断$a$b是否相等

执行man test可以查看所有测试表达式可以比较和判断的类型。下面是一个简单的if语句:

#!/bin/bash

if [ ${SHELL} = "/bin/bash" ]; then

   echo "your login shell is the bash (bourne again shell)"

else

   echo "your login shell is not bash but ${SHELL}"

fi

变量$SHELL包含有登录shell的名称,我们拿它和/bin/bash进行比较以判断当前使用的shell是否为bash

【4】 && 和 || 操作符

熟悉C语言的朋友可能会喜欢下面的表达式:

[ -f "/etc/shadow" ] && echo "This computer uses shadow passwords"

这里的 && 就是一个快捷操作符,如果左边的表达式为真则执行右边的语句,你也可以把它看作逻辑运算里的与操作。上述脚本表示如果/etc/shadow文件存在,则打印“This computer uses shadow passwords”。同样shell编程中还可以用或操作(||),例如:

#!/bin/bash

mailfolder=/var/spool/mail/james

[ -r "$mailfolder" ] || { echo "Can not read $mailfolder" ; exit 1; }

echo "$mailfolder has mail from:"

grep "^From " $mailfolder

该脚本首先判断mailfolder是否可读,如果可读则打印该文件中的"From" 一行。如果不可读则或操作生效,打印错误信息后脚本退出。需要注意的是,这里我们必须使用如下两个命令:

-打印错误信息

-退出程序

我们使用花括号以匿名函数的形式将两个命令放到一起作为一个命令使用;普通函数稍后再作说明。即使不用与和或操作符,我们也可以用if表达式完成任何事情,但是使用与或操作符会更便利很多 。

【5】 case 语句

case表达式可以用来匹配一个给定的字符串,而不是数字(可别和C语言里的switch...case混淆)。

case ... in

   ...) do something here 

   ;;

esac

解读:

case  $变量名称 in   <==关键词为 case ,还有变两前有

  "第一个变量内容")   <==每个变量内容建议用双引号括起来,关键词则为小括

号 

  程序段 

  ;;            <==每个类别结尾使用两个连续的分号来处理! 

  "第二个变量内容") 

  程序段 

  ;; 

  *)                  <==最后一个变量内容都会用 来代表所有其他值 

 不包含第一个变量内容与第二个发量内容的其他程序执行段 

  exit 1 

  ;; 

esac                  <==最终的 case 结尾! 

file命令可以辨别出一个给定文件的文件类型,如:file lf.gz,其输出结果为:

lf.gz: gzip compressed data, deflated, original filename,

last modified: Mon Aug 27 23:09:18 2001, os: Unix

我们利用这点写了一个名为smartzip的脚本,该脚本可以自动解压bzip2, gzipzip 类型的压缩文件:

 #!/bin/bash

 ftype="$(file "$1")"

 case "$ftype" in

 "$1: Zip archive"*)

    unzip "$1" ;;

 "$1: gzip compressed"*)

    gunzip "$1" ;;

 "$1: bzip2 compressed"*)

    bunzip2 "$1" ;;

 *) echo "File $1 can not be uncompressed with smartzip";;

 esac

你可能注意到上面使用了一个特殊变量$1,该变量包含有传递给该脚本的第一个参数值。也就是说,当我们运行:

smartzip articles.zip

$1 就是字符串 articles.zip

【6】 select 语句

select表达式是bash的一种扩展应用,擅长于交互式场合。用户可以从一组不同的值中进行选择:

select var in ... ; do

 break;

done

.... now $var can be used ....

下面是一个简单的示例:

#!/bin/bash

echo "What is your favourite OS?"

select var in "Linux" "Gnu Hurd" "Free BSD" "Other"; do

  break;

done

echo "You have selected $var"

该脚本的运行结果如下:

What is your favourite OS?

1) Linux

2) Gnu Hurd

3) Free BSD

4) Other

#? 1

You have selected Linux

【7】 while/for 循环

shell中,可以使用如下循环:

while [ condition ]  <==中括号内的状态就是判断表达式 

do            <==do 是循环的开始! 

  程序段落 

done          <==done 是循环的结束

只要测试表达式条件为真,则while循环将一直运行。关键字"break"用来跳出循环,而关键字”continue”则可以跳过一个循环的余下部分,直接跳到下一次循环中。

for循环会查看一个字符串列表(字符串用空格分隔),并将其赋给一个变量:

for var in ....; do

   ....

done

下面的示例会把A B C分别打印到屏幕上:

#!/bin/bash

for var in A B C ; do

   echo "var is $var"

done

下面是一个实用的脚本showrpm,其功能是打印一些RPM包的统计信息:

#!/bin/bash

# list a content summary of a number of RPM packages

# USAGE: showrpm rpmfile1 rpmfile2 ...

# EXAMPLE: showrpm /cdrom/RedHat/RPMS/*.rpm

for rpmpackage in "$@"; do

   if [ -r "$rpmpackage" ];then

      echo "=============== $rpmpackage =============="

      rpm -qi -p $rpmpackage

   else

      echo "ERROR: cannot read file $rpmpackage"

   fi

done

这里出现了第二个特殊变量$@,该变量包含有输入的所有命令行参数值。如果你运行showrpm openssh.rpm w3m.rpm webgrep.rpm,那么 "$@"(有引号就包含有 个字符串,即openssh.rpm, w3m.rpm和 webgrep.rpm$*的意思是差不多的。但是只有一个字串。如果不加引号,带空格的参数会被截断。

【8】 Shell里的一些特殊符号

在向程序传递任何参数之前,程序会扩展通配符和变量。这里所谓的扩展是指程序会把通配符(比如*)替换成适当的文件名,把变量替换成变量值。我们可以使用引号来防止这种扩展,先来看一个例子,假设在当前目录下有两个jpg文件:mail.jpgtux.jpg

#!/bin/bash

echo *.jpg

运行结果为:

mail.jpg tux.jpg

引号(单引号和双引号)可以防止通配符*的扩展:

#!/bin/bash

echo "*.jpg"

echo '*.jpg'

其运行结果为:

*.jpg

*.jpg

其中单引号更严格一些,它可以防止任何变量扩展;而双引号可以防止通配符扩展但允许变量扩展:

#!/bin/bash

echo $SHELL

echo "$SHELL"

echo '$SHELL'

运行结果为:

/bin/bash

/bin/bash

$SHELL

此外还有一种防止这种扩展的方法,即使用转义字符——反斜杆:\

echo \*.jpg

echo \$SHELL

输出结果为:

*.jpg

$SHELL

【9】 Shell里的函数

如果你写过比较复杂的脚本,就会发现可能在几个地方使用了相同的代码,这时如果用上函数,会方便很多。函数的大致样子如下:

function fname() { 

  程序段 

}

函数没有必要声明。只要在执行之前出现定义就行

那个 fname 就是我们的自定义的执行指令名称~而程序段就是我们要他执行的内容了。 要注意的是,因为 shell script 的执行方式是由上而下,由左而史, 因此在 shell script 当中的 function 的设定一定要在程序的最前面, 这样才能够在执行时被找找到可用的程序段喔!好~我们将 sh12.sh 改写一下,

自定义一个名为 printit 的函数来使用喔:

 [root@www scripts]# vi sh12-2.sh 

#!/bin/bash 

# Program: 

#  Use function to repeat information. 

# History: 

# 2005/08/29  VBird  First release 

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin 

export PATH 

 

function printit(){ 

  echo -n "Your choice is "     # 加上 -n 可以不断行继续在同一行显示 

 

echo "This program will print your selection !"

case $1 in 

  "one") 

  printit; echo $1 | tr 'a-z' 'A-Z'  # 将参数做大小写转换! 

  ;; 

  "two") 

  printit; echo $1 | tr 'a-z' 'A-Z' 

  ;; 

  "three") 

  printit; echo $1 | tr 'a-z' 'A-Z' 

  ;; 

  *) 

  echo "Usage $0 {one|two|three}" 

  ;; 

esac

以上面的例子来说,做了一个函数名称为 printit ,所以,当我在后续的程序段里面, 叧要执行 printit 的话,就表示我的 shell script 要去执行『 function printit .... 』 里面的那几个程序段落

在脚本中提供帮助是一种很好的编程习惯,可以方便其他用户(和自己)使用和理解脚本。

另外, function 也是拥有内建变量~他的内建变量与 shell script 很类似, 函数名称代表示 $0 ,而后续接变量也是以 $1, $2... 来取代的~ 这里很容易搞错喔~因为『 function fname() { 程序段 』内的 $0, $1... 等等与 shell script 的 $0 是不同的。以上面 sh12-2.sh 来说,假如我执行:『 sh sh12-2.sh one 』 这表示在 shell script 内的 $1 为 "one" 这个字符串。但是在 printit() 内的 $1 则不这个 one 无关。 我们将上面的例子再次的改写一下,让你更清楚!

[root@www scripts]# vi sh12-3.sh 

#!/bin/bash 

# Program: 

#  Use function to repeat information. 

# History: 

# 2005/08/29  VBird  First release 

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin 

export PATH 

 

function printit(){ 

  echo "Your choice is $1"   # 这个 $1 必须要参考底下指令的下达 

 

echo "This program will print your selection !" 

case $1 in 

  "one") 

  printit 1  # 请注意, printit 指令后面还有接参数! 

  ;; 

  "two") 

  printit 2

  ;; 

  "three") 

  printit 3 

  ;; 

  *) 

  echo "Usage $0 {one|two|three}" 

  ;; 

esac

在上面的例子当中,如果你输入『 sh sh12-3.sh one 』就会出现『 Your choice is 1 』的字样~ 为什么是 呢?因为在程序段落当中,我们是写了『 printit 1 』那个 就会成为 function 当中的 $1

12Shell脚本示例

脚本调试

最简单的调试方法当然是使用echo命令。你可以在任何怀疑出错的地方用echo打印变量值,这也是大部分shell程序员花费80%的时间用于调试的原因。Shell脚本的好处在于无需重新编译,而插入一个echo命令也不需要多少时间。

[root@www ~]# sh [-nvx] scripts.sh 

选项不参数: 

-n  :不要执行 script,仅查询语法的问题; 

-v  :再执行 sccript 前,先将 scripts 的内容输出到屏幕上; 

-x  :将使用到的 script 内容显示到屏幕上,这是很有用的参数! 

 

范例一:测试 sh16.sh 有无语法的问题? 

[root@www ~]# sh -n sh16.sh  

若语法没有问题,则不会显示任何信息! 

 

范例二:将 sh15.sh 的执行过程全部列出来~ 

[root@www ~]# sh -x sh15.sh  

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/root/bin 

+ export PATH 

+ for animal in dog cat elephant 

+ echo 'There are dogs.... ' 

There are dogs.... 

+ for animal in dog cat elephant 

+ echo 'There are cats.... ' 

There are cats.... 

+ for animal in dog cat elephant 

+ echo 'There are elephants.... ' 

There are elephants....

nginx日志按日期自动切割脚本如下

 #nginx日志切割脚本

#author:ce

 

#!/bin/bash

#设置日志文件存放目录

logs_path="/usr/local/nginx/logs/"

#设置pid文件

pid_path="/usr/local/nginx/nginx.pid"

 

#重命名日志文件

mv ${logs_path}access.log ${logs_path}access_$(date -d "yesterday" +"%Y%m%d").log

 

#nginx主进程发信号重新打开日志

kill -USR1 `cat ${pid_path}`

crontab 设置作业

0 0 * * * bash /usr/local/nginx/nginx_log.sh
这样就每天的00分把nginx日志重命名为日期格式,并重新生成今天的新日志文件。



2awk

     AWK是贝尔实验室1977年搞出来的文本出现神器.之所以叫AWK是因为其取了三位创始人 Alfred AhoPeter Weinberger, 和 Brian Kernighan 的Family Name的首字符。要学AWK,就得提一提AWK的一本相当经典的书《The AWK Programming Language》,它在豆瓣上的评分是9.4分!在亚马逊上居然卖1022.30元

Awk和sed 第二版

我从netstat命令中提取了如下信息作为用例:

$ cat netstat.txt

Proto Recv-Q Send-Q Local-Address          Foreign-Address             State

tcp        0      0 0.0.0.0:3306           0.0.0.0:*                   LISTEN

tcp        0      0 0.0.0.0:80             0.0.0.0:*                   LISTEN

tcp        0      0 127.0.0.1:9000         0.0.0.0:*                   LISTEN

tcp        0      0 coolshell.cn:80        124.205.5.146:18245         TIME_WAIT

tcp        0      0 coolshell.cn:80        61.140.101.185:37538        FIN_WAIT2

tcp        0      0 coolshell.cn:80        110.194.134.189:1032        ESTABLISHED

tcp        0      0 coolshell.cn:80        123.169.124.111:49809       ESTABLISHED

tcp        0      0 coolshell.cn:80        116.234.127.77:11502        FIN_WAIT2

tcp        0      0 coolshell.cn:80        123.169.124.111:49829       ESTABLISHED

tcp        0      0 coolshell.cn:80        183.60.215.36:36970         TIME_WAIT

tcp        0   4166 coolshell.cn:80        61.148.242.38:30901         ESTABLISHED

tcp        0      1 coolshell.cn:80        124.152.181.209:26825       FIN_WAIT1

tcp        0      0 coolshell.cn:80        110.194.134.189:4796        ESTABLISHED

tcp        0      0 coolshell.cn:80        183.60.212.163:51082        TIME_WAIT

tcp        0      1 coolshell.cn:80        208.115.113.92:50601        LAST_ACK

tcp        0      0 coolshell.cn:80        123.169.124.111:49840       ESTABLISHED

tcp        0      0 coolshell.cn:80        117.136.20.85:50025         FIN_WAIT2

tcp        0      0 :::22                  :::*                        LISTEN

下面是最简单最常用的awk示例,其输出第1列和第4例,

· 其中单引号中的被大括号括着的就是awk的语句,注意,其只能被单引号包含。

· 其中的$1..$n表示第几例。注:$0表示整个行。

$ awk '{print $1, $4}' netstat.txt

Proto Local-Address

tcp 0.0.0.0:3306

tcp 0.0.0.0:80

tcp 127.0.0.1:9000

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp coolshell.cn:80

tcp :::22

我们再来看看awk的格式化输出,和C语言的printf没什么两样:

$ awk'{printf "%-8s %-8s %-8s %-18s %-22s %-15s\n",$1,$2,$3,$4,$5,$6}'netstat.txt

Proto    Recv-Q   Send-Q   Local-Address      Foreign-Address        State

tcp      0        0        0.0.0.0:3306       0.0.0.0:*              LISTEN

tcp      0        0        0.0.0.0:80         0.0.0.0:*              LISTEN

tcp      0        0        127.0.0.1:9000     0.0.0.0:*              LISTEN

tcp      0        0        coolshell.cn:80    124.205.5.146:18245    TIME_WAIT

tcp      0        0        coolshell.cn:80    61.140.101.185:37538   FIN_WAIT2

tcp      0        0        coolshell.cn:80    110.194.134.189:1032   ESTABLISHED

tcp      0        0        coolshell.cn:80    123.169.124.111:49809  ESTABLISHED

tcp      0        0        coolshell.cn:80    116.234.127.77:11502   FIN_WAIT2

tcp      0        0        coolshell.cn:80    123.169.124.111:49829  ESTABLISHED

tcp      0        0        coolshell.cn:80    183.60.215.36:36970    TIME_WAIT

tcp      0        4166     coolshell.cn:80    61.148.242.38:30901    ESTABLISHED

tcp      0        1        coolshell.cn:80    124.152.181.209:26825  FIN_WAIT1

tcp      0        0        coolshell.cn:80    110.194.134.189:4796   ESTABLISHED

tcp      0        0        coolshell.cn:80    183.60.212.163:51082   TIME_WAIT

tcp      0        1        coolshell.cn:80    208.115.113.92:50601   LAST_ACK

tcp      0        0        coolshell.cn:80    123.169.124.111:49840  ESTABLISHED

tcp      0        0        coolshell.cn:80    117.136.20.85:50025    FIN_WAIT2

tcp      0        0        :::22              :::*                   LISTEN

过滤记录

我们再来看看如何过滤记录(下面过滤条件为:第三列的值为0 && 第6列的值为LISTEN)

$ awk '$3==0 && $6=="LISTEN" ' netstat.txt

tcp        0      0 0.0.0.0:3306               0.0.0.0:*              LISTEN

tcp        0      0 0.0.0.0:80                 0.0.0.0:*              LISTEN

tcp        0      0 127.0.0.1:9000             0.0.0.0:*              LISTEN

tcp        0      0 :::22                      :::*                   LISTEN

其中的“==”为比较运算符。其他比较运算符:!=, <, < >=, < p>

我们来看看各种过滤记录的方式:

$ awk' $3>0 {print $0}'netstat.txt

Proto Recv-Q Send-Q Local-Address          Foreign-Address             State

tcp        0   4166 coolshell.cn:80        61.148.242.38:30901         ESTABLISHED

tcp        0      1 coolshell.cn:80        124.152.181.209:26825       FIN_WAIT1

tcp        0      1 coolshell.cn:80        208.115.113.92:50601        LAST_ACK

如果我们需要表头的话,我们可以引入内建变量NR:

$ awk '$3==0 && $6=="LISTEN" || NR==1 '  netstat.txt

Proto Recv-Q Send-Q Local-Address          Foreign-Address             State

tcp        0      0 0.0.0.0:3306           0.0.0.0:*                   LISTEN

tcp        0      0 0.0.0.0:80             0.0.0.0:*                   LISTEN

tcp        0      0 127.0.0.1:9000         0.0.0.0:*                   LISTEN

tcp        0      0 :::22                  :::*                        LISTEN

再加上格式化输出:

$ awk'$3==0 && $6=="LISTEN" || NR==1 {printf "%-20s %-20s %s\n",$4,$5,$6}'netstat.txt

Local-Address        Foreign-Address      State

0.0.0.0:3306         0.0.0.0:*            LISTEN

0.0.0.0:80           0.0.0.0:*            LISTEN

127.0.0.1:9000       0.0.0.0:*            LISTEN

:::22                :::*                 LISTEN

内建变量

说到了内建变量,我们可以来看看awk的一些内建变量:

$0

当前记录(这个变量中存放着整个行的内容)

$1~$n

当前记录的第n个字段,字段间由FS分隔

FS

输入字段分隔符 默认是空格或Tab

NF

当前记录中的字段个数,就是有多少列

NR

已经读出的记录数,就是行号,从1开始,如果有多个文件话,这个值也是不断累加中。

FNR

当前记录数,与NR不同的是,这个值会是各个文件自己的行号

RS

输入的记录分隔符, 默认为换行符

OFS

输出字段分隔符, 默认也是空格

ORS

输出的记录分隔符,默认为换行符

FILENAME

当前输入文件的名字

怎么使用呢,比如:我们如果要输出行号:

$ awk'$3==0 && $6=="ESTABLISHED" || NR==1 {printf "%02s %s %-20s %-20s %s\n",NR, FNR, $4,$5,$6}'netstat.txt

01 1 Local-Address        Foreign-Address      State

07 7 coolshell.cn:80      110.194.134.189:1032 ESTABLISHED

08 8 coolshell.cn:80      123.169.124.111:49809 ESTABLISHED

10 10 coolshell.cn:80      123.169.124.111:49829 ESTABLISHED

14 14 coolshell.cn:80      110.194.134.189:4796 ESTABLISHED

17 17 coolshell.cn:80      123.169.124.111:49840 ESTABLISHED

指定分隔符

$  awk 'BEGIN{FS=":"} {print $1,$3,$6}' /etc/passwd

root 0 /root

bin 1 /bin

daemon 2 /sbin

adm 3 /var/adm

lp 4 /var/spool/lpd

sync5 /sbin

shutdown6 /sbin

halt 7 /sbin

上面的命令也等价于:(-F的意思就是指定分隔符)

$ awk-F: '{print $1,$3,$6}'/etc/passwd

注:如果你要指定多个分隔符,你可以这样来:

awk-F '[;:]'

再来看一个以\t作为分隔符输出的例子(下面使用了/etc/passwd文件,这个文件是以:分隔的):

$ awk-F: '{print $1,$3,$6}'OFS="\t"/etc/passwd

root    0       /root

bin     1       /bin

daemon  2       /sbin

adm     3       /var/adm

lp      4       /var/spool/lpd

sync5       /sbin

字符串匹配

我们再来看几个字符串匹配的示例:

$ awk '$6 ~ /FIN/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt

1       Local-Address   Foreign-Address State

6       coolshell.cn:80 61.140.101.185:37538    FIN_WAIT2

9       coolshell.cn:80 116.234.127.77:11502    FIN_WAIT2

13      coolshell.cn:80 124.152.181.209:26825   FIN_WAIT1

18      coolshell.cn:80 117.136.20.85:50025     FIN_WAIT2

$ $ awk '$6 ~ /WAIT/ || NR==1 {print NR,$4,$5,$6}' OFS="\t" netstat.txt

1       Local-Address   Foreign-Address State

5       coolshell.cn:80 124.205.5.146:18245     TIME_WAIT

6       coolshell.cn:80 61.140.101.185:37538    FIN_WAIT2

9       coolshell.cn:80 116.234.127.77:11502    FIN_WAIT2

11      coolshell.cn:80 183.60.215.36:36970     TIME_WAIT

13      coolshell.cn:80 124.152.181.209:26825   FIN_WAIT1

15      coolshell.cn:80 183.60.212.163:51082    TIME_WAIT

18      coolshell.cn:80 117.136.20.85:50025     FIN_WAIT2

上面的第一个示例匹配FIN状态, 第二个示例匹配WAIT字样的状态。其实 ~ 表示模式开始。/ /中是模式。这就是一个正则表达式的匹配。

其实awk可以像grep一样的去匹配第一行,就像这样:

$ awk '/LISTEN/' netstat.txt

tcp        0      0 0.0.0.0:3306            0.0.0.0:*               LISTEN

tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN

tcp        0      0 127.0.0.1:9000          0.0.0.0:*               LISTEN

tcp        0      0 :::22                   :::*                    LISTEN

我们可以使用 “/FIN|TIME/” 来匹配 FIN 或者 TIME :

$ awk'$6 ~ /FIN|TIME/ || NR==1 {print NR,$4,$5,$6}'OFS="\t"netstat.txt

1       Local-Address   Foreign-Address State

5       coolshell.cn:80 124.205.5.146:18245     TIME_WAIT

6       coolshell.cn:80 61.140.101.185:37538    FIN_WAIT2

9       coolshell.cn:80 116.234.127.77:11502    FIN_WAIT2

11      coolshell.cn:80 183.60.215.36:36970     TIME_WAIT

13      coolshell.cn:80 124.152.181.209:26825   FIN_WAIT1

15      coolshell.cn:80 183.60.212.163:51082    TIME_WAIT

18      coolshell.cn:80 117.136.20.85:50025     FIN_WAIT2

再来看看模式取反的例子:

$ awk'$6 !~ /WAIT/ || NR==1 {print NR,$4,$5,$6}'OFS="\t"netstat.txt

1       Local-Address   Foreign-Address State

2       0.0.0.0:3306    0.0.0.0:*       LISTEN

3       0.0.0.0:80      0.0.0.0:*       LISTEN

4       127.0.0.1:9000  0.0.0.0:*       LISTEN

7       coolshell.cn:80 110.194.134.189:1032    ESTABLISHED

8       coolshell.cn:80 123.169.124.111:49809   ESTABLISHED

10      coolshell.cn:80 123.169.124.111:49829   ESTABLISHED

12      coolshell.cn:80 61.148.242.38:30901     ESTABLISHED

14      coolshell.cn:80 110.194.134.189:4796    ESTABLISHED

16      coolshell.cn:80 208.115.113.92:50601    LAST_ACK

17      coolshell.cn:80 123.169.124.111:49840   ESTABLISHED

19      :::22   :::*    LISTEN

或是:

Awk '!/WAIT/' netstat.txt

折分文件

awk拆分文件很简单,使用重定向就好了。下面这个例子,是按第6例分隔文件,相当的简单(其中的NR!=1表示不处理表头)。

$ awk 'NR!=1{print > $6}' netstat.txt

$ ls

ESTABLISHED  FIN_WAIT1  FIN_WAIT2  LAST_ACK  LISTEN  netstat.txt  TIME_WAIT

$ cat ESTABLISHED

tcp        0      0 coolshell.cn:80        110.194.134.189:1032        ESTABLISHED

tcp        0      0 coolshell.cn:80        123.169.124.111:49809       ESTABLISHED

tcp        0      0 coolshell.cn:80        123.169.124.111:49829       ESTABLISHED

tcp        0   4166 coolshell.cn:80        61.148.242.38:30901         ESTABLISHED

tcp        0      0 coolshell.cn:80        110.194.134.189:4796        ESTABLISHED

tcp        0      0 coolshell.cn:80        123.169.124.111:49840       ESTABLISHED

$ cat FIN_WAIT1

tcp        0      1 coolshell.cn:80        124.152.181.209:26825       FIN_WAIT1

$ cat FIN_WAIT2

tcp        0      0 coolshell.cn:80        61.140.101.185:37538        FIN_WAIT2

tcp        0      0 coolshell.cn:80        116.234.127.77:11502        FIN_WAIT2

tcp        0      0 coolshell.cn:80        117.136.20.85:50025         FIN_WAIT2

$ cat LAST_ACK

tcp        0      1 coolshell.cn:80        208.115.113.92:50601        LAST_ACK

$ cat LISTEN

tcp        0      0 0.0.0.0:3306           0.0.0.0:*                   LISTEN

tcp        0      0 0.0.0.0:80             0.0.0.0:*                   LISTEN

tcp        0      0 127.0.0.1:9000         0.0.0.0:*                   LISTEN

tcp        0      0 :::22                  :::*                        LISTEN

$ cat TIME_WAIT

tcp        0      0 coolshell.cn:80        124.205.5.146:18245         TIME_WAIT

tcp        0      0 coolshell.cn:80        183.60.215.36:36970         TIME_WAIT

tcp        0      0 coolshell.cn:80        183.60.212.163:51082        TIME_WAIT

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

awk 'NR!=1{print $4,$5 < $6}'netstat.txt

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

$ awk 'NR!=1{if($6 ~ /TIME|ESTABLISHED/) print > "1.txt";

else if($6 ~ /LISTEN/) print > "2.txt";

else print > "3.txt" }' netstat.txt

$ ls?.txt

1.txt  2.txt  3.txt

$ cat1.txt

tcp        0      0 coolshell.cn:80        124.205.5.146:18245         TIME_WAIT

tcp        0      0 coolshell.cn:80        110.194.134.189:1032        ESTABLISHED

tcp        0      0 coolshell.cn:80        123.169.124.111:49809       ESTABLISHED

tcp        0      0 coolshell.cn:80        123.169.124.111:49829       ESTABLISHED

tcp        0      0 coolshell.cn:80        183.60.215.36:36970         TIME_WAIT

tcp        0   4166 coolshell.cn:80        61.148.242.38:30901         ESTABLISHED

tcp        0      0 coolshell.cn:80        110.194.134.189:4796        ESTABLISHED

tcp        0      0 coolshell.cn:80        183.60.212.163:51082        TIME_WAIT

tcp        0      0 coolshell.cn:80        123.169.124.111:49840       ESTABLISHED

$ cat2.txt

tcp        0      0 0.0.0.0:3306           0.0.0.0:*                   LISTEN

tcp        0      0 0.0.0.0:80             0.0.0.0:*                   LISTEN

tcp        0      0 127.0.0.1:9000         0.0.0.0:*                   LISTEN

tcp        0      0 :::22                  :::*                        LISTEN

$ cat3.txt

tcp        0      0 coolshell.cn:80        61.140.101.185:37538        FIN_WAIT2

tcp        0      0 coolshell.cn:80        116.234.127.77:11502        FIN_WAIT2

tcp        0      1 coolshell.cn:80        124.152.181.209:26825       FIN_WAIT1

tcp        0      1 coolshell.cn:80        208.115.113.92:50601        LAST_ACK

tcp        0      0 coolshell.cn:80        117.136.20.85:50025         FIN_WAIT2

统计

下面的命令计算所有的C文件,CPP文件和H文件的文件大小总和。

$ ls-l  *.cpp *.c *.h | awk'{sum+=$5} END {print sum}'

2511401

我们再来看一个统计各个connection状态的用法:(我们可以看到一些编程的影子了,大家都是程序员我就不解释了。注意其中的数组的用法)

$ awk 'NR!=1{a[$6]++;} END {for (i in a) print i ", " a[i];}' netstat.txt

TIME_WAIT, 3

FIN_WAIT1, 1

ESTABLISHED, 6

FIN_WAIT2, 3

LAST_ACK, 1

LISTEN, 4

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

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

dbus, 540KB

mysql, 99928KB

www, 3264924KB

root, 63644KB

hchen, 6020KB

awk脚本

在上面我们可以看到一个END关键字。END的意思是“处理完所有的行的标识”,即然说到了END就有必要介绍一下BEGIN,这两个关键字意味着执行前和执行后的意思,语法如下:

· BEGIN{ 这里面放的是执行前的语句 }

· END {这里面放的是处理完所有的行后要执行的语句 }

· {这里面放的是处理每一行时要执行的语句}

为了说清楚这个事,我们来看看下面的示例:

假设有这么一个文件(学生成绩表):

$ catscore.txt

Marry   2143 78 84 77

Jack    2321 66 78 45

Tom     2122 48 77 71

Mike    2537 87 97 95

Bob     2415 40 57 62

我们的awk脚本如下(我没有写有命令行上是因为命令行上不易读,另外也在介绍另一种用法):

$ cat cal.awk

#!/bin/awk -f

#运行前

BEGIN {

math = 0

english = 0

computer = 0

printf"NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL\n"

printf"---------------------------------------------\n"

}

#运行中

{

math+=$3

english+=$4

computer+=$5

printf"%-6s %-6s %4d %8d %8d %8d\n", $1, $2, $3,$4,$5, $3+$4+$5

}

#运行后

END {

printf"---------------------------------------------\n"

printf"  TOTAL:%10d %8d %8d \n", math, english, computer

printf"AVERAGE:%10.2f %8.2f %8.2f\n", math/NR, english/NR, computer/NR

}

我们来看一下执行结果:(也可以这样运行 ./cal.awk score.txt)

$ awk-f cal.awk score.txt

NAME    NO.   MATH  ENGLISH  COMPUTER   TOTAL

---------------------------------------------

Marry  2143     78       84       77      239

Jack   2321     66       78       45      189

Tom    2122     48       77       71      196

Mike   2537     87       97       95      279

Bob    2415     40       57       62      159

---------------------------------------------

TOTAL:       319      393      350

AVERAGE:     63.80    78.60    70.00

环境变量

即然说到了脚本,我们来看看怎么和环境变量交互:(使用-v参数和ENVIRON,使用ENVIRON的环境变量需要export)

$ x=5

$ y=10

$ export y

$ echo$x $y

5 10

$ awk -v val=$x '{print $1, $2, $3, $4+val, $5+ENVIRON["y"]}' OFS="\t" score.txt

Marry   2143    78      89      87

Jack    2321    66      83      55

Tom     2122    48      82      81

Mike    2537    87      102     105

Bob     2415    40      62      72

最后,我们再来看几个小例子:

#file文件中找出长度大于80的行

awk 'length>80' file

#按连接数查看客户端IP

netstat -ntu | awk'{print $5}'| cut-d: -f1 | sort| uniq-c | sort-nr

#打印99乘法表

Seq 9 | sed'H;g'| awk-vRS='''{for(i=1;i< NFiprintfdxd dsquot i NR iNR i="=NR?"\n":"\t")}'

关于其中的一些知识点可以参看gawk的手册

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

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

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

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

2sed

sed全名叫stream editor,流编辑器,用程序的方式来编辑文本,相当的hacker啊。sed基本上就是玩正则模式匹配,所以,玩sed的人,正则表达式一般都比较强。

同样,本篇文章不会说sed的全部东西,你可以参看sed的手册

用s命令替换

我使用下面的这段文本做演示:

$ cat pets.txt

This is my cat

my cat's name is betty

This is my dog

my dog's name is frank

This is my fish

my fish's name is george

This is my goat

my goat's name is adam

把其中的my字符串替换成Hao Chen’s,下面的语句应该很好理解(s表示替换命令,/my/表示匹配my,/Hao Chen’s/表示把匹配替换成Hao Chen’s,/g 表示一行上的替换所有的匹配):

$ sed "s/my/Hao Chen's/g" pets.txt

This is Hao Chen's cat

Hao Chen's cat's name is betty

This is Hao Chen's dog

Hao Chen's dog's name is frank

This is Hao Chen's fish

Hao Chen's fish's name is george

This is Hao Chen's goat

Hao Chen's goat's name is adam

注意:如果你要使用单引号,那么你没办法通过\’这样来转义,就有双引号就可以了,在双引号内可以用\”来转义。

再注意:上面的sed并没有对文件的内容改变,只是把处理过后的内容输出,如果你要写回文件,你可以使用重定向,如:

$ sed"s/my/Hao Chen's/g"pets.txt > hao_pets.txt

或使用 -i 参数直接修改文件内容:

$ sed -i "s/my/Hao Chen's/g" pets.txt

在每一行最前面加点东西:

$ sed 's/^/#/g' pets.txt

#This is my cat

#  my cat's name is betty

#This is my dog

#  my dog's name is frank

#This is my fish

#  my fish's name is george

#This is my goat

#  my goat's name is adam

在每一行最后面加点东西:

$ sed 's/$/ --- /g' pets.txt

This is my cat---

my cat's name is betty ---

This is my dog ---

my dog's name is frank ---

This is my fish ---

my fish's name is george ---

This is my goat ---

my goat's name is adam ---

顺手介绍一下正则表达式的一些最基本的东西:

· ^ 表示一行的开头。如:/^#/ 以#开头的匹配。

· $ 表示一行的结尾。如:/}$/ 以}结尾的匹配。

· \< 表示词首。 如 \<abc 表示以 abc 为首的詞。

· \> 表示词尾。 如 abc\> 表示以 abc 結尾的詞。

· . 表示任何单个字符。

· * 表示某个字符出现了0次或多次。

· [ ] 字符集合。 如:[abc]表示匹配a或b或c,还有[a-zA-Z]表示匹配所有的26个字符。如果其中有^表示反,如[^a]表示非a的字符

正规则表达式是一些很牛的事,比如我们要去掉某html中的tags:

html.txt

< code>b<This</>b< is what < code>spanstyle="text-decoration: underline;"<I</>span< meant. Understand?

看看我们的sed命令

如果你这样搞的话,就会有问题

$ sed 's/< >//g' html.txt

Understand?

要解决上面的那个问题,就得像下面这样。

其中的'[^<]' 指定了除了<的字符重复0次或多次。

$ sed 's/<[^>]*>//g' html.txt

This is what I meant. Understand?

我们再来看看指定需要替换的内容:

$ sed"3s/my/your/g"pets.txt

This is my cat

my cat's name is betty

This is your dog

my dog's name is frank

This is my fish

my fish's name is george

This is my goat

my goat's name is adam

下面的命令只替换第3到第6行的文本。

$ sed "3,6s/my/your/g"pets.txt

This is my cat

my cat's name is betty

This is your dog

your dog's name is frank

This is your fish

your fish's name is george

This is my goat

my goat's name is adam

$ cat my.txt

This is my cat, my cat's name is betty

This is my dog, my dog's name is frank

This is my fish, my fish's name is george

This is my goat, my goat's name is adam

只替换每一行的第一个s:

$ sed 's/s/S/1' my.txt

ThiS is my cat, my cat's name is betty

ThiS is my dog, my dog's name is frank

ThiS is my fish, my fish's name is george

ThiS is my goat, my goat's name is adam

只替换每一行的第二个s:

$ sed's/s/S/2'my.txt

This iS my cat, my cat's name is betty

This iS my dog, my dog's name is frank

This iS my fish, my fish's name is george

This iS my goat, my goat's name is adam

只替换第一行的第3个以后的s:

$ sed's/s/S/3g'my.txt

This is my cat, my cat'S name iS betty

This is my dog, my dog'S name iS frank

This is my fiSh, my fiSh'S name iS george

This is my goat, my goat'S name iS adam

多个匹配

如果我们需要一次替换多个模式,可参看下面的示例:(第一个模式把第一行到第三行的my替换成your,第二个则把第3行以后的This替换成了That)

$ sed '1,3s/my/your/g; 3,$s/This/That/g'my.txt

This is your cat, your cat's name is betty

This is your dog, your dog's name is frank

That is your fish, your fish's name is george

That is my goat, my goat's name is adam

上面的命令等价于:(注:下面使用的是sed的-e命令行参数)

Sed -e  '1,3s/my/your/g' -e  '3,$s/This/That/g' my.txt

我们可以使用&来当做被匹配的变量,然后可以在基本左右加点东西。如下所示:

$ sed 's/my/[&]/g' my.txt

This is [my] cat, [my] cat's name is betty

This is [my] dog, [my] dog's name is frank

This is [my] fish, [my] fish's name is george

This is [my] goat, [my] goat's name is adam

圆括号匹配

使用圆括号匹配的示例:(圆括号括起来的正则表达式所匹配的字符串会可以当成变量来使用,sed中使用的是\1,\2…)

$ sed 's/This is my \([^,]*\),.*is \(.*\)/\1:\2/g' my.txt

cat:betty

dog:frank

fish:george

goat:adam

上面这个例子中的正则表达式有点复杂,解开如下(去掉转义字符):

正则为:This is my ([^,]*),.*is (.*)
匹配为:This is my (cat),……….is (betty)

然后:\1就是cat,\2就是betty

sed的命令

让我们回到最一开始的例子pets.txt,让我们来看几个命令:

N命令

先来看N命令 —— 把下一行的内容纳入当成缓冲区做匹配。

下面的的示例会把原文本中的偶数行纳入奇数行匹配,而s只匹配并替换一次,所以,就成了下面的结果:

$ sed'N;s/my/your/'pets.txt

This is your cat

my cat's name is betty

This is your dog

my dog's name is frank

This is your fish

my fish's name is george

This is your goat

my goat's name is adam

也就是说,原来的文件成了:

This is my cat\n  my cat's name is betty

This is my dog\n  my dog's name is frank

This is my fish\n  my fish's name is george

This is my goat\n  my goat's name is adam

这样一来,下面的例子你就明白了,

$ sed'N;s/\n/,/'pets.txt

This is my cat,  my cat's name is betty

This is my dog,  my dog's name is frank

This is my fish,  my fish's name is george

This is my goat,  my goat's name is adam

a命令和i命令

a命令就是append, i命令就是insert,它们是用来添加行的。如:

# 其中的1i表明,其要在第1行前插入一行(insert)

$ sed "1 i This is my monkey, my monkey's name is wukong"my.txt

This is my monkey, my monkey's name is wukong

This is my cat, my cat's name is betty

This is my dog, my dog's name is frank

This is my fish, my fish's name is george

This is my goat, my goat's name is adam

# 其中的1a表明,其要在最后一行后追加一行(append)

$ sed"$ a This is my monkey, my monkey's name is wukong"my.txt

This is my cat, my cat's name is betty

This is my monkey, my monkey's name is wukong

This is my dog, my dog's name is frank

This is my fish, my fish's name is george

This is my goat, my goat's name is adam

我们可以运用匹配来添加文本:

# 注意其中的/fish/a,这意思是匹配到/fish/后就追加一行

$ sed"/fish/a This is my monkey, my monkey's name is wukong"my.txt

This is my cat, my cat's name is betty

This is my dog, my dog's name is frank

This is my fish, my fish's name is george

This is my monkey, my monkey's name is wukong

This is my goat, my goat's name is adam

下面这个例子是对每一行都挺插入:

$ sed"/my/a ----"my.txt

This is my cat, my cat's name is betty

----

This is my dog, my dog's name is frank

----

This is my fish, my fish's name is george

----

This is my goat, my goat's name is adam

----

c命令

c 命令是替换匹配行

$ sed "2 c This is my monkey, my monkey's name is wukong" my.txt

This is my cat, my cat's name is betty

This is my monkey, my monkey's name is wukong

This is my fish, my fish's name is george

This is my goat, my goat's name is adam

$ sed"/fish/c This is my monkey, my monkey's name is wukong"my.txt

This is my cat, my cat's name is betty

This is my dog, my dog's name is frank

This is my monkey, my monkey's name is wukong

This is my goat, my goat's name is adam

d命令

删除匹配行

$ sed'/fish/d'my.txt

This is my cat, my cat's name is betty

This is my dog, my dog's name is frank

This is my goat, my goat's name is adam

$ sed'2d'my.txt

This is my cat, my cat's name is betty

This is my fish, my fish's name is george

This is my goat, my goat's name is adam

$ sed'2,$d'my.txt

This is my cat, my cat's name is betty

p命令

打印命令

你可以把这个命令当成grep式的命令

# 匹配fish并输出,可以看到fish的那一行被打了两遍,

# 这是因为sed处理时会把处理的信息输出

$ sed'/fish/p'my.txt

This is my cat, my cat's name is betty

This is my dog, my dog's name is frank

This is my fish, my fish's name is george

This is my fish, my fish's name is george

This is my goat, my goat's name is adam

# 使用n参数就好了

$ sed -n '/fish/p'my.txt

This is my fish, my fish's name is george

# 从一个模式到另一个模式

$ sed -n '/dog/,/fish/p'my.txt

This is my dog, my dog's name is frank

This is my fish, my fish's name is george

#从第一行打印到匹配fish成功的那一行

$ sed-n '1,/fish/p'my.txt

This is my cat, my cat's name is betty

This is my dog, my dog's name is frank

This is my fish, my fish's name is george


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值