Shell脚本和编程 | 青训营笔记

269 阅读13分钟

这是我参与笔记创作活动的第3天

一、本堂课重点内容:

  • 本堂课的知识要点有哪些?
  1. Shell基础概念
  2. 命令和语法
  3. 执行过程与原理
  4. 调试与前端集成

二、详细知识点介绍:

一、Shell基础概念

Shell概念.PNG

Shell发展.PNG

Shell构成.PNG

1.1 Shell的由来

现在我们使用的操作系统(Windows、Mac OS、Android、iOS 等)都是带图形界面的,简单直观,容易上手;然而在计算机的早期,并没有图形界面,我们只能通过一个一个地命令来控制计算机,这些命令有成百上千之多。

对于图形界面,用户点击某个图标就能启动某个程序;对于命令行,用户输入某个程序的名字(可以看做一个命令)就能启动某个程序。这两者的基本过程都是类似的,都需要查找程序在硬盘上的安装位置,然后将它们加载到内存运行。然而,真正能够控制计算机硬件(CPU、内存、显示器等)的只有操作系统内核(Kernel),图形界面和命令行只是架设在用户和内核之间的一座桥梁。

由于安全、复杂、繁琐等原因,用户不能直接接触内核(也没有必要),需要另外再开发一个程序,让用户直接使用这个程序;该程序的作用就是接收用户的操作(点击图标、输入命令),并进行简单的处理,然后再传递给内核,这样用户就能间接地使用操作系统内核了。用户界面和命令行就是这个另外开发的程序,在 Linux下,这个命令行程序叫做 Shell 。

Shell 是一个应用程序,它连接了用户和 Linux 内核,让用户能够更加高效、安全、低成本地使用 Linux 内核,这就是 Shell 的本质。

Shell 本身并不是内核的一部分,它只是在内核的基础上编写的一个应用程序,它和 QQ、迅雷、Firefox 等其它软件没有什么区别。然而 Shell 也有着它的特殊性,就是开机立马启动,并呈现在用户面前;用户通过 Shell 来使用 Linux,不启动 Shell 的话,用户就没办法使用 Linux。

Shell 的另一个重要特性是它自身就是一个 解释型的程序设计语言 ,Shell 程序设计语言支持在高级语言里所能见到的绝大多数程序控制结构,比如循环,函数,变量和数组。Shell 主要用来开发一些实用的、自动化的小工具,而不是用来开发具有复杂业务逻辑的中大型软件,例如检测计算机的硬件参数、搭建 Web 运行环境、日志分析等,Shell 都非常合适。任何在提示符下能键入的命令也能放到一个可执行的 Shell程序里,这意味着用shell语言能简单地重复执行某一任务。

1.2 Shell的工作原理

Shell 程序本身的功能是很弱的,比如文件操作、输入输出、进程管理等都得依赖内核。我们运行一个命令,大部分情况下 Shell 都会去调用内核暴露出来的接口,这就是在使用内核,只是这个过程被 Shell 隐藏了起来,它自己在背后默默进行,我们看不到而已。接口其实就是一个一个的函数,使用内核就是调用这些函数。

Shell 是一个命令语言解释器(command-language interpreter),把我们在计算机上的操作或我们的命令,翻译为计算机可识别的二进制命令,传递给内核,以便调用计算机硬件执行相关的操作;同时,计算机执行完命令后,再通过Shell翻译成自然语言,呈现在我们面前。

在 Shell 中输入的命令,有一部分是 Shell 本身自带的,这叫做内置命令;有一部分是其它的应用程序(一个程序就是一个命令),这叫做外部命令。

Shell 本身支持的命令并不多,功能也有限,但是 Shell 可以调用其他的程序,每个程序就是一个命令,这使得 Shell 命令的数量可以无限扩展,其结果就是 Shell 的功能非常强大,完全能够胜任 Linux 的日常管理工作,如文本或字符串检索、文件的查找或创建、大规模软件的自动部署、更改系统设置、监控服务器性能、发送报警邮件、抓取网页内容、压缩文件等。

