本文主要介绍 Shell 脚本的基础入门知识。
1. Shell 概述
Shell 是一个命令行解释器,它为用户提供了一个向 Linux 内核发送请求以便运行程序的界面系统级程序,用户可以用 Shell 来启动,挂起,停止或者编写一些程序。
![shell 概述](https://www.hsuyeung.com/file/2022/07/22/shell%20%E6%A6%82%E8%BF%B0.png?v=2407241953)
Linux 系统中最内层的是硬件,硬件外层是内核,我们通过内核来管理硬件。
内核外层是 Shell 命令解释器,负责把各种命令转化为计算机语言然后向内核发送请求。
Shell 命令解释器外层是我们用户和外层的应用程序,我们用户或者应用程序的命令或者变成都是字符串类型,我们把这些传递给 Shell 命令解释器,由它把这些字符串转变为计算器语言并发送给内核执行并把执行的结果从计算机语言转化为我们能看懂的字符串传递给我们。
所以我们操作系统的界面其实就是 Shell 的界面,通过这个界面我们输入相应命令来由 Shell 和内核发送。
Shell 还是一个功能相当强大的编程语言,易编写,易调试,灵活性较强。Shell 是解释执行的脚本语言,在 Shell 中可以直接调用 Linux 系统命令。
2. Shell 的分类
Bourne Shell:从 1979 年起 Unix 就开始使用,它的主文件名为
sh
C Shell:主要在 BSD 版的 Unix 系统中使用,其语法和 C 语言类似而得名
2.1. 各种 Shell 区别
Bourne Shell 和 C Shell 是两种语法彼此不兼容的 Shell 类型,Bourne 主要包括 sh、ksh、Bash、psh、zsh; C 主要包括 csh、tcsh。
Bash与 sh(Bourne Shell) 兼容,现在使用的 Linux 就是使用 Bash 作为用户的基本 Shell,也就是说 Bash 是 Linux 的标准 Shell。
本机 Linux 系统支持的 Shell 类型在 /etc/shells 文件中查看
cat /etc/shells
![查看支持的 shell 类型](https://www.hsuyeung.com/file/2022/07/22/%E6%9F%A5%E7%9C%8B%E6%9C%AC%E6%9C%BALinux%E7%B3%BB%E7%BB%9F%E6%94%AF%E6%8C%81%E7%9A%84Shell%E7%B1%BB%E5%9E%8B.png?v=2407241953)
2.2. 查看本机的默认 Shell
echo $SHELL
![默认 shell](https://www.hsuyeung.com/file/2022/07/22/%E9%BB%98%E8%AE%A4shell.png?v=2407241953)
CentOS 7 的默认 Shell 为 Bash
。
2.3. bash 和常见的 sh 的联系
ls -l /bin/ | grep bash
![bash 和 sh 的联系](https://www.hsuyeung.com/file/2022/07/22/bash%E5%92%8Csh%E7%9A%84%E8%81%94%E7%B3%BB.png?v=2407241953)
可以看出 sh 是 指向 bash 的一个软链接,使用 sh 和 bash 是一样的作用。
3. Shell 脚本入门
3.1. 脚本格式
脚本以 # !/bin/bash
开头,作用是指定要使用的 Shell 解析器。
3.2. 第一个 Shell 脚本
例 1、创建一个 Shell 脚本,输出字符串 hello Shell!
# 创建一个脚本文件,这一步可以省略,直接使用 vim 命令,文件不存在会自动创建,保存后即创建成功
touch helloshell.sh
# 编辑脚本
vim helloshell.sh
向脚本中添加如下内容:
#!/bin/bash
echo "hello Shell!"
![输出 hello shell](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B1-%E8%BE%93%E5%87%BAhello%20shell.png?v=2407241953)
然后 wq !
保存退出。如果不知道 vim 操作的建议先去学习一下 vim 基础。
执行脚本:
# 方式一,bash + 脚本的相对路径或者是绝对路径
bash helloshell.sh
# 方式二,sh + 脚本的相对路径或者是绝对路径
sh helloshell.sh
# 方式三,给 Shell 脚本赋予执行权限,然后直接通过脚本的相对路径或者是绝对路径运行
chmod a+x helloshell.sh
./helloshell.sh
![执行结果](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B1%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C.png?v=2407241953)
3.3. 多命令处理
往往脚本不会向上面一样只有简单的一条语句,经常是多条语句组合完成一些复杂的需求。
例 2、创建一个脚本,使其在用户目录下创建一个 test.txt,并向该文件中追加内容“I Love Shell”。
# 创建并编辑脚本文件
vim batch.sh
向脚本中添加如下语句:
#!/bin/bash
# 切换目录
cd /home/xuyang/my-shell/blog/
# 创建文件
touch test.txt
# 向文件中追加内容
echo "I Love Shell" >> test.txt
执行脚本:
bash batch.sh
![执行结果](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B2%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C.png?v=2407241953)
如图,test.txt 文件被创建出来了,并且向其中追加了内容。
经过上面两个例子大概对 Shell 脚本有一定的了解了,接下来介绍如何 Shell 中的变量
4. Shell 中的变量
Shell 中分为系统变量
和自定义变量
,前者是系统已经定义好了的变量,可以直接使用,后者顾名思义,就是需要自己定义才可以进行使用的。
4.1. 常用系统变量
常用的系统变量有:$HOME
、$PWD
、$SHELL
、$USER
等,一般系统变量名都是全大写的。
我们来使用一下看看这些变量都代表什么意思:
# 打印当前用户的家目录
echo $HOME
# 打印当前工作目录
echo $PWD
# 打印系统使用的 Shell 版本
echo $SHELL
# 打印当前登录的用户
echo $USER
![常用系统变量](https://www.hsuyeung.com/file/2022/07/22/%E5%B8%B8%E7%94%A8%E7%B3%BB%E7%BB%9F%E5%8F%98%E9%87%8F.png?v=2407241953)
当然还有很多其余的系统变量,具体可以查阅 Linux 相关手册。
4.2. 自定义变量
4.2.1. 基本语法
- 定义变量:变量名=值
- 撤销变量:unset 变量名
- 定义静态变量:readonly 变量名=值
4.2.2. 变量定义规则
- 变量名称可以由字母、数字和下划线组成,但是不能以数字开头,环境变量名建议大写
- 等号两侧不能有空格
- 在 bash 中,变量默认类型都是字符串类型,无法直接进行数值运算
- 变量的值如果有空格,需要使用双引号或单引号括起来
- 静态变量无法使用 unset 撤销,具体撤销方法如下:
cat << EOF| gdb> attach $$> call unbind_variable("静态变量名字")> detach> EOF
4.2.3. 举例
例 3、定义变量 a 并赋值,然后撤销变量 a
# 注意等号两边不允许有空格
a=5
# 查看变量值
echo $a
# 给变量重新赋值
a=8
echo $a
# 撤销变量
unset a
# 再次查看变量值,会发现没有输出了
echo $a
![自定义普通变量](https://www.hsuyeung.com/file/2022/07/22/%E8%87%AA%E5%AE%9A%E4%B9%89%E6%99%AE%E9%80%9A%E5%8F%98%E9%87%8F.png?v=2407241953)
例 4、定义一个静态变量 SP
# 定义一个静态变量 SP 并赋值为 25
readonly SP=25
# 查看静态变量的值
echo $SP
# 试图修改静态变量的值,会报错
SP=20
![自定义静态变量](https://www.hsuyeung.com/file/2022/07/22/%E8%87%AA%E5%AE%9A%E4%B9%89%E9%9D%99%E6%80%81%E5%8F%98%E9%87%8F.png?v=2407241953)
例 5、尝试进行数值运算
c=1+2 # 打印结果真的如你所想吗?
echo c
![尝试进行数值运算](https://www.hsuyeung.com/file/2022/07/22/%E5%B0%9D%E8%AF%95%E8%BF%9B%E8%A1%8C%E6%95%B0%E5%80%BC%E8%BF%90%E7%AE%97.png?v=2407241953)
发现打印结果并不是想象中的 3,而是原封不动的将 1+2 打印出来了。印证了 在 bash 中,变量默认类型都是字符串类型,无法直接进行数值运算。
例 6、定义一个变量 p,并赋值为 “I Love Shell”
# 这样能成功吗?
p=I Love Shell
# 变量的值如果有空格,需要使用双引号或单引号括起来
p="I Love Shell"
echo $p
![定义变量并赋值字符串内容](https://www.hsuyeung.com/file/2022/07/22/%E5%AE%9A%E4%B9%89%E5%8F%98%E9%87%8F%E5%B9%B6%E8%B5%8B%E5%80%BC%E5%AD%97%E7%AC%A6%E4%B8%B2%E5%86%85%E5%AE%B9.png?v=2407241953)
例 7、Shell 能否自定义全局变量呢?答案是肯定的,接下来我们创建一个变量 E, 并将其提升为全局变量,这样其余的 Shell 程序也能访问该变量
E="I'm a global parameter"
vim global.sh
向脚本中添加如下语句:
#!/bin/bash
echo "hello Shell"
echo $E
![尝试打印“全局变量”](https://www.hsuyeung.com/file/2022/07/22/%E5%B0%9D%E8%AF%95%E6%89%93%E5%8D%B0%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F.png?v=2407241953)
然后执行脚本看看否打印出字符串“hello Shell” 和 变量 E 的值:
bash global.sh
![并没有打印出变量值](https://www.hsuyeung.com/file/2022/07/22/%E5%B9%B6%E6%B2%A1%E6%9C%89%E6%89%93%E5%8D%B0%E5%87%BA%E5%8F%98%E9%87%8F%E5%80%BC.png?v=2407241953)
发现并没有打印出变量 E 的值,这是因为变量 E 还不是全局变量,不能被其他的 Shell 脚本使用。
接下来我们将其提升为全局变量:
# 将变量 E 提升为全局变量
export E
# 执行 Shell 脚本
bash global.sh
![提升为全局变量](https://www.hsuyeung.com/file/2022/07/22/%E6%8F%90%E5%8D%87%E4%B8%BA%E5%85%A8%E5%B1%80%E5%8F%98%E9%87%8F.png?v=2407241953)
这样就正常打印出全局变量 E 的值了。
4.3. 特殊变量
Shell 中还有一些含有特殊含义的变量,也是脚本编程中经常使用到的。
4.3.1. 特殊变量:$n
4.3.1.1. 基本语法
n 为数字,$0
表示当前执行脚本的名字,$1-$9
代表脚本接收的第一到第九个参数,十以上的参数需要使用大括号包含,比如 ${10}
。
4.3.1.2. 实例
例 8、输出当前执行脚本的名字以及输入参数 1、2、3 的值
vim parameter.sh
向脚本中添加如下语句:
#!/bin/bash
# $0 表示脚本名称,$1-9表示参数值,10以上需要使用${10}
echo "$0 $1 $2 $3"
![打印输入的参数](https://www.hsuyeung.com/file/2022/07/22/%E6%89%93%E5%8D%B0%E8%BE%93%E5%85%A5%E7%9A%84%E5%8F%82%E6%95%B0.png?v=2407241953)
执行脚本:
bash parameter.sh
bash parameter.sh p1
bash parameter.sh p1 p2
bash parameter.sh p1 p2 p3
bash parameter.sh p1 p2 p3 p4
![打印结果](https://www.hsuyeung.com/file/2022/07/22/%E6%89%93%E5%8D%B0%E7%BB%93%E6%9E%9C.png?v=2407241953)
可以看出,当参数不足时将不会打印响应参数的值,当超过参数个数时,多余的参数也不会被处理。
4.3.2. 特殊变量:$#
4.3.2.1. 基本语法
没有参数,作用是获取所有输入参数的个数
4.3.2.2. 实例
例 9、获取输入参数的个数
# 继续在 parameter.sh 中进行修改,在末尾添加如下语句
# 打印输入参数的个数
echo $#
![打印输入参数的个数](https://www.hsuyeung.com/file/2022/07/22/%E8%8E%B7%E5%8F%96%E8%BE%93%E5%85%A5%E5%8F%82%E6%95%B0%E7%9A%84%E4%B8%AA%E6%95%B0.png?v=2407241953)
执行脚本:
vim parameter.sh
bash parameter.sh
bash parameter.sh p1
bash parameter.sh p1 p2
bash parameter.sh p1 p2 p3
parameter.sh p1 p2 p3
bash parameter.sh p1 p2 p3 p4
![例 9 执行结果](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B9%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C.png?v=2407241953)
上图的输出结果中有一点需要注意,即使你输入的参数个数大于了显示的参数个数,是不影响统计参数个数的,就像上图输入了 4 个参数,但是只显示了 3 个参数,那么 $# 统计的个数是按照输入的数目来决定的,这一点千万别混淆了。
4.3.3. 特殊变量:$* 和 $@
4.3.3.1. 基本语法
$* 代表命令行中输入的所有参数,但是 $* 会把所有的参数看成一个整体;
$@ 也是代表命令行中所有的参数,不过 $@ 把每个参数都单独当作一个个体。
4.3.3.2. 实例
两者的区别在这里暂时体现不出来,稍后放到循环部分进行单独介绍。
4.3.4. 特殊变量:$?
4.3.4.1. 基本语法
上一次执行的命令的返回状态。如果这个变量的值为 0
,证明上一个命令正确执行;如果这个变量的值为非 0
(具体是哪个数,由命令自己来决定),则证明上一个命令执行不正确了。
4.3.4.2. 实例
例 10、判断 helloshell.sh 脚本是否正确执行
# 正常执行
bash helloshell.sh echo $?
# 故意执行错误
bash helloshellecho $?
![执行结果](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B10%E6%89%A7%E8%A1%8C%E7%BB%93%E6%9E%9C.png?v=2407241953)
可以看出第一次返回的 0,代表正确执行了脚本,然后故意输错脚本的名字,再次使用 $? 查看执行结果,返回值为 127,非零,说明上一个命令执行失败。
5. 运算符
5.1. 常用运算符
+(加)
,-(减)
,*(乘)
,/(除)
、%(取余)
5.2. 基本语法
5.2.1. Shell 的运算表达式有两种写法:
$((运算表达式))
或 $[运算表达式]
其中使用中括号比较常见,写起来更简单些。
5.2.2. expr 运算表达式
注意:expr 关键字与运算表达式之间要有空格,运算表达式中的运算符与操作数之间也需要空格,第一种写法没有这个要求
没有特别指明,运算顺序完全符合数学的四则运算法则,即先算乘除后算加减。
如果要改变运算顺序,数学中可以加括号,但是在 expr 表达式
中不是使用括号,而是使用着重号 `` 将表达式包裹起来。
着重号在键盘 ESC 键下面,数字 1 的左边就是。不过 $[表达式]
中就可以直接使用小括号。
5.2.3. 实例
例 11、计算一些简单表达式的值
# 计算 3 + 2 的值
expr 3 + 2
# 计算 3 * 2 的值
expr 3 \* 2
# 计算 (2 + 3) * 4 的值
# 方式 1: 使用 expr 表达式
expr `expr 2 + 3` \* 4
# 方式 2: 使用 $[表达式]
echo $[(2+3)*4]
![计算一些简单表达式的值](https://www.hsuyeung.com/file/2022/07/22/%E8%AE%A1%E7%AE%97%E4%B8%80%E4%BA%9B%E7%AE%80%E5%8D%95%E8%A1%A8%E8%BE%BE%E5%BC%8F%E7%9A%84%E5%80%BC.png?v=2407241953)
从图中的打印结果可以看出:
直接使用 * 做乘法提示语法错误,需要使用 \* 来代替
使用 $((表达式)) 或 $[表达式] 不要求运算符与表达式之间有空格,expr 要求有空格,否则报错。很明显使用 $[表达式] 更符合我们的书写习惯
5.2.4. 浮点数的运算
bash 不支持浮点运算,如果需要进行浮点运算,需要借助 bc, awk (后面会介绍)处理
5.2.4.1. 使用 bc 进行浮点数运算
#加
f=$(echo "4.3+2.5"|bc)
echo "4.3+2.5=$f"
# 也可以写成一行
echo $(echo "4.3"+"2.5"|bc)
#减
f=$(echo "4.3-2.5"|bc)
echo "4.3-2.5=$f"
#乘
f=$(echo "4.30*2.50"|bc)
echo "4.3*2.5=$f"
#除
f=$(echo "4.3/2.5"|bc)
echo "4.3/2.5=$f"
#混合运算
f=$(echo "2.2/(2.2-1.1)*2+1.1"|bc)
echo "2.2/(2.2-1.1)*2+1.1=$f"
![使用 bc 进行浮点数运算](https://www.hsuyeung.com/file/2022/07/22/%E4%BD%BF%E7%94%A8bc%E8%BF%9B%E8%A1%8C%E6%B5%AE%E7%82%B9%E6%95%B0%E8%BF%90%E7%AE%97.png?v=2407241953)
5.2.4.2. 使用 awk 进行浮点数运算
#加
f=$(awk 'BEGIN{print 4.5+3.4 }')
echo "4.5+3.4=$f"
#减
f=$(awk 'BEGIN{print 4.5-3.4 }')
echo "4.5-3.4=$f"
#乘
f=$(awk 'BEGIN{print 4.5*3.4 }')
echo "4.5*3.4=$f"
#除
f=$(awk 'BEGIN{print 4.5/3.4 }')
echo "4.5/3.4=$f"
#混合
f=$(awk 'BEGIN{print (4.5-3.4)*2+3 }')
echo "(4.5-3.4)*2+3=$f"
![使用 awk 进行浮点数运算](https://www.hsuyeung.com/file/2022/07/22/%E4%BD%BF%E7%94%A8awk%E8%BF%9B%E8%A1%8C%E6%B5%AE%E7%82%B9%E6%95%B0%E8%BF%90%E7%AE%97.png?v=2407241953)
6. 条件判断
6.1. 基本语法
[ 条件表达式 ]
,注意条件表达式与中括号两边有空格。
其中,只要条件表达式非空即为 true。
比如:[ xuyang ] 返回 true,[] 返回 false。
6.2. 常用判断条件
6.2.1. 两个整数之间比较
表达式 | 含义 |
---|---|
-lt | 小于,less than |
-le | 小于等于,less equal |
-eq | 等于,equal |
-gt | 大于,greater than |
-ge | 大于等于,greater equal |
-ne | 不等于,not equal |
6.2.2. 按照文件权限进行判断
表达式 | 含义 |
---|---|
-r | 有读的权限,read |
-w | 有写的权限,write |
-x | 有执行的权限,execute |
6.2.3. 按照文件类型进行判断
表达式 | 含义 |
---|---|
-f | 文件存在并且是一个常规的文件,file |
-d | 文件存在并且是一个目录,directory |
-e | 文件存在,existence |
注意:和上面运算符一样,这里的判断条件也只能用于整数之间的判断,要想比较浮点数还是需要借助
bc
或awk
。这里就不展开讲了,有兴趣的可以再去深入了解。
6.3. 实例
例 12、判断两个整数的大小
# 25 >= 22
[ 25 -ge 22 ]
# 查看比较结果,打印 0 说明为 true,非 0 为 false
echo $?
# 20 < 29
[ 20 -lt 19 ]
echo $?
![判断两个整数的大小](https://www.hsuyeung.com/file/2022/07/22/%E5%88%A4%E6%96%AD%E4%B8%A4%E4%B8%AA%E6%95%B4%E6%95%B0%E7%9A%84%E5%A4%A7%E5%B0%8F.png?v=2407241953)
例 13、判断一个文件当前用户是否具有写、执行权限
[ -w test.txt ]
echo $?
[ -x test.txt ]
echo $?
![判断一个文件当前用户是否具有写、执行权限](https://www.hsuyeung.com/file/2022/07/22/%E5%88%A4%E6%96%AD%E4%B8%80%E4%B8%AA%E6%96%87%E4%BB%B6%E5%BD%93%E5%89%8D%E7%94%A8%E6%88%B7%E6%98%AF%E5%90%A6%E5%85%B7%E6%9C%89%E5%86%99%E3%80%81%E6%89%A7%E8%A1%8C%E6%9D%83%E9%99%90.png?v=2407241953)
例 14、判断文件是否存在
[ -e test.txt ]
echo $?
[ -e test ]
echo $?
![判断文件是否存在](https://www.hsuyeung.com/file/2022/07/22/%E5%88%A4%E6%96%AD%E6%96%87%E4%BB%B6%E6%98%AF%E5%90%A6%E5%AD%98%E5%9C%A8.png?v=2407241953)
例 15、多条件判断
多个条件一起判断需要使用逻辑运算符来进行连接多个条件。
这里介绍一下逻辑与( &&
)和逻辑或( ||
):&&
表示前一条命令执行成功时,才执行后一条命令,||
表示上一条命令执行失败后,才执行下一条命令
# 判断文件是否存在并且具有写权限,如果存在且具有写权限打印 existence,can write,否则打印 not existence
[ -e test.txt ] && [ -w test.txt ] && echo "existence,can wirte" || echo "not existence"
[ -e test ] && [ -w test.txt ] && echo "existence,can wirte" || echo "not existence"
![多条件判断](https://www.hsuyeung.com/file/2022/07/22/%E5%A4%9A%E6%9D%A1%E4%BB%B6%E5%88%A4%E6%96%AD.png?v=2407241953)
7. 流程控制
7.1. if 判断
7.1.1. 基本语法
# 语法 1
if [ 条件判断式 ];then
程序语句
fi
# 语法 2
if [ 条件判断式 ]
then
程序语句
fi
7.1.2. 注意事项
[ 条件判断式 ],中括号和条件判断式之间必须有空格
if 后要有空格
7.1.3. 实例
例 16、输入一个数字,如果是 1,则输出 "I Love Shell",如果是 2,则输出 "You Love Shell,too",如果是其余的输入,则输出"ERROR INPUT'。
vim if.sh
向脚本中添加如下语句:
#!/bin/bash
if [ $1 -eq 1 ]
then
echo "I Love Shell"
elif [ $1 -eq 2 ]
then
echo "You Love Shell,too"
else
echo "ERROR INPUT"
fi
![if elif 判断](https://www.hsuyeung.com/file/2022/07/22/if%E5%88%A4%E6%96%AD%E4%BE%8B%E5%AD%90.png?v=2407241953)
执行脚本:
bash if.sh 0
bash if.sh 1
bash if.sh 2
![输出结果](https://www.hsuyeung.com/file/2022/07/22/if%E5%88%A4%E6%96%AD%E4%BE%8B%E5%AD%90%E8%BE%93%E5%87%BA.png?v=2407241953)
7.2. case 语句
7.2.1. 基本语法
case $变量名 in
"值1")
如果变量的值等于值 1,则执行程序 1
;;
"值2")
如果变量的值等于值 2,则执行程序 2
;;
…省略其他分支…
*)
如果变量的值都不是以上的值,则执行此程序
;;
esac
7.2.2. 注意事项
case 行尾必须为单词
in
,每一个模式匹配必须以右括号)
结束;双分号
;;
表示命令序列结束,相当于 java 中的break
;最后的
*)
表示默认模式,相当于 java 中的default
。
7.2.3. 实例
例 17、同上面例 16。输入一个数字,如果是 1,则输出 "I Love Shell",如果是 2,则输出 "You Love Shell,too",如果是其余的输入,则输出"ERROR INPUT'。
# 创建并编辑脚本
vim case.sh
向脚本中添加如下语句:
#!/bin/bash
case $1 in
"1")
echo "I Love Shell";;
"2")
echo "You Love Shell,too";;
*)
echo "ERROR INPUT";;
esac
执行脚本:
bash case.sh 0
bash case.sh 1
bash case.sh 2
![case 条件示例](https://www.hsuyeung.com/file/2022/07/22/case%E5%88%86%E6%94%AF%E4%BE%8B%E5%AD%90.png?v=2407241953)
7.3. for 循环
7.3.1. 基本语法
# 语法 1
for (( 初始值; 循环控制条件; 变量变化 ))
do
程序语句
done
# 语法 2
for 变量 in 值1 值2 值3…
do
程序语句
done
7.3.2. 实例
例 18、计算 1 + 2 + 3 + ... + 100 的和
# 创建并编辑脚本
vim for1.sh
向脚本中添加如下语句:
#!/bin/bash
s=0
for ((i = 1; i <= 100; i++))
do
s=$[$s + $i]
done
echo $s
![求 1 到 100 累加和](https://www.hsuyeung.com/file/2022/07/22/1%E5%88%B0100%E7%B4%AF%E5%8A%A0%E5%92%8C%E7%A4%BA%E4%BE%8B.png?v=2407241953)
执行脚本:
bash for1.sh
![计算结果](https://www.hsuyeung.com/file/2022/07/22/%E8%AE%A1%E7%AE%97%E7%BB%93%E6%9E%9C.png?v=2407241953)
例 19、打印所有的输入参数
# 创建并编辑脚本
vim for2.sh
向脚本中添加如下语句:
#!/bin/bash
for i in $*
do
echo "I Love $i"
done
![使用 for 循环打印所有输入参数](https://www.hsuyeung.com/file/2022/07/22/%E4%BD%BF%E7%94%A8for%E5%BE%AA%E7%8E%AF%E6%89%93%E5%8D%B0%E6%89%80%E6%9C%89%E8%BE%93%E5%85%A5%E5%8F%82%E6%95%B0.png?v=2407241953)
执行脚本:
bash for2.sh Shell C Java
![输出结果](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B19%E8%BE%93%E5%87%BA%E7%BB%93%E6%9E%9C.png?v=2407241953)
例 20、比较 $* 和 $@ 的区别。
$*
和 $@
都表示传递给函数或脚本的所有参数,不被双引号“”
包裹时,都以 $1 $2 …$n
的形式输出所有参数。
当它们被双引号“”
包裹时:
$*
会将所有的参数作为一个整体,以 “$1 $2 …$n”
的形式输出所有参数;
$@
会将各个参数分开,以 “$1” “$2”…”$n”
的形式输出所有参数。
# 再次编辑刚才的 for2.sh 脚本文件
vim for2.sh
将脚本修改为如下内容:
#!/bin/bash
for i in $*
do
echo "I Love $i"
done
echo "-------------"
for j in $@
do
echo "I Love $j"
done
![未使用双引号包裹](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B20-1.png?v=2407241953)
执行脚本:
bash for2.sh Shell C Java
![输出结果](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B20-1%E8%BE%93%E5%87%BA%E7%BB%93%E6%9E%9C.png?v=2407241953)
可以看出没有使用双引号时没有任何区别。
接下来我们将脚本中的 $* 和 $@ 改为 "$*" 和 "$@" 再来测试一下。
![使用双引号包裹](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B20-2.png?v=2407241953)
![输出结果](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B20-2%E8%BE%93%E5%87%BA%E7%BB%93%E6%9E%9C.png?v=2407241953)
这次两者的区别就体现出来了,"$*" 将输入的 "Shell C Java" 当作了一个整体,因此只有一个变量。而 "$@" 分别将 Shell C Java 当作了不同的变量进行读取。
7.4. while 循环
7.4.1. 基本语法
while [ 条件判断式 ]
do 程序语句
done
7.4.2. 实例
例 21、同例 18。计算 1 + 2 + 3 + ... + 100 的和。
# 创建并编辑脚本
vim while.sh
向脚本中添加如下语句:
#!/bin/bash\
s=0
i=1
while [ $i -le 100 ]
do
s=$[ $s + $i ]
i=$[ $i + 1 ]
done
echo $s
![使用 while 循环计算 1 到 100 累加和](https://www.hsuyeung.com/file/2022/07/22/%E4%BD%BF%E7%94%A8while%E5%BE%AA%E7%8E%AF%E8%AE%A1%E7%AE%971%E5%88%B0100%E7%B4%AF%E5%8A%A0%E5%92%8C.png?v=2407241953)
执行脚本:
bash while.sh
![输出结果](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B21%E8%BE%93%E5%87%BA%E7%BB%93%E6%9E%9C.png?v=2407241953)
8. read 读取控制台输入
8.1. 基本语法
read [选项] [参数]
选项:
-p:指定读取值时的提示符;
-t:指定读取值时等待的时间(秒)。
参数:
变量名:用于接收读取值的变量名
8.2. 实例
例 22、提示用户 7 秒内输入用户名,并回显。如果 7 秒后没有输入自动关闭输入
# 创建并编辑脚本
vim read.sh
向脚本中添加如下语句:
#!/bin/bash
read -t 7 -p "input your name in 7 seconds:" name
echo $name
![7 秒后关闭输入](https://www.hsuyeung.com/file/2022/07/22/7%E7%A7%92%E5%90%8E%E5%85%B3%E9%97%AD%E8%BE%93%E5%85%A5.png?v=2407241953)
执行脚本:
bash read.sh
![输出结果](https://www.hsuyeung.com/file/2022/07/22/%E4%BE%8B22%E8%BE%93%E5%87%BA%E7%BB%93%E6%9E%9C.png?v=2407241953)
可以看出如果没有输入数据,7 秒以后自动就关闭了输入。
如果不需要限制时间,那么取消掉 -t 选项即可,不需要提示的话取消 -p 选项即可。
9. 函数
9.1. 系统函数
9.1.1. basename
9.1.1.1. 基本语法
basename [string / pathname] [suffix]
功能描述:basename 命令会删掉所有的后缀( suffix )包括最后一个('/')字符,然后将字符串显示出来。
选项:
suffix 为后缀,如果 suffix 被指定了,basename 会将 pathname 或 string 中的 suffix 去掉。
9.1.1.2. 实例
例 23、截取 /home/xuyang/my-shell/blog/test.txt 的文件名字。然后再试试将后缀去掉
basename /home/xuyang/my-shell/blog/test.txt
basename /home/xuyang/my-shell/blog/test.txt .txt
![basename 函数示例](https://www.hsuyeung.com/file/2022/07/22/basename%E5%87%BD%E6%95%B0%E7%A4%BA%E4%BE%8B.png?v=2407241953)
9.1.2. dirname
9.1.2.1. 基本语法
dirname 文件绝对路径
功能描述: 从给定的包含绝对路径的文件名中去除文件名(非目录部分),然后返回剩下的路径(目录的部分)
9.1.2.2. 实例
例 24、获取 /home/xuyang/my-shell/blog/test.txt 文件所在的目录路径
dirname /home/xuyang/my-shell/blog/test.txt
![dirname 函数示例](https://www.hsuyeung.com/file/2022/07/22/dirname%E5%87%BD%E6%95%B0%E7%A4%BA%E4%BE%8B.png?v=2407241953)
9.2. 自定义函数
9.2.1. 基本语法
[ function ] 函数名 [ () ]
{
action;
[return int;]
}
说明:
中括号内的内容表示可以不写;
参数返回,可以显式加:return 值,如果不加,将以最后一条命令运行结果,作为返回值。 return 后跟数值 n( 0 - 255 );
必须在调用函数地方之前,先声明函数,Shell 脚本是逐行运行。不会像其它语言一样先编译。
9.2.2. 实例
例 25、计算两个输入参数的和
# 创建并编辑脚本
vim func.sh
向脚本中添加如下语句:
#!/bin/bash
# 定义函数
function sum()
{
s=0;
s=$[$1+$2]
echo $s
}
read -p "请输入第一个数字:" p1
read -p "请输入第二个数字:" p2
# 调用函数
sum $p1 $p2
![自定义函数计算两数之和](https://www.hsuyeung.com/file/2022/07/22/%E8%87%AA%E5%AE%9A%E4%B9%89%E5%87%BD%E6%95%B0%E8%AE%A1%E7%AE%97%E4%B8%A4%E6%95%B0%E4%B9%8B%E5%92%8C.png?v=2407241953)
执行脚本:
bash func.sh
请输入第一个数字:100
请输入第二个数字:50
![执行自定义函数](https://www.hsuyeung.com/file/2022/07/22/%E6%89%A7%E8%A1%8C%E8%87%AA%E5%AE%9A%E4%B9%89%E5%87%BD%E6%95%B0.png?v=2407241953)
10. Shell 工具
10.1. cut
10.1.1. 说明
cut
的工作就是"剪",具体的说就是在文件中负责剪切数据用的。cut 命令从文件的每一行剪切字节、字符和字段并将这些字节、字符和字段输出。不会影响源文件内容,除非重定向写入源文件。
10.1.2. 基本语法
cut [选项参数] filename
说明: 默认分隔符是制表符( \t )
10.1.3. 常用选项参数说明
选项参数 | 功能 |
---|---|
-f | 列号,获取第几列的数据 |
-d | 分隔符,按照指定的分隔符进行切割列 |
更多参数请查阅手册:cut命令_Linux cut 命令用法详解:连接文件并打印到标准输出设备上
10.1.4. 实例
例 26、创建一个文件并切割指定的数据
# 数据准备,创建一个文本文件,并向其中写入测试数据
touch cut.txt
vim cut.txt
写入内容如下:
cheng shen wu
du zhen han
wo wo wo
lai lai lai
le le le
![准备数据](https://www.hsuyeung.com/file/2022/07/22/%E5%87%86%E5%A4%87%E6%95%B0%E6%8D%AE.png?v=2407241953)
接下来进行切割:
# 切割 cut.txt 的第一列,分隔符为空格
cut -d " " -f 1 cut.txt
# 切割 cut.txt 的第二、三列,分隔符为空格
cut -d " " -f 2,3 cut.txt
# 将 cut.txt 中 du 切割出来
cat cut.txt | grep "du" | cut -d " " -f 1
![cut 切割数据](https://www.hsuyeung.com/file/2022/07/22/cut%E5%88%87%E5%89%B2%E6%95%B0%E6%8D%AE.png?v=2407241953)
10.2. sed
10.2.1. 说明
sed
是一种流编辑器,它一次处理一行内容。
处理时,把当前处理的行存储在临时缓冲区中,称为**"模式空间"**,接着用 sed 命令处理缓冲区中的内容,处理完成后,把缓冲区的内容送往屏幕。
接着处理下一行,这样不断重复,直到文件末尾。不会影响源文件内容,除非重定向写入源文件。
10.2.2. 基本语法
sed [选项参数] 'command' filename
10.2.3. 常用选项参数说明
选项参数 | 功能 |
---|---|
-e | 直接在指令列模式上进行 sed 的动作编辑,即接着上次命令的结果继续操作 |
更多选项参数请查阅手册:sed命令_Linux sed 命令用法详解:功能强大的流式文本编辑器
10.2.5. 命令功能描述
命令( commond ) | 功能描述 |
---|---|
a | 新增,a 的后面可以接字符串,在下一行出现 |
d | 删除指定的字符串 |
s | 查找并替换指定的字符串 |
10.2.6. 实例
例 27、将 "Shell C Java" 这个字符串插入到 sed.txt 的第二行,并打印。
# 准备数据
vim sed.txt
向文件中添加如下内容:
cheng shen wu
du zhen han
wo wo wo
lai lai lai
le le le
进行处理:
sed '2a Shell C Java' sed.txt
# 2a 表示在第二行增加数据,后面跟着为要插入的数据
![sed 插入数据](https://www.hsuyeung.com/file/2022/07/22/sed%E6%8F%92%E5%85%A5%E6%95%B0%E6%8D%AE.png?v=2407241953)
再次查看文件:
cat sed.txt
![原文件并未被改变](https://www.hsuyeung.com/file/2022/07/22/%E5%8E%9F%E6%96%87%E4%BB%B6%E5%B9%B6%E6%B2%A1%E6%9C%89%E8%A2%AB%E4%BF%AE%E6%94%B9.png?v=2407241953)
会发现文件内容并没有改变。
例 28、删除 sed.txt 文件所有包含 "wo" 的行
sed '/wo/ d' sed.txt
# /wo/ 是一个正则表达式,表示包含 wo 的数据,d 代表删除满足条件的数据
![使用 sed 删除特定数据](https://www.hsuyeung.com/file/2022/07/22/%E4%BD%BF%E7%94%A8sed%E5%88%A0%E9%99%A4%E6%95%B0%E6%8D%AE.png?v=2407241953)
例 29、将 sed.txt 文件中第二行删除并将所有的 "wo" 替换成 "ni"。
sed -e '2d' -e 's/wo/ni/g' sed.txt
# s 表示替换,/wo/ni/ 表示将 wo 替换成 ni,g表示替换所有的 wo
![使用 sed 替换特定数据](https://www.hsuyeung.com/file/2022/07/22/%E4%BD%BF%E7%94%A8sed%E6%9B%BF%E6%8D%A2%E6%95%B0%E6%8D%AE.png?v=2407241953)
10.3. awk
10.3.1. 说明
一个强大的文本分析工具,把文件逐行的读入,以空格为默认分隔符将每行切片,切开的部分再进行分析处理。
10.3.2. 基本语法
awk [选项参数] 'pattern1{action1} pattern2{action2}...' filename
pattern: 表示 awk 在数据中查找的内容,就是匹配模式
action: 在找到匹配内容时所执行的一系列命令
10.3.3. 常用选项参数说明
选项参数 | 功能 |
---|---|
-F | 指定拆分输入文件使用的的分隔符 |
-v | 赋值一个用户定义的变量 |
更多选项参数请查阅手册:awk命令_Linux awk 命令用法详解:文本和数据进行处理的编程语言
10.3.4. awk 内置变量
变量名 | 说明 |
---|---|
FILENAME | 当前处理的文件名 |
NR | 已处理的记录数 |
NF | 已处理的记录数(切割后,列的个数) |
10.3.5. 实例
例 30、搜索 /etc/passwd 文件以 root 关键字开头的所有行,并输出该行的第1列和7列,且用逗号将其隔开
# 数据准备
cp /etc/passwd ./
ls -l passwd
# 搜索并输出
awk -F ":" '/^root/ {print $1","$7}' passwd
![awk 搜索数据并输出](https://www.hsuyeung.com/file/2022/07/22/awk%E6%90%9C%E7%B4%A2%E6%89%80%E5%B1%9E%E5%B9%B6%E8%BE%93%E5%87%BA.png?v=2407241953)
查看一下 passwd 文件,验证我们是否是正确的:
cat passwd
![验证搜索结果](https://www.hsuyeung.com/file/2022/07/22/%E9%AA%8C%E8%AF%81%E6%90%9C%E7%B4%A2%E7%BB%93%E6%9E%9C.png?v=2407241953)
例 31、只输出 /etc/passwd 文件的第 1 列和 7 列,且用逗号将其隔开,并在文件开头添加一行数据 "user,data",在文件最后添加一行数据 "xuyang,/bin/zuishuai"。
awk -F : 'BEGIN{print "user,data"} {print $1","$7} END{print "xuyang,/bin/zuishuai"}' passwd
![awk 在特定位置添加数据](https://www.hsuyeung.com/file/2022/07/22/awk%E5%9C%A8%E7%89%B9%E5%AE%9A%E4%BD%8D%E7%BD%AE%E6%B7%BB%E5%8A%A0%E6%95%B0%E6%8D%AE.png?v=2407241953)
![验证结果](https://www.hsuyeung.com/file/2022/07/22/%E9%AA%8C%E8%AF%81%E6%B7%BB%E5%8A%A0%E7%BB%93%E6%9E%9C.png?v=2407241953)
例 32、将 passwd 文件中的用户 id (即第三列数据)增加数值 1 并输出
awk -v i=1 -F: '{print $3+i}' passwd
![awk 对特定数据进行运算](https://www.hsuyeung.com/file/2022/07/22/awk%E5%AF%B9%E7%89%B9%E5%AE%9A%E6%95%B0%E6%8D%AE%E8%BF%9B%E8%A1%8C%E8%BF%90%E7%AE%97.png?v=2407241953)
例 33、打印 passwd 文件名,每行的行号,每行的列数
awk -F: '{print "filename:" FILENAME ", linenumber:" NR ",columns:" NF}' passwd
![awk 打印行号和列数](https://www.hsuyeung.com/file/2022/07/22/awk%E6%89%93%E5%8D%B0%E6%96%87%E4%BB%B6%E8%A1%8C%E5%8F%B7%E5%92%8C%E5%88%97%E6%95%B0.png?v=2407241953)
10.4. sort
10.4.1. 说明
sort 命令是在 Linux 里非常有用,它将文件进行排序,并将排序结果标准输出。
sort 命令既可以从特定的文件,也可以从 stdin
中获取输入。
10.4.2. 基本语法
sort [选项] [参数]
10.4.3. 常用选项参数
选项 | 说明 |
---|---|
-n | 依照数值的大小排序 |
-r | 以相反的顺序来排序 |
-t | 设置排序时所用的分隔字符 |
-k | 指定需要排序的列 |
更多选项参数请查阅手册:sort命令_Linux sort 命令用法详解:将文件进行排序并输出
10.4.4. 实例
例 34、按照 ":" 分割后的第三列倒序排序。
# 准备数据
vim sort.txt
向文件中添加如下内容:
bb:40:5.4
bd:20:4.2
xz:50:2.3
cls:10:3.5
ss:30:1.6
进行切割排序:
sort -t ":" -nrk 3 sort.txt
![sort 对数据排序](https://www.hsuyeung.com/file/2022/07/22/sort%E5%AF%B9%E6%95%B0%E6%8D%AE%E6%8E%92%E5%BA%8F.png?v=2407241953)
11. 练习题
1、使用 Linux 命令查询文件中空行所在的行号
2、有文件 score.txt 内容如下:
张三 40
李四 50
王五 60
使用 Linux 命令计算第二列的和并输出。
3、Shell 脚本里检查一个文件是否存在?存在打印 ”existence“,不存在打印 ”not existence“。
4、用 Shell 写一个脚本,对文本中无序的一列数字排序。
12. 练习题答案
#1、使用 Linux 命令查询文件中空行所在的行号
awk '/^$/ {print NR}' filename
#2、计算第二列的和并输出
cat score.txt | awk -F " " '{sum+=$2} END{print sum}'
#3、检查一个文件是否存在
vim check.sh
#!/bin/bash
if [ -f filename ]; then
echo "existence"
else
echo "not existence"
fi
0 条评论