Shell基础

1. shell script

1.1 第一个script的编写与执行

  shell 脚本其实就是纯文本文件,我们可以编辑这个文件,然后让这个文件来帮我们一次执行多个命令,或者利用一些运算与逻辑判断来帮我们达成某些功能。

在编写shell脚本,我们呢需要注意一下事项:

  1. 命令的执行是从上而下,从左而右的分析执行。
  2. 命令的执行:命令、参数间的多个空格会被忽略。
  3. 空白行也将被忽略,并且[tab]按键所得的 空白同样视为空格键。
  4. 如果读取到一个Enter符号(CR),就尝试开始执行该行命令。
  5. 如果一行的内容太多,可以使用“\[Enter]”来扩展下一行。
  6. “#”可作为批注
  • 编写第一个脚本
[] # vi sh01.sh
#!/bin/bash

echo -e "Hello World! \a \n"
exit 0
[] # sh sh01.sh

  1. 第一行#!/bin/bash 声明这个脚本使用的shell名称

  2. 第二行输出Hello World!

  3. 第三行告知执行结果。可用$?查看

  另外,也可用"chmod a+x sh01.sh; ./sh01.sh“来执行这个脚本

1.2 简单范例

  下面的范例在很多的脚本程序中都会用到,而下面的范例又都很简单,先拿来讲解。

  • 交互式脚本:变量内容由用户决定
1 [] # vi sh01.sh
2 #!/bin/bash
3 read -p "please input your  first name: " firstname #提示用户输入
4 read -p "please input your last name: " lastname #提示用户输入
5 echo -e "\nyour full name is : $firstname $lastname" #输出结果
  • 随日期变化:利用日期进行文件的创建
 1 [] # vi sh03.sh
 2 #!/bin/bash
 3 # 1. 让用户输入文件名,并取得fileuser这个变量;
 4 echo -e "I will use 'touch' command to creagte 3 file,"
 5 read -p "Please input your filename: " fileuser
 6 
 7 # 2. 为了避免用户随意按[Enter],利用变量功能分析文件名是否有设置
 8 filename=${fileuser:-"filename"}    # 开始判断是否配置文件名
 9 
10 # 3. 开始利用date命令取得所需的文件名
11 date1=$(date --date='2 days ago' +%Y%m%d)     # 前两天的日期
12 date2=$(date --date='1 days ago' +%Y%m%d)     # 前一天的日期
13 date3=$(date +%Y%m%d)                                  # 今天的日期
14 
15 file1=${filename}${date1)
16 file2=${filename}${date2}
17 file3=${filename}${date3}
18 
19 # 4. 创建文件
20 touch "$file1"
21 touch "$file2"
22 touch "$file3"
  • 数值运算:简单的加减乘除
1 [] # vi sh04.sh
2 #!/bin/bash
3 
4 echo -e "You should input 2 numbers, I will cross them! \n"
5 read -p "first number: " firstnu
6 read -p "second number: " secnu
7 total=$( ($firstnu*$secnu) )
8 echo -e "\nThe reault of $firstnu x $secnu is = $total"

  在数值运算上,我们可以用 declare -i total=$firstnu*$secnu,也可以使用上面的方式来进行。

  • var=$((运算内容))

  容易记忆,而且也比较方便,因为两个小括号内可以加上空格符。将来可以用这种方式来计算。

至于数值运算上的处理,则有 + - * / %等。

1.3 脚本执行方式区别(source, sh script, ./script)

  不同的脚本执行方式会造成不一样的结果。

  • 利用直接执行的方式来执行脚本

  当使用直接命令执行(不论是绝对路径/相对路径/还是$PATH内),或者是用bash(或sh)来执行脚本时,该脚本都会使用一个新的bash环境来执行脚本内的命令。

也就是说,使用这种执行方式时,其实脚本是子在子进程的bash内执行的,重点在于:当子进程完成后,子进程内的各项变量或操作将会结束而不会传回到父进程中。

举个例子:

1 [] # echo $firstname $lastname
2     <==确认了,这两个变量并不存在
3 [] # sh sh02.sh
4 please input your first name: VBird
5 please input your last name: Tsai
6 
7 Your full name is: VBird Tsai
8 [] # echo $firstname $lastname
9     <==事实上,这两个变量在父进程的bash中还是不存在的
  • 利用source来执行脚本:在父进程中执行

  如果你使用source来执行命令那就不一样了!同样的脚本我们来执行看看:

[] # source sh02.sh
please input your first name: VBird
please input your last name: Tsai

Your full name is: VBird Tsai
[] # echo $firstname $lastname
VBird Tsai <==有数据产生哦

1.4 善用判断式

1.4.1 $?(命令回传码)与&& 或||

  $?:命令回传码,若前一个命令的执行结果为正确,在linux下面会传回一个$?=0的值。那么我们就可以利用这个配合&&和||的帮忙,来控制命令的执行

命令执行情况说明
cmd1 && cmd2

若cmd1执行完毕且正确执行($?=0),则开始执行cmd2

若cmd1执行完毕且为错误($?!=0),则cmd2不执行

cmd1 || cmd2

若cmd1执行完毕且正确执行,则cmd2不执行

若cmd2执行完毕且错误,则cmd2开始执行

 

 

 

 

 

 

1.4.2 利用test命令的测试功能

  当要检测系统上面某些文件或者是相关的属性时,利用test命令来工作。举例来说,要检查/dmtsai是否存在时,使用:

1 [~]# test -e /dmtsai

  执行结果不会显示任何信息,但最后我们可以通过$?或&&及||来显示整个结果,例如:

