跳转到内容

Shell 编程/特性

来自 Wikibooks,开放的书籍,开放的世界


作为编程语言,shell 通常具有非常有限的特性:它们的主要功能来自于调用外部命令。

但是,有一些特性非常强大,而且在其他语言中不常见或不太为人所知。

  • 流、管道和重定向
  • 进程管理
  • 陷阱

这些特性在多核系统上特别有用,因为进程通常可以并行运行。

流允许在不需要一次读取所有数据的情况下,对任意大量数据(codata)进行操作,逐步读取输入或写入输出;最基本的流是标准输入和标准输出。最简单的方法是逐行操作文本数据。管道允许将一个程序的输出流连接到另一个程序的输入流,允许对数据进行并发处理。许多标准的Unix 命令对流进行操作,特别是用于文本处理,并设计用于管道中 - 这些命令有时被称为过滤器。shell 脚本的标准惯用语是构建一个管道,将这些命令连接起来,以快速构建复杂且相对高效的程序。

除了匿名管道之外,命名管道(也称为 FIFO)还允许复杂的进程间通信 (IPC);请参阅NamedPipes

进程管理

进程管理允许异步(不阻塞)运行和控制多个进程。如果一个命令以&结尾,则该进程将在后台运行(立即执行到下一个命令)。请注意,&是一个命令分隔符,就像;一样 - 您可以类似地写a & b就像a; b一样,您永远不需要写&;

shell 可以管理进程 - 通常只应该管理其自己的子进程 - 尽管实际上只有有限的设施是可行的。

  • 等待一个或多个进程完成:通过wait,它可以接受多个进程 ID;
  • 终止一个进程,通过kill
  • 检查一个进程的状态,通过kill -0

但是,shell 无法访问 select(2) 或 poll(2),因此它们无法等待一组进程中的一个进程完成;这严重限制了可以执行的操作。

原则上,也可以向进程发送其他信号,但这非常罕见。请参阅ProcessManagement 以获取详细的信息。

要简单地在并行运行多个进程,请使用带有-P标志的xargspexec,或者更简洁地使用GNU parallelsem 通常很有用)。

启动一个连接的进程(及其子进程)的管道会创建一个进程组 - 进程组 ID 是引导进程的进程 ID,最初是管道中的最后一个进程,可以使用带有负参数的 kill 向整个进程组发送信号(记住--,因此它不会被解释为标志!):kill 123 终止进程 123,kill -- -123 终止进程 123。pkill 和 pgrep 允许通过不同的字段选择进程,例如父进程 ID 或进程名称(不推荐,因为易碎)。要从进程中获取进程组 ID,请使用ps。请注意,没有简单的方法可以引用“一个进程及其后代”(pgrep/pkill 允许您通过父进程进行选择,从而找到直接的子进程,但这些子进程没有递归选项),因此终止或向所有后代(但不包括整个进程组)发送信号很棘手 - 请参阅“终止所有子进程的最佳方法”。

请注意,shell 通常也包含“作业”的概念,其中“作业”是引用进程组的简写(通常是从 1 开始的数字),由 fg 和 bg 命令使用。这通常只对交互式会话有用 - 对于使用进程组 ID(或只是进程 ID)的脚本来说,更简单且更健壮。进程/进程组(OS)和作业(shell)的概念很容易混淆,因此进程管理通常被称为作业控制,即使它不是交互式的。

陷阱

作为发送信号的补充,shell 脚本可以使用trap接收来自其他进程的信号,并设置处理程序来响应它们。这些主要用于运行时间更长的脚本,主要是守护进程(后台进程),允许它们被干净地终止、重新加载、挂起或恢复。这主要用于在收到 KILL 时清理,或在收到 HUP 时重新加载配置。其他可以有用地捕获的信号包括 TSTP(当作业被 shell 挂起或“停止”时)和 CONT(当作业被恢复或“继续”时) - 这允许显式挂起和继续处理程序。请参阅SignalTrap: 发送和捕获信号 以获取详细信息。

华夏公益教科书