更值得一提的是,Shell 还可以让多个外部程序发生连接,在它们之间很方便地传递数据,也就是把一个程序的输出结果传递给另一个程序作为输入。

将 Shell 在整个 Linux 系统中的地位描述成下图所示的样子。

20210307191454963.png

不论何时你键入一个命令,它都被 Linux Shell 所解释。Shell 首先检查命令是否是内部命令,不是的话再检查是否是一个应用程序,这里的应用程序可以是Linux本身的实用程序,比如 ls 和 rm,也可以是购买的商业程序,比如 xv,或者是公用软件(public domain software),就象 ghostview。然后 Shell 试着在搜索路径($PATH)里寻找这些应用程序。搜索路径是一个能找到可执行程序的目录列表。如果你键入的命令不是一个内部命令并且在路径里没有找到这个可执行文件,将会显示一条错误信息。而如果命令被成功的找到的话,shell的内部命令或应用程序将被分解为系统调用并传给Linux内核。

1.3 shell如何启动

1、进入Linux 控制台

一种进入 Shell 的方法是让 Linux 系统退出图形界面模式,进入控制台模式,这样一来,显示器上只有一个简单的带着白色文字的“黑屏”,就像图形界面出现之前的样子。这种模式称为 Linux 控制台(Console)。

从图形界面模式进入控制台模式也很简单,往往按下 Ctrl + Alt + Fn(n=1,2,3,4,5...) 快捷键就能够来回切换。

例如,CentOS 在启动时会创建 6 个虚拟控制台,按下快捷键 Ctrl + Alt + Fn(n=2,3,4,5,6) 可以从图形界面模式切换到控制台模式,按下 Ctrl + Alt + F1 可以从控制台模式再切换回图形界面模式。也就是说,1 号控制台被图形桌面程序占用了。

下图就是进入了控制台模式:

20210307201903855.png

输入用户名和密码,登录成功后就可以进入 Shell 了。

在图形界面模式下,输入密码时往往会显示为 •,密码有几个字符就显示几个 •;而在控制台模式下,输入密码什么都不会显示,好像按键无效一样,但不要惊慌,只要输入的密码正确就能够登录。

Shell 在你成功地登录进入系统后启动,并始终作为你与系统内核的交互手段直至你退出系统。你系统上的每位用户都有一个缺省的 Shell。每个用户的缺省 Shell 在系统里的 passwd 文件里被指定,该文件的路径是 /etc/passwd。passwd 文件里还包含有其他东西:每个人的用户ID号,一个口令加密后的拷贝和用户登录后立即执行的程序,(注:为了加强安全性,现在的系统一般都把加密的口令放在另一个文件–shadow中,而passwd中存放口令的部分以一个x字符代替)虽然没有严格规定这个程序必须是某个Linux shell,但大多数情况下都如此。

二、 命令和语法

Shell 编程概述

在 Linux 下有一门脚本语言叫做:Shell 脚本,这个脚本语言可以帮助我们简化很多工作,例如编写自定义命令等,所以还是很有必要学习它的基本用法的,一个简单的 hello.sh 脚本像下面这样,第一行 #!/bin/bash 标识该 Shell 脚本由哪个 Shell 解释

#!/bin/bash 

echo "Hello World!"

赋予权限才可以执行

# 赋予可执行权限
chmod a+x hello.sh

# 执行
./hello.sh

# 结果
Hello World!

\1. 编写 Shell 脚本 \2. 赋予可执行权限 \3. 执行,调试

下面来介绍具体的语法。

Shell 关键字

常用的关键字如下:

  1. echo:打印文字到屏幕

  2. exec:执行另一个 Shell 脚本

  3. read:读标准输入

  4. expr:对整数型变量进行算术运算

  5. test:用于测试变量是否相等、 是否为空、文件类型等

  6. exit:退出

