- 首页
- 作品
- Bash 脚本教程
- Bash 脚本入门
Bash 脚本入门
脚本(script)就是包含一系列命令的一个文本文件。Shell 读取这个文件,依次执行里面的所有命令,就好像这些命令直接输入到命令行一样。所有能够在命令行完成的任务,都能够用脚本完成。
脚本的好处是可以重复使用,也可以指定在特定场合自动调用,比如系统启动或关闭时自动执行脚本。
Shebang 行
执行权限和路径
env 命令
env
命令总是指向/usr/bin/env
文件,或者说,这个二进制文件总是在目录/usr/bin
。
#!/usr/bin/env NAME
这个语法的意思是,让 Shell 查找$PATH
环境变量里面第一个匹配的NAME
。如果你不知道某个命令的具体路径,或者希望兼容其他用户的机器,这样的写法就很有用。
/usr/bin/env bash
的意思就是,返回bash
可执行文件的位置,前提是bash
的路径是在$PATH
里面。其他脚本文件也可以使用这个命令。比如 Node.js 脚本的 Shebang 行,可以写成下面这样。
#!/usr/bin/env node
env
命令的参数如下。
-i
, --ignore-environment
:不带环境变量启动。
-u
, --unset=NAME
:从环境变量中删除一个变量。
--help
:显示帮助。
--version
:输出版本信息。
- 下面是一个例子,新建一个不带任何环境变量的 Shell。
$ env -i /bin/sh
注释
脚本参数
- 调用脚本的时候,脚本文件名后面可以带有参数。
$ script.sh word1 word2 word3
- 上面例子中,
script.sh
是一个脚本文件,word1
、word2
和word3
是三个参数。
- 脚本文件内部,可以使用特殊变量,引用这些参数。
$0
:脚本文件名,即script.sh
。
$1
~$9
:对应脚本的第一个参数到第九个参数。
$#
:参数的总数。
$@
:全部的参数,参数之间使用空格分隔。
$*
:全部的参数,参数之间使用变量$IFS
值的第一个字符分隔,默认为空格,但是可以自定义。
- 如果脚本的参数多于9个,那么第10个参数可以用
${10}
的形式引用,以此类推。
- 注意,如果命令是
command -o foo bar
,那么-o
是$1
,foo
是$2
,bar
是$3
。
- 下面是一个脚本内部读取命令行参数的例子。
#!/bin/bash
# script.sh
echo "全部参数:" $@
echo "命令行参数数量:" $#
echo '$0 = ' $0
echo '$1 = ' $1
echo '$2 = ' $2
echo '$3 = ' $3
- 执行结果如下。
$ ./script.sh a b c
全部参数:a b c
命令行参数数量:3
$0 = script.sh
$1 = a
$2 = b
$3 = c
- 用户可以输入任意数量的参数,利用
for
循环,可以读取每一个参数。
#!/bin/bash
for i in "$@"; do
echo $i
done
- 上面例子中,
$@
返回一个全部参数的列表,然后使用for
循环遍历。
- 如果多个参数放在双引号里面,视为一个参数。
$ ./script.sh "a b"
- 上面例子中,Bash 会认为
"a b"
是一个参数,$1
会返回a b
。注意,返回时不包括双引号。
shift 命令
getopts 命令
配置项参数终止符 --
-
和--
开头的参数,会被 Bash 当作配置项解释。但是,有时它们不是配置项,而是实体参数的一部分,比如文件名叫做-f
或--file
。
$ cat -f
$ cat --file
- 上面命令的原意是输出文件
-f
和--file
的内容,但是会被 Bash 当作配置项解释。
- 这是就可以使用配置项参数终止符
--
,它的作用是告诉 Bash,在它后面的参数开头的-
和--
不是配置项,只能当作实体参数解释。
$ cat -- -f
$ cat -- --file
- 上面命令可以正确展示文件
-f
和--file
的内容,因为它们放在--
的后面,开头的-
和--
就不再当作配置项解释了。
- 如果要确保某个变量不会被当作配置项解释,就要在它前面放上参数终止符
--
。
$ ls -- $myPath
- 上面示例中,
--
强制变量$myPath
只能当作实体参数(即路径名)解释。如果变量不是路径名,就会报错。
$ myPath="-l"
$ ls -- $myPath
ls: 无法访问'-l': 没有那个文件或目录
- 上面例子中,变量
myPath
的值为-l
,不是路径。但是,--
强制$myPath
只能作为路径解释,导致报错“不存在该路径”。
- 下面是另一个实际的例子,如果想在文件里面搜索
--hello
,这时也要使用参数终止符--
。
$ grep -- "--hello" example.txt
- 上面命令在
example.txt
文件里面,搜索字符串--hello
。这个字符串是--
开头,如果不用参数终止符,grep
命令就会把--hello
当作配置项参数,从而报错。
exit 命令
命令执行结果
- 命令执行结束后,会有一个返回值。
0
表示执行成功,非0
(通常是1
)表示执行失败。环境变量$?
可以读取前一个命令的返回值。
- 利用这一点,可以在脚本中对命令执行结果进行判断。
cd $some_directory
if [ "$?" = "0" ]; then
rm *
else
echo "无法切换目录!" 1>&2
exit 1
fi
- 上面例子中,
cd $some_directory
这个命令如果执行成功(返回值等于0
),就删除该目录里面的文件,否则退出脚本,整个脚本的返回值变为1
,表示执行失败。
- 由于
if
可以直接判断命令的执行结果,执行相应的操作,上面的脚本可以改写成下面的样子。
if cd $some_directory; then
rm *
else
echo "Could not change directory! Aborting." 1>&2
exit 1
fi
- 更简洁的写法是利用两个逻辑运算符
&&
(且)和||
(或)。
# 第一步执行成功,才会执行第二步
cd $some_directory && rm *
# 第一步执行失败,才会执行第二步
cd $some_directory || exit 1
source 命令
source
命令用于执行一个脚本,通常用于重新加载一个配置文件。
$ source .bashrc
source
命令最大的特点是在当前 Shell 执行脚本,不像直接执行脚本时,会新建一个子 Shell。所以,source
命令执行脚本时,不需要export
变量。
#!/bin/bash
# test.sh
echo $foo
- 上面脚本输出
$foo
变量的值。
# 当前 Shell 新建一个变量 foo
$ foo=1
# 打印输出 1
$ source test.sh
1
# 打印输出空字符串
$ bash test.sh
- 上面例子中,当前 Shell 的变量
foo
并没有export
,所以直接执行无法读取,但是source
执行可以读取。
source
命令的另一个用途,是在脚本内部加载外部库。
#!/bin/bash
source ./lib.sh
function_from_lib
- 上面脚本在内部使用
source
命令加载了一个外部库,然后就可以在脚本里面,使用这个外部库定义的函数。
source
有一个简写形式,可以使用一个点(.
)来表示。
$ . .bashrc
别名,alias 命令
alias
命令用来为一个命令指定别名,这样更便于记忆。下面是alias
的格式。
alias NAME=DEFINITION
- 上面命令中,
NAME
是别名的名称,DEFINITION
是别名对应的原始命令。注意,等号两侧不能有空格,否则会报错。
- 一个常见的例子是为
grep
命令起一个search
的别名。
alias search=grep
alias
也可以用来为长命令指定一个更短的别名。下面是通过别名定义一个today
的命令。
$ alias today='date +"%A, %B %-d, %Y"'
$ today
星期一, 一月 6, 2020
- 有时为了防止误删除文件,可以指定
rm
命令的别名。
$ alias rm='rm -i'
- 上面命令指定
rm
命令是rm -i
,每次删除文件之前,都会让用户确认。
alias
定义的别名也可以接受参数,参数会直接传入原始命令。
$ alias echo='echo It says: '
$ echo hello world
It says: hello world
- 上面例子中,别名定义了
echo
命令的前两个参数,等同于修改了echo
命令的默认行为。
- 指定别名以后,就可以像使用其他命令一样使用别名。一般来说,都会把常用的别名写在
~/.bashrc
的末尾。另外,只能为命令定义别名,为其他部分(比如很长的路径)定义别名是无效的。
- 直接调用
alias
命令,可以显示所有别名。
$ alias
unalias
命令可以解除别名。
$ unalias lt
参考链接