在开发的过程中,经常需要处理一些重复的工作,或者逻辑相当简单但耗时的功能,这时我们可能会考虑到用脚本来自动化完成这些工作。而 Bash 脚本是我们最容易接触到和上手的脚本语言。
这篇博客汇总一些常用的 Bash 语法,方便日后查阅学习。
hello world
不管写啥,上来先输出个hello world
。
1 |
|
创建一个文件hello.sh
包含以上内容,同时赋予执行权限,然后执行,一个hello world
就好了。
1 | # 添加执行权限 |
解释器
我们看到这个hello.sh
脚本,第一行有个 #!/bin/bash
。 这个是用来指定该脚本在 UNIX/Linux 下执行时用到的解释器。
执行cat /etc/shells
我们可以看到自己的系统中都有哪些解释器。如我的:
1 | $ cat /etc/shells |
注释
用 #
来注释。
1 |
|
变量声明
Bash 中变量命名是大小写敏感的,很多喜欢全大写。当然你也可以使用 小写英文字母,数字和下划线,但不能以数字开头。 给变量赋值的时候 =
号前后不能有空格。
1 | # 有效的 |
变量引用
当你要使用变量的时候,用 $
来引用, 如果后面要接一些其他字符,可以用{}
括起来。
1 |
|
在 Bash 中要注意 单引号 '
, 双引号 "
,反引号 ` 的区别。
单引号,双引号都能用来保留引号内的为文字值,其差别在于,双引号在遇到 $(参数替换)
, 反引号 `(命令替换) 的时候有例外,单引号则剥夺其中所有字符的特殊含义。
而反引号的作用 和 $()
是差不多的。 在执行一条命令的时候,会先执行其中的命令,再把结果放到原命令中。
1 |
|
环境变量
Linux 的环境变量包含了存储在系统中的信息。我们可以在终端中找到一些环境变量。
1 | $ env |
你可以在脚本中引用这些环境变量。
1 |
|
这里 还有更多。
内部变量
Bash 的内部变量也不少,有时我们可能会用到,如 $BASHPID
$IFS
$PWD
等,更多看这里 。
将命令输出分配给变量
可以使用 $(command)
将命令输出存储在变量中。例如这是一个info.sh
脚本内容:
1 |
|
执行(别忘了给执行权限)
1 | $ ./info.sh |
下面的脚本会将时间和日期,用户名以及系统正常运行时间保存到日志文件中。
其中 >
是重定向之一,它将覆盖文件。使用 >>
可以将输出追加到文件。
1 |
|
内建命令
Shell
内建命令是可以直接在Shell
中运行的命令。可以这么查看内建命令:
1 | $ compgen -b | sort |
也可以用 type
查看命令的类型。
1 | $ type cd |
可以用 which
命令查看可执行文件的文件路径:
1 | # which sort |
可通过 man builtins
查看内建命令的详细描述。
测试
IF条件表达式
if
后面需要接者then
:
1 | if [ condition-for-test ] |
或者,
1 | if [ condition-for-test ]; then |
如:
1 |
|
上面,我们在比较时,可以用双引号把变量引用起来。
但要注意单引号的使用。
1 |
|
上面这个就把 ‘$VAR’ 当一个字符串了。
但如果变量是多个单词,我们就必须用到双引号了,如
1 |
|
总的来说,双引号可以一直加上。
空格问题
比较表达式中,如果=
前后没有空格,那么整个表法式会被认为是一个单词,其判断结果为True
.
1 |
|
另外需要注意的是, 在判断中,中括号 [
和变量之间一定要有一个空格,=
或者 ==
。 如果缺少了空格,你可能会到这类似这样的错误:unary operator expected’ or missing
]` 。
1 | # 正确, 符号前后有空格 |
文件测试表达式
对文件进行相关测试,判断的表达式如下:
表达式 | True |
---|---|
file1 -nt file2 | file1 比 file2 新。 |
file1 -ot file2 | file1 比 file2 老。 |
-d file | 文件file存在,且是一个文件夹。 |
-e file | 文件 file 存在。 |
-f file | 文件file存在,且为普通文件。 |
-L file | 文件file存在,且为符号连接。 |
-O file | 文件 flle 存在, 且由有效用户ID拥有。 |
-r file | 文件 flle 存在, 且是一个可读文件。 |
-s file | 文件 flle 存在, 且长度大于0。 |
-w file | 文件 flle 可写入。 |
-x file | 文件 flle 可写执行。 |
可以使用man test
查看那详细的说明。
当表达式为True
时,测试命令返回退出状态 0,而表达式为False
时返回退出状态1。
1 |
|
字符串比较表达式
表达式 | True |
---|---|
string1 = string2 或 string1 == string2 | 两字符相等 |
string1 != string2 | 两个字符串不相等 |
string1 > string2 | string1 大于 string2. |
string1 < string2 | string1 小于string2. |
-n string | 字符串长度大于0 |
-z string | 字符串长度等于0 |
1 |
|
其中>&2
将错误信息定位到标准错误输出。
数字比较表达式
下面这些是用来比较数字的一些表达式。
[…] | ((…)) | True |
---|---|---|
[ “int1” -eq “int2” ] | (( “int1” == “int2” )) | 相等. |
[ “int1” -nq “int2” ] | (( “int1” != “int2” )) | 不等. |
[ “int1” -lt “int2” ] | (( “int1” < “int2” )) | int2 大于 int1. |
[ “int1” -le “int2” ] | (( “int1” <= “int2” )) | int2 大于等于 int1. |
[ “int1” -gt “int2” ] | (( “int1 > “int2” )) | int1 大于 int2 |
[ “int1” -ge “int2” ] | (( “int1 >= “int2” )) | int1 大于等于 int2 |
双括号 (())
数值的比较或者计算可以用((... ))
。
1 |
|
怎么使用 if/else 和 if/elif/else
其实上面已经展示了不少了,这里总结下if...else
和 if...elif...else
语句。
if/else
语句格式如下:
1 | if [ condition-is-true ] |
例如:
1 |
|
if/elif/else
语句格式如下:
1 | if [ condition-is-true ] |
如:
1 |
|
双中括号的使用[[]]
如用用于比较的变量不是单个单词,就需要[[]]
, 或者用单中括号(这时需要加双引号)。 在平常的使用中,最好都使用[[]]
。
与单中括号相比,双中括号具有其他功能。 如,可以对其中正则使用逻辑&&
和||
和=〜
。
1 |
|
1 |
|
怎么使用 For 循环
for
循环的使用如下:
1 | for VARIABLE_NAME in ITEM_1 ITEM_N |
例如:
1 |
|
可以在其中使用变量,如下:
1 |
|
用 for
循环重命名文件
我们举个简单的例子,用for
循环重命名当前目录下的jpg图片。
1 |
|
怎么传参
执行脚本的时候,后面可以跟着很多参数,如:
1 | $ scriptname param1 param2 param3 |
param1
到 param3
称为可选参数, 可以在脚本中用 $0
, $1
, $2
等,来引用这些参赛。例如:
1 |
|
输出:
1 | $ ./param.sh |
$0
参数0返回的是当前执行文件的名字,包括路径。
可以用 [email protected]
接受所以的参数。
1 |
|
Using this script:
1 | $ ./params.sh a b c d e f |
怎么接收用户输入
用户输入称为STDIN
。可以将read
命令与-p
(提示)选项一起使用来读取用户输入,它将输出提示字符串。 -r
选项不允许反斜杠转义任何字符。
1 | read -rp "PROMPT" VARIABLE |
例如:
1 |
|
运行:
1 | $ ./read.sh |
用大括号来表示范围 {}
如下所示,我们可以用大括号来表所一个数字或字母的范围。
1 | $ echo {0..3} |
你也可以在 for
循环中这么使用:
1 |
|
This will create different file names with different modification times.
1 | $ ls -al file_* |
怎么使用While
当 While 后的表达式结果为 true
时,执行循环内语句。
1 |
|
Output:
1 | 1 |
退出码/返回码 是什么?
每个命令都返回退出状态,范围为0-255。 0代表成功,非0代表错误。 可以用来进行错误检查。
数值 | 含义 |
---|---|
0 | 成功 |
2 | 返回内置命令,从而提示错误 |
126 | 命令找到了,但不是可执行的 |
127 | 没有找到命令 |
128+N | 由于接收到信号N,命令退出 |
怎么检查退出码
$?
包含了上一条命令执行的返回码。
1 | $ ls ./no/exist |
如,在if
表达式中检查返回码:
1 |
|
-c 1
参数表示发送一个可达包就停止发送。 然后我们检查一下ping
执行的返回码。
输出:
1 | $ ./ex.sh |
怎么连接多个命令
逻辑运算符和命令退出状态
执行命令后都有退出状态,我们可以使用 &&
和 ||
去决定下一步。
退出命令
你可以使用 exit
来决定退出码:
1 | exit 0 |
例如:
1 |
|
我们可以将该脚本通过&&
与其他脚本/命令连接。
1 | $ ./ex2.sh && ls |
如果./ex2.sh
返回状态码非0,后面的就不会执行。
逻辑与 (&&)
当&&
前面的语句返回的状态码为0
时,执行后面的语句。
1 | mkdir /tmp/bak && cp test.txt /tmp/bak |
逻辑或 (||)
当||
前面的语句返回的状态码非0
时(也就是执行失败),执行后面的语句。
1 | cp test.txt /tmp/bak/ || cp test.test.txt /tmp |
例如:
如果ping
通了,就执行后面的输出。
1 |
|
如果ping
失败了,就执行后面的输出。
1 |
|
分号 (;)
分号不是一个逻辑运算符,但你可以用它来分割语句。
1 | cp text.txt /tmp/bak/ ; cp test.txt /tmp |
管道 |
管道|
两侧的命令在各自的子shell中运行,并且两者同时启动。
如下:
第一个命令将目录更改为主目录,并列出文件和目录。
第二个命令仅显示执行该命令的文件和目录。
1 | $ echo "$(cd ~ && ls)" |
函数
在Bash
中,你可以使用function
或者直接定义一个函数。
1 | function function-name(){} |
当你调用函数的时候,只需要函数名,不用带()
。
1 |
|
在函数中,可以调用其他函数。
1 |
|
但,需要注意函数的定义顺序。如果你在函数声明的前就去调用函数,函数就不会执行。如下, 在hello
中执行now
函数,但now
是定义hello
执行下面的,结果就会出错。
1 |
|
输出:
1 | $ ./hello2.sh |
函数传参
和脚本执行的时候传参一样,函数的参数也用$1
…,[email protected]
来输出。
注意$0
这里并不是函数的名字,而是当前脚本的名字。
$N
是第N个参数,[email protected]
表示所有的参数。
1 |
|
1 |
|
变量的作用域
默认变量的作用域是全局的,必须先声明,后使用。 当然,最好在最上面就把需要的变量声明好。
1 |
|
局部变量
可以用local
来定义局部变量,且只能在函数中使用。
1 |
|
函数返回码
你可以在函数中,指定返回码:
1 | return 0 |
函数中最后执行的命令的退出状态将隐式返回。 有效代码范围为0-255。0
代表成功,$?
可以显示退出码。
1 | $ my_function |
可以在if
判断中用$?
:
1 |
|
上面这个脚本默认备份/etc/hosts
文件,除非你制定一个文件外。如果你指定一个文件参数,他会先检查文件,然后备份到/tmp
目录。
$$
返回 当前脚本执行的PID. 每次运行PID都会发生变化。当你需要多次运行脚本时,或许对你有帮助。
basename ${1}
可以从你输入的路径中提取文件的名字. 如 basename /etc/hosts
是 hosts
.
1 | $ ls /tmp |
关键字 exit 和 return
return
会跳出当前函数, exit
会结束当前脚本。
总结
这篇博客总结了常用的,我们需要了解的一些脚本语法与知识。如果向更好的使用bash
, 我们还需要进一步学习更多的命令等。希望这篇博客能对你有所帮助。