1 [~]# test -e /dmtsai && echo "exist" || echo "not exist"

  最终的结果可以告诉我们是”exist“还是”not exist“。下表展示哪些标志可以来判断。

测试的标志代表意义
关于某个文件名的”文件类型“判断,如test -e filename表示文件存在与否
-e该文件名是否存在(常用)
-f该文件名是否存在且为文件(file)(常用)
-d该文件名是否存在且为目录(directory)(常用)
-b该文件名是否存在且为一个块设备
-c该文件名是否存在且为一个字符设备
-S该文件名是否存在且为一个Socket文件
-p该文件名是否存在且为一个管道文件FIFO(pipe)
-L该文件名是否存在且为一个连接文件
关于文件的权限检测,如test -r filename表示可读否(但root权限常有例外)
-r检测该文件名是否存在且具有”可读“的权限
-w检测文件名是否存在且具有”可写“的权限
-x检测文件名是否存在且具有”可执行“的权限
-u检测文件名是否存在且具有”SUID“的属性
-g检测文件名是否存在且具有”SGID“的属性
-k检测文件名是否存在且具有”Sticky bit“的属性
-s检测文件名是否存在且为“非空白文件”
两个文件之间的比较,如:test file1 -nt file 2
-nt(newer than)判断file1是否比file2新
-ot(older than)判断file1是否比file2旧
-ef

判断file1与file2是否为同一文件,可用在判断hard link(硬连接)的判定上。

主要意义在于判定两个文件是否均指向同一个inode

关于两个整数之间的判定,例如test n1 -eq n2
-eq两数值相等(equal)
-ne两数值不等(not equal)
-gtn1大于n2
-ltn1小于n2
-gen1大于等于n2
-len1小于等于n2
判定字符串的数据
test -z string判定字符串是否为0,若string为空字符串,则为true
test -n string

判定字符串是否非为0,若string为空字符串,则为false

注:-n可以省略

test str1=str2判定str1是否等于str2,若相等,则回传true
test str1!=str2判定str1是否等于str2,若相等,则回传false
多重条件判定,例如:test -r filename -a -x filename
-a两个条件同时成立,例如 test -r file -a -x file,则file同时具有r与x权限时,才回传true
-o任何一个条件成立,例如tes -r file -o -x file,则file具有r或x权限时,就可回传true
取反,例如test ! -x file,当file不具有x权限时,回传true

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  现在我们就利用test来帮助我们写几个简单的例子。首先,判断一下,让用户输入一个文件名,我们判断:

  1) 这个文件是否存在,若不存在则给予一个“Filename does not exist”的信息,并中断程序

  2)若这个文件存在,则判断它是个文件或目录,结果输出“Filenamne is regular file”或“Filename is directory”

  3)判断一下,执行者的身份对这个文件或目录所拥有的权限,并输出权限数据。

[~]# vi sh05.sh
#!/bin/bash
# 1. 让用户输入文件名,并且判断用户是否真的有输入字符串
echo -e "Please input a filename, I will check the filename's type and permission. \n\n"
read -p "Input a filename : " filename
test -z $filename && echo "You must input a filename." && exit 0

# 2. 判断文件是否存在,若不存在则显示信息并结束脚本
test ! -e $filename && echo "The filename '$filename' DO NOT exist" && exit 0

#3. 开始判断文件类型与属性
test -f $filename  && filetype="regulare file"
test -d $filename && filetype="directory"
test -r $filename && perm="readable"
test -w $filename && perm="$perm writable"
test -x $filename && perm="$perm executable"

# 4. 开始输出信息
echo "The filename: $filename is a $filetype"
echo "And the permissiions are : $perm"

  需要注意的是,由于root在很多权限的限制上面都是无效的,所以使用root执行这个脚本时,常常会发现与ls -l观察到的结果并不相同。

1.4.3 利用判断符号[]

  其实,我们还可以利用判断符号[]来进行数据的判断!举例来说,如果我想要知道$HOME这个变量是否为空的,可以这样做:

[~]# [ -z "$HOME" ];echo $?

  使用中括号必须要特别注意,因为中括号用在很多地方,包括通配符与正则表达式等,多以如果要在bash的语法中使用中括号作为shell判断式时,

必须要注意中括号的两端有空格符来分隔,注意一下事项:

  1)在中括号[]内的每个组件都需要有空格键来分隔;

  2)在中括号内的变量,最好都以双引号括起来;

  3)在中括号内的变量,最好都以单或双引号括号起来;

  另外,中括号的使用方法与test 几乎一模一样。只是中括号比较常用在条件判断式if ... then ...fi的情况中就是了。

下面使用中括号的判断来做一个小案例:

  1)当执行一个程序的时候,这个程序会让用户选择Y或N;

  2)如果用户输入Y或y时,就显示“OK, continue”;

  3)如果用户输入N或n时,就显示“Oh, interrupt”;

  4)如果不是Y/y/N/n之内的其它字符,就显示“I don't know what your choice is”。

[~]# vi sh06.sh
#!/bin/bash
read -p "Please input (Y/N) : " yn
[ "$yn" == "Y" -o "$yn" == "y" ] && echo "OK, continue" && exit 0
[ "$yn" == "N" -o "$yn" == "n" ] && echo "Oh, interrupt!" && exit 0
echo "I don't know what your choice is" && exit 0
1.4.4 shell script的默认变量($0, $1 ...)

  shell 脚本是怎么带参数的呢?看下面的例子:

/path/to/script.sh opt1 opt2 opt3 opt4
        $0          $1    $2    $3    $4

  这样够清楚了吧?执行的脚本文件名为$0这个变量,第一个接的参数就是$1。所以,只要我们在脚本里面善用$1的话,就可以很简单的立即执行某些命令功能了。

