
Caché 和 IRIS 数据库自带的那个终端Terminal用起来什么都好唯一的痛点是中文。你在里面敲一句WRITE 中文测试屏幕上出来的经常是涓?娴嬭瘯锟斤拷或者一排问号。更离谱的是同一个脚本在 A 机器上正常在 B 机器上就乱码排查半天找不到原因。这篇文章就把我这些年处理 Caché/IRIS 终端乱码的思路、方法和踩过的坑一次讲清楚适合刚从 Windows Terminal 入门、或者天天在 Linux 服务器上用csession/iris session干活的朋友。先说明一点绝大多数乱码问题数据本身在数据库里是好的坏在显示链路。Caché/IRIS 的字符串在内存中以 Unicode 形式管理当你执行WRITE时系统要把字符编码成字节流交给终端程序终端再按自己的编码设置把字节翻译成屏幕上的字形。这一路上只要有一个环节的编码不统一中文就会花掉。所以解决方案不是去改数据而是把整条链路的编码对齐。下面我按使用场景逐个拆。1. 乱码到底卡在哪一环1.1 一条中文 WRITE 指令的完整旅程先理解一下乱码的完整链路。假设你在 Caché 终端里输入WRITE 中文测试这条命令的执行过程其实分三步。第一步终端程序把中文测试这几个字符从键盘输入编码转成内存中的 Unicode第二步数据库进程执行WRITE把 Unicode 字符串按某个编码转成字节第三步终端程序收到这些字节后按自己的显示编码去解码并渲染到屏幕。只要第二步和第三步的编码不一致乱码就出现了。举个例子如果数据库进程按 UTF-8 输出中文两个字得到的是E4 B8 AD E6 96 87这组字节而终端程序恰好用的是 GBK 代码页来解码它会把E4 B8当成一个 GBK 字符涓把AD E6当成下一个字符最终显示出来就是涓?囦之类的古怪内容。要是终端用西欧编码Latin-1解码看到的就会是䏿–‡这种字母串。我看到很多新手一遇到乱码就怀疑数据库装坏了或者全局变量里的数据丢了于是急着去重装、导数据。其实只要你在WRITE中文之前先执行一句WRITE $ASCII(中,1)看到返回的是正常数字比如某个 Unicode 码值就说明数据在内存里一点问题没有。问题只出在输出编码和终端解码这一层。1.2 三层编码链路决定了你最终看到什么把乱码问题抽象一下其实就三条链路你只需要逐条排查数据库进程对外输出时的编码。Caché/IRIS 在把字符串交给终端时用的编码由 NLSNational Language Support配置决定安装时选了中文环境、GBK 还是 UTF-8差异很大。终端程序自身的解码方式。Windows 图形终端有 Encoding 选项命令行终端csession/cterm则依赖控制台代码页Linux 下csession依赖父进程的 locale。会话工具SSH 客户端、Windows Terminal的编码设置。例如 SecureCRT、Xshell、PuTTY 都有独立的字符编码选项它们夹在中间最容易被人忽略。后面所有的解法本质上都是让这三条链路统一到同一种编码上。我的实践经验是跨平台环境优先统一到 UTF-8纯 Windows 内网环境统一到 GBKCP936也行但要注意外部数据源的编码千差万别UTF-8 兼容性最好。2. 分场景解决图形终端、命令行终端、SSH 终端2.1 Windows 图形终端Terminal / IRIS Terminal的设置Caché 和 IRIS 自带的图形终端设置入口都在菜单里。以 Caché 5.x 为例打开终端后点击Options - Terminal PreferencesIRIS 新版的路径类似通常在Edit - Preferences或工具栏的齿轮图标里。进去之后找到Encoding有的版本叫 Character Set下拉框把默认值改成UTF-8保存后重启终端。设置完不要急着高兴还有一个经常被忽略的点字体。终端渲染中文如果当前字体不支持中文字形会显示成方框□。建议把字体设置为等宽且支持中文的字体比如 Consolas、YaHei Consolas Hybrid或者直接选微软雅黑。这一步不进编码设置但往往决定最终显示效果。如果你用的是 IRIS 的图形终端还可以在启动时直接指定命名空间和编码避免每次登录后手动ZN切换。创建快捷方式时修改启动命令追加参数。以cyTerm/irisTerminal这类程序为例不同版本的参数名略有差异最稳妥的办法是在安装目录下执行cterm.exe -?或iris terminal -?查看帮助找出当前版本支持的编码参数。不要轻信网上写死的参数版本不同经常变。我建议把常用的命名空间和编码参数直接写进快捷方式例如目标填C:\InterSystems\IRIS\bin\cterm.exe -UUSER编码参数按你本版帮助为准这样双击图标就直达环境少一层出错概率。2.2 用 csession/cterm 进入时的代码页适配在 Windows 的命令行里跑csession或者cterm情况比图形终端复杂一些因为这里多了 Windows 控制台代码页这一层。代码页是 Windows 控制台用来解释字节的规则默认跟着系统区域走中文系统通常是 936GBK。如果你希望整条链路用 UTF-8最简单的方式是在启动csession之前先执行chcp 65001 csession CACHEchcp 65001把控制台代码页切到 UTF-8之后的终端输出就会按 UTF-8 解码。但这里有个坑早期版本的 Caché 在 65001 代码页下会有显示错乱比如光标乱跳、输出重叠。如果你遇到这个问题不要硬刚我后面的做法是保持代码页 936然后把数据库侧的 NLS 和输出统一成 GBK或者换用 Windows Terminal 作为宿主它对 UTF-8 的支持比传统 cmd 好得多实测基本不出现旧版终端那种兼容性问题。提到 Windows Terminal 多说一句它不是 Caché 的终端程序而是替代 cmd/PowerShell 的终端宿主。在 Windows Terminal 里启动csession一方面是界面更好看更重要的是它对 UTF-8、字体渲染的支持都远超老控制台。如果公司允许你装新软件我强烈推荐直接用 Windows Terminal csession乱码率能降一大半。2.3 SSH 登录 Linux 服务器时的编码对齐Linux 服务器上你通常用csession 实例名或iris session 实例名进入终端。这种情况下乱码往往不是数据库的问题而是 SSH 会话的编码没对齐。排查顺序我固定是这样的第一步看服务器端 locale。执行echo $LANG如果输出不是*.UTF-8比如是POSIX或en_US那么csession输出的中文字节可能会被截断或转成?。解决办法是把 locale 改成 UTF-8 再启动会话export LANGen_US.UTF-8第二步看 SSH 客户端的编码。Xshell 在会话属性 - 终端 - 编码里选 UTF-8SecureCRT 在会话选项 - 外观 - 字符编码里选 UTF-8PuTTY 在Window - Translation - Remote character set里选 UTF-8。每个工具的位置不一样但记住一点客户端编码要和服务器端 locale 一致一般都选 UTF-8。SecureCRT 有个特别坑的默认行为它有个自动选择编码的选项会根据远程服务器的 locale 自动判断。听起来很方便实际经常判断失误。比如服务器 locale 是 C非 UTF-8但数据库 NLS 输出 UTF-8SecureCRT 就按非 UTF-8 解码中文必乱。我建议直接把编码硬性指定为 UTF-8别用自动模式。这一步做完绝大多数 SSH 场景的中文乱码都能解决。如果还乱那就是 NLS 配置层的差异见下一节。3. 数据库侧的兜底方案NLS 配置与脚本编码转换3.1 NLS国家语言支持究竟是什么怎么改NLS 是 Caché/IRIS 里决定数据库以什么编码和终端对话的一套配置。你可以把它理解成一个翻译官既管输出编码也管默认字符排序规则。同一个实例NLS 的默认 Table 不同WRITE 中文送出去的字节流可能完全不同。进入 NLS 配置的方法是在终端里执行D ^%NLS回车后会弹出一个 NLS 管理菜单。不同版本菜单结构略有差别核心选项一般包括查看当前默认 Table、设置当前进程的 Table、保存到系统全局。你可以先看看当前默认编码是什么。如果显示是GB18030、GBK一类而你终端侧已经切到 UTF-8那么两者就撞车了。要修改系统级默认通常在菜单里选择保存配置的选项把当前设置写入^SYS(NLS)对应的全局节点。保存后新建的终端会话就会用新的默认值。这里我要重点提醒修改 NLS 的系统级默认会影响这个实例处理外部文件、HTTP 响应、全局变量排序等多个环节的默认编码不是只影响终端显示。在生产环境改之前一定要先在测试实例验证确认对现有业务无影响再动生产。如果你的服务器是 Windows 且安装时选了中文区域默认 NLS Table 通常是 GBK 系列Linux 安装时如果系统 locale 是 UTF-8默认 NLS Table 通常是 UTF-8 系列。所以同样一段带中文的代码在两台机器上输出结果可能完全不同这就是同一脚本 A 机正常 B 机乱的根源之一。了解了这个原理你就知道该怎么对症下药了。3.2 用 $ZCONVERT 在脚本里做编码转换有些场景下终端和 NLS 都调好了还是有乱码那多半是脚本里直接输出了字节流。典型情况是从文件读出一段 UTF-8 编码的文本、通过 HTTP 请求拿回一段 GBK 编码的响应体然后你直接WRITE把它甩到屏幕上。这时候数据库并不知道这些字节代表什么编码只管按 NLS 默认方式转成 Unicode结果自然乱。这种情况下正确的姿势是先用$ZCONVERT手动转换编码再输出。$ZCONVERT是 ObjectScript 里用于编码转换的核心函数基本思路是把外部字节串显式声明成它的实际编码转成 Unicode再按终端目标编码输出。举个我实际处理过的例子。某个接口返回的 JSON 文件是 UTF-8 编码早期在 Windows 图形终端终端设置 GBK下显示时中文全是乱码。我在读取该文件后加了这样一步SET jsonBytes ... ; 从文件或 HTTP 响应拿到的 UTF-8 字节串 SET unicodeText $ZCONVERT(jsonBytes, UTF8) WRITE unicodeText这样unicodeText是内存里的 Unicode 字符串终端负责按自己的编码渲染乱码就消失了。反过来的情况也常见你的 NLS 是 UTF-8但外部程序给你的数据是 GBK同样用$ZCONVERT转成 Unicode 再处理。补充一个细节$ZCONVERT有三种常见调用形式一种是只传目标编码做单向转换一种是传FromEncoding和ToEncoding做双语互转。具体到不同版本参数的语义细节有差异建议在出问题前先在自己的实例上拿一小段中文用$ZCONVERT来回转几遍看哪种写法符合你的版本。总之思路是统一的不要在脚本里裸奔字节流明确编码再输出。3.3 让配置对新终端会话自动生效有时候配置改好了但每次新开终端还是乱码这通常是因为你没有把配置保存到持久化位置。图形终端里改Terminal Preferences只改了当前用户的终端外观设置而D ^%NLS里改的默认 Table如果没有保存也只对当前进程生效。如果你希望每次登录都自动套用一套确定的编码可以在登录后的启动脚本里显式设置。Caché/IRIS 支持在USER命名空间或%SYS的登录流程里挂初始化代码比如在^%SYS(STARTUP)或用户自定义的启动代码块中设置当前进程的 NLS 编码。这个方法的好处是不管你是用图形终端、csession还是iris session登录只要执行了启动代码编码就自动对齐。我个人更推荐的做法是在%SYS命名空间下写好一个测试函数专门输出当前进程的 NLS 状态例如执行D ^%NLS去确认默认 Table再执行WRITE 中文验证显示。这样每次接手新环境先把脚本跑一遍五分钟内就能判断是哪一层编码没对齐而不是靠肉眼猜。4. 实战排查清单与避坑心得4.1 一眼识别乱码类型乱码其实是可以看特征定原因的。我在下面整理了一张对照表按乱码的样子快速定位问题层。屏幕上显示的乱码特征大概率原因检查方向大串问号?????当前编码不支持该字符字符映射失败NLS Table 是否包含中文字符集涓??娑?璇? 这类形似中文但语义全错的字UTF-8 字节被 GBK/GB18030 解码终端代码页或 SSH 客户端编码不是 UTF-8䏿–‡ 这种带小语种字母的串UTF-8 字节被 Latin-1/西欧编码解码SSH 客户端误选了西欧编码锟斤拷编码被反复转码污染UTF-8→GBK→UTF-8数据文件或接口曾经被错误转换后入库替换符GBK 或非 UTF-8 字节被 UTF-8 解码终端设置为 UTF-8但数据实际是 GBK这张表的核心逻辑是先看乱码像不像中文如果像 涓这种生僻中文字基本是 UTF-8 被 GBK 解如果像 ä¸这种小语种字符基本是 UTF-8 被西欧解如果直接是 ?说明字符集缺字形。方向判断对了再决定调哪一层。4.2 一套可以复用的排查顺序遇到乱码我建议按下面这个顺序走而不是随机改设置先确认数据字节本身的编码。如果你有问题的文本来自文件或外部接口一定要先明确它是 UTF-8 还是 GBK。最简单的办法是把这段文本导出到文件用十六进制工具比如 Notepad 的 HEX 插件、或者xxd命令看字节UTF-8 编码的中文一般以E4~E9开头的三字节序列居多GBK 中文一般是两个字节首字节范围B0~F7、次字节A1~FE。这一步能避免后面瞎调。然后对齐终端编码。确认你的终端或 SSH 客户端当前用的编码和上面判断出的数据编码一致。Windows 图形终端看 Encoding 选项SSH 客户端看字符集选项Linux 本地csession看LANG。最后验证 NLS。在终端里执行D ^%NLS确认当前默认 Table 和你希望的输出编码一致。如果全部对齐了WRITE 中文测试应该马上正常。验证时有个顺带的技巧执行Do $SYSTEM.License.ShowSummary()这个命令会输出许可证信息含中文说明的部分也能反映出会话编码是否正确。首次跑完还是乱的话就用它来辅助判断是哪个环节没到位顺便把许可证信息也确认一遍。这个命令是日常排查后非常快速的验证工具。4.3 我踩过的几个终端乱码坑第一个坑是改了终端编码却忘了重启。图形的 Terminal Preferences 有些设置是启动时读取的修改后不重启不会生效。我不是第一次遇到调了半天才发现还是老进程。第二个坑是Linux 服务器的 locale 影响全局。csession是在 bash 进程里启动的子进程csession默认会继承父进程的 locale 相关环境变量。如果你在登录服务器之后手动 export 了非 UTF-8 的 LANG或者用 systemd 服务方式启动会话locale 可能不是你预期的那样。排查时记得先echo $LANG。第三个坑是SecureCRT 的自动编码判断。它默认的自动经常帮倒忙尤其跨服务器跳板的时候会把 UTF-8 判断成非 UTF-8。后来我全部改成显式指定 UTF-8乱码率大幅下降。第四个坑比较隐蔽全局变量里存了已经转过的字节流。比如你用$ZCONVERT把 UTF-8 字节存进全局然后又用$ZCONVERT转回 Unicode转来转去数据本身已经脏了这时候调终端怎么都没用。遇到这种怎么调都乱的情况要回到数据源头重新读取原始字节再走一遍干净的转换流程。第五个坑是命令版本差异。cterm.exe -?和iris terminal -?打印出来的参数表在不同小版本之间会有变化网上搜到的老参数可能在新版已废弃。最可靠的办法永远是查本机帮助不要盲抄。处理了这么多乱码之后我个人的体会是90% 的终端乱码根本不用动数据库问题都在 SSH 客户端、终端编码和 locale 这三层上。做对一件事就够了——先判断数据是什么编码再看终端在用什么编码解码然后层层对齐不要凭感觉瞎设置。真正需要动 NLS 的场景其实很少一旦动就要谨慎评估生产影响。最后再分享一个小技巧每接手一台新服务器我会把echo $LANG、D ^%NLS、WRITE 中文这三条命令按顺序执行一遍录成一段固定的验证流程。几分钟下来当前环境的编码底线就摸清了。以后再遇到乱码直接按这张已验证的基线去比对定位速度能快很多。