Linux 一“文”搞定Shell编程

又到了毕业季,又得去找实习工作。。。

最近在某直聘软件上找实习工作,看到有许多工作都需要会Shell编程的,然后自己对Shell编程也是一知半解,所以趁着最后还有半个月在校时间,索性学习一下!


Shell是一个命令行解释器,它接收应用程序或用户命令,然后调用操作系统内核去实现操作!

Shell还是一个功能相当强大的编程语言,易编写、易调试、灵活性强。

Shell也可以说是一门脚本语言!

Shell 分为 bash 和 sh,他们两个从宏观来讲其实都是一个东西;


目录

一、Shell 脚本入门

1.1 Shell脚本的三种执行方式

二、变量

2.1 系统预定义变量

2.2 自定义变量

2.2.1 基本语法

2.2.2 变量定义规则

2.2.3 案例

2.3 特殊变量

2.3.1 $n

2.3.2 $#

2.3.3 $*

2.3.4 $@

2.3.5 $?

三、运算符

四、条件判断

4.1 基本语法

4.2 常用判断条件

        4.2.1 字符串之间的比较        

         4.2.2 整数之间的比较

        4.2.3 按照文件权限进行判断

       4.2.4 按照文件类型进行判断

4.3 多条件判断  &&   || 

五、流程控制

5.1 if 判断

5.1.1 单分支

5.1.2 多分支

5.1.3  (( )) 在 if 中的使用

5.2 case 语句

5.2.1 基本语法

5.2.2 案例

5.3 for 循环

5.3.1  for

5.3.2  for  in        (增强for循环,使用的更多一点)

5.3.3  $*  与  $@  的区别

5.4 while 循环

六、read 读取控制台输入

七、函数

7.1 系统函数

7.1.1 basename

7.1.2 dirname

7.2 自定义函数

7.2.1 基本语法

7.2.2 注意事项

7.2.3 案例

八、综合运用 - 实现文件归档

九、正则表达式入门

9.1 常规匹配

9.2 常用特殊字符

9.2.1    ^

9.2.2    $

9.2.3   .

9.2.4   *

9.2.5  字符区间(中括号):[ ]

9.2.6   \

9.3 练习,匹配手机号码

十、文本处理工具

10.1   cut

10.1.1 基本用法

10.1.2 选项参数说明

10.1.3 案例

10.2   gawk

10.2.1 基本用法

10.2.2 选项参数说明

10.2.3 案例

10.2.4  gawk 的内置变量

10.2.5 案例

十一、总结


一、Shell 脚本入门

Shell脚本一般都以  #!/bin/bash   开头(指定解析器)

且一般脚本文件默认都以  .sh  作为后缀(大家都是这样使用的)

#  是其注释符号;

如下,编写一个输出hello world!的脚本:

helloworld.sh 

#!/bin/bash

echo "hello world!"

1.1 Shell脚本的三种执行方式

1.1.1 bash 脚本相对路或绝对路径  或者  sh 脚本相对路或绝对路径

例如:

bash + 脚本的绝对路径

ygt@YGT:~/share/knowledge$ bash /home/ygt/share/knowledge/Shell/helloworld.sh
hello world!
ygt@YGT:~/share/knowledge$

bash + 脚本的相对路径

ygt@YGT:~/share/knowledge/Shell$ bash ./helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$

sh + 脚本的绝对路径

ygt@YGT:~/share/knowledge/Shell$ sh /home/ygt/share/knowledge/Shell/helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$

sh + 脚本的相对路径

ygt@YGT:~/share/knowledge/Shell$ sh ./helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$

1.1.2 直接输入脚本的绝对路径或者相对路径执行脚本(此方式脚本必须要有执行的权限'x')

赋予脚本执行的权限:chmod +x helloworld.sh

相对路径执行脚本:

ygt@YGT:~/share/knowledge/Shell$ ./helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$

绝对路径执行脚本:

ygt@YGT:~/share/knowledge/Shell$ /home/ygt/share/knowledge/Shell/helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$

注意:第一种方式执行脚本,本质是bash解析器帮你执行脚本,所以脚本本身不需要执行权限‘x’也可以执行;第二种方式执行脚本,本质是脚本自己去执行,所以需要执行权限'x'.

3. 使用 ‘.’ 或者 source

跟第一种方式类型

ygt@YGT:~/share/knowledge/Shell$ . helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ source helloworld.sh
hello world!
ygt@YGT:~/share/knowledge/Shell$

注意:第一种方式和第二种方式都是在当前Shell中打开一个子Shell来执行脚本内容,当脚本内容结束,则子Shell关闭,回到父Shell中;第三种方式是在当前Shell中执行脚本,而无需打开子Shell。

打开子Shell与不打开子Shell的区别在于,环境变量的继承关系,如在子Shell中设置的当前变量,父Shell是不可见的。


二、变量

变量分为局部变量和全局变量,局部变量即只能在本Shell中去使用,而全局变量可以在本Shell或者其子Shell中去使用。(系统预定义的变量是全局变量,自定义的是局部变量) 

2.1 系统预定义变量

常用的系统变量:$HOME、$PWD、$SHELL、$USER

可以使用echo 或者 printenv 将变量中的值打印出来

ygt@YGT:~/share/knowledge/Shell$ printenv HOME
/home/ygt
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ echo $HOME
/home/ygt
ygt@YGT:~/share/knowledge/Shell$

可以使用  set   查看当前Shell中的所有变量;

2.2 自定义变量

2.2.1 基本语法

  • 定义变量:变量名=变量值        注意,=号前后不能有空格
  • 撤销变量:unset  变量名
  • 声明静态变量(常量):readonly  变量名=变量值        注意,不能unset