看个例子:

#!/bin/bash 

echo "Hello Shell"

# 读入变量
read VAR
echo "VAR is $VAR"

# 计算变量
expr $VAR - 5

# 测试字符串
test "Hello"="HelloWorld"

# 测试整数
test $VAR -eq 10

# 测试目录
test -d ./Android

# 执行其他 Shell 脚本
exec ./othershell.sh

# 退出
exit

运行前,你需要新建一个 othershell.sh 的文件,让它输出 I'm othershell,并且中途需要一次输入,我这里输入的是 10:

Hello Shell
10
VAR is 10
5
I'm othershell

学习任何一门语言都要了解它的变量定义方法,Shell 也不例外。

Shell 变量

Shell 变量分为 3 种: \1. 用户自定义变量 \2. 预定义变量 \3. 环境变量

定义变量需要注意下面 2 点: \1. 等号前后不要有空格:NUM=10 \2. 一般变量名用大写:M=1

使用 $VAR 调用变量:

echo $VAR1

1. 用户自定义变量

这种变量只支持字符串类型,不支持其他字符,浮点等类型,

常见有这 3 个前缀:

1. unset:删除变量

2. readonly:标记只读变量

3. export:指定全局变量

一个例子:

#!/bin/bash 

# 定义普通变量
CITY=SHENZHEN

# 定义全局变量
export NAME=cdeveloper

# 定义只读变量
readonly AGE=21

# 打印变量的值
echo $CITY
echo $NAME
echo $AGE

# 删除 CITY 变量
unset CITY
# 不会输出 SHENZHEN
echo $CITY

运行结果:

SHENZHEN
cdeveloper

1变量定义及赋值

[root@localhost ~]:~$ v1=hello
[root@localhost ~]:~$ echo $v1
hello

但是要注意的是,赋值处必须为一个整体,不能有空格。

[root@localhost ~]:~$ v2=hello world
No command 'world' found, did you mean:
 Command 'tworld' from package 'tworld' (universe)
world: command not found

想要包含空格,需要用单引号或双引号包围,如

[root@localhost ~]:~$ v2="hello world"
[root@localhost ~]:~$ echo $v2
hello world
[root@localhost ~]:~$ v3='hello world'
[root@localhost ~]:~$ echo $v3
hello world