除了这些数字的变量之外,还有一些较为特殊的变量可以在脚本内使用来调用这些参数。

  1)$#:代表后接的参数 个数;

  2)$@:代表 “$1“、”$2“、”$3”之意,每个变量都是独立的(用双引号括起来)

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

  $@和$*基本上还是有所不同。不过,一般使用情况下可以直接记忆$@即可。下面做个练习:

假设我们要执行一个可以携带参数的脚本,执行该脚本后屏幕会显示如下的数据:

  1)程序的文件名

  2)共有几个参数

  3)若参数的个数小于2则告知用户参数数量太少

  4)全部的参数内容

  5)第一个参数

  6)第二个参数

[~]# vi sh07.sh
#!/bin/bash

echo "The script name is   ==> $0"
echo "Total parmameter is ==> $#"

[ "$#" -lt 2 ] && echo "The number of parameter is less than 2. stop here." && exit 0

echo "Your whole parameter is ==> '$@'"
echo "Your 1st parameter is ==> $1"
echo "Your 2st parameter is ==> $2"
  • shift:参数变量号码偏移

  直接看例子:

[~]# vi sh08.sh
#!/bin/bash

echo "Total param ==> $#"
echo "param is ==> '$@'"

shift # 进行一次 一个变量的偏移

echo "Total param ==> $#"
echo "param is ==> '$@'"

shift 3 # 进行一次 三个变量的偏移

echo "Total param ==> $#"
echo "param is ==> '$@'"

1.5 条件判断式

1.5.1 利用if ... then
  • 单层、简单条件判断式

  如果只有一个判断式要进行,那么可以简单的这样看:

if [ 条件判断式 ]; then
    ....
fi

  至于条件判断式的判断方法,与前面讲的相同。较特别的是,如果有多个条件要判别时,除了sh06.sh那个案例所写的,也就是

将多个条件写入一个中括号中之外,还可以有多个中括号来隔开,而括号与括号之间,则以&& 或 ||隔开,他们的意义式:

  1)&& 代表 and

  2)|| 代表 or

  所以,在使用中括号的判断式中,&&与||就与命令执行的状态不同了。举例来说,sh06.sh里的判断式可以这样修改:

  [ "$yn" == "Y" -o "$yn" == "y"  ] 可替换为 [ "$yn" == "Y" ] || [ "$yn" == "y" ]

[~] # vi sh06-2.sh
#!/bin/bash
read -p "Please input (Y/N): " yn

if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
    echo "OK, continue"
    exit 0
fi

if [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
    echo "Oh, interrupt!"
    exit 0
fi

echo "I don't know what your choice is" && exit 0
  • 多重、复杂条件判断式
if [ 条件判断式 ]; then
    ...
else
    ...
fi

if [ 条件判断式1 ]; then
    ...
elif [ 条件判断式2 ]; then
    ...
else
    ...
fi

  我们可以将sh06-2.sh写成这样:

[~] # vi sh06-3.sh
#!/bin/bash
read -p "Please input (Y/N): " yn

if [ "$yn" == "Y" ] || [ "$yn" == "y" ]; then
    echo "OK, continue"
    exit 0
elif [ "$yn" == "N" ] || [ "$yn" == "n" ]; then
    echo "Oh, interrupt!"
    exit 0
else
    echo "I don't know what your choice is"
fi

  接下来玩个大点的例子,结合grep和netstat。我们可以利用 netstat -tuln来取得目前主机有启动的服务。

  重点是Local Address(本地主机的IP与端口对应)那个字段,它代表的是本机所启动的网络服务。几个常见的端口对应关系为:

  80:www

  22:ssh

  25:mail

  111:RPC

  631:CUPS(打印服务功能)

  假设我的主机有兴趣要检测的是比较常见的端口时,那如何通过netstat取检测主机是否有开启这四个主要的网络服务端端口呢?

[~] # vi sh10.sh
#!/bin/bash

# 1. 先做一些告知操作
echo "Now, I will detect your Linux server's services!"
echo -e "The www, ftp, ssh, and mail will be detect! \n"

# 2. 开始进行一些测试工作,并且输出信息
testing=$(netstat -tuln | grep ":80")    # 检测port 80在否
if [ "$testing" != "" ]; then
    echo "www is running in your system."
fi

testing=$(netstat -tuln | grep ":22")    # 检测port 22在否
if [ "$testing" != "" ]; then
    echo "SSH is running in your system."
fi

testing=$(netstat -tuln | grep ":21")    # 检测port 21在否
if [ "$testing" != "" ]; then
    echo "FTP is running in your system."
fi

testing=$(netstat -tuln | grep ":25")    # 检测port 25在否
if [ "$testing" != "" ]; then
    echo "MAIL is running in your system."
fi
1.5.2 利用case ... esac判断

  格式:

case $var in
    "第一个变量的内容")
        ...
        ;;
    "第二个变量的内容")
        ...
        ;;
    *)
        ...
        ;;
esac

  举个例子:

[~] # vi sh09.sh
#!/bin/bash

# read -p "Input your choice: " choice
# case $choice in
case $1 in "one") echo "Your choice is ONE" ;; "two") echo "your choice is TWO" ;; *) echo "Usage $0 {one|two}" ;; esac
1.5.3 利用function功能

  什么是函数(function)?简单的说,函数可以在脚本中做出一个类似自定义执行命令的东西,简化代码。

  function的语法如下:

function fname() {
    ....      
}        

  fname就是我们的自定义的执行命令名称。需要注意的是:因为脚本的执行方式是从上到下,从左到右的,所以在脚本当中function的设置一定要在