2.2.2 变量定义规则

  • 变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写;
  • 等号两侧不能由空格;
  • 在bash中,变量默认类型都是字符串类型,无法直接进行数值运行;
  • 变量的值如果有空格,需要使用双引号或单引号括起来。

2.2.3 案例

        2.2.3.1 定义变量name

ygt@YGT:~/share/knowledge/Shell$ name=hello
ygt@YGT:~/share/knowledge/Shell$ echo $name
hello
ygt@YGT:~/share/knowledge/Shell$

        2.2.3.2 给变量name重新赋值

ygt@YGT:~/share/knowledge/Shell$ name=world
ygt@YGT:~/share/knowledge/Shell$ echo $name
world
ygt@YGT:~/share/knowledge/Shell$

        2.2.3.3 撤销变量name

ygt@YGT:~/share/knowledge/Shell$ unset name
ygt@YGT:~/share/knowledge/Shell$ echo $name

ygt@YGT:~/share/knowledge/Shell$

        2.2.3.4 声明静态变量(常量)age,静态变量不能修改,也不能撤销unset

ygt@YGT:~/share/knowledge/Shell$ readonly age=24
ygt@YGT:~/share/knowledge/Shell$ echo $age
24
ygt@YGT:~/share/knowledge/Shell$ age=25
-bash: age: 只读变量
ygt@YGT:~/share/knowledge/Shell$ unset age
-bash: unset: age: 无法取消设定: 只读 variable
ygt@YGT:~/share/knowledge/Shell$

        2.2.3.5 在bash中,变量默认类型都是字符串类型,无法进行直接数值运算

ygt@YGT:~/share/knowledge/Shell$ sum=1+2
ygt@YGT:~/share/knowledge/Shell$ echo $sum
1+2
ygt@YGT:~/share/knowledge/Shell$

        2.2.3.6 变量的值如果有空格,需要使用双引号或者单引号括起来

ygt@YGT:~/share/knowledge/Shell$ name="hello world!"
ygt@YGT:~/share/knowledge/Shell$ echo $name
hello world!
ygt@YGT:~/share/knowledge/Shell$

注意,以上创建的都是局部变量,只能在本Shell中进行使用!

        2.2.3.7 把局部变量提升为全局变量,可供其他Shell程序使用

                export  变量名

ygt@YGT:~/share/knowledge/Shell$ export sum

                在helloworld.sh文件中输出sum的值

ygt@YGT:~/share/knowledge/Shell$ vim helloworld.sh


#!/bin/bash
echo "hello world!"
echo $sum


ygt@YGT:~/share/knowledge/Shell$ ./helloworld.sh
hello world!
1+2

注意,在子Shell中对父Shell的全局变量做修改,只能在子Shell中看到效果,而无法影响到父Shell;

例如,在helloworld.sh文件中修改sum的值,然后输出查看效果,执行helloworld.sh脚本,然后再执行echo $sum 查看sum的值

#!/bin/bash

echo "hello world!"
echo $sum
sum=100
echo $sum
ygt@YGT:~/share/knowledge/Shell$ ./helloworld.sh
hello world!
1+2
100
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ echo $sum
1+2
ygt@YGT:~/share/knowledge/Shell$

由此可见,sum的值没有被修改,还是1+2

但如果使用  '.'  或者 source 去执行脚本的话,就可以正常修改!

ygt@YGT:~/share/knowledge/Shell$ . helloworld.sh
hello world!
1+2
100
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ echo $sum
100
ygt@YGT:~/share/knowledge/Shell$

如果想要实现两数相加的话,可以使用括号( )括起来或者中括号括[ ]起来!

ygt@YGT:~/share/knowledge/Shell$ age=$((1+2))
ygt@YGT:~/share/knowledge/Shell$ echo $age
3
ygt@YGT:~/share/knowledge/Shell$ age=$[2+3]
ygt@YGT:~/share/knowledge/Shell$ echo $age
5
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ a=10
ygt@YGT:~/share/knowledge/Shell$ b=20
ygt@YGT:~/share/knowledge/Shell$ age=$[a+b]
ygt@YGT:~/share/knowledge/Shell$ echo $age
30
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ age=$[a+10]
ygt@YGT:~/share/knowledge/Shell$ echo $age
20
ygt@YGT:~/share/knowledge/Shell$

2.3 特殊变量

a = 10 

注意,使用单引号 ' ' 将 变量 $a包起来,Shell会将其解析成字符串$a;如果使用双引号 " " 将变量$a包起来,Shell会将其解析成变量$a,即会获取到10这个值。

2.3.1 $n

        $n (功能描述:n 为数字,$0 代表该脚本名称,$1-$9 代表第一到第九个参数,十以上的参数需要用大括号包含,如${10},${11}等)

例:

编写脚本,内容如下:

#!/bin/bash

echo '以下为$n的用法'
echo "文件名:$0"
echo "第一个参数:$1"
echo "第二个参数:$2"
echo "第三个参数:$3"
ygt@YGT:~/share/knowledge/Shell$ ./special.sh one 二 3
以下为$n的用法
文件名:./special.sh
第一个参数:one
第二个参数:二
第三个参数:3
ygt@YGT:~/share/knowledge/Shell$

2.3.2 $#

$# (功能描述:获取所有输入参数个数,常用于循环,判断参数的个数是否正确以及加强脚本的健壮性)

例:

编写脚本,内容如下:

#!/bin/bash

echo "参数个数:$#"
ygt@YGT:~/share/knowledge/Shell$ ./special.sh one 二 3
参数个数:3
ygt@YGT:~/share/knowledge/Shell$

2.3.3 $*

