Shell 编程/入门
一个 shell 脚本 是一个由 Unix shell,一个 命令行解释器 运行的程序。简单来说,它是一系列要执行的命令,就像在命令行中输入一样,可以作为一个单独的命令调用 - 它是一个“脚本”,供 shell 运行。这样可以避免重复输入,简化调用,并允许脚本像任何其他程序一样使用,包括被其他 shell 脚本或其他程序调用。从 shell 的角度来看,脚本是一系列它执行的命令;从另一个程序的角度来看,脚本只是一个可以像任何其他程序一样执行的程序。更正式地说,shell 语言是 shell 的 脚本语言,隐含地也是主机操作系统的脚本语言:它们允许人们轻松地调用命令。
除了简单地列出命令之外,shell 通常还提供编程语言功能,例如变量和控制流结构,因此允许将复杂程序编写为脚本。原则上,任何脚本都可以在命令行中输入 - “脚本”和 shell 命令序列之间没有根本区别 - 但除了简短的结构(例如 for 循环)之外,通常不会这样做。
基本的 shell 脚本非常容易编写:只需使用 shell 会话的转录即可。但是,与编译程序(例如 C 程序)相比,shell 脚本运行速度非常慢,甚至与调用库而不是单独进程的解释程序(例如 Python 脚本)相比,也很慢,并且很难将复杂程序编写为 shell 脚本。因此,它们主要适合那些广泛调用操作系统(特别是文件操作)或其他程序的冗长但简单的任务,特别是在 系统管理 中。更复杂或对性能敏感的任务则用通用语言编写,传统上是 C,最近是 Perl 或 Python。由于易于编写,shell 脚本也非常适合一次性代码或快速原型设计,就像其他脚本语言一样,并且为用户提供了一个非常好的入门,无论是通用的编程还是操作系统,特别是对于熟悉命令行的用户。
本书介绍与 sh 兼容的 shell。
存在各种 shell,它们的行为各不相同,因此这些 shell 都有不同的编程语言。大多数 shell 都是 Bourne shell (sh;见 Bourne Shell 脚本编写) 的变体,共享基本语法,但增加了功能,相关的编程语言被称为 sh 的“方言”。如今,在 Linux 上最常见的是 bash,“Bourne-again shell”(见 Bash Shell 脚本编写),它是一个提供许多功能的复杂 shell。 Korn shell (ksh) 也被使用,主要是在专有 Unix 系统(根据 POSIX 标准)和 BSD 操作系统上。但是,也使用其他 shell,例如 Almquist shell (ash),特别是在资源受限的环境中,出于安全目的,或者为了许可 - 这些 shell 通常会省略交互式功能,使它们更小、更安全 - 编写跨 sh 方言兼容的脚本称为“可移植的 shell 脚本”。可移植的 shell 脚本编写需要将自己限制在目标语言兼容的功能子集中 - 这在很大程度上等同于将自己限制在原始 Bourne shell 语言中,但有一些微妙的边缘情况需要注意。
C shell (csh) 及其变体是主要的非 sh 兼容 shell。它也可以用脚本编写(见 C Shell 脚本编写),但这并不常见,因为该语言存在严重问题。 [1] 因此,csh 在 1980 年代和 1990 年代主要用作交互式 shell,与 sh 的一些变体一起用于脚本编写。由于 sh 兼容的 shell 添加了 csh 的交互式功能,这种情况已经基本消失。因此,对于本书而言,shell 指的是 sh 兼容的 shell。
Shell 脚本本质上是操作系统的脚本语言,因此适合涉及调用操作系统工具(例如文件操作)或多个其他命令的任务,逻辑和高级语言功能相对较少。相比之下,涉及复杂逻辑或调用很少其他命令的任务 - 或者可以调用库而不是单独命令 - 最好在通用语言中编写,如今通常是高级语言,如 Python 或 Ruby。早期的 Perl 就是为此目的而编写的 - 更高级的 shell 脚本编写 - 并且保留了 shell 脚本编写的大部分风格和语法;它在 1980 年代后期到 2000 年代初期非常流行,但此后人气下降。
Shell 脚本编写的主要应用是自动化重复任务或编写一次性脚本以完成复杂的一次性任务。Shell 脚本编写是 系统管理 中的一项关键技能,也用于为程序或程序组合编写脚本,前提是存在良好的命令行界面,但没有内置的脚本语言或库。
Shell 脚本相对于其他语言的主要优势是轻松调用其他命令,而无需冗长的语法(显式函数调用和引用),而主要缺点是功能有限,语法笨拙且难以处理(特别是由于与交互式使用的兼容性以及引用的缘故),以及执行速度慢(由于运行单独进程的开销,特别是 上下文切换)。
与其使用执行单独命令的 shell 脚本,如果库可用,可以编写一个调用库的程序,这样可以避免单独进程的时间开销(以及空间开销,如果动态链接),但代价是链接库。这会产生其他成本:如果静态链接,则需要在编译时付出时间成本,磁盘上的空间成本以及运行时空间成本(由于包含库);而如果动态链接,则需要在运行时付出时间成本,尽管动态链接每个库只需要执行一次。
相反,对于非常简单的命令,特别是单行命令,shell 别名可以避免执行单独脚本的开销。例如,对于目录列表快捷方式,别名
alias lsal='clear;ls -al'
比脚本
#!/bin/sh
clear
ls -al