程序的最前面。看下面的例子,把sh09.sh脚本改成:

[~] # vi sh09-1.sh
#!/bin/bash

function printit(){
    echo -n "Your choice is "    # 加上-n可以不断行在同一行显示
}

case $1 in
    "one")
        printit; echo $1
        ;;
     "two")
        printit; echo $1
        ;;
    *)
        echo "Usage $0 {one|two}
        ;;
esac

  另外,function也是拥有内置变量的。它的内置变量和脚本很类似,函数名称用$0表示,后续的变量用$1, $2, ....等表示。举例:

[~] # vi sh09-2.sh
#!/bin/bash

function printit(){
    echo "Your choice is $1"
}

case $1 in
    "one")
        printit 1
        ;;
    "two")
        printit 2
        ;;
    *)
        echo "Usage $0 {one|two}"
        ;;
esac

1.6 循环(loop)

  除了if...then...fi这种条件判断式之外,循环可能是程序中最重要的一环。循环有一种依据判断式达成与否的不定循环,还有另一种

已经固定要跑多少次的固定循环。

1.6.1 while do done,until do dne(不定循环)

  一般来说,不定循环最常见的就是下面这两种状态了:

while [ condition ]
do
    ...
done

  当condition条件成立时,就进入循环体中,直到condition的条件不成立才停止的意思。还有另外一种不定循环的方式:

until [ condition ]
do 
    ...
done

  这种方式正好与while相反,当condition条件成立时,就终止循环,否则就持续进行循环。

下面举个例子,假设我们要用户输入yes或者YES才结束程序的执行,否则就一直让用户输入字符串。

[~] # vi sh13.sh
#!/bin/bash

while [ "$yn" != "yes" -a "$yn" != "YES" ]
do
    read -p "Please input yes/YES to stop this program: " yn
done

echo "OK! you input the correct answer."

  如果使用until,它的条件会变成这样:

[~] # vi sh13-2.sh
#!/bin/bash

until [ "$yn" == "yes -o "$yn" == "YES" ]
do
    read -p "Please input yes/YES to stop this program: " yn
done

echo "OK! you input the correct answer."

  如果我们要来计算1+2+3+...+100的值,利用循环是这样的:

[~] # vi sh14.sh
#!/bin/bash

s=0
i=0
while [ "$i" != "100" ]
do
    i=$(($i+1))
    s=$(($s+$i))
done
echo "The result of '1+2+3+...+100' is ==> $s"

  如果想让用户输入一个数字让程序有1+2+..直到用户输入的数字为止,该如何编写?

1.6.2 for...do...done (固定循环)

  相对于while,until的循环方式是必须要“符合某个条件”的状态,for这种语法则是“已经知道要进行几次循环”的状态,它的语法是:

for var in con1 con2 con3 ...
do
    ...
done

  以上面的例子来说,这个$var变量内容在循环工作室:

  1. 第一次循环时,$var的内容为con1;

  2. 第二次循环时,$var的内容为con2;

  3. 第三次循环时,$var的内容为con3;

  做一个简单的练习。假设有三种动物,分别是 dog cat pig三种,想每一行都输出这样:“There are dogs ...”之类的字样:

[~] # vi sh15.sh
#!/bin/bash

for animal in dog cat pig
do 
    echo "There are ${animal}s..."
done

  再举个复杂点的例子,由于系统上面的各种账号都是写在/etc/passwd内的第一个字段,能不能通过管道命令的cut找出单纯的账号名称后,

以id及finger分别检查用户的标识符与特殊参数呢?程序如下:

[~] # vi sh16.sh
#!/bin/bash

users=$(cut -d ':' -f1 /etc/passwd)
for username in $users
do
    id $username
    finger $username
done

  再举个例子,我想要利用ping这个可以判断网络状态的命令。来进行网络状态的实际检测时,我想要检测的域是本机所在的192.168.1.1~192.168.1.100

由于有100台主机,总不会要我在for后面舒服1到100吧?此时可以这样做

[~] # vi sh17.sh
#!/bin/bash

network="192.168.1"
for sitenu in $(seq 1 100)
do
    ping -c 1 -w 1 ${network}.${sitenu} &> /dev/null && result=0 || result=1
    if [ "$result" == 0 ]; then
        echo "Server ${network}.${sitenu} is UP."
    else
        echo "Serve ${network}.${sitenu} is DOWN."
    fi
done

  最后,让我们来玩判断式加上循环的功能!我想要让用户输入某个目录文件名,然后找出某目录内的文件名的权限:

[~] # vi sh18.sh
#!/bin/bash

# 1. 先看看目录是否存在
read -p "Please input a directory: " dir
if [ "$dir" == "" -o ! -d "$dir" ]; then
    echo "The $dir is NOT exist in your system."
    exit 1
fi

# 2. 开始测试文件
filelist=$(ls $dir)
for filename in $filelist
do
    perm=""
    test -r "$dir/$filename" && perm="$perm readable"
    test -w "$dir/$filename" && perm="$perm writeable"
    test -x "$dir/$filename" && perm="$perm executable"
    echo "The file $dir/${filename}'s permission is $perm"
done
1.6.3 for...do...done 的数值处理

  除了上述的方法之外,for循环还有另外一种写法!语法如下:

for ((初始值;限制值;执行步长))
do
    ...
done

  这种语法适合于数值方式的运算中,for后面的括号内的三串内容意义为:

  • 初始值:某个变量在循环当中的初始值,直接以类似i=1设置好;
  • 限制值:当变量的值在这个限制值的范围内,就继续循环,例如i<=100
  • 执行步长:每做一次循环时的变化量。例如 i=i+1
[~] # vi sh19.sh
#!/bin/bash

read -p "Please input a number, I will count for 1+2+3..+your input: " nu

s=0
for ((i=1; i<=$nu; i=i+1))
do
    s=$(($s+$i))
done
echo "The result of '1+2+3+...+$nu' is ==> $s"

2. 正则表达式

2.1 基础正则表达式

  正则表达式是处理字符串的一种表达方式,需要支持工具来辅助才行。这里我们就先介绍一个最简单的字符串选取功能的工具程序--grep。

介绍完grep的功能之后,就进入正则表达式的特殊字符的处理能力了。

  首先,先了解一些特殊的符号:

特殊符号代表意义
[:alnum:]代表英文大小写字符及数字,即0-9,A-Z,a-z
[:alpha:]代表任何英文大小写字符,即A-Z,a-z
[:blank:]代表空格键与[tab]键
[:cntrl:]代表键盘上面的控制按键,即包括CR,LF,Tab,Del等
[:digit:]代表数字而已,即0-9
[:graph:]除了空格符(空格键与[tab]键)外的其它所有按键
[:lower:]代表小写字符,即a-z
[:print:]代表任何可以被打印出来的字符
[:punct:]代表标点符号,即 " ' ? ! ; : # $
[:upper:]代表大写字符,即A-Z
[:space:]任何会产生空白的字符,包括空格键[tab]CR等
[:xdigit:]代表十六进制的数字类型,包括0-9, A-F, a-f

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2.1.1 grep的一些高级参数
[~] # grep [-A] [-B] [--color=auto] '搜索字符串' filename
参数:
-A:后面可加数字,为after的意思,除了列出该行外,后续的n行也列出来
-B:后面可加数字,为befer的意思,除了列出该行外,前面的n行也列出来
--color=auto:可将正确的那个选取数据列出颜色

范例一:用dmesg列出内核信息,再以grep找出内含eth的那行
[~] # dmesg | grep 'eth'
[~] # dmesg | grep -n --color=auto 'eth'
[~] # dmesg | grep -n -A3 -B2 --color=auto 'eth'

  grep最重要的功能就是进行字符串数据的对比,然后将符合用户需求的字符串打印出来。需要说明的是grep在数据中查找一个字符串时,是以整行为单位来

进行数据的选取的!也就是说,假如一个文件内有10行,其中有两行具有你所查找的字符串,则将那两行显示再屏幕上,其它的就丢弃了。

2.1.2 基础正则表达式练习

  要了解正则表达式最简单的方法就是由实际练习去感受。所以在归纳正则表达式特殊符号前,我们先以下面这个文件的内容来进行正则表达式的理解。

先说明一下,下面的练习大前提是:

  • 语系使用“export LANG=C"的设置值;
  • grep已经使用alias设置成为"grep --color=auto”

先下载文件:http://linux.vbird.org/linux_basic/0330regularex/regular_express.txt

1)例题一:查找特定字符串

  查找特定字符串很简单吧?假设我们要从刚才的文件当中取得the这个特定字符串,最简单的方式就是这样:

