1. 升级前先定位你的macOS里Ruby到底从哪来先说个我自己的真实经历。前阵子有个项目一直报You have already activated ...查了大半天最后发现根源就是macOS自带的Ruby版本太老而项目依赖的新版gem不认它。如果你也在Mac上写脚本、跑Jekyll、做Rails开发或者被某个开源工具要求Ruby 3.0折磨过那这篇文章就是给你准备的。这篇文章会把macOS升级Ruby这件事拆开揉碎从定位安装来源、备份检查、两种主流升级路线Homebrew和rbenv到升级后必做的清理验证以及我踩过的坑全都写出来。很多人拿到Mac第一反应是ruby -v看到个2.6.10就觉得系统自带Ruby挺好的能跑就行。但问题恰恰出在这里macOS自带的Ruby是给系统工具用的比如某些内置脚本Apple更新它的频率极低而且有两个硬伤——路径权限锁死、版本常年落后。你用的插件、构建工具、命令行工具一旦要求新版本Ruby系统自带的那个就成了定时炸弹。所以升级前先搞清楚你的Ruby到底装在哪这一步比升级本身还重要。1.1 三条常见安装路径先对号入座用下面几条命令一分钟就能摸清家底。打开终端Terminal逐个执行which ruby ruby -v正常情况下你会看到三类结果之一输出路径说明优先级/usr/bin/ruby系统自带Ruby不建议直接动它/usr/local/opt/ruby/bin/rubyIntel或/opt/homebrew/opt/ruby/bin/rubyApple SiliconHomebrew安装的Ruby推荐升级目标~/.rbenv/shims/rubyrbenv管理的Ruby推荐升级目标多版本控制利器如果你发现which ruby指向/usr/bin/ruby说明你一直在用系统自带版本。这时候千万别手贱去改/usr/bin/ruby的权限或者硬替换它——macOS的系统文件受SIP保护即使你强制改成功了下次系统更新也会被还原而且可能引发系统脚本异常。正确做法是装一个用户级的Ruby然后把PATH顺序调整好。1.2 为什么我不建议直接动系统自带版本系统自带Ruby位于/usr/bin下是Apple为了支撑系统脚本而内置的。你可以把它理解成毛坯房里的水电管线——不是给你装修用的你硬要改造它轻则物业SIP拦着不让动重则全楼断电影响系统工具。而且系统Ruby的版本是固定的Apple只会在重大系统更新时才顺手升一级你指望它跟上Ruby社区的发布节奏基本不现实。所以升级Ruby的本质不是把系统自带的替换掉而是另外装一个新版本让终端默认用新版本。理解了这一点后面所有命令都不难。2. 备份与依赖检查避免升级后项目全线崩盘版本升级最怕的不是升不上去而是升上去了项目跑不起来了。Ruby的gem生态非常敏感一个gem可能依赖另一个gem的特定版本Ruby本身的大版本切换比如2.7到3.x还会带来语法和API的不兼容。我见过不少同行升级完Ruby后项目启动直接报LoadError或者SyntaxError就是因为没做准备。2.1 先给项目拍个体检照进入你的项目目录执行ls -la | grep -E Gemfile|\.ruby-version|\.ruby-gemset cat Gemfile 2/dev/null | grep ruby cat .ruby-version 2/dev/null这三个文件是项目Ruby配置的户口本。Gemfile里如果写了ruby 3.0.0那你的Ruby版本就不能低于3.0.ruby-version文件则直接指定了要用哪个版本。如果没有这两个文件说明项目没做版本锁定升级后风险会大一些——因为所有gem都会按新Ruby的兼容性重新解析依赖。我的建议升级前务必把Gemfile.lock备份一份。这个文件锁定了当前所有gem的精确版本号是你项目能跑的保险单。万一升级后出问题用bundle install --local加回旧的Gemfile.lock就能快速回退。cp Gemfile.lock Gemfile.lock.bak2.2 系统依赖和编译环境检查Ruby很多gem比如pg、nokogiri、mysql2需要编译原生扩展这就依赖于macOS的命令行开发工具。升级前先确认这些工具还在xcode-select -p # 如果报错执行xcode-select --install另外有些gem还依赖openssl、libffi等系统库。用Homebrew管理的库在升级Ruby时一般不会有太大问题但如果你用的是rbenv编译新版Ruby时可能碰到找不到openssl的报错。这时候需要提前装好依赖brew install openssl readline libyaml libffi注意Homebrew安装的openssl是keg-only的不会自动加入PATH这个坑我在后面排错部分会详细说。2.3 升级前的最后一道保险跑一遍测试如果你的项目有现成的测试套件比如RSpec、Minitest升级前先全量跑一遍确保升级前的状态是绿的。这样升级后再跑一次有任何失败都可以明确归因于Ruby版本变化而不是项目本身的问题。bundle exec rspec 2/dev/null || rake test 2/dev/null如果你没有测试套件至少把项目能启动、能访问首页这个主链路手动验证一遍。3. Homebrew路线快速升级到最新稳定版如果你手头只有一个项目或者不想折腾多版本管理Homebrew是最快、最省心的升级路线。Homebrew会帮你把Ruby装到独立的目录里不污染系统自带的那份。3.1 安装或更新HomebrewmacOS上如果还没有Homebrew用官方命令装官方地址网上随便搜得到。已经装了的先更新到最新brew update brew upgrade ruby这条命令会把Homebrew里已安装的Ruby升级到最新稳定版。执行完后你可能会看到类似这样一行提示ruby is keg-only, which means it was not symlinked into /usr/local这个提示很关键。keg-only的意思是Homebrew不会自动把新版本Ruby放到全局PATH里因为系统自带了一份/usr/bin/rubyHomebrew不想和它冲突。要想让终端默认用新版Ruby你需要手动调整PATH。3.2 处理PATH冲突让新版Ruby接管终端在~/.zshrcmacOS默认是zsh或者~/.bash_profile里添加export PATH/opt/homebrew/opt/ruby/bin:$PATH如果你用的是Intel芯片的Mac路径换成/usr/local/opt/ruby/bin。然后让配置生效source ~/.zshrc再验证一下which ruby ruby -v如果which ruby显示的是Homebrew的路径说明升级成功。如果还指向/usr/bin/ruby说明PATH顺序不对——生效顺序是前面的优先确保Homebrew的路径排在了/usr/bin前面。3.3 Homebrew版Ruby的常见坑Homebrew路线最大的坑在于系统Ruby和Homebrew Ruby共存gem命令默认走哪个完全取决于PATH。你如果后面又跑了sudo gem install xxx很可能因为权限原因装到了系统Ruby的目录里然后项目启动又开始报错。我的做法是装gem一律不加sudo让gem命中当前PATH里的Ruby即Homebrew版并且用gem env home检查一下安装路径gem env home # 应该显示 /opt/homebrew/lib/ruby/gems/... 或者 /usr/local/lib/ruby/gems/...如果显示的是/Library/Ruby/Gems/...说明gem命令仍然指向系统Ruby你需要先修正PATH。4. rbenv路线一次性解决多项目版本冲突Homebrew适合只有一个项目、只想用最新版的场景。但如果你同时维护三五个项目有的要Ruby 2.7有的要3.2还有的每天切来切去那就必须上版本管理工具。我在rbenv和RVM之间选了rbenv原因很简单rbenv是轻量的shim层只切换Ruby版本不干预gem——它通过自动生成的shims目录拦截ruby命令然后按项目目录下的.ruby-version文件决定调用哪个版本。RVM虽然功能全面但会直接替换gem、打乱shell环境对我来说太重了。4.1 安装rbenv并初始化brew install rbenv ruby-build然后用rbenv自带的初始化脚本配置shellecho eval $(rbenv init -) ~/.zshrc source ~/.zshrc验证安装rbenv -vruby-build插件负责编译安装Ruby装完rbenv后它就自动可用了。4.2 安装指定版本并设为全局默认先看看有哪些版本可选rbenv install -l然后安装你需要的版本比如3.2.2这是当前比较稳定的一个3.2系列版本rbenv install 3.2.2这里要提醒一句rbenv安装Ruby会从源码编译时间取决于你的网速和机器性能一般3~5分钟。期间如果报错多半是缺依赖库回到第2.2节把依赖装齐再重试。装完后设置全局版本和局部版本rbenv global 3.2.2 # 全局默认 rbenv local 3.2.2 # 当前目录锁定会生成 .ruby-versionrbenv global是一个兜底版本rbenv local更精准——你只需要在项目的根目录执行rbenv local 3.2.2就会生成一个.ruby-version文件之后任何人进入这个目录rbenv都会自动切到3.2.2。4.3 rbenv和gem的配合rbenv最省心的地方在于gem命令也被它管理。执行which gem会看到~/.rbenv/shims/gem也就是说你装的gem会跟着当前Ruby版本走不会像Homebrew那样发生system gem和user gem打架的问题。每次切换Ruby版本后建议重新执行一次gem list --local bundle install这样就能确保当前版本的gem依赖是完整的。注意切换版本后之前版本装的gem不会自动带过来这是正常的别慌。5. 升级后验证与收尾PATH、gem、bundler一个都不能少升级完只是万里长征第一步。我见过太多人升级完Ruby跑ruby -v没问题就高高兴兴下班了结果第二天同事拉代码项目直接跑崩。所以我把升级后的验证清单单独列出来每一项都值得你过一遍。5.1 确认当前ruby和gem的真实身份无论你走Homebrew还是rbenv升级后的第一件事——彻底关掉终端再重开一个然后执行ruby -v which ruby gem -v which gem列一个表对照一下检查项Homebrew版预期rbenv版预期which ruby/opt/homebrew/opt/ruby/bin/ruby~/.rbenv/shims/rubygem env home/opt/homebrew/lib/ruby/gems/...~/.rbenv/versions/3.2.2/lib/ruby/gems/...如果which ruby和gem env home不匹配——比如ruby指向rbenv但gem指向Homebrew说明你PATH配置有残留两套工具链打架了。解决办法是把rbenv的PATH配置放在Homebrew的前面或者在.zshrc里只保留一种管理方式。5.2 升级gem工具链和项目依赖Ruby升级后gem本身的版本和新Ruby不一定完全适配需要更新gem系统gem update --system然后在你项目的根目录重新安装bundle依赖bundle install这一步会把Gemfile.lock里锁定的所有gem重新编译。如果项目依赖的原生扩展gem比如nokogiri、ffi在编译时卡住了多半是缺libxml2、pkg-config这类依赖。用Homebrew补齐brew install pkg-config libxml25.3 清理旧版本产生的残留升级最大的副作用是留下了一堆旧版本gem和缓存。经验是每升级一个大版本至少清理一次gem cleanup gem list --local | column -tgem cleanup会删除当前版本库里所有gem的过期版本定期执行能让环境干净不少。rbenv用户还可以定期删掉不再用的Ruby旧版本rbenv versions rbenv uninstall 2.7.0 # 举个例子确认不需要后删除5.4 常见的三个升级后遗症快速自查症状原因解决方案command not found: pod或command not found: jekyllgem安装到了新Ruby目录但PATH里没生效重新打开终端确认PATH配置必要时重装对应gembundle: command not foundbundler没装到当前Ruby版本gem install bundler项目启动报LoadError缺gem版本Gemfile.lock与当前Ruby版本不兼容删掉Gemfile.lock重新bundle install需要特别注意的是删掉Gemfile.lock这种操作属于破釜沉舟只建议在确认项目代码本身不需要改动的情况下做。如果项目里用到了锁得比较死的私有gem源重新解析依赖可能出幺蛾子最好先尝试保留Gemfile.lock执行bundle update gem名做局部更新。6. 排错实录升级过程中最常见的三类问题最后这部分写几个我在升级过程中真实踩过的坑每一个都是血泪教训。希望你能绕过它们。6.1 权限问题Operation not permitted某次在同事的机器上帮他升级Ruby执行gem update --system直接报Operation not permitted。原因是那台机器上的gem命令指向了系统自带的Ruby目录系统文件受SIP保护不允许写入。解决方法不是sudo强杀而是先确认gem env home指向的路径。如果确实指向/Library/Ruby/Gems/说明你还在用系统Ruby。先回到第3.2节或第4.1节把PATH调整好让gem指向用户级Ruby目录再执行更新就顺畅了。6.2 编译错误openssl的锅有一回我用rbenv安装Ruby 3.0.0编译时报错说找不到openssl.h。查了半天发现Homebrew的openssl是keg-only不会主动让编译器找到它。解决办法有两种任选其一# 方法一安装时显式指定openssl路径 RUBY_CONFIGURE_OPTS--with-openssl-dir$(brew --prefix openssl) rbenv install 3.0.0 # 方法二直接把openssl路径写进环境变量 export LDFLAGS-L/opt/homebrew/opt/openssl/lib export CPPFLAGS-I/opt/homebrew/opt/openssl/include在macOS上编译Ruby或装原生扩展gemopenssl是头号大坑。建议开装之前先把openssl、libffi、readline这几个常用依赖装好省得编译到一半才报错。6.3 gem原生扩展无法编译环境变量和pkg-config升级完Ruby之后大家最容易遇到的一个具体报错是 gem 执行bundle install时某个原生扩展宝石编译不了错误里通常带着pkg-config或者Could not find libxml2。这类问题十有八九是编译环境的pkg-config路径没配对。先确认pkg-config存在pkg-config --version再确认你要编译的库能被pkg-config搜到比如libxml2pkg-config --exists libxml-2.0 echo found || echo not found如果显示not found用Homebrew装好库后需要让pkg-config能找到Homebrew的库路径。macOS上Homebrew的库默认会在/opt/homebrew/lib/pkgconfig下把这个路径加到pkg-config的搜索路径里export PKG_CONFIG_PATH/opt/homebrew/lib/pkgconfig:$PKG_CONFIG_PATH这个环境变量建议写进~/.zshrc不然每次开新终端都要重设一遍。我在项目里折腾过一个老旧的Rails应用这个变量治好了一堆编译报错。最后再分享一个自己坚持了好几年的习惯——每次升级Ruby后不急着跑业务代码先建一个临时目录写个最小的Ruby脚本调几个常用的gem库跑一遍比如读个文件、连个数据库、发个HTTP请求。几分钟时间就能确认环境是健康的后面开工就安心多了。升级这条路宁可慢一点稳一点也别图快跳过验证步骤磨刀不误砍柴工这句话在开发环境管理上真的永远适用。