$* (功能描述:这个变量代表命令行中所有的参数,$*把所有的参数看成一个整体

例:

编写脚本,内容如下:

#!/bin/bash

echo '输出$*:'
echo $*
ygt@YGT:~/share/knowledge/Shell$ ./special.sh one 二 3
输出$*:
one 二 3
ygt@YGT:~/share/knowledge/Shell$

2.3.4 $@

$@ (功能描述:这个变量代表命令行中所有的参数,不过$@把每个参数区分对待

即$@获取到的是存储所有参数的列表,或者说是存储所有参数的数组,可以使用for循环遍历一个一个将其获取出来(后面会讲到如何进行遍历)

例:

编写脚本,内容如下:

#!/bin/bash

echo '输出$@'
echo $@
ygt@YGT:~/share/knowledge/Shell$ ./special.sh one 二 3
输出$@
one 二 3
ygt@YGT:~/share/knowledge/Shell$

2.3.5 $?

$? (功能描述:最后一次执行的命令的返回状态。如果这个变量的值为0,证明上一个命令正确执行;如果这个变量的值为非 0(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了)

例:

判断helloworld.sh脚本是否执行成功 

root@YGT:/home/ygt/share/knowledge/Shell# ./helloworld.sh
hello world!

100
root@YGT:/home/ygt/share/knowledge/Shell# echo $?
0
root@YGT:/home/ygt/share/knowledge/Shell#
root@YGT:/home/ygt/share/knowledge/Shell# ./helloworld1.sh
bash: ./helloworld1.sh: 没有那个文件或目录
root@YGT:/home/ygt/share/knowledge/Shell# echo $?
127
root@YGT:/home/ygt/share/knowledge/Shell#

三、运算符

通过上面我们知道,Shell是无法直接进行运算的,例如 a=1+2  输出遍历a的结果就是1+2;

Shell有两种方式可以进行运行:“$((运算式))” 或 “$[运算式]”

例: 

root@YGT:/home/ygt/share/knowledge/Shell# a=$((10 + 20))
root@YGT:/home/ygt/share/knowledge/Shell# echo $a
30
root@YGT:/home/ygt/share/knowledge/Shell# a=$[10*20]
root@YGT:/home/ygt/share/knowledge/Shell# echo $a
200
root@YGT:/home/ygt/share/knowledge/Shell#
root@YGT:/home/ygt/share/knowledge/Shell# a=10
root@YGT:/home/ygt/share/knowledge/Shell# b=20
root@YGT:/home/ygt/share/knowledge/Shell#
root@YGT:/home/ygt/share/knowledge/Shell# c=$(($a + $b))
root@YGT:/home/ygt/share/knowledge/Shell# echo $c
30
root@YGT:/home/ygt/share/knowledge/Shell# a=$[$b + $c]
root@YGT:/home/ygt/share/knowledge/Shell# echo $a
50
root@YGT:/home/ygt/share/knowledge/Shell#

或者 c=$((a + b))     a=$[b + c]    这样也是可以的!

案例,新建add.sh文件,实现参数输入两个数,相加后输出

#!/bin/bash

sum=$[$1+$2]

echo sum=$sum
root@YGT:/home/ygt/share/knowledge/Shell# ./add.sh 10 20
sum=30
root@YGT:/home/ygt/share/knowledge/Shell#

四、条件判断

4.1 基本语法

        (1). test  条件        

        (2). [  条件  ]        (注意  条件  前后要有空格)

        比较完成后,使用 $? 查看结果,0 表示匹配(真),1 表示不匹配(假)

4.2 常用判断条件

        4.2.1 字符串之间的比较        

                (注:如果是字符串之间的比较 ,用等号 “=” 判断相等;用 “!=” 判断不等。)

                4.2.1.1  =  等于

ygt@YGT:~/share/knowledge/Shell$ a=hello
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ test $a = hello    # 注意,等号两边需要有空格
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$ test $a = Hello    # 注意,等号两边需要有空格
ygt@YGT:~/share/knowledge/Shell$ echo $?
1
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ a=hello
ygt@YGT:~/share/knowledge/Shell$ b=world
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ [ $a = $b ]    # 注意,[ ] 内部也需要有空格与条件分割
ygt@YGT:~/share/knowledge/Shell$ echo $?
1
ygt@YGT:~/share/knowledge/Shell$ b=hello
ygt@YGT:~/share/knowledge/Shell$ [ $a = $b ]    # 注意,[ ] 内部也需要有空格与条件分割
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$

        注意,如果等号两边没有空格,那么Shell会将其解析成一个值,所以结果会是0(真);当条件中什么也没有的时候,结果就为1(假) ,如下:

ygt@YGT:~/share/knowledge/Shell$ a=hello
ygt@YGT:~/share/knowledge/Shell$ test $a=Hello
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ a=hello
ygt@YGT:~/share/knowledge/Shell$ [ $a=hello ]
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ [ sdfjogwoegjowg ]
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ [ ]
ygt@YGT:~/share/knowledge/Shell$ echo $?
1
ygt@YGT:~/share/knowledge/Shell$

               4.2.1.2  !=  不等于

ygt@YGT:~/share/knowledge/Shell$ a=hello
ygt@YGT:~/share/knowledge/Shell$ [ $a != hello ]
ygt@YGT:~/share/knowledge/Shell$ echo $?
1
ygt@YGT:~/share/knowledge/Shell$ [ $a != Hello ]
ygt@YGT:~/share/knowledge/Shell$ echo $?
0
ygt@YGT:~/share/knowledge/Shell$

         4.2.2 整数之间的比较

-eq 等于(equal)
-ne 不等于(not equal)
-lt 小于(less than)
-le 小于等于(less equal)
-gt 大于(greater than)
-ge 大于等于(greater equal)
ygt@YGT:~$ a=10
ygt@YGT:~$ b=20
ygt@YGT:~$ [ $a -eq $b ]        # 判断10是否等于20
ygt@YGT:~$ echo $?
1
ygt@YGT:~$ [ $a -lt $b ]        # 判断10是否小于20
ygt@YGT:~$ echo $?
0
ygt@YGT:~$ [ 10 -ge 10 ]        # 判断10是否大于等于10
ygt@YGT:~$ echo $?
0
ygt@YGT:~$

        4.2.3 按照文件权限进行判断

-r 判断文件是否有读的权限(read)
-w 判断文件是否有写的权限(write)
-x 判断文件是否有执行的权限(execute)
ygt@YGT:~$ touch test.txt
ygt@YGT:~$ ll
总用量 1
drwxr-xr-x 9 ygt  ygt      4096  6月  5 23:07 ./
drwxr-xr-x 3 root root     4096 11月 16  2022 ../
-rw-rw-r-- 1 ygt  ygt         0  6月  5 23:07 test.txt
ygt@YGT:~$
ygt@YGT:~$ [ -r test.txt ]        # 判断test.txt文件是否有读的权限
ygt@YGT:~$ echo $?
0
ygt@YGT:~$ [ -x test.txt ]        # 判断test.txt文件是否有执行的权限
ygt@YGT:~$ echo $?
1
ygt@YGT:~$

       4.2.4 按照文件类型进行判断

-e 判断文件是否存在(existence)
-f 判断文件是否存在并且是一个常规的文件(file)
-d 判断文件是否存在并且是一个目录(directory)
ygt@YGT:~$ ll
总用量 2
drwxr-xr-x 10 ygt  ygt      4096  6月  5 23:17 ./
drwxr-xr-x  3 root root     4096 11月 16  2022 ../
drwxrwxr-x  2 ygt  ygt      4096  6月  5 23:17 test/
-rw-rw-r--  1 ygt  ygt         0  6月  5 23:07 test.txt
ygt@YGT:~$
ygt@YGT:~$ [ -e test.txt ]
ygt@YGT:~$ echo $?
0
ygt@YGT:~$ [ -e test ]
ygt@YGT:~$ echo $?
0
ygt@YGT:~$ [ -f test ]
ygt@YGT:~$ echo $?
1
ygt@YGT:~$ [ -d test ]
ygt@YGT:~$ echo $?
0
ygt@YGT:~$ [ -f test.txt ]
ygt@YGT:~$ echo $?
0
ygt@YGT:~$

4.3 多条件判断  &&   || 

多条件判断(&& 表示前一条命令执行成功时,才执行后一条命令;|| 表示上一条命令执行失败后,才执行下一条命令)

也可以用于短路执行!

ygt@YGT:~$ a=10
ygt@YGT:~$ [ $a -gt 5 ] && echo "$a > 5"
10 > 5
ygt@YGT:~$ [ $a -lt 5 ] || echo "$a 不小于 5"
10 不小于 5
ygt@YGT:~$
ygt@YGT:~$ [ $a -lt 20 ] && echo "$a < 20" || echo "$a >= 20"
10 < 20
ygt@YGT:~$
ygt@YGT:~$ a=30
ygt@YGT:~$ [ $a -lt 20 ] && echo "$a < 20" || echo "$a >= 20"
30 >= 20
ygt@YGT:~$

也可以用于命令当中!

ygt@YGT:~$ ls || ll
5       test          test.txt
ygt@YGT:~$

ls 命令执行成功后,其后面的 ll 命令就不会执行了!


五、流程控制

流程控制在任何程序都是非常重要的,例如有if、case、for、while;

5.1 if 判断

5.1.1 单分支

if [ 条件判断式 ]; then    # 注意,这里的 ; 是有的
程序
fi

或者

if [ 条件判断式 ]
then
程序
fi

新建文件写入以下内容: (传入一个整数,判断其是否小于10)

#!/bin/bash

if [ $1 -lt 10 ]; then
        echo "$1 < 10"
fi

if [ "$2"x = "abc"x ]    # 特殊用法,即使没有参数2,也可以正常运行
then
        echo "相等"
fi
ygt@YGT:~/share/knowledge/Shell$ ./process.sh 5
5 < 10

当然,还可以使用 && 或者 || 进行多条件就判断;

ygt@YGT:~/share/knowledge/Shell$ a=20
ygt@YGT:~/share/knowledge/Shell$ if [ $a -gt 18 ] && [ $a -lt 40 ]; then echo OK; fi
OK

&& 和 || 可以使用 -a-o 替换,然后条件写在一个 [ ] 内即可;(-a 等于 and    -o 等于 or) 

ygt@YGT:~/share/knowledge/Shell$ a=20
ygt@YGT:~/share/knowledge/Shell$ if [ $a -gt 18 -a $a -lt 40 ]; then echo OK; fi
OK

5.1.2 多分支

if [ 条件判断式 ]; then
程序
elif [ 条件判断式 ]; then
程序
else
程序
fi

新建文件写入以下内容: 

#!/bin/bash

if [ $1 -lt 18 ]; then
	echo "未成年"
else
	echo "已成年"
fi


if [ $2 -ge 0 -a $2 -lt 60 ]; then
	echo "不及格"
elif [ $2 -ge 60 -a $2 -le 90 ]; then
	echo "及格"
elif [ $2 -gt 90 -a $2 -le 100 ]; then
	echo "优秀"
else
	echo  "成绩有误"
fi
[centos7@localhost Shell]$ ./process.sh 24 70
已成年
及格
[centos7@localhost Shell]$ ./process.sh 16 -1
未成年
成绩有误
[centos7@localhost Shell]$ 

5.1.3  (( )) 在 if 中的使用

使用 (( )) 即可在括号里面使用类似C语言的用法;当然,一般不推荐这样去使用!

ygt@YGT:~/share/knowledge/Shell$ a=10
ygt@YGT:~/share/knowledge/Shell$ if (( $a > 20 )); then echo OK; else echo notOK; fi
notOK

5.2 case 语句

5.2.1 基本语法

case $变量名 in
"值 1")
如果变量的值等于值 1,则执行程序 1
;;
"值 2")
如果变量的值等于值 2,则执行程序 2
;;
*)
如果变量的值都不是以上的值,则执行此程序(defualt)
;;
esac