2、单引号(')和双引号(")的区别

上面的示例中看到hello world使用单引号或双引号包围再赋值给变量时,两者效果相同,但是其中的区别在哪里?

[root@localhost ~]:~$ a="hello"
[root@localhost ~]:~$ b="$a world"
[root@localhost ~]:~$ echo $b
hello world
[root@localhost ~]:~$ c='$a world'
[root@localhost ~]:~$ echo $c
$a world

可以看到,单引号中的$a保持原样输出。而双引号中的$a会替换成其变量值。

3、``符号

这个符号在数字键1的左侧,与单引号很类似。但是其功能与单引号双引号都有不同。在该符号中的命令会被执行。

[root@localhost ~]:~$ d=`date`
[root@localhost ~]:~$ echo $d
Wed Dec 28 06:31:13 PST 2016

如果不想使用这个符号,可以用$()替换

[root@localhost ~]:~$ e=$(date)
[root@localhost ~]:~$ echo $e
Wed Dec 28 06:31:48 PST 2016

4、命令行交互read

有时候我们希望在脚本运行时能根据用户的输入决定脚本后续执行逻辑,比如在安装插件的时候经常会让用户选择输入[N/Y]的时候。   比如有一个脚本script_test.sh

read -p "Please input [Y/N]: " yn
if [ "$yn" == "N" -o "$yn" == "n" ]; then
  echo "NO"
elif [ "$yn" == "Y" -o "$yn" == "y" ]; then
  echo "YES"
fi

在运行时根据用户的输入决定if分支的走向。运行结果如下

[root@localhost ~]:~$ sh script_test.sh
Please input [Y/N]: y
YES

read命令的使用形式为

read [-pt] variable
  参数p:后面可以接提示符
  参数t:后面可以接秒数

例如,

read -p "please input your name" -t 5 name

表示将输入内容赋值给变量name,用户有5秒钟的输入时间。

5、定义变量类型declare

默认情况下,变量的赋值内容都是字符类型的。例如下面的代码,我们期望的是输出一个求和值,但是输出的是一个求和表达式。

[root@localhost ~]:~$ sum=100+300+500
[root@localhost ~]:~$ echo $sum
100+300+500

如果想要输出求和后的值,可以使用declare命令。

[root@localhost ~]:~$ declare -i sum=100+300+500
[root@localhost ~]:~$ echo $sum
900

declare命令的使用形式如下:

declare [-aixr] variable
  参数a:将variable定义为数组
  参数i:将variable定义为整型(integer)
  参数x:将variable设置成环境变量,类似于export的作用
  参数r:variable为readonly类型,值不能被更改

Shell中的集合类型

1、数组(array)

(1)数组定义和赋值

数组中的元素用括号包围,各元素之间用空格隔开。例如

[root@localhost ~]:~$ array_name=(v0 v1 v2 v3)

可以重新设置指定元素的内容,如下所示

[root@localhost ~]:~$ array_name[2]=v22
[root@localhost ~]:~$ echo ${array_name[2]}
v22

(2)数组元素访问

输出该数组中所有元素:

[root@localhost ~]:~$ echo ${array_name[*]}
v0 v1 v22 v3
[root@localhost ~]:~$ echo ${array_name[@]}
v0 v1 v22 v3

数组元素下标从0开始,想要访问指定位置的元素,使用[]指定下标值,如下所示

[root@localhost ~]:~$ echo ${array_name[0]}
v0
[root@localhost ~]:~$ echo ${array_name[1]}
v1
[root@localhost ~]:~$ echo ${array_name[3]}
v3
[root@localhost ~]:~$ echo ${array_name[2]}
v2
[root@localhost ~]:~$ echo ${array_name[4]}

(3)获取数组长度

获取数组长度使用如下命令

[root@localhost ~]:~$ echo ${#array_name[@]}
4
[root@localhost ~]:~$ echo ${#array_name[*]}
4

获取数组中单个元素的长度使用如下命令

[root@localhost ~]:~$ echo ${#array_name[2]}
3

2、map

map类型中存储的都是键值对。   在Shell中定义map变量如下所示:

declare -A m=(["a"]="1" ["b"]="2")

输出所有的key

[root@localhost ~]:~$ echo ${!m[@]}
a b

输出所有的value

[root@localhost ~]:~$ echo ${m[@]}
1 2

输出指定key对应的value

[root@localhost ~]:~$ echo ${m["a"]}
1
[root@localhost ~]:~$ echo ${m["c"]}

添加元素

[root@localhost ~]:~$ m["c"]="3"
[root@localhost ~]:~$ echo ${m["c"]}
3

map中键值对的个数

[root@localhost ~]:~$ echo ${#m[@]}
3

2. 预定义变量

预定义变量常用来获取命令行的输入,有下面这些:

  1. $0 :脚本文件名
  2. $1-9 :第 1-9 个命令行参数名
  3. $# :命令行参数个数
  4. $@ :所有命令行参数
  5. $* :所有命令行参数
  6. $? :前一个命令的退出状态
  7. $$ :执行的进程 ID

一个例子:

#!/bin/bash 

echo "print $"
echo "$0 = $0"
echo "$1 = $1"
echo "$2 = $2"
echo "$# = $#"
echo "$@ = $@"
echo "$* = $*"
echo "$$ = $$"
echo "$? = $?"

执行./hello.sh 1 2 3 4 5 的结果:

print $

# 程序名
$0 = ./hello.sh

# 第一个参数
$1 = 1

# 第二个参数
$2 = 2

# 一共有 5 个参数
$# = 5

# 打印出所有参数
$@ = 1 2 3 4 5

# 打印出所有参数
$* = 1 2 3 4 5

# 进程 ID
$$ = 9450

# 之前没有执行其他命令或者函数
$? = 0

3. 环境变量

环境变量默认就存在,常用的有下面这几个: \1. HOME:用户主目录 \2. PATH:系统环境变量 PATH \3. TERM:当前终端 \4. UID:当前用户 ID \5. PWD:当前工作目录,绝对路径

还是看例子:

#!/bin/bash

echo "print env"

echo $HOME
echo $PATH
echo $TERM
echo $PWD
echo $UID

运行结果:

print env

# 当前主目录
/home/orange

# PATH 环境变量
/home/orange/anaconda2/bin:后面还有很多

# 当前终端
xterm-256color

# 当前目录
/home/orange

# 用户 ID
1000

Shell 变量就介绍到这里,下面来介绍 Shell 的变量运算。

Shell中的字符操作

在任何语言中对字符串的操作都是非常频繁的。字符串的操作主要包括,字符串截取,字符串替换等。   接下来的示例中,都以字符串https://www.zhihu.com/people/4k8k作为初始字符串。

str="https://www.zhihu.com/people/4k8k"

1、字符串删除

删除前面的http://

[root@localhost ~]:~$ echo ${str#https://}
www.zhihu.com/people/4k8k

删除后面的dabokele

[root@localhost ~]:~$ echo ${str%/4k8k}
https://www.zhihu.com/people

#从前往后截取,%从后往前截取。

示例中表示将符合的最短数据删除,如果使用两个#,或者两个%,则表示将符合的最长数据删除。 2、字符串截取

可以从字符串的指定位置开始截取,同时可以指定截取的位数,如下所示:

[root@localhost ~]:~$ echo ${str:2}     // 从第二位开始截取到最末尾,第一个字符下标为0
tps://www.zhihu.com/people/4k8k
[root@localhost ~]:~$ echo ${str:2:3}     // 从第二位开始顺序截取三个字符
tps
[root@localhost ~]:~$ echo ${str:(-6):3}     // 从倒数第六位开始,截取三个字符,最后一个字符下标为-1
e/4

3、字符串替换

https替换成HTTP

[root@localhost ~]:~$ echo ${str/https/HTTP}
HTTP://www.zhihu.com/people/4k8k
  • 使用一个斜杠(/)表示只替换第一个遇到的字符。
  • 使用两个斜杠(//)则表示替换全部符合的字符。
  • 使用#匹配以指定字符开头的字符串。
  • 使用%匹配以指定字符开头的字符串。
[root@localhost ~]:~$ echo ${str/e/E}
https://www.zhihu.com/pEople/4k8k
[root@localhost ~]:~$ echo ${str//e/E}
https://www.zhihu.com/pEoplE/4k8k
[root@localhost ~]:~$ echo ${str/#h/H}     // 匹配开头的那个h
Https://www.zhihu.com/people/4k8k
[root@localhost ~]:~$ echo ${str/e/E}
https://www.zhihu.com/pEople/4k8k
[root@localhost ~]:~$ echo ${str/%e/E}     // 匹配最后那个E,前一个匹配中匹配的是people中的e
https://www.zhihu.com/people/4k8k

4、字符串默认值

假设以下这个场景,如果变量name没有赋过值,则给一个默认值default,否则使用指定的值。

[root@localhost ~]:~$ echo $name
[root@localhost ~]:~$ echo ${name-default}
default
[root@localhost ~]:~$ name="ckm"
[root@localhost ~]:~$ echo ${name-default}
ckm

但是,如果已经将变量name设置成“”,则结果如下:

[root@localhost ~]:~$ name=""
[root@localhost ~]:~$ echo ${name-default}

如果变量内容为“”或者变量未初始化则给默认值,可以在-前加个冒号,使用:-

[root@localhost ~]:~$ name=""
[root@localhost ~]:~$ echo ${name-default}
[root@localhost ~]:~$ echo ${name:-default}
default

5、字符串拼接

字符串拼接如下所示

[root@localhost ~]:~$ echo "aaa""bbb"
aaabbb
[root@localhost ~]:~$ echo "aaa"$str
aaahttps://www.zhihu.com/people/4k8k
[root@localhost ~]:~$ echo "aaa$str"
aaahttps://www.zhihu.com/people/4k8k

6、字符串长度

求字符串长度用#操作,如下所示

[root@localhost ~]:~$ echo ${#str}
33

7、字符串split成数组

在以空格为分隔符分割字符串成数组时操作最简单。

[root@localhost ~]:~$ s="a b c d e"
[root@localhost ~]:~$ a=($s)
[root@localhost ~]:~$ echo ${a[*]}
a b c d e
[root@localhost ~]:~$ echo ${a[2]}
c

所以,如果需要指定特定字符进行分割,而原字符串中又没有空格时,可以先将特定字符替换成空格,然后按照上述进行分割,如下所示,

[root@localhost ~]:~$ s="a,b,c,d,e"
[root@localhost ~]:~$ a=(${s//,/ })
[root@localhost ~]:~$ echo ${a[*]}
a b c d e
[root@localhost ~]:~$ echo ${a[2]}
c

如果字符串中本身已有空格,并且期望的分隔符不是空格,按如下方法进行分割。首先将IFS变量替换成指定字符,分割后再将IFS更新为原字符。

[root@localhost ~]:~$ s="a b,c,d,e"
[root@localhost ~]:~$ old_ifs="$IFS"
[root@localhost ~]:~$ s="a b,c,d,e"
[root@localhost ~]:~$ OLD_IFS="$IFS"
[root@localhost ~]:~$ IFS=","
[root@localhost ~]:~$ a=($s)
[root@localhost ~]:~$ IFS="$OLD_IFS"
[root@localhost ~]:~$ echo ${a[*]}
a b c d e
[root@localhost ~]:~$ echo ${a[0]}
a b
[root@localhost ~]:~$ echo ${#a[*]}
4

8、字符串包含

有时候需要判断字符串str1中是否包含字符串str2,使用=~操作符。   

str1="hello"
str2="ell"
if [[ $str1 =~ $str2 ]];then
 echo "$str1 contains $str2"
fi

查看运行结果   

[root@localhost ~]~$ sh script_test.sh 
hello contains ell

Shell 运算

我们经常需要在 Shell 脚本中计算,掌握基本的运算方法很有必要,下面就是 4 种比较常见的运算方法,功能都是将 m + 1: \1. m=[m+1]\2.m=expr[ m + 1 ] \2. m=`expr m + 1` # 用 “ 字符包起来 \3. let m=m+1 \4. m=$(( m + 1 ))

来看一个实际的例子:

#!/bin/bash 

m=1
m=$[ m + 1 ]
echo $m

m=`expr $m + 1`
echo $m

# 注意:+ 号左右不要加空格
let m=m+1
echo $m

m=$(( m + 1 ))
echo $m

运行结果:

2
3
4
5

a)算术运算符

算术运算符的使用格式如下

a=10
b=20
val=expr $a + $b

常用的算术运算符包括

b)关系运算符   关系运算符的使用格式如下

a=10
b=20
$a -eq $b

常用的关系运算符包括

c)布尔运算符   常用的布尔运算符如下

d)字符串运算符   常用的字符串运算符如下

e)文件测试运算符   常用的文件测试运算符如下

了解了基本的运算方法,下面进一步来学习 Shell 的语句。

 Shell 语句

Shell 语句跟高级语言有些类似,也包括分支,跳转,循环,下面就带着大家一个一个突破。

0、循环接收脚本参数shift

测试脚本如下:

echo '原始参数: ' $*
shift
echo 'shift后参数: ' $*
shift 2
echo 'shift 2后参数: ' $*12345

查看脚本运行结果

[root@localhost ~]:~$ sh script_test.sh a b c d e f g
原始参数:  a b c d e f g
shift后参数:  b c d e f g     // 移除第一个参数a
shift 2后参数:  d e f g     // 继续移除前两个参数b和c

1. if 语句

这个跟高级语言的 if - else - if 类似,只是格式有些不同而已,也来看个例子吧:

#!/bin/bash 

read VAR

# 下面这两种判断方法都可以,使用 [] 注意左右加空格
#if test $VAR -eq 10
if [ $VART -eq 10 ]
then
    echo "true"
else
    echo "false"
fi

2. case 语句

case 语句有些复杂,要注意格式:

case表达式的使用格式如下

case $变量 in
  "内容1")
      程序1
  ;;
  "内容2")
      程序2
  ;;
  *)     #匹配其他所有情况
      程序3
  ;;
esac

看一个示例,如果第一个参数为hello,则打印hello world。如果第一个参数是bye,则打印bye bye。如果是另外的情况,则输出该参数。

case $$1 in
  "hello")
      echo "hello world"
  ;;
  "bye")
      echo "bye bye"
  ;;
  "*")
      echo $1
  ;;
esac

运行结果

[root@localhost ~]:~$ sh script_test.sh hello
hello world
[root@localhost ~]:~$ sh script_test.sh bye
bye bye
[root@localhost ~]:~$ sh script_test.sh hehe
hehe
#!/bin/bash 

read NAME
# 格式有点复杂,一定要注意
case $NAME in
    "Linux")
        echo "Linux"
        ;;
    "cdeveloper")
        echo "cdeveloper"
        ;;
    *)
        echo "other"
        ;;