[~] # grep -n 'the' regular_express.txt

  那如果想要反响选择呢?也就是说,当该行没有’the‘这个字符串时才显示在屏幕上,那就直接使用:

[~] # grep -vn 'the' regular_express.txt

  接下来,如果想要取得不论大小写的'the'这个字符串,则:

[~] # grep -in 'the' regular_express.txt

2)例题二:利用中括号[]来查找集合字符

  如果我想要查找test或taste这两个单词时,可以发现,其实他们有共同的't?st'存在。这个时候我可以这样来查找:

[~] # grep -n 't[ae]st' regular_express.txt

  了解了吧?其实[]里面不论有几个字符,它都值代表着“一个”字符,所以,上面的例子说明了,需要的字符串时“tast“或”test“两个字符串而已!

而如果想要查找到所有oo的字符时,则使用:

[~] # grep -n 'oo' regular_express.txt

  但是,如果我不想要oo前面有g的话呢?此时,可以利用在集合字符的反响选择[^]来完成:

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

  再来,假设我oo前面不想要有小写字符,所以我可以这样写[^abcdef..z]oo,但是这样写太累了,可以简化成下面这样:

[~] # grep -n '[^a-z]oo' regular_express.txt

  也就是说,当我们在一组集合字符中,如果该字符组是连续的,例如大写英文/小写英文/数字等,就可以使用

[a-z],[A-Z],[0-9]等方式来书写。但由于考虑到语系对于编码顺序的影响,因此除了连续编码使用'”-“之外,也可以使用如下的方法来取得前面两个测试的结果:

[~] # grep -n '[^[:lower:]]oo' regular_express.txt

[~] # grep -n '[[:digit:]]' regular_express.txt

3)例题三:行首与行尾字符^$

  我们在例题一中,可以查询到一行字符串里面有the的,那如果我想要让the只在行首列出呢?这个时候就要使用制符表了,我们可以这样做:

[~] # gep -n '^the' regular_express.txt

  如果我想要开头是小写字符的那一行就列出呢?可以这样:

[~] # grep -n '^[a-z]' regular_express.txt

[~] # grep -n '^[[:lower:]]' regular_express.txt

  如果我不想要开头是英文字母,则可以这样:

[~] # grep -n '^[^a-zA-Z]' regular_express.txt

[~] #grep -n '^[^[:alpha:]]' regular_express.txt

  看到了吧,那个^符号在字符集符号(中括号[])之内与之外是不同的!在[]内代表”反向选择“,在[]之外则代表定位在行首的意义。