注意事项:

  • case 行尾必须为单词 “in” ,每一个模式匹配必须以右括号 “)” 结束;
  • 双分号 “;;” 表示命令序列结束,相当于 C/C++ 中的 break;
  • 最后的 “*)” 表示默认模式,相当于 C/C++ 中的 default;

主要注意以上语法不同,其他用法和C/C++的switch case用法一致。

5.2.2 案例

新建文件写入以下内容: 

#!/bin/bash

case $1 in
"1")
	echo "one"
;;
"2")	
    echo "two"
;;
"3")	
    echo "three"
;;
*)
	echo "other"
;;
esac
[centos7@localhost Shell]$ ./process_case.sh 2
two

5.3 for 循环

for 循环有两种方式,for 和 for  in

5.3.1  for

 使用 (( )) 即可在括号里面使用类似C语言的用法;for 循环可以这样去使用!

for (( 初始值; 循环控制条件; 变量变化 ))
do
程序
done

新建文件写入以下内容:  (计算1+...+n)

#!/bin/bash

sum=0

for (( i=1; i <= $1; i++ )); do
        sum=$[ $sum + $i ]
        #sum=$[ sum + i ]    # 貌似这样写也是可以的
        #sum=$((sum + i))    # 貌似这样写也是可以的  
        #let sum=sum+i 
