执行 bg %1 后作业运行,但 shell 保留终端前台;执行 fg %1 后运行中的作业取得前台。终端同一时刻只有一个前台进程组。

作业控制在进程组之间交接终端前台

交互式作业控制让 shell 管理共享控制终端的多个进程组。终端同一时刻只有一个前台组,其他作业可以在后台运行,也可以保持停止状态。前台归属、是否运行和 shell 的作业表,是三种不同状态。后台作业不等于已经暂停,停止作业也不等于已经终止;判断当前交互为什么没有进展,需要先分清这些状态。 是否运行与终端前台归属是不同状态。bg 恢复作业但不交出键盘,fg 恢复前台交互。shell 作业表不等于持久服务状态。 坏掉的样子 启用终端信号处理时,输入已配置的中断或暂停字符,会由终端驱动向前台组发送信号,不是只发送给最后打印文字的那个进程。控制字符重新映射、终端模式变化以及应用信号处理函数,都可能改变结果。因此只说按某个键,并不能保证在每个程序和每套终端配置中得到相同反应,还要核对实际处理条件。 普通后台作业尝试读取自己的控制终端时,会被 SIGTTIN 停止。把它放到后台,并不会给它单独分配一份键盘输入流。输出又有不同规则:后台写入通常允许,还会受 TOSTOP 等终端设置影响。因此后台执行不意味着彻底脱离终端,输入、输出和前台信号各有自己的约束,需要分别观察。 %1 这类作业标识属于某个 shell 的作业表,不是全局进程编号。换一个终端或进入非交互 shell 后,不能假定相同号码仍然管理相同任务。管道和后台命令如何分组,也取决于真实 shell 调用是否启用了作业控制。交互终端中看见的行为,不能不加检查地套到所有脚本启动路径上。 快速验证 把脚本保存为 jobcontrol.py,在装有 Bash 和 Python 的 Linux 环境中运行 python3 jobcontrol.py。保留的 2026-10-15 审查曾使用 Linux 6.18.44、Bash 5.2.37 和 Python 3.12.14 原样重跑两种语言样本。这些是历史观察,本次源码审查没有重新运行交互实验。脚本创建自己的伪终端与交互 Bash,不会把控制字符发给你用于启动它的终端: ...

九月 27, 2027 · 2 分钟 · XB Field Notes
父进程导出的 local 产生两份子进程环境副本。子进程 A 将自己的 local 改为 child;子进程 B 与父进程仍保留 local。箭头只指向子进程,没有修改回传箭头。

环境变量属于各自的进程

环境是程序启动时收到的一组名称与字符串值。子进程通常接收父进程选定的环境,修改自己的副本不会回写父进程或兄弟进程。这是一条有用的配置边界,却不是命令没有副作用的保证:程序仍可能根据配置修改共享文件、数据库或其他资源。理解变量如何传递,与判断程序实际会做什么,是两个需要分别核对的问题。 沿直接启动者传入的值检查。环境副本不会回写;但子 Shell、新执行的 Bash 程序与当前 Shell 中的点命令,保留和修改的状态并不相同。 导出决定另一个程序能收到什么 Shell 变量与环境条目有关联,但并不是同一概念。DEMO_SETTING=local 创建或更新 Shell 参数,export DEMO_SETTING 才把它标记为启动外部命令时应包含的环境项。父 Shell 能打印变量,不代表新启动的 Bash 就能看到未导出的值。把赋值写进某个配置文件,也不会让每个 Shell 自动读取它;还必须确认文件是否在对应启动路径上被加载。 Bash 从环境继承的变量已经带有导出属性,之后重新赋值会保留该属性。因此子进程修改值后,再启动孙进程时可以直接传递新值,不要求每一代重复写一次导出命令。unset 删除变量,Bash 的 export -n 则取消导出属性但保留当前 Shell 中的值。真正需要确认的是直接启动者传入了什么,而不是机械地搜索祖先脚本里出现了多少次导出语句。 ...