esac

运行结果:

# 输入 Linux
Linux
Linux

# 输入 cdeveloper
cdeveloper
cdeveloper

# 输入其他的字符串
hello
other

3. for 循环

for … do … done for循环的格式如下

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

下面这个示例循环打印输入参数列表

for arg in $*
do
  echo $arg
done

运行结果如下

[root@localhost ~]:~$ sh script_test.sh a b c d e
a
b
c
d
e

(3)for … do … done的另一种形式 for循环的另一种个数如下

for ((初始值; 目标值; 步长))
do
  程序
done

循环输出110中的奇数

for ((i=1; i<=10; i=i+2))
do
  echo $i
done

运行结果如下

[root@localhost ~]:~$ bash script_test.sh
1
3
5
7
9

4. while 循环

注意与 for 循环的区别:

(1)while do done, until do done while循环的格式如下

while [ condition ]
do
  程序
done

while循环相反的是until循环。

until [ condition ]
do
  程序
done

while循环中,当条件满足使,就执行其中的程序。而until循环中,当条件不成立时就终止循环。

下面举例用两种循环来实现当输入为yes时跳出循环。    a) while循环示例如下

while [ "$yn" != "yes" ]
do
  read -p "Please input yes to stop: " yn
done
echo "Stop!"

运行结果