done

echo "sum = $sum"

注意,随着Linux Shell的更新,可以使用  let  关键字去执行现在高级语言的用法

例如:let sum=sum+i          let sum++        等等

ygt@YGT:~/share/knowledge/Shell$ ./sum.sh 10
sum = 55

5.3.2  for  in        (增强for循环,使用的更多一点)

for 变量 in 值 1 值 2 值 3…
do
程序    # 变量 的取值为 in 后面的值
done
#!/bin/bash

for os in Linux Windows Macos; do
        echo $os;
done
ygt@YGT:~/share/knowledge/Shell$ ./sum.sh
Linux
Windows
Macos
ygt@YGT:~/share/knowledge/Shell$

当然,in 后面还可以给他一个集合,使用 { .. } 包起来,例如:

ygt@YGT:~/share/knowledge/Shell$ for i in {1..100}; do sum=$[ $sum + $i ]; done; echo "sum = $sum"
sum = 5050
ygt@YGT:~/share/knowledge/Shell$

5.3.3  $*  与  $@  的区别

$*和$@都表示传递给函数或脚本的所有参数,不被双引号“”包含时,都以$1 $2 …$n的形式输出所有参数。 

 $*:将参数当作一个整体;

$@:将参数当作一个一个独立的个体

新建文件输入以下内容:

(如果代码中 $* 和 $@ 不用双引号括起来,他们实现的效果是一样的,就没法看出区别了)

#!/bin/bash

echo '$*:'
for pro in "$*"; do     # 注意这里$*一定要用双引号括起来,就可以看出其将参数看作是一个整体
        echo $pro
done

echo '$@:'
for pro in "$@"; do     # 注意这里$@一定要用双引号括起来,就可以看出其将参数看作是分开的独立个体
        echo $pro
done
ygt@YGT:~/share/knowledge/Shell$ ./process_for.sh 1 2 3 a b c
$*:
1 2 3 a b c
$@:
1
2
3
a
b
c
ygt@YGT:~/share/knowledge/Shell$

$* 将参数看成了整体的一个字符串,而$@ 却将参数看出独立的个体

5.4 while 循环

while [ 条件判断式 ]
do
程序
done
#!/bin/bash

i=1
sum=0

#while (( i <= $1 )); do        # 也可以使用 (( )) 替代[ ]
while [ $i -le $1 ]; do
        sum=$[ $sum + $i ]
        i=$[ $i + 1 ]
        #let sum=sum+i
        #let i++
done

echo "sum = $sum"

(可以使用 let 关键字修饰执行高级语言的用法!) 

代码中,(( )) 和 [ ] 之间都可以互换使用!

一般while都是使用 [ ]

ygt@YGT:~/share/knowledge/Shell$ ./process_while.sh 100
sum = 5050

六、read 读取控制台输入

read (选项) (参数)

①选项:

        -p:指定读取值时的提示符;

        -t:指定读取值时等待的时间(秒)如果 -t 不加表示一直等待

                (加了 -t 时间结束后,会结束read操作,继续往下执行)

②参数

        变量:指定读取值的变量名

新建文件输入以下内容: 

#!/bin/bash

read -t 7 -p "请输入您的名字:" name

#if [ "$name" == "" ]; then
if [ ! $name ]; then
        echo "Name is NULL"
else
        echo "Your name is $name"
fi
ygt@YGT:~/share/knowledge/Shell$ ./process_read.sh
请输入您的名字:李华
Your name is 李华
ygt@YGT:~/share/knowledge/Shell$ ./process_read.sh
请输入您的名字:Name is NULL
ygt@YGT:~/share/knowledge/Shell$