那如果我要找出行尾结束为小数点(.)的那一行,该如何处理?

[~] # grep -n '\.$' regular_express.txt

  小数点有其它含义,下面会介绍到,所以需要用"\"来进行转义。还有就是5-9行没显示的问题,这就要考虑到linux和windows的断行字符的问题

用 cat -An regular_express.txt | head -n 10 | tail -n 6查看一下。

  那怎么找出空白行呢?可以这样做:

[~] # grep -n '^$' regular_express.txt

  再来,假设你已经知道一个程序脚本或者配置文件中,空白行与开头为#的那一行是批注,那怎么将这些行屏蔽掉呢?

[~] # cat -n /etc/syslog.conf
# 在CentOS中,结果可以发现有空行和以#开头的行输出

[~] # grep -v '^$' /etc/syslog.conf | grep -v '^#'

4)例题四:任意一个字符.,与重复字符*

  .(小数点):代表一定有一个任意字符的意思;

  *(星号):代表重复前一个0到无穷次的意思,为组合形态。

  假设需要找出g??d的字符串,即共有四个字符,开头是g而结束是d,可以这样做:

[~] # grep -n 'g..d' regular_express.txt

  如果我想要列出有oo,ooo, oooo等的数据,也就是说,至少要有两个(含两个)o以上,该怎么做?是o*还是oo*还是ooo*呢?

因为*代表的是重复0个或者多个前面的RE字符的意思,因此, ”o*“代表的是具有空字符或一个o以上的字符,特别注意,因为允许空字符(就是有没有字符都可以),所以

grep -n 'o*' regular_express.txt 将会把所有的数据都打印出来

  如果是”oo*“呢?则第一个o肯定必须要存在,第二个o可有可无,所以是 o,oo,ooo等,都可以被列出来

  同理,当需要至少两个以上的o字符串时,就需要ooo*,即,

[~] # grep -n 'ooo*' regular_express.txt

  这样可以理解了把。现在出个练习,如果我想要字符串开头与结尾都是g,但是两个g之间仅能存在至少一个o,就是gog,goog, gooog等,该怎么做?

[~] # grep -n 'goo*g' regular_express.txt

  再来一题,如果想要找出g开头与g结尾的字符串,当中的字符可有可无,那该如何是好?是g*g吗?

  用g*g测试一下,结果会发现结果不是预期的那样,为什么? 因为g*g里面的g*代表空字符或一个以上的g再加上后面的g,因此,这个表达式的内容就是g,gg,

所以只要该行当中有一个以上的g就符合需求了。

  那我们该怎么做呢?可以利用任意一个字符”.“,即”g.*g“的做法。”.*”代表零个或过个任意字符的意思

[~] # grep -n 'g.*g' regular_express.txt

5)例题五:限定连续RE字符范围{}

  前面,我们可以利用.与*来这只0个到无限多个重复字符,那如果我想要限制一个范围区间内的重复字符数呢?举例来说,我想要找出2-5个o的连续字符串,

该怎么做?这个时候就要使用到限定范围的字符{}了,但因为{与}的符号在shell是有特殊意义的,因此,我们必须要使用转义字符\来让它失去特殊意义才行。

假设我要找到两个o的字符串,可以是:

[~] # grep -n 'o\{2\}' regular_express.txt

  这样看似乎和ooo*的字符没有什么区别啊?那么换个查找的字符串,假设我们要找出g后面接2到5个o,然后再接一个g的字符串,是这样:

[~] # grep -n 'go\{2,5\}g' regular_express.txt

  如果我想要2个以上的gooo...g呢? 除了可以是gooo*g,也可以这样:

[~] # grep -n 'go\{2,\}g' regular_express.txt
2.1.3 基础正则表达式字符

  经过上面的几个简单例题,我们可以将基础的正则表达式特殊字符归纳成表,如下:

RE字符意义与范例
^word

意义:待查找的字符串(word)在行首

范例:查找行首为#开始的那一行,并列出行号

grep -n '^#' regular_express.txt

word$

意义:待查找的字符串(word)在行尾

范例:将行尾为!的那一行打印出来,并列出行号

grep -n '!$' regular_express.txt

.

意义:代表一定有一个任意字符的字符

范例:查找的字符串可以是(eve)(eae)(eee)(ee)

grep -n 'e.e' regular_express.txt

\

意义:转义字符,将特殊符号的特殊意义去除

范例:查找含有单引号的那一行

grep -n \' regular_express.txt

*

意义:重复零个到无穷多个的前一个字符

范例:找出含有(es)(ess)等的字符串

grep -n 'ess*' regular_express.txt

[list]

意义:从字符集合的RE字符里面找出想要选取的字符

范例:查找含有(gl)(gd)的那一行

grep -n 'g[ld]' regular_express.txt

[n1-n2]

意义:从字符集合的RE字符里找出想要选取的字符范围

范例:找出含有任意数字的那一行

grep -n '[0-9]' regular_express.txt

[^list]意义:从字符集合的RE字符里找出不要的字符串或范围
\{n,m\}

意义:连续n到m个的前一个RE字符,若为\{n\}则是连续n个的前一个RE字符,若为\{n,\}

则是连续n个以上的前一个RE字符

范例:在g与g之间有2到3个的o存在的字符串

grep -n 'go\{2,3\}g' regular_express.txt

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  再次强调:正则表达式的特殊字符与一般在命令行输入命令的“通配符”并不相同,例如,在通配符当中的*代表的是零都无限多个字符的意思,

但是在正则表达式中,*则是重复0到无穷多个的前一个RE字符的意思,使用的意义并不相同,不要搞混了!

