BMC运维利器:PSL脚本get_vars()变量读取与状态记录实战
机房里的服务器多了以后你会发现大部分 BMC 操作其实就两件事把状态读出来把操作记下来。读状态大家都会用ipmitool sensor或者 Redfish 查询但记状态这件事很多人就不怎么讲究了。我最近在整理一套跑在 BMC 上的 PSL 巡检脚本最常用、也最容易被忽略的一个函数是get_vars()。它是 PSL 函数系列里的第 50 个函数功能本身并不复杂——把当前脚本环境里注册过的变量集合读出来。可就是这么一个不起眼的接口在做批量日志收集、资产信息登记、账号变更记录这几类事情时帮我省了非常多的事也避免了好几回归档时这个结果到底是哪台机器、哪个时间跑出来的这种无从查起的尴尬。这篇东西就是围绕get_vars()展开的。我会从它的定位和原理说起再给几个我实际在用的脚本片段最后把我在不同品牌 BMC 上踩过的坑也一并列出来。如果你平时负责服务器硬件运维、写 BMC 自动化脚本或者想把机器上的巡检动作变成可追溯、可查询的脚本日志这篇文章应该能给你一些可以直接抄走的思路。1. 看懂 get_vars()一个被低估的状态读取接口1.1 PSL 脚本里的记忆是从哪来的BMC 上的 PSL 不是一种通用编程语言它更像是一个嵌在管理控制器里的轻量脚本环境。你通过 SSH 或者 Web 控制台进入 BMC 后可以执行命令、定义函数、声明变量甚至可以写一点简单的条件判断和循环逻辑。但它和你在 Linux shell 里写的脚本有一个非常大的区别普通的 shell 脚本变量跟着进程走进程结束变量就没了而 PSL 环境里的变量有一部分是可以跨命令、跨函数保留的。get_vars()就是用来读取这部分可保留变量的函数。与之对应的写入函数是set_vars()你通过set_vars()把一个键值对写进当前环境上下文之后再调用get_vars()就能把刚才写进去的内容读出来。很多人第一次听到这个解释会觉得这不就是全局变量吗对本质上就是。但它比程序里的全局变量要更持久——它绑定在 BMC 的脚本运行环境上只要你还在这个环境里变量就一直有效不用像 shell 脚本那样反复 source 一个配置文件。我打个比方你就明白了。set_vars()相当于你把一张便签贴到工位旁边的白板上get_vars()则是回头看一眼白板确认便签还在不在。PSL 脚本里如果有多段逻辑、多个函数需要共享同样一份信息白板就是最方便的中转站。1.2 为什么不直接用普通变量或者系统查询命令你可能会问我在 PSL 里直接写x 10然后用print(x)不行吗行但那只在当前执行块里有效。函数一返回局部变量基本就丢了你没法在另一个函数里安全地拿到同一个值。PSL 里当然也有一些自带的状态查询命令可以读取传感器、FRU 信息但那些命令返回的是硬件状态不是你自己脚本里产生的状态。举个例子。我写批量巡检脚本时会在读取每台机器的 CPU 温度后把当前温度值告警阈值记到变量里供后面的输出函数和日志函数共同使用。如果不用set_vars()和get_vars()我只能在每个函数里重复执行一次传感器查询既慢又容易拿到不一致的数据。把一次查询的结果放到环境变量里后面所有函数都通过get_vars()去取既保证数据一致也少跑了很多次 IPMI 命令。所以get_vars()的真正价值不是读变量这三个字本身而是它给了你一个跨函数共享状态的可靠通道。理解了这一点你再看它的语法和参数思路就会顺很多。1.3 什么场景下你才会主动想起它说实话在脚本只有三五行的场景里get_vars()确实没什么存在感。我最早接触它也是因为在写一个状态机一样的 PSL 脚本脚本里要根据 BMC 当前的开关机状态决定下一步动作而这些动作分散在几个不同的 function 里。那个状态机跑起来之后我在每个关键节点都会把当前状态、错误码、时间戳写进环境变量然后在下一次调用时用get_vars()把它们取出来接着算。时间一长我就发现这玩意儿其实非常适合三类场景第一类是批量巡检。循环处理多台机器时每台的序列号、温度、最后巡检时间都是状态需要跨函数传递。第二类是日志收集。收集日志本身是个多步骤动作每个步骤的结果如果不记录下来最后打包出来的日志就是一团乱。第三类是账号或配置变更操作。改了密码、改了网络配置之后至少要留下改没改成功、什么时间改的这种痕迹。你不需要专门建一套数据库get_vars()配合适当的输出函数就能把状态留在 BMC 里。2. 语法拆解与运行机制2.1 三种调用形式按需选择在我接触过的 PSL 实现里get_vars()大体支持三种调用方式。不同厂商的 BMC 在细节上会有一点出入但整体逻辑是一致的。第一种是不带任何参数直接返回当前环境里所有已注册变量的键值集合。这种方式适合调试你想快速看看脚本跑到现在白板上到底贴了多少便签、每个便签写了什么一条get_vars()就能全部打出来。第二种是传入一个具体的变量名比如get_vars(sn)只返回你想看的那个变量。这种写法效率不一定比全量读取高但可读性好很多而且不容易被同名变量干扰。第三种是传入一个前缀字符串比如get_vars(temp_)返回所有变量名以该前缀开头的键值对。我在批量巡检时经常用这个温度传感器不止一个我把它们命名为temp_cpu0、temp_cpu1、temp_inlet最后汇总时用get_vars(temp_)一次性拿全。我整理了一个简单的对照表方便你在实际使用前快速判断该用哪种形式。调用形式返回内容典型场景get_vars()全部已注册变量调试、快速查看环境状态get_vars(key)指定变量的值可按需传入第二参数做默认值函数内部读取单一状态get_vars(prefix_)以指定前缀开头的变量集合批量采集同类数据2.2 返回值的数据结构比你想的更值得注意get_vars()的返回结果通常是一个键值集合对象不是普通字符串。你可以直接把它当作字典或映射来处理用点号或者方括号访问其中的字段。但也正因为它是结构化对象直接print出来的时候不同 BMC 上的表现会不一样。有的实现会输出成keyvalue的多行格式有的则输出一段类 JSON 文本。我建议你在写正式脚本之前先做一次最小的验证在 PSL 环境里执行set_vars(test_key, hello)再执行get_vars(test_key)看看返回值的具体结构。这一步能帮你避免后面写了一大堆遍历逻辑结果发现返回值格式和你预期完全对不上的问题。另一个容易踩的细节是get_vars()取出来的值很多时候是字符串类型。哪怕你set_vars时存进去的是一个整数读出来也可能会变成字符串。这不一定是函数的问题而是 PSL 变量系统本身存储方式导致的。所以在做算术运算或数值比较之前记得先做类型转换或者至少用字符串比较来规避问题。2.3 函数作用域里的变量可见性在 PSL 里定义 function函数内部自己声明的局部变量默认是私有的离开函数就消失。如果你想跨函数共享就得依靠set_vars()/get_vars()这种环境级别的变量容器。这里有个容易搞混的点你在 function 内部调用get_vars()能不能读到另一个函数写进去的全局变量答案是可以的因为get_vars()访问的是整个 PSL 运行环境上下文不区分调用者是谁。反过来你在 function 内部用set_vars()写入的变量其他函数同样能通过get_vars()读到。也就是说变量的作用域边界不在于它在哪个函数里被写入而在于变量写入了哪一层容器。普通赋值写的是局部容器set_vars()写的是全局环境容器。理解了这个你在排查为什么函数里读不到变量的时候就不会一头雾水了。3. 实战用 get_vars() 撑起三个日常场景3.1 批量巡检时把序列号和资产编号带出系统我之前维护过一批设备资产登记表上只有 IPMI 地址和机房位置序列号和 BMC 固件版本经常对不上号。后来我写了一个 PSL 巡检脚本每台机器跑完就把资产相关数据写进变量统一输出成一个结果文件。核心思路是这样的脚本先通过系统命令读取产品序列号、BMC 固件版本、当前开机状态然后用set_vars()把结果写进环境变量。所有数据收集完之后再用一个汇总函数把get_vars()读到的信息格式化输出。// 采集阶段 set_vars(sn, fru_get_product_serial()); set_vars(fw_ver, bmc_get_firmware_version()); set_vars(power, get_power_state()); // 汇总阶段 vars get_vars(sn); print(Serial: vars); vars get_vars(fw_ver); print(Firmware: vars); vars get_vars(power); print(PowerState: vars);这里有个小技巧如果你怕某个变量没写入导致get_vars()返回空值可以在读取时给一个默认值。不同实现里写法可能不一样但思路都是读不到就返回一个约定好的兜底值避免汇总函数因为空值直接报错。我习惯默认值给N/A这样输出文件里每一列都会是完整的不会因为某台机器信息缺失而对不齐。3.2 日志一键收集后留下可追溯标记再说个大家每天都在用的动作一键收集 BMC 日志。搜bmc一键收集日志怎么看的人特别多很多人收集完日志就把压缩包下载走了完全没留下任何这个包是什么时候收集的、收集时 BMC 状态如何的记录。等过了几天你拿着三个日志包在手上根本分不清哪个是故障当时的现场。我在 PSL 脚本里做了一个很简单的改进收集日志前先用set_vars()记下开始时间戳收集完成后把结束时间戳、日志文件路径、编译状态都写进环境变量里。最后通过get_vars()把它们统一写到 BMC 的某个只读注释区域或者输出文件头部。set_vars(collect_begin, get_timestamp()); // 执行日志收集动作 set_vars(collect_end, get_timestamp()); set_vars(log_file, /tmp/bmc_log.tar.gz); set_vars(collect_status, success); result get_vars(collect_); print(Collection info:); print(result);这样做的直接好处是日志包和收集记录永远是一致的。你不需要额外维护一个 Excel 表来记录哪台机器在什么时候收过日志脚本自己就把状态记住了。而且因为get_vars()支持前缀读取你把所有和收集相关的变量都用collect_开头命名后面想看全部信息一条命令就出来了非常方便。3.3 账号修改类操作的状态记录搜sr650重置bmc密码超微x11ssl-f主板如何修改bmc管理口的账号和密码这类问题的人多半是遇到了忘记密码或者账号锁定的情况。我特别理解这个场景因为我自己也处理过很多次。重置密码本身并不难难的是你改完之后要能说清楚这台机器是谁在什么时候改的改的时候 BMC 处于什么状态。PSL 脚本完全可以承担这个记录工作。我在执行密码修改之前先把当前时间、操作人代号、目标账号名记下来改完之后把结果状态也写进去。这样后续无论哪时需要审计只要进入这台 BMC 的 PSL 环境执行一次get_vars()就能看到完整的操作痕迹。set_vars(op_user, ops-liu); set_vars(op_time, get_timestamp()); set_vars(op_target, ADMIN); // 执行密码修改 set_vars(op_result, success); // 复核 print(get_vars(op_target)); print(get_vars(op_result));需要提醒的是PSL 环境里的变量持久化能力不是无限期的。BMC 重启之后环境变量还能不能保留取决于厂商实现和存储介质。有些实现会把变量落到非易失存储里重启后get_vars()还能读到有些只是存在内存里BMC 一重启就全没了。所以如果需要长期保存操作记录我建议你在脚本里加一步把get_vars()的结果 dump 到日志文件里再想办法把文件同步到外面的日志中心。只依赖get_vars()本身是有风险的。4. 踩坑实录get_vars() 返回空值与变量丢失问题4.1 变量注册了却读不到排查链路我在初期使用get_vars()时踩得最多的一个坑就是明明执行了set_vars(my_key, my_value)紧接着执行get_vars(my_key)返回的却是空值或者直接提示找不到这个变量。遇到这种情况我的建议是不要急着怀疑函数坏了先按下面这条链路排查。第一步确认set_vars()有没有真的执行成功。有的 PSL 实现里set_vars()写入失败并不会抛异常而是静默失败。你可以在写入之后立刻调用一次无参的get_vars()看看输出里有没有你刚才写的那个键。没有的话基本可以断定写入就没成功。第二步检查变量名的大小写。get_vars()对变量名的匹配是否区分大小写不同平台不一样。我遇到过一台 BMC 上写set_vars(SN,value)再用get_vars(sn)死活读不到改成get_vars(SN)立刻就好了。这种问题排查起来非常费时间所以我现在的习惯是变量名统一用小写并且用下划线分词写进项目规范里。第三步检查变量名里有没有特殊字符。连字符、空格、点号这类符号在某些实现里会造成解析问题。我踩过一次用set_vars(bmc-fw-ver, 1.0)存变量的坑后面get_vars()怎么读都读不到完整键名最后换成下划线bmc_fw_ver就好了。从那以后我的 PSL 变量名只允许小写字母、数字、下划线。4.2 类型和引号引发的离奇结果第二个高频问题是get_vars()读出来的值和存进去的时候对不上。最常见的是数字变成字符串然后是布尔值变成true/false字符串。我之前写过一个温度告警脚本比较变量时直接写的是数字比较结果 PSL 引擎把字符串和数字做比较逻辑完全混乱一会儿告警一会儿不告警。解决方式不复杂在比较之前显式做类型转换。虽然不同平台转换函数名字可能不同但思路是一致的——先把读到的字符串转成整数再参与判断。var tmp_str get_vars(temp_now); tmp str_to_int(tmp_str); if (tmp 85) { print(High temperature warning); }另外如果你在set_vars()写入时用了引号不匹配的方式比如把一个包含单引号的值直接写进去有些实现会截断导致get_vars()出来一个残缺字符串。这类问题最隐蔽因为它不是语法错误不会报异常只是值不对。我的做法是所有需要写入的文本值在写入前统一做一次转义和清洗至少要把引号、换行符处理掉。4.3 跨 session 的持久化陷阱第三个坑是最容易让人骂娘的你在 SSH 登录 BMC 的会话里写入了变量也读到了一切正常。但你把 SSH 断开重新登录第二次再执行get_vars()发现之前写入的变量全部消失了。这不是 bug而是平台的设计差异。很多 BMC 的 PSL 环境变量存放在当前会话上下文里会话结束上下文释放变量自然也清空了。只有部分实现会把环境变量持久化到 BMC 的存储介质中跨会话保留。针对这一点我的建议很简单搞清楚你的 BMC 属于哪一类。如果在你的平台上变量不跨会话保留就不要把重要状态只放在set_vars()里而是要搭配文件写入功能。比如把关键状态写到/tmp/xxx.status或者类似路径下次进入时读取文件内容。get_vars()依然可以用但要把它当作会话内的快速缓存而不是长期存储。这样理解之后你就不会因为信息丢了而抓狂了。4.4 和其他函数混用时的执行顺序问题还有一个非常隐蔽的问题我在写一个多函数相互调用的脚本时踩过get_vars()读到的是当前时刻的环境变量快照不是脚本最开始进入时的快照。假如你有一个函数 A 在脚本开头调用了get_vars()并把这个集合保存在局部变量里然后函数 B 之后又写入了新变量再回头用函数 A 里那个旧集合时新变量是读不到的。这就引出一个很重要的习惯需要保持数据一致的地方不要在函数入口读一次变量集合缓存起来然后在函数末尾再用。你应该在使用点即时调用get_vars()或者明确设计好变量生命周期避免读得太早、用得太晚。我在状态机脚本里曾经因为这个问题查了整整一个下午——状态明明已经更新了输出函数拿到的却还是旧状态。最后发现就是因为在函数入口处把get_vars()的返回结果存成了一个局部变量后续用的都是那个旧对象。改成每次需要时即时读取一切恢复正常。5. 版本差异、安全边界与我的个人习惯5.1 不同 BMC 厂商的 PSL 实现差异我在实际使用中接触到的主流服务器 BMCPSL 脚本环境的表现并不完全一致。有的叫 PSL有的叫类似名字的脚本接口有的则直接在 IPMI 命令之上提供一层封装。虽然get_vars()这个名字在很多平台上都能用但返回结构、参数形式、持久化机制都有差别。比如超微的 BMC在 SSH 进去之后脚本环境比较完整set_vars()/get_vars()这类接口我用着一直挺顺手。浪潮、华为的一些机器我也试过整体逻辑相似但返回值格式偶尔会出现差异尤其表现在数字类型处理和特殊字符转义上。DELL 的 iDRAC 则更倾向于用 Redfish 和 RACADM 完成同类事情PSL 环境相对封闭我没有在上面重度使用过get_vars()。所以这里给一句话总结拿get_vars()之前先看你这台 BMC 的环境到底叫什么、支持哪些命令、变量是否持久化。不要拿 A 平台的习惯直接套到 B 平台上。最好的方法就是像我前面说的那样刚拿到机器时先跑一遍set_vars()get_vars()的最小验证把平台性格摸清楚再写正式脚本。5.2 把 get_vars() 纳入自动化平台时的注意事项如果你不只是手动 SSH 进 BMC 操作而是想用脚本批量驱动多台服务器那get_vars()带来的便利也需要配合外部自动化平台才能发挥最大价值。我现在的做法是在 BMC 的 PSL 脚本里所有关键节点都会调用get_vars()读取当前状态并把它输出成keyvalue格式的一行文本。外部调度平台再通过脚本命令抓取这一行文本导入到统一监控系统里。这样PSL 环境里的记忆就被平移到了更外层的自动化平台BMC 重启、会话断开都不影响最终的结果。安全方面也要提一下get_vars()本身只是一个读取函数风险不大但它读出来的变量可能会包含账号名、操作时间、文件路径等敏感信息。如果你把这些信息通过输出重定向到共享日志目录就要做好访问控制。尤其是当你用set_vars()记录了操作人代号和管理员账号时这类输出文件尽量不要放在所有人都能读的路径下。5.3 写 PSL 脚本的几个个人习惯最后分享几个我在写 PSL 脚本时养成的习惯不一定都对但可以给你做参考。第一变量名统一使用小写加下划线。理由前面说过避免大小写和特殊字符导致的读取问题。第二每个set_vars()写入之后如果不是性能敏感场景可以立刻跟一条get_vars()做断言确认。这样写虽然啰嗦一点但在脚本初期的调试阶段能省下大量时间。第三所有脚本里涉及的关键路径、文件名、账号名尽量从get_vars()读取不硬编码在输出函数里。这样以后改路径、改账号只需要调一处set_vars()就行不用满脚本翻。第四重要变量不要依赖get_vars()做长期持久化凡是需要跨重启保留的信息同步落一份到文件里。慢慢地你会发现get_vars()这种看起来平平无奇的函数反而决定了你在 BMC 上写的脚本到底是一次性命令堆砌还是一个真正能积累状态、能维护、能追溯的小系统。至少我现在只要写新的 PSL 功能第一件事就是规划好变量命名和set_vars()/get_vars()的使用边界而不是急着写业务逻辑。这种习惯养成之后BMC 上的脚本维护成本会低很多排查问题的速度也会快不少。