一、脚本
Bash脚本由Bash能够解释的命令组成。所有的注释以#
开头直到该行结尾:
echo abc # 显示 abc
echo abc; # 每一行后面的分号是可选的
echo a; echo b # 在这种情况下,第一个命令后的分号不可省
echo a & # &和;都是行结束符,但&的意思是当前命令放到后台执行
echo a &; # 错误,&和;只能用一个
; # 错误,跟高级程序语言不同的是,行结束符不是可以随意添加的
使用/bin/bash script.sh
命令来运行脚本不需要脚本有可执行权限。使用./script.sh
来运行脚本则需要先使用chmod +x
来给脚本文件增加执行权限。
如果脚本文件有执行权限,则脚本第一行以#!
开头可以指明该文件运行的解释器的绝对路径,包括/bin/bash
, /bin/csh
, /bin/awk -F
, /usr/bin/python
等,甚至也可以是/bin/cat
, /bin/rm
等。这样的话,当使用./script.sh
命令来运行该脚本时,bash会去调用脚本指定的解释器来运行该脚本。
二、变量
Bash脚本中的变量在使用前无需定义。定义的格式为:
name=Value # = 左边不能有空格,否则会被视为命令 name 的参数
随后可以使用该变量、改变该变量的值,或者删除该变量:
echo "${name}" # 引用变量的标准形式
echo "$name" # 在没有歧义的情况下,{}可以省略
name=abc # 给变量赋一个新的值
name= # 将变量置空,但是变量还存在
unset name # 将变量从当前的 shell 中删除
在脚本中定义的变量都是只在当前shell中可见的,对其子shell不可见。若需要变量对子shell可见,则需要将变量导出到环境中:
export var1=XXX # 定义时直接导出
var2=YYY
export var2 # 先定义,再单独导出
command # 在command中能看见$var1和$var2的值
或者可以使用env
来运行command
,也能达到上面的效果:
env var1=XXX var2=YYY command
var1=XXX var2=YYY command # env可以省略
echo $var1 $var2 # 空;
# 与上面不同,这样不会把var1和var2加入到当前shell中
三、位置参数
运行脚本的命令行给出的参数可以在脚本中通过位置参数得到。第一个位置参数是$1
,第二个是$2
,以此类推。若位置参数大于等于10,则必须用${10}
来引用。
脚本名本身不算作位置参数。
可以使用shift
指令让所有的位置参数左移,即$1
被删除,$2
变为$1
,$3
变为$2
,以此类推。
可以使用set
指令重新设置位置参数:
set -- a b c # $1, $2, $3 依次为 a, b, c
# -- 表示该命令(set)的选项已经结束,之后以-开头的参数也不解释为选项
四、特殊变量
: $* # 扩展为所有的位置参数的字符串
: $@ # 扩展为所有的位置参数的列表
: $# # 位置参数的数量
: $0 # 当前脚本的路径
: $$ # 当前进程的PID
: $? # 上一个命令的退出码
: $_ # 上一个命令的最后一个参数
: $! # 上一运行的后台进程的PID
: $- # 当前的option flags
遍历脚本的所有参数:
for option in "$@" # or: for option
do
: do something
done
五、输入输出
echo
可用于简单的输出。它依次输出自己的各个参数:
echo a b c d # a b c d
echo "a b c d" # a b c d
echo "-n" # 没有输出,因为 echo 将 -n 视为它的一个选项
printf
用于格式化的输出,脚本中应该坚持使用printf
:
printf "%s\n" a b c "d e"
会打印出:
a
b
c
d e
如果格式化参数不够,printf
会对剩下的参数重复使用前面的格式化参数。
printf
还可以通过-v
选项将字符串输出到变量中,即给变量赋值:
printf -v num1 "%d" "1"
printf "%d\n" $num1 # 打印1
read
用于读取标准输入并将读到的内容赋值给指定的变量:
read line # 默认读取一行的内容赋给line变量,如果不指定变量,则默认存放在 REPLY 变量中
read a b c rest # 读取一行,前三个空白分隔的词分别赋给a, b, c,剩下的赋给rest
read -r line # 在读取一行的时候保留行内的反斜杠\
六、函数
符合POSIX标准的函数定义形式为:
name() <compound command>
其中,<compound command>
是被{}
或者()
括起来的一连串的命令,或者是被(())
或者[[]]
包含起来的表达式,甚至也可以是一个块级的case
、while
等。
ksh和bash也允许在name()之前再加上function
关键字,不过这个关键字也没太大用处。另外,name后面的()
永远都是空的,因为函数的调用也就是命令的调用,是依靠位置参数来传参的。
在函数里使用return <code>
来设置一个0到255的退出码,调用者可以通过$?
得到。
函数只是为<compound command>
定义一个指代的命令而已,因此,与管道重定向一样,只有当函数体是用()
包围时,该函数的调用才会产生子shell,这时,在函数体里定义的任何变量,在当前脚本中都是访问不到的。但如果函数体是用{}
包围的,则该函数是在当前shell中被执行,该函数中定义的任何变量在该函数调用完成之后对脚本是可见的。这也是shell函数返回内容的方法(return
只设置退出码,不能返回内容)。另外,如果要在{}
中定义局部变量,则要使用local
关键字:
f() {
local A B=3 C
echo $A $B $C
}