Jenkins Pipeline中Groovy字符串比较异常,无法读取Properties值
问题:Jenkins Pipeline读取UTF16LE编码文件时Properties键匹配失败
在Jenkins Pipeline中执行Groovy代码读取UTF16LE编码的version.txt文件,尝试获取FullVersionStr属性时出现异常:
- 无论使用
props["FullVersionStr"]、props.FullVersionStr,还是遍历Properties进行字符串比较(==、equalsIgnoreCase()),均无法匹配到该属性 - 输出显示Properties中存在
FullVersionStr键,但取值始终为null
相关代码与执行输出
Jenkins Pipeline代码
pipeline { agent any parameters { [...skip...] } stages { stage('Build') { steps { [...skip...] } post { always { script{ try { echo "* loading ini: ${env.buildVerInfoFile} ..." def content = readFile "${env.buildVerInfoFile}" Properties props = new Properties() InputStream is = new ByteArrayInputStream(content.getBytes()); props.load(is) env.VN = props["FullVersionStr"] echo " = ver: ${env.VN}; props:${props.toString()}" env.VN = props.FullVersionStr echo " = ver.a: ${env.VN}; " def toFind = "FullVersionStr" props.each { prop, val -> if(prop in ["metaClass","class"]) return def pn = prop.toString() def q = (pn == toFind) if(q) { env.VN = val echo " = set vn: ${env.VN}/${val}; " } echo " == (${q}) [${pn}]: ${val}" } echo " = ver.b: ${env.VN}; " } catch (Exception exc) { echo 'ERR: exception occurred: ' + exc.toString() } } [...skip...] } success { [...skip...] } } } } }
执行输出
loading ini: C:\XBuild.main\build\log\ARC\version.txt ... [Pipeline] readFile [Pipeline] echo = ver: null; props:{=, FullVersionStr=3.3.0.152, ReleaseVersionStr=2020.1.1.1, BuildStarted=2025-04-06,14:53:27.193, i??i??dummy=123} [Pipeline] echo = ver.a: null; [Pipeline] echo == (false) : [Pipeline] echo == (false) FullVersionStr: 3.3.0.152 [Pipeline] echo == (false) ReleaseVersionStr: 2020.1.1.1 [Pipeline] echo == (false) BuildStarted: 2025-04-06,14:53:27.193 [Pipeline] echo == (false) i??i??dummy: 123 [Pipeline] echo = ver.b: null; [Pipeline] } [Pipeline] // script
环境信息:Jenkins 2.492.3、JDK 21、Groovy 2.4.21、Windows Server 2022 Std,version.txt由PowerShell生成,编码为UTF16LE。
原因分析
核心问题是编码解码不匹配:
readFile默认使用系统编码(Windows下通常为GBK/UTF-8)读取UTF16LE编码的文件,导致内容被错误解码,字符串中混入不可见的乱码字符或字节序标记(BOM)- 错误解码后的内容转成字节流加载到Properties时,键名的实际值并非表面显示的
FullVersionStr,而是包含隐藏字符的字符串,因此直接用字面量匹配失败 - 输出中的空键
{=,...}和乱码键i??i??dummy,都是UTF16LE编码被错误解码的典型表现
解决方案
方案1:指定编码读取文件
使用readFile的encoding参数,直接以UTF-16LE编码读取文件,避免转码错误:
def content = readFile file: "${env.buildVerInfoFile}", encoding: "UTF-16LE" Properties props = new Properties() // 将正确解码的字符串转成UTF16LE字节流加载 props.load(new ByteArrayInputStream(content.getBytes("UTF-16LE")))
方案2:直接读取文件字节流(更可靠)
跳过readFile的中间转码,直接读取文件字节流并指定UTF16LE编码加载,同时手动处理UTF16LE的BOM(0xFFFE):
File verFile = new File("${env.buildVerInfoFile}") Properties props = new Properties() verFile.withInputStream { is -> // 跳过UTF16LE的BOM标记 int b1 = is.read() int b2 = is.read() if (!(b1 == 0xFF && b2 == 0xFE)) { is.reset() // 不是BOM则重置流指针 } // 用UTF16LE编码读取流内容 props.load(new InputStreamReader(is, "UTF-16LE")) }
调试排查技巧
如果仍有问题,可以输出键名的字节码,确认是否存在隐藏字符:
props.each { prop, val -> def pn = prop.toString() echo "键名: ${pn}, 十六进制字节: ${pn.getBytes('UTF-8').encodeHex()}" }
对比正常FullVersionStr的字节码,即可定位是否存在乱码或隐藏字符。
修改后的完整Script示例
script{ try { echo "* loading ini: ${env.buildVerInfoFile} ..." // 采用方案2:直接读取文件字节流 File verFile = new File("${env.buildVerInfoFile}") Properties props = new Properties() verFile.withInputStream { is -> int b1 = is.read() int b2 = is.read() if (!(b1 == 0xFF && b2 == 0xFE)) { is.reset() } props.load(new InputStreamReader(is, "UTF-16LE")) } env.VN = props["FullVersionStr"] echo " = ver: ${env.VN}; props:${props.toString()}" env.VN = props.FullVersionStr echo " = ver.a: ${env.VN}; " def toFind = "FullVersionStr" props.each { prop, val -> if(prop in ["metaClass","class"]) return def pn = prop.toString() def q = (pn == toFind) if(q) { env.VN = val echo " = set vn: ${env.VN}/${val}; " } echo " == (${q}) [${pn}]: ${val}" } echo " = ver.b: ${env.VN}; " } catch (Exception exc) { echo 'ERR: exception occurred: ' + exc.toString() } }
内容的提问来源于stack exchange,提问作者dmitry_bond
相关产品推荐
相关产品推荐

