现在我们使用的操作系统(Windows、Mac os、Android、IOS等)都是带图形化界面的,简单直观,容易上手,对专业用户(程序员、工程师等)和普通用户都非常适用;计算机离不开图形界面。
然而现在计算机的早期并没有图形界面,我们只能通过一个一个的命令来控制计算机,这些命令有成百上千个之多,且不说记住这些命令非常困难,每天面对没有任何色彩的“黑屏”本身就是一件枯燥的事情;这个时代的计算机还远远谈不上炫酷和普及,只有专业人员才能使用
根据运行方式:
编译运行:源代码 --> 编译器 --> 二进制文件(程序文件)
解释运行:源代码 --> 运行时启动解释器,由解释器边解释边运行
根据其编程过程中功能的实现是调用库还是调用外部的程序文件:
shell脚本编程:利用系统上的命令及编程组件进行编程
完整编程:利用库或编程组件进行编程
编程模型:过程式编程语言,面向对象的编程语言
程序:指令+程序
过程式编程语言:以指令为中心来组织代码,数据是服务于代码
顺序执行
选择执行
循环执行
代表:c,bash,Python
对象式编程:以数据为中心来组织代码,围绕数据来组织指令
类(class):实例化成为对象,method
代表:Java,c++,Python
几乎所有编程语言的教程都是从著名的“Hello World”开始的,出于这种传统的尊重(或者说落入俗套),我们第一个脚本也输出“Hello World”。
打开文本编辑器,新建一个文本文件,命名为test.sh
在test.sh中输入代码:
#!/bin/bash
echo "Hello World" #这是一条语句
第1行的 #! 是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪种shell;后面的 /bin/bash 就是指明了解释器的具体位置
第2行的 # 及其后面的内容是注释。shell脚本中所有以 # 开头的都是注释(当然以 #! 开头的除外)。写脚本的时候,多写注释是非常有必要的,以方便其他人能看懂你的脚本,也方便后期自己维护时看懂自己的脚本----实际上,即便是自己写的脚本,在经过一段时间后也很容易忘记。
下面写出了一段稍微复杂的shell脚本:
#!/bin/bash
# Copyright (c)http://c.biancheng.net/shell/
echo "What is your name?"
read NAME
echo "Hello,$NAME"
在第4行中表示终端读取用户输入的数据,并将数据赋值给NAME变量。read命令用来从标准输入文件(standard input,stdin,一般就是指键盘)读取用户输入的数据。
第6行表示输出变量NAME的内容。注意在变量名前边要加上$,否则变量名会作为字符串的一部分处理
上面我们编写了一个简单的shell脚本,现在我们让它运行起来。
运行shell脚本有两种方法,一种在新进程中运行,一种是当前shell进程中运行
在新进程中运行shell脚本有多种方法。
(1)将shell脚本作为程序运行
shell脚本也是一种解释执行的程序,可以在终端直接调用(需要使用 chmod 命令给shell脚本加上执行权限),如下所示:
[root@centos7 ~]#cd /tmp/ #切换到 test.sh 所在的目录
[root@centos7 tmp]#chmod +x test.sh #给脚本文件执行权限
[root@centos7 tmp]#./test.sh #执行脚本文件
Hello world #运行结果
第2行,chmod +x 表示给 test.sh 增加执行权限
第3行中 ./ 表示当前目录,整条命令的意思是执行当前目录下的 test.sh 脚本。如果不写 ./,Linux会到系统路径(由 PATH 环境变量指定)下查找 test.sh ,而系统路径下显然不存在这个脚本,所以会执行失败。
注意:通过这种方式运行脚本,脚本文件第一行的 #!/bin/bash 一定要写对,好让系统查找到正确的解释器
(2)将 shell 脚本作为参数传递给 bash 解释器
你也可以直接运行 bash 解释器,将脚本文件的名字作为参数传递给 bash ,如下所示:
[root@centos7 ~]#cd /tmp/ #切换到 test.sh 所在的目录
[root@centos7 tmp]#/bin/bash test.sh #使用 bash 的绝对路径
Hello World #运行结果
注意:通过这种方式运行脚本,不需要在脚本文件第一行指定解释器信息,指了也没用。
要更加简洁的写法是运行 bash 命令。bash 是一个外部命令,shell 会在 /bin 目录中找到对应的应用程序,也即是 /bin/bash。
[root@centos7 tmp]#bash test.sh
Hello World
这两种写法在本质上是一样的;第一种写法给出了绝对路径,会直接运行 bash 解释器;第二种写法通过 bash 命令找到 bash 解释器所在的目录,然后再运行,只不过多了一个查找的过程而已。
可能会有一些疑惑,怎么才能知道开启了新进程呢?既然如此,那就验证一下吧。
Linux 中的每一个进程都有一个唯一的 ID,称为 PID ,使用 $$ 变量就可以获取当前进程的 PID。$$ 是 shell 中的特殊变量。
首先编写如下脚本,并命名为 check.sh
#!/bin/bash
echo $$
然后使用以上两种方式来运行 check.sh
[root@centos7 tmp]#echo $$
1438 #当前进程的PID
[root@centos7 tmp]#chmod +x check.sh
[root@centos7 tmp]#./check.sh
1480 #新进程PID
[root@centos7 tmp]#echo $$
1438 #当前进程PID
[root@centos7 tmp]#/bin/bash check.sh
1481 #新进程PID
进程 PID 都不一样,当然就是两个进程了
这里需要引入一个新命令----source 命令。source 是 shell
内置命令的一种,它会读取脚本文件中的代码,并依次执行所有语句。你也可以理解为,source
命令会强制执行脚本文件中的全部命令,而忽略脚本文件的权限。
source 命令的用法:
source FILENAME
可简写为 . FILENAME
两种写法效果相同。对于第二种写法,注意点号 . 和文件名中间有一个空格
例如,使用 source 运行上节的 test.sh
[root@centos7 tmp]#source test.sh #使用source
Hello World
[root@centos7 tmp]#. test.sh #使用点号
Hello World
使用 source 命令不用给脚本增加执行权限
检测是否在当前 shell 进程中
我们仍然借助 $$ 变量来输出进程的 PID ,如下所示:
[root@centos7 tmp]#echo $$
1438 #当前进程 PID
[root@centos7 tmp]#source test.sh
1438 #shell脚本所在进程 PID
[root@centos7 tmp]#. test.sh
1438 #shell脚本所在进程 PID
进程 PID 都是一样的,当然是同一个进程了。
注意:脚本中的空白行会被解释器忽略;脚本中,除了shebang,余下所有以 # 开头的行,都会被视作注释行而被忽略,此即为注释行;shell 脚本的运行是通过运行一个子 shell 进程实现的
作为初学者,可能看不懂这些运行方式有什么区别,没关系,暂时留个疑问吧
如果需要在新进程中运行 shell 脚本,我一般使用 bash test.sh 这种写法;如果在当前进程中运行 shell 脚本,我一般使用 . test.sh 这种写法。这是我个人风格。
最后演示一个脚本;本例中使用 read 命令从键盘读取用户输入的内容并赋值给 NAME 变量,最后再显示器上输出
#!/bin/bash
echo "what is your name"
read NAME
echo "Hello,${NAME}_Mr"
运行脚本:
[root@centos7 tmp]#. test.sh
what is your name
xingxinchao #运行脚本会让你输入内容,然后按下回车
Hello,xingxinchao_Mr #运行结果
变量是任何一种编程语言都必不可少的组成部分,变量用来存放各种数据。脚本语言在定义变量时通常不需要指明类型,直接赋值就可以,shell 变量也遵循这个规则。
在bash shell 中,每一个变量的值默认都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储,
这意味着,bash shell 在默认情况下不会区分变量类型,即使你将整数和小数赋值给变量,它们也会被视作为字符串,这一点和大部分的编程语言不同。例如C语言或者C++中,变量分为整数、小数、字符串、布尔等多种类型。
当然,如果有必要,你也可以使用 shell declare 关键字显示定义变量的类型,但在一般情况下没有这个需求,shell开发者在编写代码时自行注意值的类型即可
变量赋值:shell支持以下三种定义变量的方式:
VAR_NAME=VALUE
VAR_NAME='VALUE'
VAR_NAME="VALUE"
注意:赋值号 = 的周围不能有空格,这可能和大部分其它编程语言不一样。
shell 变量的命名规范和大部分编程语言都一样:
1.变量名由数字、字母、下划线组成
2.必须以字母或者下划线开头;不能以数字开头
3.不能使用 shell 里的关键字(通过 help 命令可以查看保留关键字)
示例:NAME=xingxinchao
echo $NAME
VAR_NAME 是变量名,VALUE 是赋给变量的值。如果 VALUE 不包含任何空白符(例如空格、tab 缩进等),那么可以不使用引用;如果 VALUE 包含了空白符,那么就必须使用引号引起来。使用单引号和双引号也是有区别的,稍后我们会详细说明。
注意:赋值号 = 的周围不能有空格,这可能和大部分其它编程语言不一样。
使用一个定义过的变量,只要在变量名前面加美元符号 $ 即可,如:
[root@centos7 ~]#NAME=xing_mr
[root@centos7 ~]#echo $NAME
xing_mr
[root@centos7 ~]#echo ${NAME}
xing_mr
变量名外面的花括号 { } 是可选的,加不加都行,花括号是为了帮助解释器识别变量的边界,比如下面这种情况:
[root@centos7 ~]#skill="shell"
[root@centos7 ~]#echo "I am good at ${skill} Script"
I am good at shell Script
如果不给 skill 变量加花括号,写成 echo “I am good at $skillScript”,解释器就会把 $skillScript 当成一个变量(其值为空),代码执行结果就不是我们期望的样子了。
推荐给所有变量加上花括号 { } ,这是个良好的编程习惯
已定义的变量,可以被重新赋值,如:
[root@centos7 ~]#url="www.xingxinchao.com"
[root@centos7 ~]#echo ${url}
www.xingxinchao.com
[root@centos7 ~]#url="https://www.baidu.com"
[root@centos7 ~]#echo ${url}
https://www.baidu.com
第二次对变量赋值时不能在变量名前加 $,只有在使用变量时才能加 $
前面我们还有一个疑问,定义变量时,变量的值可以由单引号 ’ ’ 引起来,也可以由双引号 " "
引起来,它们到底有什么区别呢?不妨以下面的代码为例来说明:
编辑脚本 url.sh;以下是脚本内容
#!/bin/bash
url="https://www.xingxinchao.com"
website1='Dashen Chinese website: ${url}'
website2="Dashen Chinese website: ${url}"
echo ${website1}
echo ${website2}
运行结果:
[root@centos7 ~]#source url.sh
Dashen Chinese website: ${url}
Dashen Chinese website: https://www.xingxinchao.com
单引号:’ ’ ;称为强引用;即单引号里面是什么就输出什么,即使内容中有变量和命令(命令需要反引起来)也会把它们原样输出。这种方式比较适合定义显示纯字符串的情况,即不希望解析变量、命令等的场景。
双引号:" " ;称为弱引用;即输出时先解析里面的变量和命令,而不是把双引号中的变量名和命令原样输出。这种方式比较适合字符串中附带有变量和命令并且想将其解析后再输出的变量定义。
建议:如果变量的内容是数字,那么可以不加引号;如果真的需要原样输出就加单引号;其他没有特别要求的字符串等最好都加上双引号,定义变量时加双引号是最常见的使用场景
shell 也支持将命令的执行结果赋值给变量,常见的有以下两种方式:
VAR=`COMMAND`
VAR=$(COMMAND)
第一种方式把命令反引号 (位于 ESC 键的下方),反引号和单引号非常相似,容易产生混淆,所以不推荐使用这种方式
第二种方式把命令用 $() 包围起来,区分更加明显,所以推荐使用这种方式。
例如:我在 /tmp 目录中创建了一个名为 log.txt 的文本文件,用来记录我的日常工作。下面的代码中,使用 cat 命令将了 log.txt 的内容读取出来,并赋值给一个变量,使用 echo 命令输出
[root@centos7 tmp]#cat log.txt #查看文件 log.txt 内容
邢新朝正在编写shell
[root@centos7 tmp]#echo ${log} #再没有定义log变量的时,log为空
[root@centos7 tmp]#log=$(cat log.txt) #引用命令结果赋值给log变量
[root@centos7 tmp]#echo ${log}
邢新朝正在编写shell
使用 readonly 命令 或者 declare -r 命令可以将变量定义为只读变量,只读变量的值不能被改变。
示例:
[root@centos7 tmp]#declare -r NAME=xing_mr
[root@centos7 tmp]#echo ${NAME}
xing_mr
[root@centos7 tmp]#NAME=xingxinchao
bash: NAME: 只读变量
使用 unset 命令可以删除变量。
语法:unset VAR_NAME
变量被删除后不能再次被使用;unset 命令不能删除只读变量
shell 变量的作用域(scope),就是 shell 变量的有效范围(可以使用的范围)
比如,在不同的作用域中,同名的变量不会相互干涉,就好像 A 班有个叫小明的同学,B
班也有个叫小明的同学,虽然他们都叫小明(对应于变量名),但是由于所在的班级(对应于作用域)不同,所以不会造成混乱。但是如果同一个班级中有两个叫小明的同学,就必须用类似于“大小明”、“小小明”这样的命名区分他们。
1、局部变量(local variable):作用域仅为某代码片段(函数上下文);变量只能在函数内部使用
2、全局变量(本地变量):作用域仅为当前 shell 进程;变量只能在当前 shell 进程中使用
3、环境变量(environment variable):作用域仅为当前 shell 及其子进程;变量可以还可以在子进程中使用
4、位置参数变量:向执行脚本的 shell 进程传递的参数
5、特殊变量:shell 内置的有特殊功用的变量
本地变量:作用域仅为运行脚本的shell进程的生命周期;因此,其作用范围为当前shell脚本程序文件
变量赋值:NAME=VALUE
变量引用:${NAME},$NAME
' ':单引号是强引用;变量名不会替换为其值
" ":双引号是弱引用;变量名会替换为其值
查看变量:set 命令
撤销变量:unset NAME #注意:此处非变量引用
环境变量:
变量赋值:
(1)export NAME=VALUE
(2)NAME=VALUE
export NAME
(3)declare -x NAME=VALUE
(4)NAME=VALUE
declare -x NAME
变量引用:${NAME},$NAME
注意:bash内嵌了许多环境变量(通常为大写字符),用于定义bash的工作环境
例如:PATH/HISTFILE/HISTSIZE/HISTFILESIZE/HISTCONTROL/SHELL/HOME/UID/PWD
查看环境变量:
(1)export
(2)declare -x
(3)printenv
(4)env
撤销环境变量: unset NAME
局部变量:作用域是函数的生命周期;在函数结束时被自动摧毁(注意:在函数中定义的局部变量不会覆盖本在地变量中与局部变量同名的值)
变量赋值:
local NAME #注意:仅适用于函数中定义
只读变量:
(1)declare -r NAME
(2)readonly NAME
只读变量无法重新赋值,并且不支持撤销;存活时间为当前 shell 进程的生命周期,随 shell 进程终止而终止
bash中我们可以进行算数运算
shell中常用的算数运算符如下所示
+:对两个变量做加法
-:对两个变量做减法
*:对两个变量做乘法
/:对两个变量做除法
%:取模运算,第一个变量除以第二个变量的余数
**:对两个变量做幂运算
在shell的算数运算中有以下几种方式
名称 | 语法 | 示例 |
---|---|---|
算数扩展 (( )) | VAR=$((算数表达式)) | A=$((1+2)) |
使用 [ ] | VAR=$[算数表达式] | A=$[1+2] |
使用内置命令 declare | declare -i VAR=算数表达式 | declare -i A=1+2 |
使用内置命令 let | let VAR=算数表达式 | let A=1+2 |
使用内置命令 expr | VAR=`expr 算数表达式` | A=`expr 1+2` |
注意事项:用expr表示后面的表达式为一个运算。需要注意的是,` 并不是一个单引号,而是 “tab” 键上面的那个符号,表达式和运算符之间要有空格,例如 2+2 是不对的,必须写成 2 + 2.
用 let 表示后面的表达式为一个数学运算。表达式中的变量前不必有$,如果表达式中包含了空格或其他特殊字符,则必须引起来.
# COMMAND1;COMMAND2;COMMAND3;COMMANDN;.......
逻辑运算:
运算数:真或假
(1)真:true,yes,on,1
(2)假:false,no,off,0
与运算:&&
(1)1 && 1 = 1
(2)1 && 0 = 0
(3)0 && 1 = 0
(4)0 && 0 = 0
或运算:||
(1)1 || 1 = 1
(2)0 || 1 = 1
(3)1 || 0 = 1
(4)0 || 0 = 0
非运算:!
!1 = 0
!0 = 1
短路法则:
# COMMAND1 && COMMAND2
COMMAND1为“假”,则COMMAND2不会执行
否则。COMMAND1为“真”,则COMMAND2必须执行
# COMMAND1 || COMMAND2
COMMAND1为“真”,则COMMAND2不会再执行
否则,COMMAND1为“假”,则COMMAND2必须执行
判断某需求是否满足,需要由测试机制来实现
如何编写测试表达式以实现所需的测试
(1) 执行命令,并利用命令状态返回值来判断
0:成功
1-255:失败
(2) 测试表达式
test EXPRESSION
[ EXPRESSION ]
[[ EXPRESSION ]]
注意:EXPRESSION 前后两端必须有空格,否则为语法错误
bash的测试类型:
1.数值测试
2.字符串测试
3.文件测试
数值测试:数值比较
-eq:是否等于;[ $num1 -eq $num2 ]
-ne:是否不等于
-gt:是否大于,表示左侧是否大于右侧
-ge:是否大于等于
-lt:是否小于
-le:是否小于等于
字符串测试:
==:是否等于
>:是否大于
<:是否小于
!=:是否不等于
~=:左侧字符是能够被右侧的 PATTERN(模式)所匹配
-z "STRING":判断指定的字串是否为空,空则为真,不空则假
-n "STRING":判断指定的字符串是否不空,不空则真,空则为假
注意:(1)字符串要加引号(2)字符串比较建议使用双中括号
文件测试:
存在性测试:
-a FILE
-e FILE
文件的存在性测试,存在则为真,否则则为假
存在性及文件类型测试:
-b FILE:是否存在并且为块设备文件
-c FILE:是否存在并且为字符设备文件
-d FILE:是否存在并且为目录文件
-f FILE:是否存在并且为普通文件
-h FILE 或 -L FILE:是否存在并且为符号链接文件
-p FILE:是否存在并且为命名管道文件
-S FILE:是否存在并且为套接字文件
文件权限测试:
-r FILE:是否存在并且对当前用户可读
-w FILE:是否存在并且对当前用户可写
-x FILE:是否存在并且对当前用户可执行
文件特殊权限:
-u FILE:是否存在并且拥有 SUID 权限
-g FILE:是否存在并且拥有 SGID 权限
-k FILE:是否存在并且拥有 sticky 权限
文件是否有内容:
-s FILE:是否有内容,有内容为真,否则则为假
双目测试:
FILE1 -ef FILE2:FILE1与FILE2是否为指向同一个文件系统的相同inode的硬链接
FILE1 -nt FILE2:FILE1是否新于FILE2
FILE1 -ot FILE2:FILE1是否旧于FILE2
组合测试条件:
逻辑运算:
第一种方式:
COMMAND1 && COMMAND2
COMMAND1 || COMMAND2
!COMMAND
第二种方式:
EPRESSION1 -a EXPRESSION2
EPRESSION1 -o EXPRESSION2
!EPRESSION
默认是脚本中执行的最后一条命令的状态返回值
自定义状态退出状态码
exit [n]:n为自己指定的状态码
注意:shell 进程遇到 exit 时,即会终止,因此整个脚本执行即为结束
我们可以在执行 shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$n。n代表一个数字,1为执行脚本的第一个参数,2为执行脚本的第二个参数,以此类推...
单分支 if 语句
第一种 if 语句语法如下:
if <条件表达式>;then
代码分支
fi
第二种 if 语句语法如下:
if <条件表达式>
then
代码分支
fi
双分支 if 语句格式:
if <条件表达式>;then
<条件为真时执行的分支>
else
<条件为假时执行的分支>
fi
多分支 if 语句结构
if <条件表达式>;then
<分支1>
elif <条件表达式>;then
<分支2>
else
<分支3>
for循环格式:
for VARIBLE in LIST;do
循环体
done
for循环的特殊用法:
for ((控制变量初始化;条件判断表达式;控制变量修正表达式;))
循环体
done
控制变量初始化:仅在循环代码开始运行时执行一次
控制变量的修正语句:每轮循环结束会先进行控制变量修正运算,而后再做条件判断
while循环格式:
while CONDITION;do
循环体
循环体控制变量修正表达式
done
进入while循环条件:CONDITION测试为“真”
退出while循环条件:CONDITION测试为“假”
while循环的特殊用法(遍历文件的行)
while read VARIABEL; do
循环体
doen < /PATH/TO/SOMEFILE
依次读取/PATH/FORM/SOMEFILE文件中的每一行,且将其赋值给VARIABLE变量
until循环格式:
until CONDITION;do
循环体
循环控制变量修正表达式
done
进入until循环条件:CONDITION测试为“假”
退出until循环条件:CONDITION测试为“真”
case $VARIBLE in
PAT1)
分支1
;;
PAT2)
分支2
;;
...
*)
分支n
;;
esac
case:支持glob风格的通配符
*:任意长度的任意字符
?:任意单个字符
[]:范围内任意单个字符
a|b:a或b
过程式编程:
代码重用
模块化编程
结构化编程
一段独立功能的代码当作一个整体,并为之取一个名字;命名的代码段;此即为函数
注意:定义函数的代码段不会自动执行,只有被调用时被执行;所谓调用函数,在代码中给定函数名即可
函数名出现的任何位置,在代码执行时,都会被自动替换为函数代码
语法一:
function F_NAME {
...函数体...
}
语法二:
f_name() {
...函数体...
}
函数的生命周期:每次被调用创建函数,返回时终止
其状态返回结果为函数体中运行的最后一条命令的状态结果
自定义状态返回值,需要使用:return
rerun [0-255]
0:成功
1-255:失败
函数的返回值:
函数的执行结果返回值:
(1)使用echo或printf命令进行输出
(2)函数体中调用的命令的执行结果
函数的退出状态码:
(1)默认取决于函数体中最后一条命令的退出状态码
(2)自定义:return
函数可以接受参数:
传递参数给函数时:
在函数体当中,可以直接使用$1,$2,...引用传递给函数的参数;还可以在函数中使用$*或$@引用所有参数,$#引用传递参数的个数
在调用函数时,在函数名后面以空白符分隔给定参数列表即可,例如 testfunc arg1 arg2 arg3
函数递归:函数直接或间接调用自身
1 #!/bin/bash
2 #
3 fact() {
4 if [ $1 -eq 0 -o $1 -eq 1 ];then
5 echo 1
6 else
7 echo $[ $1*$( fact $[$1-1])]
8 fi
9 }
10
11 fact $1
循环进入条件:
for:列表元素非空
while:条件测试结果为真
unit:条件测试结果为假
退出条件:
for:列表元素遍历完成
while:条件测试结果为假
unit:条件测试结果为真
循环控制语句之continue:
continue:提前结束本轮循环,而直接进入下一轮循环的条件测试
while CONDITION1; do
COMD1
...
if CONDITION2; then
continue
fi
COMDn
...
done
提前跳出循环(break)
break:提前跳出循环
while CONDITION1; do
CMD1
...
if CONDITION2; then
break
fi
done
while true; do
循环体
done
退出条件:某个测试条件满足时,让循环体执行break命令
功用:延迟时间;常用于在shell脚本中延迟时间(将目前动作延迟一段时间)
格式:sleep [--help] [--version] number[smhd]
示例:
sleep 5 #默认单位为秒
sleep 5m #延迟5分钟
h:小时
d:天
用户交互:通过键盘输入数据,从而完成变量赋值操作
read [OPTION]... [NAME]...
选项:
-p "PROMPT"
-t "TIMEOUT"
#!/bin/bash
#
read -p "Enter username:" name
[ -z "$name" ] && echo "a username is needed." && exit 2
read -p "Enter password for $name,[password]:" password
[ -z "$password" ] && password="password"
if id $name &> /dev/null; then
echo "$name exists."
else
useradd $name
echo "$password" | passwd --stdin $name &> /dev/null
echo "Add user $name finished."
fi
程序:指令+数据
指令:命令
数据:变量、文件
变量:存储单个元素的内存空间
数组:存储多个元素的连续的内存空间
数组名:整个数组只有一个名字
数组索引机制:编号从0开始
数组名[索引]
引用数组:${array_name[index]}
注意:bash-4及以后的版本,支持自定义索引格式,而不仅仅是0,1,2,3,...数字格式;此类数组称之为“关联数组”
声明数组:
declare -a NAME:声明一个索引数组
declare -A NAME:声明一个关联数组
数组中元素的赋值方式:
(1)一次只赋值一个元素
array_name[index]=value
(2)一次赋值多个元素
array_name=("value1" "value2" "value3" ...)
注意:bash支持稀疏格式的数组
(4)read -a array_name
read -a array_name
"value0" "vlaue2" "value3" ...
注意:value之间需要空格
引用数组中的元素:${array_name[index]}
注意:引用时,只给数组名,表示引用索引为0的元素
数组的长度(数组中元素的个数)
${#array_name[*]}
${#array_name[@]}
引用数组中的所有元素
${array_name[*]}
${array_name[@]}
数组元素切换
${array[@]:offset:number}
offset:要跳过的元素个数
number:要取出的元素个数;省略number时,表示取偏移量之后的所有元素
向非稀疏格式数组中追加元素
array_name[${#array_name[*]}]=VALUE
删除数组中的某元素
unset array_name[index]
关联数组:
declare -A array_name
array_name=([index_name="VALUE1" [index_name2]="VALUE2" ...)
字符串切片:
${var:offset}:var中,跳过前offset个字符,取其后的所有字符
${var:offset:number}:var中,跳过前offset个字符,取其后number个字符
${name: -length}:var中,取最后length个字符(注意冒号后需要空格)
letngth:是数字
基于模式取变量中国字符
${var#*word}:其中word是指定分隔符;功能:自左而右,查找var变量中所存储的字符串中,第一次出现的word分隔符,删除字符串开头至此分隔符之间的所有字符
${var##*word}:其中word是指定分隔符;功能:自左而右,查找var变量中所存储的字符串中,最后一次出现的word分隔符,删除字符串开头至此分隔符之间的所有字符
示例:mypath="/etc/init.d/functions"
${mypath##*/}:functions
${mypath#*/}:etc/init.d/functions
${var%word*}:其中word是指定分隔符;功能:自右而左,查找var变量中所存储的字符串中,第一次出现的word分隔符,删除字符串尾部至此分隔符之间的所有字符
${var&&word*}:其中word是指定分隔符;功能:自右而左,查找var变量中所存储字符串中,最后一次出现的word分隔符,删除字符串尾部至此分隔符之间的所有字符
示例:mypath="/etc/init.d/functions"
${mypath%/*}:/etc/init.d
url=http://www.xingxinchao.com:80
${url%:*}:http://www.xingxinchao.com
${url%%:*}:http
查找替换:
${var/PATTERN/SUBSTI}:查找var所表示的字符串中,第一次被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串
${var//PATTERN/SUBSTI}:查找var所表示的字符串中,所有被PATTERN所匹配到的字符串,并将其全部替换为SUBSTI所表示的字符串
${var/#PATH/SUBSTI}:查找var所表示的字符串中,行首被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串
${var/%PATH/SUBSTI}:查找var所表示的字符串中,行尾被PATTERN所匹配到的字符串,将其替换为SUBSTI所表示的字符串
注意:PATTERN中使用glob风格的通配符;不支持正则表达式
查找删除
${var/PATTERN}:查找var所表示的字符串中,删除第一次被PATTERN所匹配的字符串
${var//PATTERN}:查找var所表示的字符串中,删除所有被PATTERN所匹配到的字符串
${var/#PATTERN}:查找var所表示的字符串中,删除开头被PATTERN所匹配到的字符串
${var#%PATTERN}:查找var所表示的字符串中,删除尾部被PATTERN所匹配到的字符串
字符大小写转换:
${var^^}:把var中的所有小写字母转换为大写
${var,,}:把var中的所有大写字母转换为小写
变量赋值判断
${var:-VALUE}:如果var变量为空,或未设置;那么返回VALUE;否则,则返回var变量的值、
${var:=VALUE}:如果var变量为空,或未设置;那么返回VALUE;并将VALUE赋值给var变量;否则,则返回var变量的值
${var:+VALUE}:如果var变量不空,则返回VALUE
${var:?ERROR_INFO}:如果var变量为空,或未设置,则返回EEROR_INFO为错误信息提示;否则则返回var的值
bash -n /PATH/TO/SOME_SCRIPT
bash -x /PATH/TO/SOME_SCRIPT