在之前的学习中,我们已经掌握了许多强大的命令行工具,就像是收集了满满一箱的乐高积木。我们可以手动地、一块一块地拼接它们,解决各种问题。但你是否想过,如果能设计一张图纸,让Shell这个“机器人助手”自动为我们完成复杂的拼装工作,那该多好?

这完全可以做到!通过编写Shell脚本,我们就能将零散的命令组合成强大的自动化程序,让Shell化身为能够独立完成复杂任务序列的得力干将。
简单来说,Shell脚本就是一个纯文本文件,里面装着我们希望Shell按顺序执行的一系列命令。你可以把它想象成一份给Shell的“任务清单”或者一个“电影剧本”。 Shell会逐行阅读这份文件,然后像一个忠实的演员一样,一丝不苟地执行我们写下的每一个指令,仿佛我们正在命令行前亲自输入一样。
Shell的独特之处在于,它既是与系统交互的强大命令行界面,也是一种脚本语言的解释器。 这意味着,几乎所有能在命令行上做的事情,都能在脚本里实现;反之亦然。我们之前学习的许多技巧都侧重于即时交互,而现在,我们要探索如何利用Shell的编程特性,释放其全部潜力。
要成功创建并运行一个Shell脚本,我们需要完成三件大事。这就像上演一出戏剧,需要先有剧本,再授权演出,最后还要找到合适的舞台。
Shell脚本是纯文本文件,所以我们需要一个文本编辑器来书写。遵循编程界的传统,我们来创建一个经典的“Hello World”程序作为第一个脚本。打开你喜欢的文本编辑器,输入以下内容:
|#!/bin/bash # 这是我们的第一个脚本,它将向世界问好! echo 'Hello World!'
让我们来解密这段代码。最后一行 echo 'Hello World!' 我们很熟悉,就是让终端打印出一行文字。
第二行以 # 开头,这是一个注释。注释是写给人类读者看的笔记,Shell会忽略它。你可以在脚本的任何地方用它来解释代码的用途,甚至可以在命令的末尾添加注释。
第一行 #!/bin/bash 则暗藏玄机。它虽然也以#开头,但#!这个组合非常特殊,它被称为 Shebang。
Shebang是脚本的“身份证”或“开场白”。它必须位于脚本的第一行,用来告诉操作系统:“嘿,请使用/bin/bash这个程序(也就是Bash Shell)来解释和执行我下面的代码!”
忘记或写错Shebang是初学者常犯的错误,会导致脚本无法正确执行。
现在,我们将这个文件保存为 hello_world。
刚刚创建的 hello_world 文件,在系统看来只是一段普通的文本,就像一份写好了但未被批准的计划书。我们必须明确授予它“可执行”的身份,系统才敢运行它。还记得我们在权限部分学过的嘛?这个授权仪式通过 chmod 命令完成。
|# 查看一下文件当前权限,你会发现它没有 'x' (执行)权限 $ ls -l hello_world -rw-r--r-- 1 user user 63 Mar 07 10:10 hello_world # 使用 chmod 755 赋予执行权限 $ chmod 755 hello_world # 再次查看,现在它有了 'x' 权限,变成可执行文件了! $ ls -l hello_world -rwxr-xr-x 1 user user 63 Mar 07 10:10 hello_world
755 是一个常用的权限组合,它意味着文件的所有者(也就是你)可以读、写、执行它,而其他人只能读取和执行。对于只给自己使用的私人脚本,你也可以使用 700 权限,这样就只有你自己能操作它了。
现在我们的脚本已经具备了执行资格,是时候让它登台表演了。尝试在终端直接输入它的名字:
|$ hello_world bash: hello_world: command not found
出错了!为什么系统找不到我们的脚本?这和它的“住址”有关。当我们输入一个命令时,Shell并不会搜遍整个电脑,而是只在一些固定的“常用地址簿”里查找。这个地址簿,就是一个名为 PATH 的环境变量。
我们可以用 echo 命令看看这个地址簿里都记录了哪些路径:
|$ echo $PATH /home/user/bin:/usr/local/bin:/usr/bin:/bin
这是一个以冒号分隔的目录列表。Shell会依次在这些目录中寻找与我们输入的命令同名的可执行文件。问题显而易见:我们刚刚创建的 hello_world 脚本所在的目录,并不在这个列表里。
为了解决这个问题,我们有两种主要方式。第一种是使用明确的路径来“点名”要执行的脚本:
|$ ./hello_world Hello World!
这里的 ./ 指的就是“当前目录”。这个方法虽然能解决问题,但每次执行都要多打几个字符,不够优雅。
更一劳永逸的方法,是把脚本的“住址”也加入到Shell的“地址簿”(PATH)中。一个最佳实践是,在你的家目录 (~) 下创建一个名为 bin 的目录,专门用来存放你自己的个人脚本。
|$ mkdir ~/bin $ mv hello_world ~/bin
然后,我们将 ~/bin 这个路径添加到 PATH 变量中。这样,每次我们登录时,Shell都会自动把这个目录加载到它的搜索路径里。
大多数Linux发行版已经为你做好了这个设置,只要 ~/bin 目录存在,下次登录时它就会被自动添加。如果没有,你可以通过修改 ~/.bashrc 文件来手动添加。
现在,无论你位于哪个目录下,都可以直接通过名字来调用你的脚本了,就像使用 ls 或 cd 一样方便!
|$ hello_world Hello World!
随着你编写的脚本越来越多,如何更好地组织和书写它们就变得重要起来。良好的习惯不仅能让你自己用起来更方便,也能让别人更容易理解你的代码。
我们已经知道 ~/bin 是存放个人脚本的绝佳位置,可以看作是你的“私人工具箱”。当脚本需要被系统上的所有用户共享时,一个约定俗成的“公共工具箱”是 /usr/local/bin。
而对于那些只有系统管理员(root用户)才有权执行的管理类脚本,则有专门的“管理员工具箱”,即 /usr/local/sbin。遵循这些存放规范,就像把工具分门别类地放在工具房里,让整个系统环境井然有序。
编写脚本不仅仅是为了让电脑执行,更是为了让人类(包括未来的你)能够轻松阅读和维护。清晰易读的代码是高质量脚本的标志。因此在合适的地方添加注释,使用有意义的变量名,以及使用合适的缩进和换行,都是提高代码可读性的重要技巧。
许多命令的选项都有“短形式”和“长形式”,比如 ls -a 和 ls --all。在命令行中,为了追求速度,我们倾向于使用短选项。但在脚本里,我们应该反其道而行之,优先使用长选项。
ls --all --directory 虽然比 ls -ad 啰嗦,但它的含义一目了然,无需查阅手册就能明白其意图。这种自解释的代码风格能极大地提升脚本的可读性。
当遇到一个非常长、非常复杂的命令时,把它写在一行里会让读者头晕眼花。我们可以使用反斜杠 \ 作为续行符,将一个长命令分解成多行,并配合缩进,使其逻辑结构清晰地展现出来。
观察下面这个复杂的 find 命令,它被写成了一行:
|$ find playground \( -type f -not -perm 0600 -exec chmod 0600 ‘{}’ ‘;’ \) -or \( -type d -not -perm 0700 -exec chmod 0700 ‘{}’ ‘;’ \)
第一眼看过去是不是感觉无从下手?现在,我们用换行和缩进来改造它:
|find playground \ \( \ -type f \ -not -perm 0600 \ -exec chmod 0600 ‘{}’ ‘;’ \ \) \ -or \ \( \ -type d \ -not -perm 0700 \ -exec chmod 0700 ‘{}’ ‘;’
这样一格式化,命令的结构和逻辑——“寻找 playground 目录下的文件或目录,如果文件权限不是0600则修改,或者如果目录权限不是0700则修改”——就变得一清二楚了。这是提升复杂脚本可维护性的关键技巧。