[root@localhost ~]:~$ sh script_test.sh
Please input yes to stop: no
Please input yes to stop: no
Please input yes to stop: yes
Stop!

b) until循环示例如下

until [ "$yn" == "yes" ]
do
  read -p "Please input yes to stop: " yn
done
echo "Stop!"

运行结果如下

[root@localhost ~]:~$ sh script_test.sh
Please input yes to stop: no
Please input yes to stop: yes
Stop!

until 语句与上面的循环的不同点是它的结束条件为 1

#!/bin/bash 

i=0  

# i 大于 5 时,循环结束 
until [[ "$i" -gt 5 ]]     
do  
    echo $i
    i=$[ $i + 1 ]
done

6. break

Shell 中的 break 用法与高级语言相同,都是跳出循环,来看个例子:

#!/bin/bash 

for VAR in 1 2 3
do
#   如何 VAR 等于 2 就跳出循环
    if [ $VAR -eq 2 ]
    then
        break
    fi

    echo $VAR
done

运行结果:

11

7. continue

continue 用来跳过本次循环,进入下一次循环,再来看看上面的例子:

#!/bin/bash 

for VAR in 1 2 3
do
#   如果 VAR 等于 2,就跳过,直接进入下一次 VAR = 3 的循环 
    if [ $VAR -eq 2 ]
    then
        continue    
    fi

    echo $VAR
