#!
是一个约定的标记,它告诉系统这个脚本需要什么解释器来运行,即:使用哪一种 shell。#!
被称为,例如使用
新建一个 test.sh 的文件,内容如下:
第一种方式:作为可执行程序
1、当前 test.sh 是没有可执行权限的,首先使脚本文件具有执行权限。
# 使脚本文件具有执行权限
# 执行脚本,需注意要加目录的标识
# 也可以用 source 来执行脚本,跟上面的写法是等价的,但是不需要脚本具有执行权限
是找不到命令的,要用./test.sh 告诉系统,就在当前目录找。
通过这种方式运行 bash 脚本,第一行一定要写对,好让系统(Shell 程序)查找到正确的解释器。如果是使用标准默认的 shell,可以省去第一行。
第二种方式:作为解释器参数
直接运行解释器,其参数就是 Shell 脚本的文件名。
这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用。
- 单行注释:以
#
开头,到行尾结束。
如果有段代码要频繁的注释和取消注释,可以用花括号括起来,定义成一个函数,没有地方调用这个函数,这块代码就不会执行,达到了和注释一样的效果。
- 局部变量:局部变量是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。
-
环境变量:环境变量是从父进程中继承而来的变量,对当前 Shell 会话内所有的程序和脚本都可见。创建它们跟创建局部变量类似,但使用的是
export
关键字,shell 脚本也可以定义环境变量。 - shell 变量(系统变量):shell 变量是由 shell 程序设置的特殊变量。shell 变量中有一部分是环境变量,有一部分是局部变量,这些变量保证了 shell 的正常运行。
可以使用等号操作符为变量赋值:varName=value
,varName 是变量名,value 是赋值给变量的值。
- 首字母必须为字母(a-z,A-Z),剩下的部分只能使用英文字母,数字下划线
- 中间不能有空格,可以使用下划线,如果有空格,必须使用单引号或双引号
注意:varName=value
的等号两边没有空格,变量值如果有空格,需要用引号包住。
访问变量的语法形式为:${varName}
和$varName
,变量名外面的花括号是可选的,加不加都行,加花括号是为了帮助解释器识别变量的边界(推荐加花括号)。
因为 Shell 使用空白字符来分隔单词,所以上面的例子中需要加上花括号来告诉 Shell 这里的变量名是 fruit,而不是 fruits
注意:使用单引号时,变量不会被扩展(expand),仍依照原样显示。这意味着echo '$var'
会显示$var
。使用双引号时,如果$var
已经定义过,那么echo "$var"
会显示出该变量的值,如果没有定义过,则什么都不显示。
使用 readonly 命令可以将变量定义为只读变量,只读变量的值不能被改变
使用 unset 命令可以删除变量,变量被删除后不能再次使用。unset 命令不能删除只读变量
Shell 特殊变量(系统变量)
上面讲过变量名的命名规则,但是还有一些包含其他字符的变量有特殊含义,这样的变量被称为特殊变量。
传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是\$1 |
传递给脚本或函数的参数个数 |
传递给脚本或函数的所有参数 |
传递给脚本或函数的所有参数,被双引号("")包含时,与\$\*稍有不同 |
函数名称(仅在函数内值) |
上个命令的退出状态,或函数的返值 |
显示 shell 使用的当前选项(flag),后面扩展中检测是否为交互模式时会用到 |
当前 Shell 进程 ID。对于 Shell 脚本,就是这些脚本所在的进程 ID |
最后一个后台运行的进程 ID 号 |
命令行参数:运行脚本时传递给脚本的参数成为命令行参数,命令行参数用 $n 表示。第一个命令行参数:Linux 第二个命令行参数:Shell
$?
可以获取上一个命令的退出状态。所谓退出状态,就是上一个命令执行后的返回结果。退出状态是一个数字,一般情况下,大部分命令执行成功会返回 0,失败会返回 1。$?
也可以表示函数的返回值。
shell 字符串可以使用单引号 ''
,也可以使用双引号""
, 也可以不用引号。
- 单引号:不识别变量,单引号中间不能出现单独的单引号(使用转义字符转义也不行),可以成对出现实现字符串拼接。
- 双引号:可以识别变量,双引号中可以出现用转义字符转义的双引号
设置一个字符串变量,下面的都是对这个变量的操作
${#var}:获得变量值的长度
${varx}:通过索引位置截取子字符串
#此时 var 还是没有定义,所以输出:var is${var:?message}:如果变量 var 为空、没有定义或者已被删除,那么将消息 message 送到标准错误输出。
可以用来检测变量 var 是否可以被正常赋值。若此替换出现在 shell 脚本中,那么脚本将停止运行。
数组是可以存储多个值的变量,这些值可以单独引用,也可以作为整个数组来引用。数组的下标从 0 开始,下标可以是整数或算数表达式,其值应该大于等于 0。
${colors[*]}
和${colors[@]}
有些细微的差别,在将数组中的每个元素单独一行输出的时候,有没有被引号包住会有不同的差别,在引号内,${colors[@]}
将数组中的每个元素扩展为一个单独的参数,数组元素中的空格得以保留。
# :0:2 去除数组中从0开始,长度为2的数组元素
Shell 中有很多运算符,包括算数运算符、关系运算符、布尔运算符、字符串运算符和文件测试符。
原生 bash 不支持简单的数学运算,较为常用的是借助expr
来实现数学运算。
算数运算符列表,变量 a 是 10 变量 b 是 50
a=$b 就是正常的变量赋值
|
- 表达式和运算符之间要有空格,例如
1+1
是错误的,必须写成1 + 1
- 完整的表达式要用反引号 ` 包住
- 条件表达式要放在方括号之间,并且要有空格,例如
[${a}==${b}]
是错误的,必须写成[ ${a} == ${b} ]
条件运算符(关系运算符)
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
关系运算符列表,变量 a 是 10 变量 b 是 50
检测两个数是否相等,相等返回 true |
检测两个数是否不相等,不相等返回 true |
检测左边的数是否大于右边的数,如果是,返回 true |
跟 -gt 一样,不过因为兼容性问题,可能要在 [[]] 表达式中使用 |
检测左边的数是否小于右边的数,如果是,返回 true |
跟 -lt 一样,不过因为兼容性问题,可能要在 [[]] 表达式中使用 |
检测左边的数是否大于等于右边的数,如果是,返回 true |
检测左边的数是否小于等于右边的数,如果是,返回 true |
条件运算符(布尔运算符、逻辑运算符、字符串运算符)
跟-a 类似,逻辑的 AND,不过需要使用 [[]] 表达式
|
检测两个数字或字符串是否相等,相等返回 true |
检测两个数字或字符串是否相等,不相等返回 true |
相等。比较两个数字或字符串,如果相等返回 true(不推荐使用,有兼容性问题) |
检测字符串长度是否为 0,为 0 返回 true |
检测字符串长度是否为 0,不为 0 返回 true |
检测变量是否存在或不为空,存在或不为空返回 true |
文件目录判断运算符列表
判断文件是否存在,当 filename 存在且是正规文件时(既不是目录,也不是设备文件)返回 true |
判断目录是否存在,当 pathname 存在且是目录时返回 true |
判断【某个东西】是否存在,当 pathname 指定的文件或目录存在时返回 true |
同上,已经过时,而且使用的时候还有另外一个与的逻辑,容易混淆 |
判断是否是一个非空文件,当 filename 存在并且文件大小大于 0 时返回 true |
判断是否可读,当 pathname 指定的文件或目录存在并且可读时返回 true |
判断是否可执行,当 pathname 指定的文件或目录存在并且可执行时返回 true |
判断是否可写,当 pathname 指定的文件或目录存在并且可写时返回 true |
判断是否是一个块文件,当 filename 存在且是块文件时返回 true |
判断是否是一个字符文件,当 filename 存在且是字符文件时返回 true |
判断是否是一个符号链接,当 filename 存在且是符号链接时返回 true |
在条件语句中,由 []
或 [[]]
包起来的表达式被称为检测命令或基元。
if...else 经常跟 test
命令结合使用,test
命令用于检查某个条件是否成立,与方括号[]
类似(它们两个在/usr/bin 中是用软连接指向的)。
case 语句匹配一个值或一个模式,如果匹配成功,执行想匹配的命令。适用于需要面对很多情况,分别要采取不同的措施的情况。
echo "你输入的数字是:" #运行后可以自己输入数字体验
注意:可以在 )
前用 |
分割多个模式。
语法中的列表是一组值(数字、字符串)组成的序列,每个值通过空格分隔。这些值还可以是通配符或大括号扩展,例如 *.sh
和 {1..5}
。
while 循环会不断的检测一个条件,只要这个条件返回 true,就执行一段命令。被检测的条件跟 if 中的一样。while 也可用于从输入文件中读取数据。
# do 也跟条件写在一行,前面需要加分号 ;until 循环是检测一个条件,只要条件是 false 就会一直执行循环,直到条件条件为 true 时停止。它跟 while 正好相反。
select 循环的语法跟 for 循环基本一致。它帮助我们组织一个用户菜单。
select 会打印列表的值以及他们的序列号到屏幕上,之后会提示用户选择,用户通常看到的提示是\$?,用户输入相应的信号,选择的结果会被保存到变量中。
PS3="选择你要安装的包管理工具,输入序号:"break 命令允许跳出所有循环(终止执行后面的所有循环)。在嵌套循环中 break 命令后面还可以跟一个整数,表示跳出几层循环。
# 当 x 等于 2,并且 y 等于 0,就跳出循环continue 命令跟 break 命令类似,只有一点差别,它不会跳出所有循环,仅仅跳出当前循环。同样,continue 后面也可以跟一个数字,表示跳出第几层循环。
# 当 x 等于 2,并且 y 等于 0,就跳出循环- shell 函数必须先定义后使用,调用函数仅使用其函数名即可。
- 函数定义时,
function
关键字可有可无 - 函数返回值:可以显式的使用 return 语句,返回值类型只能为整数(0-255)。如果不加 return 语句,会默认将最后一条命令运行结果作为返回值。
- 函数返回值在调用该函数后,通过
$?
获得。
语法:中括号内表示可选
位置参数是在调用一个函数并传给它参数时创建的变量,见上文 Shell 特殊变量。 # $10 不能获取第10个参数,需要用 ${10},当 n>=10 时,需要使用 ${n} 获取参数。(其中有兼容性,某些Shell解释器两种都能获取到)
Unix 命令默认从标准输入设备(stdin)获取输入,将结果输出到标准输出设备(stdout)显示。一般情况下,标准输入设备就是键盘,标准输出设备就是显示器。
shell 接收输入,并以字符序列或字符流的形式产生输出。这些流能被重定向到文件或另一个流中。
一般情况下,每个 Unix/Linux 命令都会打开三个文件:标准输入文件、标准输出文件、标准错误文件,三个文件描述符:
0 |
重定向让我们可以控制一个命令的输入来自哪里,输出结果到什么地方。
输出重定向:命令的输出不仅可以是显示器,还可以很容的转义到文件,这被称为输出重定向。
输入重定向:使 Unix 命令也可以从文件获取输入,这样本来要从键盘获取输入的命令会转移到文件读取内容。
有一个文件是 test.sh,用两种方式输出文件的行数
#输出:14 没有文件名
第一个例子会输出文件名,第二个不会,因为它仅仅知道从标准输入读取的内容。
以下操作符在控制流的重定向时会被用到:
将输出已追加的方式重定向 |
Here 文档语法(见下文扩展),将开始标记 tag 和结束标记 tag 之间的内容作为输入 |
如果希望 stderr 重定向到 file,可以这样写:
#&[n] 代表是已经存在的文件描述符,&1 代表输出 &2代表错误输出 &-代表关闭与它绑定的描述符
如果希望 stdin 和 stdout 都重定向,可以这样写:
#提一下 << 这个连续两个小符号, 他代表的是『结束的输入字符』的意思。这样当空行输入eof字符,输入自动结束,不用ctrl+D
如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null。
/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃,如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。
#test1.sh 没有的情况下,将错误输出信息关闭掉
#将错误输出2 绑定给 正确输出 1,然后将 正确输出 发送给 /dev/null设备 这种常用
#& 代表标准输出 ,错误输出 将所有标准输出与错误输出 输入到/dev/null文件
像其他语言一样,Shell 也可以加载外部脚本,将外部脚本的内容合并到当前脚本。shell 中加载外部脚本有两种写法。
两种方式效果相同,简单起见,一般使用点号(.),但是!注意点号(.)和文件名中间有一个空格
shell 提供了用于 debug 脚本的工具。如果想采用 debug 模式运行某脚本,可以在其 shebang 中使用一个特殊的选项。(有些 shell 不支持)
或者在执行 Bash 脚本的时候,从命令行传入这些参数
有时我们只需要 debug 脚本的一部分。这种情况下,使用 set 命令会很方便。这个命令可以启用或禁用选项。 使用-
启用选项,使用+
禁用选项。
1、用来在运行结果之前,先输出执行的那一行命令
2、执行脚本时,如果遇到不存在的变量会报错,并停止执行。(默认是忽略报错的)
顺便说一下,如果命令行下不带任何参数,直接运行set
,会显示所有的环境变量和 Shell 函数。
3、执行脚本时,发生错误就终止执行。(默认是继续执行的)
set -e
根据返回值来判断,一个命令是否运行失败。但是,某些命令的非零返回值可能不表示失败,或者开发者希望在命令失败的情况下,脚本继续执行下去。这时可以暂时关闭 set -e,该命令执行结束后,再重新打开 set -e。
4、管道命令执行失败,脚本终止执行
管道命令就是多个子命令通过管道运算符(|
)组合成为一个大的命令。Bash 会把最后一个子命令的返回值,作为整个命令的返回值。最后一个子命令不失败,管道命令就总是会执行成功的,因此set -e
会失效,后面的命令会继续执行。
set -o pipefail 用来解决这个情况,只要一个子命令失败,整个管道命令就会失败,脚本就会终止执行。
上面的命令可以放在一起使用:
脚本解释器在环境变量中指定
除了比较常见的用路径指定脚本解释器的方式,还有一种是指定环境变量中的脚本解释器。 指定环境变量中的脚本解释器
这样做的好处是,系统会自动在PATH
环境变量中查找指定的程序(如例子中的 bash)。因为程序的路径是不确定的,比如安装完新版本的 bash 后,我们有可能会把这个新的路径添加到PATH
中,来“隐藏”老版本的 bash。所以操作系统的PATH
变量有可能被配置为指向程序的另一个版本,如果还是直接用
所有的程序,包括 Shell 启动的程序运行时都可以访问的变量就是环境变量。在 shell 脚本中使用export
可以定义环境变量,但是只在当前运行的 shell 进程中有效,结束进程就没了。如果想持久化,需要将环境变量定义在一些列配置文件中。
- Interactive 模式:通常是指读写数据都是从用户的命令行终端(terminal),用户输入命令,并在回车后立即执行的 shell。
进入 bash 交互模式时也可以用 --login 参数来决定是否是登录模式:
配置文件(启动文件)加载顺序
- Non-Interactive 模式:通常就是执行脚本(script)的时候,此时配置项是从环境变量中读取和执行的,也就是
env
或者printenv
命令输出的配置项。
如上图所示,开启一个 Shell 进程时,有一些参数的值也会影响到配置文件的加载。如--rcfile,--norc 等。
常用的 shell 环境变量:
命令搜索路径,以冒号为分隔符 |
用户主目录的路径名,是 cd 命令的默认参数 |
当前运行的 Shell 的全路径名 |
#输出个别的环境变量值的两种方式
全局变量是对所有用户都需要使用的变量,可以将新的变量或修改过的变量设置放在/etc/profile文件中,但升级了发行版该文件也会更新,所以这点要注意 (对所有用户)。
最好是在/etc/profile.d目录中创建一个以.sh结尾的文件,把所有新的变量或修改过的变量全部放在此文件中(对所有用户)。
对于存储个人用户永久性bash shell变量的地方是$HOME/.bashrc文件。这一点适用于所有类型的shell进程(仅对当前用户)。
$*
和$@
都表示传递给函数或脚本的所有参数,不被双引号("")包含时,都是以"$1" "$2" ... "\\$n"
形式把所有参数一个一个单独输出。
但是当他们被双引号包含是,"$*"
会将所有的参数作为一个整体,以"$1 $2 ... $n"
的形式输出所有参数。"$@"
还是跟之前一样,把所有参数分开,一个一个的输出。
#输出:打印出没有引号的 $*
#输出:打印出有引号的 "$*"
#输出:打印出没有引号的 $@
#输出:打印出有引号的 "$@"
使用echo
命令时,使用-e
可以对转义字符进行替换。使用-E
可以禁止转义,默认也是不转义的;使用-n
选项可以禁止插入换行符。
换页(FF),将当前位置移到下页开头 |
水平制表符(tab 键) |
命令替换是指 Shell 可以先执行命令,将输出结果暂时保存,在适当的地方输出。
命令替换的语法是:反引号 ``。
在 bash 中,\$()与 ``(反引号)都是用来作命令替换的。先完成引号里的命令行,然后将其结果替换出来,再重组成新的命令行。
相同点:\$() 与 `` 在操作上,这两者都是达到相应的效果
不同点:`` 很容易与''搞混乱,尤其对初学者来说,而\$( )比较直观;不过 \$() 有兼容性问题,有些类 Unix 系统不支持。
1、(()) 可直接用于整数计算
2、(()) 可重新定义变量值,用于判断条件或计算等
3、(()) 可用于进制转换
\$(())可以将其他进制转成十进制数显示出来。语法:$((N#xx))
,其中,N 为进制,xx 为该进制下某个数值,命令执行后可以得到该进制数转成十进制后的值。
从上面可以看出,test
和[
属于 Shell 的内置命令,[[
属于 Shell 的保留关键字。
在使用上,test
和[
是等价的,因为是命令,所以需要跟它的参数使用空格隔开。
因为 ]
作为最后一个参数表示条件结束,而像<
、>
符号会被理解为重定向,导致错误
[[
是关键字,能够按照常规的语义理解其中的内容,双中括号中的表达式看作一个单独的语句,并返回其状态码。
推荐使用[[
来进行各种判断,可以避免很多错误。
如下展示单中括号会引发的错误
#例如$a为空,就会报语法错误,因为 [ 命令拿到的实际上只有 ==、1、] 三个参数可以理解为“嵌入文档”。Here Document 是 Shell 中的一种特殊的重定向方式,它的基本形式如下:
- 结尾的 delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
- 开始的 delimiter 前后的空格会被忽略掉。