文章目录
- Shell简介
- 什么是Shell
- Shell环境
- 第一个Shell脚本
- Shell脚本的运行方法
- Shell基础语法
- Shell变量
- Shell传递参数
- Shell字符串
- Shell字符串截取
- Shell数组
- Shell运算符
Shell简介
什么是Shell
-
Shell是一种程序设计语言。作为命令语言,它交互式解释和执行用户输入的命令或者自动地解释和执行预先设定好的一连串的命令;作为程序设计语言,它定义了各种变量和参数,并提供了许多在高级语言中才具有的控制结构,包括循环和分支。
-
Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁。
-
Shell 既是一种命令语言,又是一种程序设计语言。
Shell环境
- Shell 编程跟 JavaScript、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。
- Linux 的 Shell 种类众多,常见的有:
- Bourne Shell(/usr/bin/sh或/bin/sh)
- Bourne Again Shell(/bin/bash)
- C Shell(/usr/bin/csh)
- K Shell(/usr/bin/ksh)
- Shell for Root(/sbin/sh)
- 在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为 #!/bin/bash。
- #! 告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。
第一个Shell脚本
- 打开文本编辑器(可以使用 vi/vim 命令来创建文件),新建一个文件 test.sh,扩展名为 sh(sh代表shell),扩展名并不影响脚本执行,见名知意就好。
- 输入一些代码,第一行一般是这样:
#!/bin/sh echo "hello world !"
- 代码解析
- #! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪 一种 Shell。
- echo 命令用于向窗口输出文本(相当于python中的print)。
Shell脚本的运行方法
- 作为可执行程序
- 建立存放脚本文件的文件夹,在文件夹中创立.sh格式的脚本文件,在其中执行脚本
- 如创建名字为 demo1.sh 的脚本文件,通过 vim demo1.sh 进入,并进行编辑,写入代码
- 保存后退出,cd 到所创建用来存放脚本文件的文件夹下,通过以下操作运行脚本
chmod u+x demo1.sh # 使脚本具有执行权限 ./demo1.sh # 执行脚本
- 注意,一定要写成 ./test.sh,而不是 test.sh,运行其它二进制的程序也一样,直接写 test.sh,linux 系统会去 PATH 里寻找有没有叫 test.sh 的,而只有 /bin, /sbin, /usr/bin,/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 test.sh 是会找不到命令的,要用 ./test.sh 告诉系统说,就在当前目录找。
- 作为解释器参数
这种运行方式是直接运行解释器,其参数就是 shell 脚本的文件名,如:/bin/sh demo1.sh
Shell基础语法
Shell变量
-
变量是任何一种编程语言都必不可少的组成部分,变量用来存放各种数据。脚本语言在定义变量时通常不需要指明类型,直接赋值就可以,Shell 变量也遵循这个规则。
-
在 Bash shell 中,每一个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。
-
这意味着,Bash shell 在默认情况下不会区分变量类型,即使你将整数和小数赋值给变量,它们也会被视为字符串,这一点和大部分的编程语言不同。例如在C语言或者 C++ 中,变量分为整数、小数、字符串、布尔等多种类型。当然,如果有必要,你也可以使用 Shell declare 关键字显式定义变量的类型,但在一般情况下没有这个需求,Shell 开发者在编写代码时自行注意值的类型即可。
-
定义变量
- Shell 支持以下三种定义变量的方式:
url=www.baidu.com name='baidu' author="shell编程"
注意:赋值号=的周围不能有空格,这可能和你熟悉的大部分编程语言都不一样。
- Shell 支持以下三种定义变量的方式:
-
Shell 变量的命名规范和大部分编程语言都一样:
- 变量名由数字、字母、下划线组成;
- 必须以字母或者下划线开头;
- 不能使用 Shell 里的关键字(通过 help 命令可以查看保留关键字)。
-
变量定义举例:
url=www.baidu.com name='baidu' author="shell编程" echo $url echo $name echo $author
-
使用变量
使用一个定义过的变量,只要在变量名前面加美元符号$即可,如:
a="linux" echo $a echo ${a}
变量名外面的花括号{ }是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界,比如下面这种情况:
skill="python" echo "I am good at ${skill}web"
如果不给 skill 变量加花括号,写成echo “I am good at ${skill}Web”,解释器就会把 $skillWeb 当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。
-
推荐给所有变量加上花括号{ },这是个良好的编程习惯。
-
修改变量的值
- 已定义的变量,可以被重新赋值,如:
url="http://www.baidu.com" echo ${url} url="http://www.baidu.com/shell/" echo ${url}
第二次对变量赋值时不能在变量名前加,只有在使用变量时才能加$。
-
单引号和双引号的区别
- 前面我们还留下一个疑问,定义变量时,变量的值可以由单引号’ '包围,也可以由双引号" "包围,它们到底有什么区别呢?不妨以下面的代码为例来说明:
url="www.baidu.com" website1='百度网站:${url}' website2="百度网站:${url}" echo $website1 # 百度网站:${url} echo $website2 # 百度网站:www.baidu.com
- 以单引号’ '包围变量的值时,单引号里面是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。这种方式比较适合定义显示纯字符串的情况,即不希望解析变量、命令等的场景。
- 以双引号" "包围变量的值时,输出时会先解析里面的变量和命令,而不是把双引号中的变量名和命令原样输出。这种方式比较适合字符串中附带有变量和命令并且想将其解析后再输出的变量定义。
- 前面我们还留下一个疑问,定义变量时,变量的值可以由单引号’ '包围,也可以由双引号" "包围,它们到底有什么区别呢?不妨以下面的代码为例来说明:
-
将命令的结果赋值给变量
- Shell 也支持将命令的执行结果赋值给变量,常见的有以下两种方式:
variable=`Shell` variable=$(Shell)
- 第一种方式把命令用反引号(位于 Esc 键的下方)包围起来,反引号和单引号非常相似,容易产生混淆,所以不推荐使用这种方式;第二种方式把命令用$()包围起来,区分更加明显,所以推荐使用这种方式。
- Shell 也支持将命令的执行结果赋值给变量,常见的有以下两种方式:
-
只读变量
- 使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变。
- 下面的例子尝试更改只读变量,结果报错:
myurl='www.zxs.com' readonly myurl myurl='www.zxs.com/nb'
-
删除变量
- 使用 unset 命令可以删除变量。语法:
unset variable_name
- 变量被删除后不能再次使用;unset 命令不能删除只读变量。
- 举个例子:
comm=`ps` unset comm echo "$comm"
上面的脚本没有任何输出
Shell传递参数
- 我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……
- 特殊字符处理参数说明
参数处理 | 说明 |
---|---|
$# | 传递到脚本的参数个数 |
$* | 以一个单字符串显示所有向脚本传递的参数。 |
$$ | 脚本运行的当前进程ID号 |
$! | 后台运行的最后一个进程的ID号 |
$@ | 与$*相同,但是使用时加引号,并在引号中返回每个参数。 |
$- | 显示Shell使用的当前选项,与set命令功能相同 |
$? | 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误 |
例子:
以下实例我们向脚本传递11个参数(注意:超过10的参数需要用{}括起来),并分别输出,其中 $0 为执行的文件名(包含文件路径):
#!/bin/sh
# 接受文件外部传参
# 超过十的数字用括号括起来
echo "执行的文件名为$0"
echo "接受第1个参数$1"
echo "接受第2个参数$2"
echo "接受第3个参数$3"
echo "接受第4个参数$4"
echo "接受第5个参数$5"
echo "接受第6个参数$6"
echo "接受第7个参数$7"
echo "接受第8个参数$8"
echo "接受第9个参数$9"
echo "接受第10个参数${10}"
echo "接受第11个参数${11}"
echo "$# 个参数"
echo "输入的参数为$@"
echo "输入的参数作为一个字符串显示为$*"
# 输入:./demo1.sh 43 554 234 24 6 35 35 53 35 546 24
# 输出:
执行的文件名为demo1.sh
接受第1个参数43
接受第2个参数554
接受第3个参数234
接受第4个参数24
接受第5个参数6
接受第6个参数35
接受第7个参数35
接受第8个参数53
接受第9个参数35
接受第10个参数546
接受第11个参数24
11 个参数
输入的参数为43 554 234 24 6 35 35 53 35 546 24
输入的参数作为一个字符串显示为43 554 234 24 6 35 35 53 35 546 24
- $* 与 $@ 区别:
- 相同点:都是引用所有参数。
- 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,,则 " * " 等价于 “1 2 3”(传递了一个参数),而 “@” 等价于 “1” “2” “3”(传递了三个参数)。
Shell字符串
- 字符串(String)就是一系列字符的组合。字符串是 Shell 编程中最常用的数据类型之一(除了数字和字符串,也没有其他类型了)
- 字符串可以由单引号’ '包围,也可以由双引号" "包围,也可以不用引号。它们之间是有区别的。
- 三种形式的区别
- 由单引号’ '包围的字符串:
- 任何字符都会原样输出,在其中使用变量是无效的。
- 字符串中不能出现单引号,即使对单引号进行转义也不行。
- 由双引号" "包围的字符串:
- 如果其中包含了某个变量,那么该变量会被解析(得到该变量的值),而不是原样输出。
- 字符串中可以出现双引号,只要它被转义了就行。
- 不被引号包围的字符串
- 不被引号包围的字符串中出现变量时也会被解析,这一点和双引号" "包围的字符串一样。
- 字符串中不能出现空格,否则空格后边的字符串会作为其他变量或者命令解析。
- 由单引号’ '包围的字符串:
- 我们通过代码来演示一下三种形式的区别:
#!/bin/sh n=66 str1=www.aaa.com$n str2="shell \"script\" $n" str3='shell "script" $n' echo $str1 # www.aaa.com66 echo $str2 # shell "script" 66 echo $str3 # shell "script" $n
运行结果解析:
(1)str1 中包含了$n,它被解析为变量 n 的引用。
$n后边有空格,紧随空格的是 str2;Shell 将 str2 解释为一个新的变量名,而不是作 为字符串 str1 的一部分。
(2)str2 中包含了引号,但是被转义了(由反斜杠\开头的表示转义字符)。str2 中也包含了$n,它也被解析为变量 n 的引用。
(3)str3 中也包含了$n,但是仅仅是作为普通字符,并没有解析为变量 n 的引用。
- 获取字符串长度
- 在 Shell 中获取字符串长度很简单,具体方法如下:
string_name 表示字符串名字。${#string_name}
- 下面是具体的演示:
str="http://www.baidu.com" echo ${#str} # 运行结果: 20
- 在 Shell 中获取字符串长度很简单,具体方法如下:
- Shell字符串拼接(连接、合并)
- 在 Shell 中你不需要使用任何运算符,将两个字符串并排放在一起就能实现拼接,非常简单粗暴。请看下面的例子:
name="Shell" url="http://www.baidu.com" str1=$name$url #中间不能有空格 str2="$name $url" #如果被双引号包围,那么中间可以有空格 str3=$name": "$url #中间可以出现别的字符串 str4="$name: $url" #这样写也可以 str5="${name}Script: ${url}index.html" #这个时候需要给变量名加上大括号 echo $str1 # Shellhttp://www.baidu.com echo $str2 # hell http://www.baidu.com echo $str3 # Shell: http://www.baidu.com echo $str4 # Shell: http://www.baidu.com echo $str5 # ShellScript: http://www.baidu.comindex.html
- 在 Shell 中你不需要使用任何运算符,将两个字符串并排放在一起就能实现拼接,非常简单粗暴。请看下面的例子:
Shell字符串截取
-
Shell 截取字符串通常有两种方式:从指定位置开始截取和从指定字符(子字符串)开始截取。
-
从指定位置开始截取
- 这种方式需要两个参数:除了指定起始位置,还需要截取长度,才能最终确定要截取的字符串。
- 既然需要指定起始位置,那么就涉及到计数方向的问题,到底是从字符串左边开始计数,还是从字符串右边开始计数。答案是 Shell 同时支持两种计数方式。
-
从指定字符(子字符串)开始截取
- 这种截取方式无法指定字符串长度,只能从指定字符(子字符串)截取到字符串末尾。Shell 可以截取指定字符(子字符串)右边的所有字符,也可以截取左边的所有字符。
-
从指定位置开始截取
- 从字符串左边开始计数
如果想从字符串的左边开始计数,那么截取字符串的具体格式如下:
其中,string 是要截取的字符串,start 是起始位置(从左边开始,从 0 开始计数),length 是要截取的长度(省略的话表示直到字符串的末尾)。${string:start:length}
- 例如:
url="www.baiduu"
echo ${url: 2: 9} # w.baiduu
url="www.baiduu.com"
echo ${url: 2} #省略 length,截取到字符串末尾 w.baiduu.com
2) 从右边开始计数
如果想从字符串的右边开始计数,那么截取字符串的具体格式如下:
${string:start:length}
同第 1) 种格式相比,第 2) 种格式仅仅多了0-,这是固定的写法,专门用来表示从字符串左边开始计数。
- 强调两点:
- 从左边开始计数时,起始数字是 0(这符合程序员思维);从右边开始计数时,起始数字是 1(这符合常人思维)。计数方向不同,起始数字也不同。
- 不管从哪边开始计数,截取方向都是从左到右。
- 例如:
url="www.baiduu.com"
echo ${url: 0-13: 9} #
echo ${url: 0-13} #省略 length,截取到字符串末尾
- 从指定字符(子字符串)开始截取
- 使用 # 号截取右边字符
使用#号可以截取指定字符(或者子字符串)右边的所有字符,具体格式如下:
其中,string 表示要截取的字符,chars 是指定的字符(或者子字符串),*是通配符的一种,表示任意长度的字符串。*chars连起来使用的意思是:忽略左边的所有字符,直到遇见 chars(chars 不会被截取)。${string#*chars}
- 例子:
url="http://www.baidu.com/index.html"
echo ${url#*:} # //www.baidu.com/index.html
以下写法也可以得到同样的结果:
echo ${url#*p:} # //www.baidu.com/index.html
echo ${url#*ttp:} # //www.baidu.com/index.html
- 如果不需要忽略 chars 左边的字符,那么也可以不写*,例如:
echo ${url#http:} # //www.baidu.com/index.html
- 注意,以上写法遇到第一个匹配的字符(子字符串)就结束了。例如:
echo ${url#*/} # /www.baidu.com/index.html
- 如果希望直到最后一个指定字符(子字符串)再匹配结束,那该怎么写呢?
- 指定到最后一个指定字符(子字符串)再匹配结束,那么可以使用##,具体格式为:
${string#*chars}
- 例子:
echo ${url#*/} # /www.baidu.com/index.html echo ${url##*/} # index.html
2)使用 % 截取左边字符
使用%号可以截取指定字符(或者子字符串)左边的所有字符,具体格式如下:
${string%chars*}
请注意的位置,因为要截取 chars 左边的字符,而忽略 chars 右边的字符,所以应该位于 chars 的右侧。其他方面%和#的用法相同。
- 例子:
echo ${url%/*} # http://www.baidu.com
echo ${url%%/*} # http:
- 字符串操作知识点汇总
- 最后,我们对以上 8 种格式做一个汇总,请看下表:
- 最后,我们对以上 8 种格式做一个汇总,请看下表:
Shell数组
- 数组中可以存放多个值。Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小。
- 与大部分编程语言类似,数组元素的下标由 0 开始。
- Shell 数组用括号来表示,元素用"空格"符号分割开,语法格式如下:
array_name=(value1 value2 ... valuen)
- 例子:
array_name=(A B "c" D)#也可以使用数字下标来定义数组
array_name[0]=value0
array_name[1]=value1
array_name[2]=value2
- 读取数组
- 读取数组元素值的一般格式是:
${array_name[index]}
- 以下实例通过数字索引读取数组元素:
echo "第一个元素为: ${array_name[0]}" # 第一个元素为: A
echo "第二个元素为: ${array_name[1]}" # 第二个元素为: B
echo "第三个元素为: ${array_name[2]}" # 第三个元素为: C
echo "第四个元素为: ${array_name[3]}" # 第四个元素为: D
- 关联数组
- Bash 支持关联数组,可以使用任意的字符串、或者整数作为下标来访问数组元素。
- 关联数组使用 declare 命令来声明,语法格式如下:
declare -A array_name
- -A 选项就是用于声明一个关联数组。
- 关联数组的键是唯一的。
- 以下实例我们创建一个关联数组 site,并创建不同的键值:
declare -A site=(["goodle"]="www.google.com ["taobao"]="www.taobao.com")
- 我们也可以先声明一个关联数组,然后再设置键和值:
declare -A site site["google"]="www.gooogl.com" site["taobao"]="www.taobao.com"
- 访问关联数组元素可以使用指定的键
echo ${site["google"]} # www.gooogl.com
- 获取数组中的所有元素
- 使用 @ 或 * 可以获取数组中的所有元素,例如:
echo ${site[*]} # www.google.com www.taobao.com echo ${site[@]} # www.google.com www.taobao.com
- 获取数组的长度
- 获取数组长度的方法与获取字符串长度的方法相同,例如:
echo ${#site[*]} # 2 echo ${#site[@]} # 2
Shell运算符
- Shell 和其他编程语言一样,支持多种运算符,包括:
- 算数运算符
- 关系运算符
- 布尔运算符
- 字符串运算符
- 文件测试运算符
- 原生bash不支持简单的数学运算,但是可以通过其他命令来实现,例如 awk 和 expr,expr 最常用。
- expr 是一款表达式计算工具,使用它能完成表达式的求值操作。
- 例如,两个数相加(注意使用的是反引号 ` 而不是单引号 '):
a=`expr 2 + 2`
echo "两数之和为: $a" # 两数之和为: 4
-
注意:
(1)表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2,这与我们熟悉的大多数编程语言不一样。
(2)完整的表达式要被 -
算术运算符
- 下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:
- 注意:条件表达式要放在方括号之间,并且要有空格,例如: [ a = = a== a==b] 是错误的,必须写成 [ $a == $b ]。
- 下表列出了常用的算术运算符,假定变量 a 为 10,变量 b 为 20:
-
算术运算符实例:
a=10
b=20
val=`expr $a + $b`
echo "a + b : $val" # a + b : 30val=`expr $a - $b`
echo "a - b : $val" # a - b : -10val=`expr $a \* $b`
echo "a * b : $val" # a * b : 200val=`expr $b / $a`
echo "b / a : $val" # b / a : 2val=`expr $b % $a`
echo "b % a : $val" # b % a : 0if [ $a == $b ]
thenecho "a 等于 b"
fi
if [ $a != $b ]
thenecho "a 不等于 b"
fi
# a 不等于 b
- 注意:
(1)乘号()前边必须加反斜杠()才能实现乘法运算;
(2)if…then…fi 是条件语句,后续将会讲解。
(3)在 MAC 中 shell 的 expr 语法是:$((表达式)),此处表达式中的 "" 不需要转义符号 “” 。
- 关系运算符
- 关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
- 下表列出了常用的关系运算符,假定变量 a 为 10,变量 b 为 20:
- 关系运算符实例:
# 关系运算符
a=10
b=20
if [ $a -eq $b ]
thenecho "$a -eq $b : a 等于 b"
elseecho "$a -eq $b : a 不等于 b"
fi
# 10 -eq 20 : a 不等于 b
if [ $a -ne $b ]
thenecho "$a -ne $b : a 不等于 b"
elseecho "$a -ne $b : a 等于 b"
fi
# 10 -eq 20 : a 不等于 b
if [ $a -gt $b ]
thenecho "$a -gt $b : a 大于 b"
elseecho "$a -gt $b : a 不大于 b"
fi
# 10 -gt 20 : a 不大于 b
if [ $a -lt $b ]
thenecho "$a -lt $b : a 小于 b"
elseecho "$a -lt $b : a 不小于 b"
fi
# 10 -lt 20 : a 小于 b
if [ $a -ge $b ]
thenecho "$a -ge $b : a 大于或等于 b"
elseecho "$a -ge $b : a 小于 b"
fi
# 10 -ge 20 : a 小于 b
if [ $a -le $b ]
thenecho "$a -le $b : a 小于或等于 b"
elseecho "$a -le $b : a 大于 b"
fi
# 10 -le 20 : a 小于或等于 b
- 布尔运算符
- 下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:
- 下表列出了常用的布尔运算符,假定变量 a 为 10,变量 b 为 20:
- 布尔运算符实例:
# bool运算符 !-o -a
a=10
b=20
if [ $a -gt 11 -o $b -lt 25 ];
thenecho "a大于11 或 b小于25 为真"
elseecho "两个都为假"
fi
# 返回 True
if [ $a -gt 11 -a $b -lt 25 ];
thenecho "a大于6 并 b小于25 为真"
elseecho "其中一个为假"
fi
# 返回 Flase
- 逻辑运算符
- 以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:
- 以下介绍 Shell 的逻辑运算符,假定变量 a 为 10,变量 b 为 20:
- 逻辑运算符实例:
# 逻辑运算符 && ||
a=10
b=20
if [[ $a -gt 6 && $b -lt 21 ]];
thenecho "两者都为真"
elseecho "存在假货"
fi
# 返回 True
- 字符串运算符
- 下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”
- 下表列出了常用的字符串运算符,假定变量 a 为 “abc”,变量 b 为 “efg”
- 实例:
# 字符串运算符 = != -n -z $
if [ -n "$cc" ];
thenecho "$cc"echo "cc变量不为空"
elseecho "cc变量为空"
fi
- 文件测试运算符
- 文件测试运算符用于检测 Unix 文件的各种属性。
- 属性检测描述如下:
- 实例:
dirs='/root/aa'
if [ -d $dirs ];
thenecho "$dirs 是一个文件夹"
elseecho "$dirs 不是一个文件夹"
fifiles='./demo2.sh'
if [ -f $files ];
thenecho "$files 是一个文件"
elseecho "$files 不是一个文件"
fiif [ -x $files ];
thenecho "$files 是一个可执行文件"
elseecho "$files 不是一个可执行文件"
fi