done

运行结果:

1
3

下面介绍 Shell 编程中比较重要的函数,好像每种编程语言的函数都很重要。

六 Shell 函数

函数可以用一句话解释:带有输入输出的具有一定功能的黑盒子,相信有过编程经验的同学不会陌生。那么,我们先来看看 Shell 中函数定义的格式。

1. 定义函数

有 3 种常见格式:

格式一:
function 函数名 () {  
        指令...  
}  

格式二:
function 函数名 {  
        指令...  
} 

格式三:
函数名 () {  
    指令...  
}

例如:

#!/bin/bash 

function hello_world(){
    echo "hello world fun"
    echo $1 $2
    return 1
}


function hello_world{
    echo "hello world fun"
    echo $1 $2
    return 1
}

hello()
{
    echo "hello fun"
}

2. 调用函数

如何调用上面的 3 个函数呢?

# 1. 直接用函数名调用 hello 函数
hello

# 2. 使用「函数名 函数参数」来传递参数
hello_world 1 2   # 1 2就是传递的参数$1 $2这里是使用位置参数传递的

# 3. 使用「FUN=`函数名 函数参数`」 来间接调用
FUN=`hello_world 1 2`
echo $FUN

3. 获取返回值

如何获取 hello_world 函数的返回值呢?还记得 $? 吗?