七、函数

小案例,拼接文件名(date函数),格式为:文件名_log_时间戳

#!/bin/bash

filename="$1"_log_$(date +%s)
echo $filename

date 是一个系统函数, +%s 是给其传入的参数,从而获取时间戳返回 

ygt@YGT:~/share/knowledge/Shell$ ./date.sh test
test_log_1686280637
ygt@YGT:~/share/knowledge/Shell$

系统函数的调用方式:$(系统函数)         又名:命令替换

7.1 系统函数

这里介绍两个比较常用的系统函数,basename 和 dirname;

7.1.1 basename

        7.1.1.1 基本语法

                basename [string / pathname] [suffix]

                (功能描述:basename 命令会删掉所有的前缀包括最后一个(‘/’)字符,然后将字符串显示出来。

                basename 可以理解为取路径里的文件名称

                选项: suffix 为后缀,如果 suffix 被指定了,basename 会将 pathname 或string 中的suffix 去掉。

注意,basename只能截取带有 /  的字符串!

        7.1.1.2 案例

ygt@YGT:~/share/knowledge/Shell$ basename /home/ygt/share/knowledge/Shell/date.sh
date.sh
ygt@YGT:~/share/knowledge/Shell$ basename /home/ygt/share/knowledge/Shell/date.sh .sh
date
ygt@YGT:~/share/knowledge/Shell$ basename ./date.sh
date.sh
ygt@YGT:~/share/knowledge/Shell$ basename /abcsdf/asdgwr/erg43
erg43

7.1.2 dirname

        7.1.2.1 基本语法

                dirname 文件绝对路径

                 (功能描述:从给定的包含绝对路径的文件名中去除文件名(非目录的部分),然后返回剩下的路径(目录的部分))

                 dirname 可以理解为取文件路径的绝对路径名称

        7.1.2.2 案例

ygt@YGT:~/share/knowledge/Shell$ dirname /home/ygt/share/knowledge/Shell/date.sh
/home/ygt/share/knowledge/Shell
ygt@YGT:~/share/knowledge/Shell$ dirname ./date.sh
.
ygt@YGT:~/share/knowledge/Shell$ dirname /sadfsg/gwgwrga/agwgg
/sadfsg/gwgwrga

还有一个更好玩的玩法,在任何路径调用脚本,都输出脚本所在的全路径:

#!/bin/bash

echo $(cd $(dirname $0); pwd)
ygt@YGT:~/share/knowledge/Shell$ ./date.sh
/home/ygt/share/knowledge/Shell

ygt@YGT:~/share/knowledge/Shell$ cd /
ygt@YGT:/$ /home/ygt/share/knowledge/Shell/date.sh
/home/ygt/share/knowledge/Shell

ygt@YGT:/$ cd /home/ygt/share/knowledge/shared_bike/src/
ygt@YGT:~/share/knowledge/shared_bike/src$ ../../Shell/date.sh
/home/ygt/share/knowledge/Shell

7.2 自定义函数

7.2.1 基本语法

[ function ] funname [()]
{
    Action;
    [return int;]
}

7.2.2 注意事项

  • 必须在调用函数地方之前,先声明函数,shell 脚本是逐行运行。不会像其它语言一样先编译;
  • 函数返回值,只能通过$?系统变量获得,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。return 后跟数值 n(0-255);
  • 因为通过$?获取到的返回值只能是0-255之间;所以可以使用echo将数据输出,然后使用$()去调用函数,即可获得echo输出的结果,即获得函数的返回值;
  • 定义函数时无需指定参数,调用函数时直接传参,在函数内部使用$1、$2...$n去获取参数值! 

7.2.3 案例

新建文件输入以下内容:

#!/bin/bash

function add() {
        s=$[ $1 + $2 ]
        return $s       # 注意,只能返回0-255之间的数
}

read -p "请输入第一个整数:" a
read -p "请输入第二个整数:" b

add $a $b
echo "$a + $b = $?"
ygt@YGT:~/share/knowledge/Shell$ ./func.sh
请输入第一个整数:10
请输入第二个整数:20
10 + 20 = 30
ygt@YGT:~/share/knowledge/Shell$ ./func.sh
请输入第一个整数:100
请输入第二个整数:200
100 + 200 = 44
ygt@YGT:~/share/knowledge/Shell$

因为 $? 只能获取值在0-255之间,所以当超过时,就返回不了正确的值了;

为了解决这样的问题,可以使用 echo 将数值输出,然后使用$()调用函数去接收;如下:

#!/bin/bash

function add() {
        s=$[ $1 + $2 ]    # s=$(( $1 + $2 ))
        echo $s
}

read -p "请输入第一个整数:" a
read -p "请输入第二个整数:" b

sum=$( add $a $b )
echo "$a + $b = $sum"
ygt@YGT:~/share/knowledge/Shell$ ./func.sh
请输入第一个整数:100
请输入第二个整数:200
100 + 200 = 300

注意,如果函数中有多个echo输出,在调用函数的地方只能获取到最后一个echo输出的值!


八、综合运用 - 实现文件归档

这是一个对以上前七章的知识点的综合运用,实现对文件归档操作!

实际生产应用中,往往需要对重要数据进行归档备份;

需求:实现一个每天对指定目录归档备份的脚本,输入一个目录名称(末尾不带/),将目录下所有文件按天归档保存,并将归档日期附加在归档文件名上,放在/hong/ygt 下;

这里用到了归档命令:tar

后面可以加上-c 选项表示归档,加上-z 选项表示同时进行压缩,得到的文件后缀名为.tar.gz。

脚本实现如下:

#!/bin/bash

# 首先判断输入的参数个数是否为1
if [ $# -ne 1 ]; then
        echo "参数个数错误!应输入一个参数,作为归档目录名"
        exit
fi

# 从参数中获取目录名称
if [ -d $1 ]; then
        echo
else
        echo
        echo "目录不存在!"
        echo
        exit
fi

DIR_NAME=$(basename $1)
DIR_PATH=$(cd $(dirname $1); pwd)

# 获取当前日期
DATE=$(date +%y%m%d)

# 定义生成的归档文件名
FILE=Shell_${DIR_NAME}_${DATE}.tar.gz
DEST=/home/ygt/$FILE

# 开始归档目录文件
echo "开始归档..."
echo

tar -zcf $DEST $DIR_PATH/$DIR_NAME

if [ $? -eq 0 ]; then
        echo
        echo "归档成功!"
        echo "归档文件为:$DEST"
        echo
else
        echo "归档失败!"
        echo
fi


九、正则表达式入门

正则表达式使用单个字符串来描述、匹配一系列符合某个语法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些符合某个模式的文本。在Linux 中,grep,sed,awk 等文本处理工具都支持通过正则表达式进行模式匹配。

9.1 常规匹配

一串不包含特殊字符的正则表达式匹配它自己,例如:

ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep DATE
DATE=$(date +%y%m%d)
FILE=Shell_${DIR_NAME}_${DATE}.tar.gz

就可以匹配到包含DATE的行!

9.2 常用特殊字符

9.2.1    ^

^  匹配一行的开头,例如:

ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep ^i
if [ $# -ne 1 ]; then
if [ -d $1 ]; then
if [ $? -eq 0 ]; then
ygt@YGT:~/share/knowledge/Shell$

9.2.2    $

$  匹配一行的结束,例如:

ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep gz$
FILE=Shell_${DIR_NAME}_${DATE}.tar.gz
ygt@YGT:~/share/knowledge/Shell$

9.2.3   .

.  匹配一个任意的字符,例如:

ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep h.me
DEST=/home/ygt/$FILE
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep h...
if [ $# -ne 1 ]; then
        echo "参数个数错误!应输入一个参数,作为归档目录名"
        echo "目录不存在!"
FILE=Shell_${DIR_NAME}_${DATE}.tar.gz
DEST=/home/ygt/$FILE
echo "开始归档..."
        echo "归档成功!"
        echo "归档文件为:$DEST"
        echo "归档失败!"
ygt@YGT:~/share/knowledge/Shell$

9.2.4   *

* 不单独使用,他和上一个字符连用,表示匹配上一个字符 0 次或多次,例如:

ygt@YGT:~/share/knowledge/Shell$ cat /etc/passwd | grep ro*t
root:x:0:0:root:/root:/bin/bash
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
ygt@YGT:~/share/knowledge/Shell$

会匹配 rt, rot, root, rooot, roooot 等所有行.

另外还有 +  ,匹配1次或n次; 匹配0次或1次!

另外:.*  结合起来使用,意思是,匹配任意多个任意字符!

9.2.5  字符区间(中括号):[ ]

[ ] 表示匹配某个范围内的一个字符,例如:

        [6,8] —— 匹配 6 或者 8 [0-9]------匹配一个 0-9 的数字;

        [0-9]* —— 匹配任意长度的数字字符串;

        [a-z] —— 匹配一个 a-z 之间的字符;

        [a-z]* —— 匹配任意长度的字母字符串;

        [a-c, e-f] —— 匹配 a-c 或者 e-f 之间的任意字符;

ygt@YGT:~/share/knowledge/Shell$ echo "541rooabcbbat" | grep r[o,a,b,c]*t
541rooabcbbat
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ cat /etc/passwd | grep r[a-z]*t
root:x:0:0:root:/root:/bin/bash
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
ygt@YGT:~/share/knowledge/Shell$

9.2.6   \

表示转义,并不会单独使用。由于所有特殊字符都有其特定匹配模式,当我们想匹配某一特殊字符本身时(例如,我想找出所有包含 '$' 的行),就会碰到困难。此时我们就要将转义字符和特殊字符连用,来表示特殊字符本身,例如:

ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep '\$'
if [ $# -ne 1 ]; then
if [ -d $1 ]; then
DIR_NAME=$(basename $1)
DIR_PATH=$(cd $(dirname $1); pwd)
DATE=$(date +%y%m%d)
FILE=Shell_${DIR_NAME}_${DATE}.tar.gz
DEST=/home/ygt/$FILE
tar -zcf $DEST $DIR_PATH/$DIR_NAME
if [ $? -eq 0 ]; then
        echo "归档文件为:$DEST"
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ cat archive.sh | grep '/\$'
DEST=/home/ygt/$FILE
tar -zcf $DEST $DIR_PATH/$DIR_NAME
ygt@YGT:~/share/knowledge/Shell$

就可以匹配到包含有 $ 的行了!就可以匹配到包含有 /$ 的行了!

9.3 练习,匹配手机号码

ygt@YGT:~/share/knowledge/Shell$ echo "15712345678" | grep ^1[3,4,5,7,8][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]&
ygt@YGT:~/share/knowledge/Shell$ 15712345678
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ echo "15712345678" | grep -E ^1[3,4,5,7,8][0-9]{9}&                                                                                                  
ygt@YGT:~/share/knowledge/Shell$ 15712345678

另外,正则表达式还有其他很多高级的用法,这里还没有介绍,后期有时间学习了,再补上!


十、文本处理工具

10.1   cut

cut 的工作就是“剪”,具体的说就是在文件中负责剪切数据用的。cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段输出;

10.1.1 基本用法

        cut [选项参数] filename

        说明:默认分隔符是制表符

10.1.2 选项参数说明

选项参数 功能
-f 列号,提取第几列
-d 分隔符,按照指定分隔符分割列,默认是制表符“\t”
-c 按字符进行切割 后加加 n 表示取第几列 比如 -c 1

10.1.3 案例

10.1.3.1 准备数据

ygt@YGT:~/share/knowledge/Shell$ vim cut.txt
ygt@YGT:~/share/knowledge/Shell$ cat cut.txt
guang shen dong
zhou zhen guan
wo wo wo
lai lai lai
le le le

10.1.3.2 以空格分割,获取第一列

ygt@YGT:~/share/knowledge/Shell$ cut -d " " -f 1 cut.txt
guang
zhou
wo
lai
le

10.1.3.3 以空格分割,获取第二、三列

ygt@YGT:~/share/knowledge/Shell$ cut -d " " -f 2,3 cut.txt
shen dong
zhen guan
wo wo
lai lai
le le

10.1.3.4 在 cut.txt 文件中切割出 guang

ygt@YGT:~/share/knowledge/Shell$ cat cut.txt | grep guang | cut -d " " -f 1
guang

10.1.3.5 切割出/etc/passwd 文件的用户、路径和bash

ygt@YGT:~/share/knowledge/Shell$ cat /etc/passwd | grep bash$
root:x:0:0:root:/root:/bin/bash
ygt:x:1000:1000:ygt,,,:/home/ygt:/bin/bash
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ cat /etc/passwd | grep bash$ | cut -d ":" -f 1,5,7
root:root:/bin/bash
ygt:ygt,,,:/bin/bash
ygt@YGT:~/share/knowledge/Shell$

10.1.3.6 截取某一范围内的列

cat /etc/passwd | grep bash$ | cut -d ":" -f 2-4
cat /etc/passwd | grep bash$ | cut -d ":" -f -4
cat /etc/passwd | grep bash$ | cut -d ":" -f 3-

10.1.3.7 截取ip地址

ygt@YGT:~/share/knowledge/Shell$ ifconfig | grep "广播"
          inet 地址:192.168.160.128  广播:192.168.160.255  掩码:255.255.255.0
ygt@YGT:~/share/knowledge/Shell$
ygt@YGT:~/share/knowledge/Shell$ ifconfig | grep "广播" | cut -d " " -f 12 | cut -d ":" -f 2
192.168.160.128
ygt@YGT:~/share/knowledge/Shell$

10.2   gawk

一个强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理;

10.2.1 基本用法

gawk   [选项参数]   ‘/pattern1/{action1}   /pattern2/{action2}...’   filename

pattern:表示 gawk 在数据中查找的内容,就是匹配模式;

action:在找到匹配内容时所执行的一系列命令;

10.2.2 选项参数说明

选项参数 功能
-F 指定输入文件分隔符
-v 赋值一个用户定义变量

10.2.3 案例

注意:只有匹配了 pattern 的行才会执行 action

10.2.3.1 搜索 /etc/passwd 文件以 root 关键字开头的所有行,并输出该行的第7 列

ygt@YGT:~$ cat /etc/passwd | gawk -F ":" '/^root/{print $7}'
/bin/bash

10.2.3.2 搜索 /etc/passwd 文件以 root 关键字开头的所有行,并输出该行的第1 列和第7 列,中间以“,”号分割

ygt@YGT:~$ cat /etc/passwd | gawk -F ":" '/^root/{print $1","$7}'
root,/bin/bash

10.2.3.3 只显示/etc/passwd 的第一列和第七列,以逗号分割,且在所有行前面添加列名user,shell ,在最后一行添加"end!!!"

ygt@YGT:~$ cat /etc/passwd | gawk -F ":" 'BEGIN{print "user, shell"}/bash$/{print $1","$7} END{print "end!!!"}'
user, shell
root,/bin/bash
ygt,/bin/bash
end!!!
ygt@YGT:~$

注意:BEGIN 在所有数据读取行之前执行;END 在所有数据执行之后执行。

10.2.3.4 将 /etc/passwd 文件中的用户 id 增加数值 1 并输出

cat /etc/passwd | gawk -F ":" '{print $3}'             # 这是没有+1的
cat /etc/passwd | gawk -v i=1 -F ":" '{print $3+i}'    # 这是有+1的

10.2.4  gawk 的内置变量

变量 说明
FILENAME 文件名
NR 已读的记录数(行号)
NF 浏览记录的域的个数(切割后,列的个数)

10.2.5 案例

10.2.5.1 统计 /etc/passwd 文件名,每行的行号,每行的列数

ygt@YGT:~$ gawk -F : '{print "文件名:"FILENAME", 行号:"NR", 列号:"NF}' /etc/passwd
文件名:/etc/passwd, 行号:1, 列号:7
文件名:/etc/passwd, 行号:2, 列号:7
文件名:/etc/passwd, 行号:3, 列号:7
# 。。。中间省略。。。
文件名:/etc/passwd, 行号:24, 列号:7
文件名:/etc/passwd, 行号:25, 列号:7

10.2.5.2 查询 ifconfig 命令输出结果中的空行所在的行号

ygt@YGT:~$ ifconfig | gawk '/^$/ {print "空行:"NR}'
空行:9
空行:18
ygt@YGT:~$

十一、总结

至此,Linux Shell 基本入门教程完毕!

虽说没有很牛逼的知识点,但是学完这些,足以应付日常工作!

另外,本教程是本人在哔哩哔哩观看《尚硅谷》的视频学习而来!

猜你喜欢

转载自blog.csdn.net/cpp_learner/article/details/131047527
今日推荐