2.1.4 sed工具

  在了解了一些正则表达式的基础应用之后,有两个命令可以练习一下,就是sed与下面会介绍的awk了。这两个工具可是相当有用的。

  先来谈一谈sed,sed本身也是一个管道命令,可以分析standard input的,而且sed还可以将数据进行替换,删除,新增,选取特定行等功能

用法如下:

[~] # sed [-nefr] [动作]
参数:
-n:使用安静模式。在一般sed的用法中,所有来自STDIN
    的数据一般都会被列出到屏幕上。但如果加上-n参数后,则只有经过
    sed特殊处理的那一行(或操作)才会被列出来
-e:直接在命令模式上进行sed的动作编辑
-f:直接将sed的动作写在一个文件内,-f filename则可以执行filename内的sed动作
-r:sed的动作支持的是扩展型正则表达式的语法(默认是基础正则表达式语法)
-i:直接修改读取的内容,而不是有屏幕输出

动作说明:[n1[,n2]] function
n1,n2:不见得会存在,一般代表选择性动作的行数,举例来说,如果我的动作是需要在10到20行之间的,则“10,20[动作行为]”

function 有下面这些参数:
a:新增,a的后面可以接字符串,而这些字符串会在新的一行出现(目前的下一行)
c:替换,c的后面可以接字符串,这些字符串可以替换n1 n2之间的行
d:删除,因为是删除,所以d后面通常不接任何参数
i:插入,i的后面可以接字符串,而这些字符串会在新的一行出现(目前的上一行)
p:打印,也就是将某个选择的数据打印出来,通常p会与参数sed -n一起运行
s:替换,可以直接进行替换的工作。通常这个s的动作可以搭配
    正则表达式,例如1,20s/old/new/g就是

1)以行动单位的新增/删除功能

范例一:将 /etc/passwd的内容列出并且打印行号,同时,请将第2~5行删除

[~] # nl /etc/passwd | sed '2,5d'

  sed的动作为’2,5d',那个d就是删除。注意一下,原本应该是要执行sed -e才对,没有-e也行。同时也要注意的是

sed后面接的动作,必须以两个单引号括住

  如果题型变换一下,举例来说,如果只要删除第2行,可以使用  nl /ec/passwd | sed '2d'

  若是要删除第3行到最后一行,则是  nl /etc/passwd | sed '3,$d',那个$代表最后一行

范例二:承上例,在第二行后加上 drink tea

[~] # nl /etc/passwd | sed '2a drink tea'

  在a后面加上的字符串就已将出现在第二行后面。如果要在第二行前呢?  nl /etc/passed | sed '2i drink tea'就对了,

范例三:在第二行后面加入两行字,例如 "Drank tea or ..."与“drink beer?”

[~] # nl /etc/passwd | sed '2a Drink tea or ......\ 
>drink beer?'

  这个范例的重点是我们可以新增不只一行,可以新增好几行,但是每一行之间都必须要以反斜杠\ 来进行新行的增加。

2)以行动单位的替换与显示功能

范例四:将第2~5行的内容替换成为 “No 2-5 number”

[~] # nl /etc/passwd | sed '2,5c No 2-5 number'

  以前要列出11-20行,还要通过head -n 20 | tail -n 10 之类的方法来处理,sed可以直接使用下面的方法:

[~] # nl /etc/passwd | sed -n '5,7p'

3)部分数据的查找并替换的功能

  除了整行的处理模式之外,sed还可以用行为单位进行部分数据的查找并替换的功能,格式类似这样:

sed 's/要被替换的字符串/新的字符串/g'

  下面看一个例子:

# 1. 先查看源信息,用ifconfig查询IP

[~] # ifconfig eth0

# 2. 利用关键字配合grep选取出关键的一行数据
[~] # ifconfig eth0 | grep 'inet addr'

# 3. 将IP前面的部分删除
[~] # ifconfig eth0 | grep 'inet addr' | sed 's/^.*addr://g'

# 4. 将IP后面的部分删除
[~] # ifconfig eth0 | grep 'inet addr' | sed 's/^.*addr://g' | sed 's/Bcase.*$//g'

  做这种的思路就是一步一步的来,做一步查看一下,不对的就修改。

  再看一个例子:

# 1.使用grep将关键字MAN所在的行取出来
[~] # cat /etc/man.config | grep 'MAN'

# 2. 删除批注之后的数据
[~] # cat /etc/man.config | grep 'MAN' | sed 's/#.*$/g'

# 从上面可以看出来,原本批注的数据都变成空行,所以,接下来要删除空行
[~] # cat /etc/man.config | grep 'MAN' | sed 's/#.*$/g' | sed 's/^$/d'

4)直接修改文件内容(危险操作)

  使用-i选项可直接修改文件。

  例如想要将regular_express.txt文件中的每一行末尾的.换成!,可以这样 sed -i 's/\.$/\!/g' regular_express.txt

2.2 扩展正则表达式

  使用范围更广的扩展型正则表达式的表示方式会更方便。举个简单的例子,如果我们要去除空白行与行首为#的行,使用的是

  grep -v '^$' regular_express.txt | grep -v '^#'

  需要使用到管道命令查找两次,如果通过扩展型正则表达式,可以简化为:

  egrep -v '^$|^#' regular_express.txt

  熟悉正则表达式后,到这个扩展型的正则表达式,其实就是多几个重要的特殊符号。如下表所示:

RE字符意义与范例
+

意义:重复一个或一个以上的前一个RE字符

范例:查找(god)(g00d)(goood)等的字符串。那个o+代表一个以上的o,所有,下面的