hello_world 1 2
# $? 可用于获取前一个函数的返回值,这里结果是 1 
echo $?

4. 定义本地变量

使用 local 来在函数中定义本地变量:

fun()
{
    local x=1
    echo $x
}

俗话说,程序 3 分靠写,7 分靠调,下面我们就来看看如何调试 Shell 程序。

七 Shell 调试

使用下面的命令来检查是否有语法错误

sh -n script_name.sh1

使用下面的命令来执行并调试 Shell 脚本

sh -x script_name.sh1

来看个实际的例子,我们来调试下面这个 test.sh 程序:

#!/bin/bash

for VAR in 1 2 3
do
    if [ $VAR -eq 2 ]
    then
        continue    
    fi
    echo $VAR
done

首先检查有无语法错误:

sh -n test.sh1

没有输出,说明没有错误,开始实际调试:

sh -x test.sh1

调试结果如下:

+ [ 1 -eq 2 ]
+ echo 1
1
+ [ 2 -eq 2 ]
+ continue
+ [ 3 -eq 2 ]
+ echo 3
3

其中带有 + 表示的是 Shell 调试器的输出不带 + 表示我们程序的输出

三、执行过程与原理

3.1执行

执行.PNG

3.2执行过程

执行过程.PNG

3.3大括号展开

大括号展开.PNG

3.4波浪号展开

波浪号展开.PNG

3.5参数展开

参数展开.PNG

3.6命令替换

命令替换.PNG

3.7数学计算

数学计算.PNG

3.8文件名展开

文件名展开.PNG

四、调试与前端集成

1.调试

调试.PNG

2.vscode配置

vscode配置.PNG

3.前端集成

前端集成.PNG

三、课后个人总结:

  • 本章有什么知识点不容易掌握?

    这里我总结了一些初学 Shell 编程容易犯的错误,大多都是语法错误:

    1. [] 内不能嵌套 (),可以嵌套 []

    2. $[ val + 1 ] 是变量加 1 的常用方法

    3. [] 在测试或者计算中里面的内容最好都加空格

    1. 单引号和双引号差不多,单引号更加严格,双引号可以嵌套单引号

    2. 一定要注意语句的格式,例如缩进