九月 25, 2027 · 1 分钟 · XB Field Notes
一个 ./*.txt 模式展开成三个 Bash 参数,./two words.txt 保持完整。

Bash 的通配符在命令执行前就展开

Bash 命令中的通配符通常会在目标程序启动前变成一组路径。它让批量处理很方便,也让同一行命令受到当前目录和 shell 选项的影响。程序拿到的是已经形成的参数,不会收到标记来告诉它哪些参数来自星号。因此,写命令时应先确定:希望传入实际匹配的文件,还是希望把模式本身交给另一个程序解释。两者需要不同的引号写法,不能依靠目标程序猜测意图。 文件名展开保留各个匹配项内的空格。未匹配策略和接收程序的语义仍需分别处理。 先匹配,再保留参数边界 Bash 先进行分词,再进行文件名展开。没有引用的星号、问号和方括号模式可能因此生成多个参数。但匹配到的文件名即使包含空格,也仍然是一个参数,因为文件名展开之后不会再执行一次分词。常见问题发生在下一步:循环拿到了完整路径,却用没有引号的变量把它传给另一个命令。循环体中的 "$file" 仍然必要,不能因为前一步通配成功就省掉。 如果接收方需要模式,就应引用模式。例如 find . -name '*.txt' 将匹配规则交给 find;去掉引号后,Bash 会先在当前目录展开它,find 得到的可能是多个文件名,已经不是原来要表达的条件。变量里包含星号时也一样:引用变量会传递字面星号,不引用则可能触发文件名展开。引号只控制当前 shell 如何形成参数,不会禁止接收方按自己的语法解释收到的文本。 通配符匹配目录项,并不保证结果都是普通文件。名为 archive.txt 的目录也能匹配 *.txt,只想处理普通文件时还需要判断类型。普通星号不会越过斜杠,默认也不匹配名字开头的点;需要隐藏文件时,应明确使用相应模式或开启 dotglob。遍历整棵目录树是另一个需求,不能把当前目录的一次展开误认为递归搜索。 环境变量也能改变结果集合。Bash 中非空的 GLOBIGNORE 会过滤匹配结果,并且产生开启 dotglob 的副作用。因此,从交互会话继承状态的脚本可能意外纳入隐藏文件。下面的实验会清除 GLOBIGNORE 及较新版本的排序覆盖变量 GLOBSORT,并明确指定相关选项,让断言对应一套可重复的环境,而不是依赖读者终端的偶然配置。 ...

九月 8, 2027 · 1 分钟 · XB Field Notes
对于外部可执行文件,裸命令词 tool 沿外部查找路径选择文件;./tool 则沿独立的直接路径选择指定文件。

shell 找不到那条裸命令时

“找不到命令”说明一次查找失败,却不代表 Shell 每次都遍历了 PATH 的全部目录。在普通 Bash 中,不带斜杠的命令词可能对应函数、内建命令、缓存的可执行路径,或者刚刚搜索到的程序。更早的输入读取阶段,别名还可能已经改写命令。面对报错,应先检查真正发生故障的那个 Shell,而不是立即重装一个其实已经存在的工具。 在真正出错的 Shell 中检查解析结果。函数、内建命令、路径缓存和 PATH 顺序可能选择不同命令;含斜杠时直接选择路径。选中命令不等于执行成功。缓存顺序来自原文示例,不是本轮新做的运行测试。 先确认是哪一层查找给出结果 展开之后,Bash 先检查函数,再检查内建命令,之后才查找外部程序。type name 描述当前解析结果,type -a name 可以看到其他定义或候选项。command -v name 适合检查该 Shell 中是否存在某个命令,但结果不保证是文件路径,也可能说明它是函数或内建命令。因此不能无条件把输出交给复制文件或读取文件的工具。 ...

五月 20, 2027 · 1 分钟 · XB Field Notes
通过引用的 $@ 转发时,四个精确值保留四个独立边界。

$@ 等特殊变量给出函数的完整入参清单

包装脚本应当把收到的参数按原有边界继续传递。在 Bash 的命令参数位置,"$@" 会让每个位置参数分别成为一个带引号的词,因此能保留空值、空格、换行和字面通配符。只用两个简单文件名测试成功,并不能证明包装脚本正确;一旦它把参数列表拼成字符串再拆开,真实调用者传入的边界就可能丢失。转发是否可靠,应当用能暴露这种差异的输入来验证。 应转发列表本身。引号保留参数边界,但不决定下游选项解析,也不会让其他 shell 状态自动局部化。 参数列表与拼接文本不同 位置参数属于当前脚本或正在执行的函数。$# 返回参数数量,内容为空字符串的参数也计算在内。"$1" 读取第一个参数,"${10}" 读取第十个;后者需要花括号,以区别于第一个参数后面接字符零的写法。$0 表示启动的 shell 或脚本名称,不会作为额外元素出现在 "$@" 中,不能把它误算进业务参数列表。 在普通命令边界上,"$@" 为每个参数保留一个独立词;完全没有位置参数时,它不产生任何参数。相比之下,"$*" 始终形成一个字符串,用 IFS 的第一个字符连接各个值。IFS 未设置时使用空格,设为空字符串时则不插入分隔符。它并不会删除参数内部原有的空格,但各参数原来的边界通常已经无法恢复,因此不适合转发任意列表。 省略引号的 $@ 不是等价简写,展开后的内容仍可能被分词和文件名展开处理。调用者传入的字面星号,到了包装脚本所在目录可能突然匹配出一组文件。往拼接文本里补引号字符也不能恢复结构。需要把列表保存到之后使用时,在 Bash 中写成 args=("$@"),并通过 "${args[@]}" 转发,即使随后调整位置参数,保存的列表仍保留各个元素。 坏掉的样子 函数执行期间拥有自己的位置参数列表。调用时传入的参数暂时替换当前可见列表,返回时恢复调用者的列表。函数内部使用 set 或 shift 修改的是这次函数调用的参数,但这不代表其他所有 shell 状态都自动局部化。例如,没有局部声明就修改 IFS,可能影响调用者之后的命令。参数作用域和普通变量作用域应分别理解,不能互相推导。 ...

四月 16, 2027 · 2 分钟 · XB Field Notes
当前状态轨道先出现失败工作的 7,随后成功日志产生 0。立即保存的分支进入独立的 saved=7 轨道,并一直保留到显式 return 或 exit。

退出码是用一个数字汇报成败

退出状态是 shell 用来决定下一步执行什么的小整数。对 shell 控制流来说,零表示成功,非零进入失败分支,但这并不意味着每个数字都有统一的业务含义。搜索没有匹配、输入格式错误和服务器不可用,都可能返回非零,却需要不同处理。决定重试、忽略或终止之前,应先阅读目标命令的契约。不能只因为状态不为零,就把所有结果归为同一种事故。 趁状态仍对应目标命令时立即保存。日志、控制流变换和后台完成检查之后,仍需保留并传播正确结果。 让状态始终对应原来的命令 Bash 通过 $? 提供最近前台命令的状态。需要保留时,必须在记录日志、执行另一个条件判断或清理操作之前保存;这些操作也有自己的状态,会覆盖原本想观察的证据。函数通常使用最后执行的命令状态作为返回值,因此,末尾一句成功的日志输出就可能把前面的失败包装成整个函数成功,调用者看到的结果与实际工作脱节。 ...

三月 27, 2027 · 1 分钟 · XB Field Notes
在同一个共享 Shell 中,函数取得自己的位置参数表并移位;返回后调用者原有列表恢复。共享变量与这些临时参数帧并存。

Shell 函数像脚本一样读取自己的参数

Shell 函数把一组命令组织到同一个名称之下,并接收本次调用自己的位置参数。普通调用时,函数体在当前 Shell 中执行,不会自动启动独立进程。这方便复用操作,也意味着赋值、切换目录及其他 Shell 状态变化可能影响调用方。函数接口除了规定输入,还应明确输出、退出状态和有意产生的副作用,不能只因为把几行命令包进了函数,就认为它们已经与外层隔离。 普通函数调用共享 Shell。返回时恢复位置参数帧,但嵌套辅助函数仍能修改动态局部绑定;数据和失败状态需要分别约定。此图表示原文实验预期,本轮未重新执行。 每次调用都有自己的参数列表 调用 demo_func first second 后,这次函数内部的前两个位置分别是传入的两个值,参数数量为二。再次调用其他函数,会暂时安装下一层自己的参数列表;返回后,调用者的列表重新可见。因此,在被调用函数中移位,不会消耗调用者的位置参数。Bash 的 $0 也不会因此变成当前函数名称,不能用它判断自己正在哪个函数里面。 使用 "$@" 转发整份列表,才能保留空参数、空格以及通配符字符各自的边界。应先检查数量,再读取必需的位置,同时区分省略参数与明确传入空字符串。Shell 函数没有自动执行这些规则的参数声明,调用时语法上能写出一串值,并不意味着这些值已经满足函数的业务约定。 局部变量使用动态作用域 Bash 的局部声明会创建一个变量,遮住外层的同名绑定;函数返回之后,原来的绑定再次可见。若修改调用者变量并不是接口的一部分,应为临时值、循环计数器等声明局部变量。面向多个解释器的可移植脚本,不能假设每个 Shell 都实现了 Bash 的这套局部变量设施,解释器要求必须与使用的语法保持一致。 ...

三月 16, 2027 · 1 分钟 · XB Field Notes
标准输出带 first、内部换行、second 及两个末尾换行;捕获结果为 first、换行、second。内部换行保留,两个末尾换行都被删除。

命令替换把一条命令的输出嵌进另一行

命令替换会收集一个命令的标准输出,再把得到的文本放入外层表达式。常见的 $(command) 适合获取时间戳、计算出的标签或其他较小的文本值,但它并不是任意字节的透明传输通道,也不是可靠的文件名列表容器。使用前需要理解三个边界:末尾换行如何处理、内部命令在哪个执行环境运行,以及失败状态如何传递。只看到变量里有内容,不能证明整个操作成功。 捕获文本与退出状态走不同路径。引号保留结果参数,不能恢复已删除的换行。标准 $(...) 隔离 Shell 状态,却不会撤销外部副作用。此图表示原文实验预期,本轮未重新执行。 先收集文本,再决定怎样使用 本文讨论的标准命令替换形式会让 Bash 在子 shell 环境中运行内部命令,收集标准输出,并删除末尾所有换行符。位于文本中间的换行仍然保留。双引号能阻止后续分词和文件名展开,却不能恢复已经删除的尾部换行。如果需要精确保留最后几个字节,应该使用文件或数据流,而不是依靠 shell 变量原样往返。 赋值语句 result=$(producer) 会把处理后的文本存进变量,不在此处拆成多个参数。后续使用时仍应写成 "$result",保留它的参数边界。类似地,consumer "$(producer)" 会传递一个参数,即使结果为空。去掉引号可能得到多个参数,也可能完全没有参数;输出中的星号还可能匹配当前目录的文件,让同样的生产者输出在不同目录产生不同调用结果。 标准错误和标准输出彼此独立,除非主动重定向,诊断信息仍可能显示在终端,而变量只收集结果正文。将两路输出合并后,警告文本可能污染本应供程序读取的值。因此,应先确定生产者的输出契约,再决定是否合并;直接吞掉所有诊断信息也可能让空结果失去可解释的原因。输出内容、诊断信息和退出状态应分别检查。 不要把这里的作用域结论推广到较新 Bash 的每一种捕获语法。本文使用的美元符号加圆括号形式具有实验验证的子 Shell 行为;较新的当前环境捕获形式会保留不同的副作用。说明一个捕获值时,应明确语法与解释器版本,而不是把所有能够取得文本的写法都视为等价。 ...

三月 3, 2027 · 1 分钟 · XB Field Notes