Ruby 预定义全局变量与常量完全指南:从$!到DATA的源码级剖析
【免费下载链接】rubyThe Ruby Programming Language项目地址: https://gitcode.com/GitHub_Trending/ru/ruby
本指南以 doc/language/globals.md 为骨架,系统梳理 Ruby 语言内置的全部预定义全局变量(如$!、$~、$/、$LOAD_PATH等)与预定义全局常量(如STDIN、ARGV、RUBY_VERSION、DATA等),逐一说明其含义、初始值、可写性、重置时机,并结合当前仓库的 C 源码(ruby.c、io.c、process.c、load.c)与标准库 lib/English.rb 揭示这些特殊变量的底层实现与别名机制。读完本文,你将能熟练运用这些全局变量编写脚本、调试异常、处理命令行输入输出,并理解-0/-a/-F/-l/-p/-d/-v等命令行选项是如何映射到这些变量的。
一、概述:什么是预定义全局变量与常量
Ruby 解释器在启动时预定义了一组全局变量(以$开头)和全局常量(全大写)。它们承载着运行时最核心的状态:最近一次异常、最近一次正则匹配结果、标准输入输出流、进程信息、加载路径、调试开关等。
其中一部分变量带有「密码般」的符号名(如$!、$~、$/),Ruby 提供名为 English 的标准库,为它们提供可读的英文别名(如$ERROR_INFO、$LAST_MATCH_INFO、$INPUT_RECORD_SEPARATOR)。启用方式只有一行:
require 'English'在 lib/English.rb 中,这些别名全部通过alias关键字实现,例如:
alias $ERROR_INFO $! alias $LAST_MATCH_INFO $~ alias $INPUT_RECORD_SEPARATOR $/ alias $OUTPUT_RECORD_SEPARATOR $\ alias $PROCESS_ID $$ alias $CHILD_STATUS $? alias $ARGV $* alias $MATCH $& alias $PREMATCH $` alias $POSTMATCH $' alias $LAST_PAREN_MATCH $+从源码可以确认,English模块本身只是一个占位声明(module English end if false,见 lib/English.rb),真正起作用的就是文件顶层的这些alias语句。加载该库后,两种写法完全等价。
阅读表格的方法
原文档为每个变量给出了五列属性:
| 列 | 含义 |
|---|---|
| Variable | 符号形式的变量名 |
| \English | require 'English'后可用的英文别名 |
| Contains | 变量实际保存的内容 |
| Initially | 程序启动时的初始值 |
| Read-Only | 是否只读(只读变量无法在程序中赋值) |
| Reset By | 哪些操作会重置/更新该变量 |
下文将按「异常、匹配数据、分隔符、流、进程、调试、其他、已废弃」的顺序逐一详解。
二、异常相关:$!与$@
$!($ERROR_INFO)
$!保存由Kernel#raise设置的 Exception 对象,初始为nil,只读。在rescue块中它指向当前捕获的异常:
begin raise RuntimeError.new('Boo!') rescue RuntimeError p $! end输出:
#<RuntimeError: Boo!>英文别名:$ERROR_INFO(由 lib/English.rb 定义)。
$@($ERROR_POSITION)
$@等价于$!.backtrace,返回由回溯位置组成的数组:
begin raise RuntimeError.new('Boo!') rescue RuntimeError pp $@.take(4) end输出类似:
["(irb):338:in `<top (required)>'", "/snap/ruby/317/lib/ruby/3.2.0/irb/workspace.rb:119:in `eval'", ...]英文别名:$ERROR_POSITION。在 lib/English.rb 中注明它是线程局部的(thread local)。底层实现上,这两个变量与解释器维护的「当前异常」状态紧密相关:在 eval.c 中可以看到,当ruby_debug为真且当前异常信息非 nil 时,解释器会把每个被 raise 的异常打印到$stderr——这正是$DEBUG相关行为的实现位置(详见下文「调试相关」)。
三、匹配数据:$~、$&、$``、$'、$+、$1…$n`
这组变量保存当前作用域内最近一次成功的模式匹配的信息,被统称为「匹配数据」。它们具有线程局部与帧(frame)局部的特性。详细的正则匹配语义可参考 doc/language/regexp/methods.rdoc 中的 Global Variables 一节。
| 变量 | English 别名 | 内容 | 可写 |
|---|---|---|---|
$~ | $LAST_MATCH_INFO | 由匹配生成的 MatchData 对象 | 否 |
$& | $MATCH | 匹配到的子串 | 否 |
$` | $PREMATCH | 匹配位置左侧的子串 | 否 |
$' | $POSTMATCH | 匹配位置右侧的子串 | 否 |
$+ | $LAST_PAREN_MATCH | 最后匹配到的分组 | 否 |
$n | 无 | 第 n 个分组匹配到的内容 | 是 |
$1、$2等数字变量没有 English 别名,且原文档明确它们是只读的(Read-Only = Yes)。
lib/English.rb 对这些变量做了额外说明,值得注意:
$&、$``、$'、$1–$9全部由$~派生而来,**对$~` 赋值会同步改变这些派生变量的值**;$+保存编号最高的那个分组的内容,例如"cat" =~ /(c|a)(t|z)/之后$+为"t";- 这些变量都是当前作用域局部(local to the current scope)且只读的。
四、记录分隔符:$/与$\
$/($INPUT_RECORD_SEPARATOR,别名$RS、$-0)
输入记录分隔符,初始为换行符"\n"。gets等读取方法依据它来确定「一条记录」的边界;若设为nil,gets将一次性读取整个文件。它由命令行选项-0设置,且除命令行选项之外将其设为非 nil 值已被标记为废弃(deprecated)。
-0的具体规则见 doc/language/options.md:
- 无参数时,记录分隔符为
0x00; - 参数为
0时,分隔符为''(空串); - 参数在
1..0377(八进制)时,分隔符为对应字符; - 参数为其他八进制值时,分隔符为
nil。
实际效果:
$ ruby -0 -e 'p $/' "\x00" $ ruby -00 -e 'p $/' "" $ ruby -012 -e 'p $/' "\n" $ ruby -015 -e 'p $/' "\r" $ ruby -0377 -e 'p $/' "\xFF" $ ruby -0400 -e 'p $/' nil英文别名:$INPUT_RECORD_SEPARATOR、$RS(见 lib/English.rb)。符号名$-0与$/等价。
$\($OUTPUT_RECORD_SEPARATOR,别名$ORS)
输出记录分隔符,初始为nil。当命令行选项-l给出时,会把$/的值复制给它。它被追加到print、IO#write等每次输出的末尾(默认nil表示不加任何文本)。同样的,除命令行选项之外将其设为非 nil 值已被废弃。英文别名:$OUTPUT_RECORD_SEPARATOR、$ORS(见 lib/English.rb)。
五、标准流:$stdin、$stdout、$stderr、$<、$>、$.、$_
$stdin/$stdout/$stderr
当前的(可重定向的)标准输入、输出、错误流,初始值分别为STDIN、STDOUT、STDERR:
$stdin # => #<IO:<STDIN>> $stdout # => #<IO:<STDOUT>> $stderr # => #<IO:<STDERR>>这三个变量是可写的,程序中可以重新赋值以重定向输出。它们的底层实现在 io.c:$stdin、$stdout、$stderr通过rb_define_virtual_variable注册了 getter/setter,并且被标记为 ractor 局部变量(rb_gvar_ractor_local),同时$>与$stdout共享同一组 getter/setter。
$<($DEFAULT_INPUT)
指向「默认输入」的只读变量:若ARGF非空则指向ARGF,否则指向$stdin。英文别名:$DEFAULT_INPUT。正如 lib/English.rb 所注释的,它支持大量类似File对象的方法(gets、each_line、filename、lineno、read、seek等),并且是只读的。
$>($DEFAULT_OUTPUT)
默认输出流,初始为$stdout,是Kernel#print与Kernel#printf的目的地。英文别名:$DEFAULT_OUTPUT(见 lib/English.rb)。在 io.c 中它与$stdout共用同一实现,因此给$stdout赋值会同步影响$>。
$.($INPUT_LINE_NUMBER / $NR)
最近一次读取的流中的输入位置(行号),初始为 0,由某些读取方法重置。英文别名:$INPUT_LINE_NUMBER、$NR。
$_($LAST_READ_LINE)
最近一次读取的流返回的字符串行,初始为nil。Kernel中许多字符串相关方法默认以$_为操作对象。它在 lib/English.rb 中被注明是当前作用域局部、线程局部的变量;在 io.c 中,$_通过虚拟变量注册并被标记为 ractor 局部且动态装箱(rb_gvar_box_dynamic)。
六、进程信息:$0、$*、$$、$?、$LOAD_PATH、$LOADED_FEATURES
$0($PROGRAM_NAME)
初始为正在执行的脚本名,可在程序中重新赋值(例如修改进程标题)。底层实现在 ruby.c:$0与$PROGRAM_NAME都通过rb_define_hooked_variable挂到rb_progname上,setter 为set_arg0;同时Process.setproctitle也提供了等价能力(ruby.c)。
$*($ARGV)
指向 ARGV 数组(命令行参数),只读。英文别名:$ARGV。注意 lib/English.rb 的补充:这个数组是被 Ruby 解释器处理过参数之后的剩余部分(解释器自身的选项已被移除)。
$$($PROCESS_ID / $PID)
当前进程的进程 ID,与Process.pid相同,只读。在 process.c 中被标记为 ractor 局部变量。英文别名:$PROCESS_ID、$PID。
$?($CHILD_STATUS)
初始为nil,之后保存为最近一个退出的子进程创建的Process::Status对象;线程局部、只读。底层实现在 process.c:$?通过虚拟变量get_CHILD_STATUS读取,而子进程退出状态由rb_last_status_set(process.c)写入(例如rb_syswait等系统调用路径,见 process.c)。英文别名:$CHILD_STATUS。
$LOAD_PATH($:、$-I)
被Kernel#load与Kernel#require搜索的路径数组,初始为 Ruby 默认值,只读(数组中元素本身冻结,见 load.c)。
它有一个非常实用的单例方法$LOAD_PATH.resolve_feature_path(feature)(实现于 load.c 的rb_resolve_feature_path),返回结果有三种:
[:rb, path]:path是将被加载的 Ruby 文件路径;[:so, path]:path是将被加载的共享对象(.so)文件路径;nil:不存在该 feature。
示例:
$LOAD_PATH.resolve_feature_path('timeout') # => [:rb, "/snap/ruby/317/lib/ruby/3.2.0/timeout.rb"] $LOAD_PATH.resolve_feature_path('date_core') # => [:so, "/snap/ruby/317/lib/ruby/3.2.0/x86_64-linux/date_core.so"] $LOAD_PATH.resolve_feature_path('foo') # => nil$LOADED_FEATURES($")
已加载文件的路径数组,初始为 Ruby 默认值,只读。在 load.c 中通过get_LOADED_FEATURES虚拟变量实现;require 成功加载新 feature 时会向其中追加路径(若数组被冻结则抛出错误,见 load.c)。
$LOADED_FEATURES.take(10) # => ["enumerator.so", "thread.rb", "fiber.so", "rational.so", "complex.so", "ruby2_keywords.rb", "/snap/ruby/317/lib/ruby/3.2.0/x86_64-linux/enc/encdb.so", "/snap/ruby/317/lib/ruby/3.2.0/x86_64-linux/enc/trans/transdb.so", "/snap/ruby/317/lib/ruby/3.2.0/x86_64-linux/rbconfig.rb", "/snap/ruby/317/lib/ruby/3.2.0/rubygems/compatibility.rb"]七、调试相关:$FILENAME、$DEBUG、$VERBOSE
$FILENAME
ARGF.filename方法的返回值,即当前正在读取的命令行文件名(命令行无文件参数时为'-'),只读。
$DEBUG(别名$-d)
- 初始为
true当且仅当命令行选项-d或--debug被给出,否则为false; - 可在运行程序中被设置为任意布尔值;
- 为
true时,每个被 raise 的异常都会被打印到$stderr。这一行为在 eval.c 与 ruby.c 中均有体现(RTEST(ruby_debug)判断)。
底层实现在 ruby.c:$DEBUG与$-d共享debug_getter/debug_setter,并标记为 ractor 局部(ruby.c)。
$VERBOSE(别名$-v、$-w)
- 初始为
true当命令行选项-v或-w被给出,否则为false; - 可在运行程序中被设为任意值或
nil; - 为
true时启用 Ruby 警告; - 为
nil时禁用警告,包括Kernel#warn的输出。
底层实现在 ruby.c:$VERBOSE、$-v、$-w共享verbose_getter/verbose_setter,setter 会把任意真值归一化为Qtrue;另有$-W(只读)通过opt_W_getter将 verbose 状态映射为整数:nil → 0、false → 1、true → 2(ruby.c)。
八、其他变量:$-F、$-a、$-i、$-l、$-p、$F
这组变量与命令行选项一一对应,其中$-p、$-l、$-a在 ruby.c 中被标记为 ractor 局部变量。
| 变量 | 对应选项 | 内容与说明 |
|---|---|---|
$-F($;) | -F | String#split的默认字段分隔符,必须是 String 或 Regexp;除命令行选项外设置为非 nil 已被废弃。英文别名$FS、$FIELD_SEPARATOR |
$-a | -a | 是否给出-a,只读 |
$-i | -i | -i选项给出的扩展名,无则nil;是ARGF.inplace_mode的别名 |
$-l | -l | 是否设置-l,只读 |
$-p | -p | 是否给出-p,只读 |
$F | -a+-n/-p | 若给出-a,在每次-l/-p循环开始时把$_按$-F拆分得到的数组赋给它 |
关于-F的字段分隔语义,doc/language/options.md 说明其参数即为split使用的输入字段分隔符。值得留意的是 lib/English.rb 还提供了$;的两个可读别名$FS与$FIELD_SEPARATOR(原文档未列入 English 表,属于标准库额外提供的别名)。
九、已废弃的变量:$=与$,
$=:原用于「是否区分大小写」的开关,早已废弃;$,:print/Array#join参数之间的输出分隔串,默认nil(不加任何文本),亦已废弃。标准库中仍保留了它的别名$OFS、$OUTPUT_FIELD_SEPARATOR(见 lib/English.rb)。
十、预定义全局常量
流常量:STDIN、STDOUT、STDERR
标准输入、输出、错误流,分别是$stdin、$stdout、$stderr的默认值:
STDIN # => #<IO:<STDIN>> STDOUT # => #<IO:<STDOUT>> STDERR # => #<IO:<STDERR>>环境常量:ENV、ARGF、ARGV、TOPLEVEL_BINDING
| 常量 | 内容 |
|---|---|
ENV | 当前环境变量名与值的 Hash |
ARGF | 命令行给出的多个文件的虚拟拼接(未给文件、给出"-"或全部读完后,回退到$stdin) |
ARGV | 给定的命令行参数数组(在 ruby.c 中通过rb_define_global_const("ARGV", rb_argv)定义) |
TOPLEVEL_BINDING | 顶层作用域的 Binding |
ENV.take(5) # => [["COLORTERM", "truecolor"], ["DBUS_SESSION_BUS_ADDRESS", "unix:path=/run/user/1000/bus"], ["DESKTOP_SESSION", "ubuntu"], ["DISPLAY", ":0"], ["GDMSESSION", "ubuntu"]] TOPLEVEL_BINDING # => #<Binding:0x00007f58da0da7c0>版本常量:RUBY_*
这些常量描述当前 Ruby 解释器自身的版本与构建信息,全部由 version.c 提供:
| 常量 | 内容 | 示例(Ruby 3.2.2) |
|---|---|---|
RUBY_VERSION | 版本号 | "3.2.2" |
RUBY_RELEASE_DATE | 发布日期 | "2023-03-30" |
RUBY_PLATFORM | 平台标识 | "x86_64-linux" |
RUBY_PATCHLEVEL | 补丁级别(开发版为-1) | 53 |
RUBY_REVISION | Git 提交哈希 | "e51014f9c05aa65cbf203442d37fef7c12390015" |
RUBY_COPYRIGHT | 版权字符串 | "ruby - Copyright (C) 1993-2023 Yukihiro Matsumoto" |
RUBY_ENGINE | 实现名称 | "ruby" |
RUBY_ENGINE_VERSION | 实现版本 | "3.2.2" |
RUBY_DESCRIPTION | 完整描述 | "ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-linux]" |
从源码看,这些常量的数据源定义在 version.c:RUBY_REVISION在非发布构建中为"HEAD";ruby_version、ruby_release_date、ruby_platform、ruby_patchlevel分别是各常量的 C 层数据(version.c),而RUBY_DESCRIPTION则由这些片段拼装而成(version.c)。因此,不同构建环境(或开发版)下这些常量的实际值会与文档示例不同,这是正常的。
嵌入数据常量:DATA
DATA当且仅当程序中包含__END__行时被定义。此时DATA是一个 File 对象,内容为__END__之后的所有行,且读取位置定位在首行:
p DATA DATA.each_line { |line| p line } __END__ Foo Bar Baz输出:
#<File:t.rb> "Foo\n" "Bar\n" "Baz\n"这是 Ruby 脚本内嵌数据(self-contained data section)的经典用法,常用于小工具的测试数据或模板。
十一、速查总表
综合以上内容,全部预定义全局变量一览(含 English 别名):
| 类别 | 变量 | English 别名 | 只读 |
|---|---|---|---|
| 异常 | $! | $ERROR_INFO | 是 |
| 异常 | $@ | $ERROR_POSITION | 是 |
| 匹配 | $~ | $LAST_MATCH_INFO | 否 |
| 匹配 | $& | $MATCH | 否 |
| 匹配 | $` | $PREMATCH | 否 |
| 匹配 | $' | $POSTMATCH | 否 |
| 匹配 | $+ | $LAST_PAREN_MATCH | 否 |
| 匹配 | $1…$n | 无 | 是 |
| 分隔符 | $/、$-0 | $INPUT_RECORD_SEPARATOR、$RS | 否 |
| 分隔符 | $\ | $OUTPUT_RECORD_SEPARATOR、$ORS | 否 |
| 流 | $stdin/$stdout/$stderr | 无 | 否 |
| 流 | $< | $DEFAULT_INPUT | 是 |
| 流 | $> | $DEFAULT_OUTPUT | 否 |
| 流 | $. | $INPUT_LINE_NUMBER、$NR | 否 |
| 流 | $_ | $LAST_READ_LINE | 否 |
| 进程 | $0 | $PROGRAM_NAME | 否 |
| 进程 | $* | $ARGV | 是 |
| 进程 | $$ | $PROCESS_ID、$PID | 是 |
| 进程 | $? | $CHILD_STATUS | 是 |
| 进程 | $LOAD_PATH($:、$-I) | 无 | 是 |
| 进程 | $LOADED_FEATURES($") | 无 | 是 |
| 调试 | $FILENAME | 无 | 是 |
| 调试 | $DEBUG($-d) | 无 | 否 |
| 调试 | $VERBOSE($-v、$-w) | 无 | 否 |
| 其他 | $-F($;) | $FS、$FIELD_SEPARATOR | 否 |
| 其他 | $-a | 无 | 是 |
| 其他 | $-i | 无 | 否 |
| 其他 | $-l | 无 | 是 |
| 其他 | $-p | 无 | 是 |
| 其他 | $F | 无 | 否 |
| 废弃 | $=、$, | $OFS、$OUTPUT_FIELD_SEPARATOR | — |
如需进一步了解这些变量与命令行选项(-0、-F、-a、-i、-l、-p、-d、-v、-w)的对应关系,可查阅 doc/language/options.md;正则匹配相关变量的完整语义可参考 doc/language/regexp/methods.rdoc;英文别名的完整清单见 lib/English.rb。
【免费下载链接】rubyThe Ruby Programming Language项目地址: https://gitcode.com/GitHub_Trending/ru/ruby
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考