Bash 脚本

Bash 脚本(script)就是一个包含一系列 Bash 命令的文件。Shell 读取这个文件,执行文件中的所有命令,就好像这些命令直接输入到命令行一样。所有能够在命令行中完成的任务,也能够用脚本来实现。

Shebang 行

脚本的第一行通常以#!字符开头,这个字符称为 Shebang,表示该文件是脚本。在 Shebang 后面是指定的脚本解释器,即使用什么程序执行该脚本,Bash 脚本的解释器一般是/bin/sh

#!/bin/sh

如果用户的 Bash 可执行文件不在/bin/sh,脚本就无法执行了。为了保险,可以写成下面这样。

#!/usr/bin/env bash

上面命令使用/usr/bin/env命令,返回 Bash 的可执行文件。env命令的详细介绍,请看后文。

每个脚本都应包含一个 Shebang 行。如果缺少该行,就需要手动调用解释器。举例来说,脚本是script.sh,有 Shebang 行的时候,可以直接调用执行。

$ ./script.sh

如果没有 Shebang 行,就只能手动调用解释器执行。

$ /bin/sh ./script.sh
# 或者
$ bash ./script.sh

上面命令中,最后一行的bash命令一般是/bin/sh的别名。

脚本的执行权限

脚本如果要直接执行,除了必须有 Shebang 行以外,还需要有执行权限。可以使用下面的命令,赋予脚本执行权限。

$ chmod +x ./script.sh

脚本调用的时候,一般需要指定脚本的路径。如果将脚本放在环境变量$PATH指定的目录中,就不需要指定路径了。建议可以主目录里面新建一个~/bin目录,专门存放可执行脚本,然后把~/bin加入$PATH

$ export PATH=$PATH:~/bin

上面命令的意思是,改变环境变量$PATH,在可执行文件的目录清单中加入~/bin,然后所有当前 Shell 的子 Shell 都可以获得这个新的环境变量$PATH。Bash 脚本总是在一个新建的子 Shell 里面执行,所以就可以直接输入脚本文件名,来运行脚本。

$ script.sh

上面的命令没有指定脚本路径,因为script.sh$PATH指定的目录中。

这种命令行直接改写$PATH变量的做法,一旦退出了当前 Shell,就会失效。如果你希望长期有效,可以这行export命令写入主目录下面的.bash_profile文件或.profile文件(取决于发行版)。

注释

Bash 脚本中,#表示注释。

# 本行是注释
echo 'Hello World!'

echo 'Hello World!' # 井号后面的部分也是注释

启动顺序

每一个 Bash 脚本启动的时候,会读取一系列配置文件。这分成登录 Shell 和非登录 Shell 两种情况。

登录 Shell 读取配置文件的顺序如下。

  • /etc/profile:所有用户的全局配置。
  • ~/.bash_profile:用户的个人配置,可用于扩展或覆盖全局配置。
  • ~/.bash_login:如果找不到~/.bash_profile,Bash 则尝试读取此文件。
  • ~/.profile:如果找不到~/.bash_profile~/.bash_login,Bash 尝试读取此文件。

非登录 Shell 读取配置文件的顺序如下。

  • /etc/bash.bashrc:所有用户的全局配置。
  • ~/.bashrc:用户的个人配置,可用于扩展或覆盖全局配置。

除了读取上面的配置文件之外,非登录 Shell 还会从它的父进程继承环境变量。

普通用户对 Shell 的配置修改,一般写在~/.bashrc文件里面。非登录 Shell 默认会读取这个文件,而登录 Shell 大多数情况下也会通过变通方法读取该文件。比如,.bash_profile文件通常会像下面这样写。

# .bash_profile
# Get the aliases and functions
if [ -f ~/.bashrc ]; then
    . ~/.bashrc
fi

# User specific environment and startup programs
PATH=$PATH:$HOME/bin
export PATH

上面代码中,如果~/.bashrc文件存在,就会执行这个文件。这就是“登录 Shell”读取~/.bashrc的方法。另外,最后两行的作用是在环境变量$PATH里面,追加$HOME/bin目录,然后export命令的作用是将这个变量输出给当前 Shell 的所有子进程。

别名

alias命令用来为一个命令指定别名。

alias NAME=DEFINITION

上面命令中,Name是新命令的名称,DEFINITION是这个新命令对应的要执行的事情。

下面的例子是指定ls -ltr命令的别名为lt,运行结果是按照修改时间(-t)的倒序(-r),列出文件的详细信息(-l)。

$ alias lt='ls -ltr'

指定别名以后,就可以像使用其他命令一样使用别名。一般来说,都会把常用的别名写在~/.bashrc的末尾。

下面是定义一个today命令的写法。

$ alias today='date +"%A, %B %-d, %Y"'
$ today
星期一, 一月 6, 2020

直接调用alias命令,可以显示所有别名。

$ alias

unalias命令可以解除别名。

$ unalias lt

程序

别名只适合封装简单的单个命令,如果要封装复杂的多行命令,就需要 Bash 函数。

Bash 函数的语法如下。

fn() {
  # codes
}

上面命令中,fn是自定义的函数名,函数代码就写在大括号之中。

下面是显示当前日期时间的函数。

today() {
  echo -n "Today's date is: "
  date +"%A, %B %-d, %Y"
}

这个函数可以写在脚本文件里面,调用的时候直接写函数名即可。

$ today

env 命令

env命令的可执行文件总是在/usr/bin目录里面,作用是在指定环境之中运行一个程序。/usr/bin/env bash的意思是,找到bash的可执行文件,然后在 Bash 环境中运行脚本。

相应的,如果要执行Node脚本,可以写成下面这样。

#!/usr/bin/env node

#!/usr/bin/env NAME这种语法的意思是,命令 Shell 查找$PATH环境变量里面第一个匹配的NAME。如果你不知道某个命令的路径,这样的写法就很有用。它的好处是,只要bash的路径是在$PATH路径里面,就总是能找到它。

env命令的参数如下。

  • -i, --ignore-environment:不带环境变量启动
  • -u, --unset=NAME:从环境变量中删除一个变量
  • --help:显示帮助
  • --version:输出版本信息

下面是一个例子,新建一个不带任何环境变量的Shell。

$ env -i /bin/sh

exit 命令

exit命令用于终止当前脚本的执行,并向 Shell 返回一个退出值。

$ exit

上面命令中止当前脚本,将最后一条命令的退出状态,作为整个脚本的退出状态。

exit命令后面可以跟参数,该参数就是退出状态。

# 退出值为0(成功)
$ exit 0

# 退出值为1(失败)
$ exit 1

退出时,脚本会返回一个退出值。脚本的退出值,0表示正常,1表示发生错误,2表示用法不对,126表示不是可执行脚本,127表示命令没有发现。如果脚本被信号N终止,则退出值为128 + N。简单来说,只要退出值非0,就认为执行出错。

下面是一个例子。

if [ $(id -u) != "0" ]; then
  echo "根用户才能执行当前脚本"
  exit 1
fi

上面的例子中,id -u命令返回用户的 ID,一旦用户的 ID 不等于0(根用户的 ID),脚本就会退出,并且退出码为1,表示运行失败。

上一条命令的退出值,可以用系统变量$?查询。使用这个命令,可以知道上一条命令是否执行成功。

exitreturn命令的差别是,return命令是函数的退出,并返回一个值给调用者,脚本依然执行。exit是整个脚本的退出,如果在函数之中调用exit,则退出函数,并终止脚本执行。

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「IT平头哥联盟」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

results matching ""

    No results matching ""