执行结果会将第1,9,13行列出来。

egrep -n 'go+d' regular_express.txt

?

意义:零个或一个的前一个RE字符

范例:查找(gd)(dod)。

egrep -n 'go?d' regular_express.txt

|

意义:用或(or)的方式找出数个字符串

范例:查找gd或good这两个字符串,注意,是“或”,所以第1,9,14三行都可以被打印

出来。如果还想要找出dog呢?

egrep -n 'gd|good' regular_express.txt

egrep -n 'gd|good|dog' regular_express.txt

()

意义:找出“组”字符串

范例:查找(glad)或(good)这两个字符串,因为g与d是重复的,所以,我就可以将la与

oo列于()当中,并以|来分隔开来

egrep -n 'g(la|oo)d' regular_express.txt

()+

 意义:多个重复组的判别

范例:将“AxyzxyzxyzxyzC” 用echo显示,然后使用如下的方法查找一下!

echo 'AxyzxyzxyzxyzC' | egrep 'A(xyz)+C'

上面的例子意思是说,我要找开头是A结尾是C,中间有一个以上XYZ字符串的意思

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

2.3 awk:好用的数据处理工具

  awk也是一个非常棒的数据处理工具。相比于sed常常作用与一整行的处理,awk则比较倾向与将一行分成数个字段来处理。

因此,awk相当适合处理小型的数据。awk通常运行的模式是这样的:

  awk '条件类型 1{动作 1} 条件类型 2{动作 2} ...' filename

  awk 后面接两个单引号并加上大括号{}来设置想要对数据进行的处理动作。awk可以处理后续接的文件,也可以读取来自前个命令的输出。

awk 主要还是处理每一行的字段内的数据,而默认的字段的分隔符为空格键或[tab]键。举例来说,我们用last可以将登陆者的数据取出来。若想

要取出账号与登陆者IP,且账号与IP之间以[tab]隔开,则会变成这样:

last -n 5 | awk '{print $1 "\t" $3}

  上面是awk常用的动作。通过print的功能将字段数据列出来。字段的分隔则以空格键或[tab]按键来隔开。因为不论那一行我都要处理,因此,就

不需要有条件类型的限制。使用awk的时候,需要先确认一下数据源的格式,如果是连续性的数据,就不要有空格或[tab]在内,否则会发生误判。

  每行的每个字段都是有变量名称的,$1表示的是第一列以此类推。$0表示整行数据。那么awk怎么知道我到底这个数据有几行几列呢,这就需要

awk的内置变量帮忙了,如下表所示:

变量名称代表意义
NF每一行($0)拥有的字段总数
NR目前awk所处理的是第几行数据
FS目前的分隔字符,默认是空格

 

 

 

  

  继续上面last -n 5的例子来说明,如果我想要:

  • 列出每一行的账号(就是$1)
  • 列出目前处理的行数(就是awk内置的NR变量)
  • 且说明,该行有多少字段(就是awk内的NF变量)

则可以这样表示:

last -n 5 | awk '{print $1 "\t lines: " NR "\t columes: " NF}'

# 注意!在awk内的NR,NF等变量要用大写,且不需要$
  • awk的逻辑运算符

  既然有需要用到条件的类别,自然就需要一些逻辑运算。例如下面这些:

运算符代表意义
>大于
<小于
>=大于等于
<=小于等于
==等于
!=不等于

 

 

 

 

 

 

  举例说明,在/etc/passwd当中是以冒号来作为字段的分隔,该文件中第一字段为账号,第三字段则是UID。那么假设我要查阅,第三列小于10以下的数据

并且仅列出账号与第三列,那么可以这样做:

[~] # cat /etc/passwd | awk '{FS=":"} $3 < 10 {print $1 "\t" $3}'

  但是会发现第一行没有正确显示出来,这是因为我们读入第一行的时候,那些变量$1,$2,...默认还是以空格键为分隔的,所以虽然我们定义了FS=":",但是

却仅能在第二行后才开始生效。那怎么解决呢?我们可以预先设置awk的变量,利用BEGIN这个关键字,这样做:

[~] # cat /etc/passwd | awk 'BEGIN {FS=":"} $3 < 10 {print $1 "\t" $3}'

  除了BEGIN外,还有END。另外,如果用awk来进行“计算功能”呢?一下例子来看,假设我有一个薪资数据表文件名为pay.txt,内容是这样的:

Name    1st    2nd    3th
VBird    23000    24000    25000
DMTsai    21000    20000    23000
Bird2    43000    42000    41000

  如何帮我计算每个人的总额呢?而且我还想要格式化输出,可以这样考虑:

  1)第一行只是说明,所以第一行不要进行加总(NR==1时的处理)

  2)第二行以后就会有加总的情况出现(NR>=2以后处理)

[~] # cat pay.txt | awk 'NR==1 {printf "%10s %10s %10s %10s %10s \n",$1, $2, $3, $4, "Total"} \ 
NR>=2 {total=$2+$3+$4; printf "%10s %10s %10s %10s %10.2f\n", $1, $2, $3, $4, total}
'

  针对以上例子有几个重要事项应该要先说明:

  • 所有awk的动作,都是在{}内完成,如果需要多个命令辅助时,可以利用分号“;”间隔,或者直接以[Enter]按键来隔开每个命令
  • 逻辑运算中,等于的情况,一定要使用 ==
  • 格式化输出时,在printf的格式设置中,必须加上就\n才能分行
  • 与bash、shell的变量不同,在awk中,变量可以直接使用,不需要加上$

  awk也可以用if条件判断的。

 

转载于:https://www.cnblogs.com/wolf-thirteen/p/9750559.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值