Shell 语法

Shell 语法

转载:

一. 什么是Shell

Shell是一个命令行解释器,它为用户提供了一个面向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用Shell来启动、挂起、停止甚至是编写一些程序。
​ Shell的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive),Shell还有一种执行命令的方式称为批处理(Batch),用户事先写一个Shell脚本(Script),其中有很多条命令,让Shell一次把这些命令执行完,而不必一条一条地敲命令。Shell脚本和编程语言很相似,也有变量和流程控制语句,但Shell脚本是解释执行的,不需要编译,Shell程序从脚本中一行一行读取并执行这些命令,相当于一个用户把脚本中的命令一行一行敲到Shell提示符下执行。
Shell还是一个功能相当强大的编程语言,易编写,易调试,灵活性较强。Shell是解释执行的脚本语言,在Shell中可以直接调用Linux系统命令。

img

Shell具体可描述为:外层应用程序是一些命令,是非机器语言,比如ls , useradd等;而内核则识别的是机器语言0和1,让Shell解释器当一个“中介”,由非机器语言通过Shell解释器向机器语言的转化,转化后内核将命令传递给硬件执行。更直白的说,Shell就是我们操控的Linux终端界面,输入命令得到想要的功能。

二. Shell分类

  • 由于历史原因,UNIX系统上有很多种Shell:
  • sh(Bourne Shell):由Steve Bourne开发,各种UNIX系统都配有sh。
  • csh(C Shell):由Bill Joy开发,随BSD UNIX发布,它的流程控制语句很像C语言,支持很多Bourne Shell所不支持的功能:作业控制,命令历史,命令行编辑。
  • ksh(Korn Shell):由David Korn开发,向后兼容sh的功能,并且添加了csh引入的新功能,是目前很多UNIX系统标准配置的Shell,在这些系统上/bin/sh往往是指向/bin/ksh的符号链接。
  • tcsh(TENEX C Shell):是csh的增强版本,引入了命令补全等功能,在FreeBSD、MacOS X等系统上替代了csh。
  • bash(Bourne Again Shell):由GNU开发的Shell,主要目标是与POSIX标准保持一致,同时兼顾对sh的兼容,bash从csh和ksh借鉴了很多功能,是各种Linux发行版标准配置的Shell,在Linux系统上/bin/sh往往是指向/bin/bash的符号链接。虽然如此,bash和sh还是有很多不同的,一方面,bash扩展了一些命令和参数,另一方面,bash并不完全和sh兼容,有些行为并不一致,所以bash需要模拟sh的行为:当我们通过sh这个程序名启动bash时,bash可以假装自己是sh,不认扩展的命令,并且行为与sh保持一致。
  • bash示例:
1
[root@localhost ~]# vim /etc/passwd # 其中最后一列显示了用户对应的shell类型 root:x:0:0:root:/root:/bin/bash nobody:x:65534:65534:nobody:/nonexistent:/bin/sh syslog:x:101:103::/home/syslog:/bin/false itcast:x:1000:1000:itcast,,,:/home/itcast:/bin/bash ftp:x:115:125:ftp daemon,,,:/srv/ftp:/bin/false

用户在命令行输入命令后,一般情况下Shell会fork并exec该命令,但是Shell的内建命令例外,执行内建命令相当于调用Shell进程中的一个函数,并不创建新的进程。以前学过的cd、alias、umask、exit等命令即是内建命令,凡是用which命令查不到程序文件所在位置的命令都是内建命令,内建命令没有单独的man手册,要在man手册中查看****内建命令****,应该执行
itcast$ man bash-builtins

如export、shift、if、eval、[、for、while等等。内建命令虽然不创建新的进程,但也会有Exit Status,通常也用0表示成功非零表示失败,虽然内建命令不创建新的进程,但执行结束后也会有一个状态码,也可以用特殊变量$?读出。

本文主要介绍的是Bourne Again Shell ,也就是Bash。Bash 是大多数Linux 系统默认的 Shell。

本文摘要

本文主要介绍 Linux Shell 编程的基础知识,包含下面 8 个方面

  1. Shell 编程概述
  2. Shell 关键字
  3. Shell 变量
  4. Shell 运算
  5. Shell 语句
  6. Shell 函数
  7. Shell 调试
  8. Shell 易错点

Shell 编程概述

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

1
2
3
#!/bin/bash 

echo "Hello World!"

赋予权限才可以执行

1
2
3
4
5
6
7
8
# 赋予可执行权限
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:退出

看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/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:

1
2
3
4
5
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 调用变量:

1
echo $VAR1

1. 用户自定义变量

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

常见有这 3 个前缀:

\1. unset:删除变量

\2. readonly:标记只读变量

\3. export:指定全局变量

一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash 

# 定义普通变量
CITY=SHENZHEN

# 定义全局变量
export NAME=cdeveloper

# 定义只读变量
readonly AGE=21

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

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

运行结果:

1
2
SHENZHEN
cdeveloper

1变量定义及赋值

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

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

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

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

1
2
3
4
5
6
[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使用单引号或双引号包围再赋值给变量时,两者效果相同,但是其中的区别在哪里?

1
2
3
4
5
6
7
[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的左侧,与单引号很类似。但是其功能与单引号双引号都有不同。在该符号中的命令会被执行。

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

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

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

4、命令行交互read

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

1
2
3
4
5
6
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分支的走向。运行结果如下

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

read命令的使用形式为

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

例如,

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

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

5、定义变量类型declare

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

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

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

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

declare命令的使用形式如下:

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

二、Shell中的集合类型

1、数组(array)

(1)数组定义和赋值

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

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

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

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

(2)数组元素访问

输出该数组中所有元素:

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

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

1
2
3
4
5
6
7
8
9
[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)获取数组长度

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

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

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

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

2、map

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

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

输出所有的key

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

输出所有的value

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

输出指定key对应的value

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

添加元素

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

map中键值对的个数

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

2. 预定义变量

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

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

一个例子:

1
2
3
4
5
6
7
8
9
10
11
#!/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 的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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:当前工作目录,绝对路径

还是看例子:

1
2
3
4
5
6
7
8
9
#!/bin/bash

echo "print env"

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

运行结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
print env

# 当前主目录
/home/orange

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

# 当前终端
xterm-256color

# 当前目录
/home/orange

# 用户 ID
1000

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

三、Shell中的字符操作

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

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

1、字符串删除

删除前面的http://

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

删除后面的dabokele

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

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

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

2、字符串截取

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

1
2
3
4
5
6
[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

1
2
[root@localhost ~]:~$ echo ${str/https/HTTP}
HTTP://www.zhihu.com/people/4k8k
  • 使用一个斜杠(/)表示只替换第一个遇到的字符。
  • 使用两个斜杠(//)则表示替换全部符合的字符。
  • 使用#匹配以指定字符开头的字符串。
  • 使用%匹配以指定字符开头的字符串。
1
2
3
4
5
6
7
8
9
10
[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,否则使用指定的值。

1
2
3
4
5
6
[root@localhost ~]:~$ echo $name
[root@localhost ~]:~$ echo ${name-default}
default
[root@localhost ~]:~$ name="ckm"
[root@localhost ~]:~$ echo ${name-default}
ckm

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

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

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

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

5、字符串拼接

字符串拼接如下所示

1
2
3
4
5
6
[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、字符串长度

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

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

7、字符串split成数组

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

1
2
3
4
5
6
[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

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

1
2
3
4
5
6
[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更新为原字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
[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,使用=~操作符。   

1
2
3
4
5
str1="hello"
str2="ell"
if [[ $str1 =~ $str2 ]];then
echo "$str1 contains $str2"
fi

查看运行结果   

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

四 Shell 运算

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

来看一个实际的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/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

运行结果:

1
2
3
4
2
3
4
5

a)算术运算符

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

1
2
3
a=10
b=20
val=expr $a + $b

常用的算术运算符包括

img

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

1
2
3
a=10
b=20
$a -eq $b

常用的关系运算符包括

img

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

img

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

img

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

img

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

五 Shell 语句

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

0、循环接收脚本参数shift

测试脚本如下:

1
2
3
4
5
echo '原始参数: ' $*
shift
echo 'shift后参数: ' $*
shift 2
echo 'shift 2后参数: ' $*12345

查看脚本运行结果

1
2
3
4
[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 类似,只是格式有些不同而已,也来看个例子吧:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash 

read VAR

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

2. case 语句

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

case表达式的使用格式如下

1
2
3
4
5
6
7
8
9
10
11
case $变量 in
"内容1")
程序1
;;
"内容2")
程序2
;;
*) #匹配其他所有情况
程序3
;;
esac

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

1
2
3
4
5
6
7
8
9
10
11
case $$1 in
"hello")
echo "hello world"
;;
"bye")
echo "bye bye"
;;
"*")
echo $1
;;
esac

运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[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

运行结果:

1
2
3
4
5
6
7
8
9
10
11
# 输入 Linux
Linux
Linux

# 输入 cdeveloper
cdeveloper
cdeveloper

# 输入其他的字符串
hello
other

3. for 循环

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

1
2
3
4
for var in con1 con2 con3 ...
do
程序
done

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

1
2
3
4
for arg in $*
do
echo $arg
done

运行结果如下

1
2
3
4
5
6
[root@localhost ~]:~$ sh script_test.sh a b c d e
a
b
c
d
e

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

1
2
3
4
for ((初始值; 目标值; 步长))
do
程序
done

循环输出110中的奇数

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

运行结果如下

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

4. while 循环

注意与 for 循环的区别:

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

1
2
3
4
while [ condition ]
do
程序
done

while循环相反的是until循环。

1
2
3
4
until [ condition ]
do
程序
done

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

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

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

运行结果

1
2
3
4
5
[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循环示例如下

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

运行结果如下

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

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

1
2
3
4
5
6
7
8
9
10
#!/bin/bash 

i=0

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

6. break

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

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash 

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

echo $VAR
done

运行结果:

1
11

7. continue

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

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash 

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

echo $VAR
done

运行结果:

1
2
1
3

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

六 Shell 函数

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

1. 定义函数

有 3 种常见格式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
格式一:
function 函数名 () {
指令...
}

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

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

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/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
2
3
4
5
6
7
8
9
# 1. 直接用函数名调用 hello 函数
hello

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

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

3. 获取返回值

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

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

4. 定义本地变量

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

1
2
3
4
5
fun()
{
local x=1
echo $x
}

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

七 Shell 调试

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

1
sh -n script_name.sh1

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

1
sh -x script_name.sh1

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

1
2
3
4
5
6
7
8
9
10
#!/bin/bash

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

首先检查有无语法错误:

1
sh -n test.sh1

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

1
sh -x test.sh1

调试结果如下:

1
2
3
4
5
6
7
8
+ [ 1 -eq 2 ]
+ echo 1
1
+ [ 2 -eq 2 ]
+ continue
+ [ 3 -eq 2 ]
+ echo 3
3

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

八 Shell 易错点

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

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

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

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

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

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

总结

本文主要介绍了 Shell 编程的基础知识,一篇文章不可能面面俱到,大家有不懂的还希望多多用搜索引擎,尽量养成自学的好习惯,谢谢阅读,一定要实践哦 :)


Shell 语法
http://example.com/2023/09/25/云原生-部署/Shell-语法/
作者
where
发布于
2023年9月25日
许可协议