【问题标题】:Why does Groovy's .equals() fail in a jenkins pipeline script when interpolating parameters into a double quoted string?为什么将参数插入双引号字符串时,Groovy 的 .equals() 在 jenkins 管道脚本中失败?
【发布时间】:2019-04-24 18:33:12
【问题描述】:

(注意:我看过类似的问题(例如this,其中问题是无法修剪 shell 命令的输出),但我认为这种情况是不同的。)

我在 Groovy 中有一个使用参数的管道脚本(通过 properties([parameters([...)。当我将参数的值插入双引号字符串时,它无法通过.equals() 检查两个捕获的(我的意思是“捕获的 和 .trim()d!”)标准输出(其中是我的用例),甚至是一个简单的字符串文字。

我可以使用.trim() 来解决这个问题,(尽管你可以看到,通过我的回应和检查.length().trim() 没有任何东西),但我怀疑只有“有效" 因为它隐含了.toString()——这也是一个成功的解决方法。

这对我来说似乎是一个错误,但实际上这是我使用 Groovy 的第一周,所以也许我遗漏了一些东西——谁能解释一下?

即使是简单的文字 "foo" 也会失败(即 "foo".equals("${params.foo_the_parameter}")。插值参数是其他类型的对象还是什么?

[EDIT 在从@Matias Bjarland 得到答案后,我修改了下面的代码以使用 println 而不是 shelled echo,因为它使输出更简洁。他建议的解决方案反映在注释块中。]

我的 groovy 代码:

node() {
    properties([
        parameters([
            string(
                defaultValue: 'foo',
                description: 'This is foo',
                name: 'foo_the_parameter'
            )
        ])
    ])

    /* this is what I learned from the accepted answer
    bob="${params.foo_the_parameter}"
    println("class of interpolated param is ${bob.class}")
    simple_foo="foo"
    println("class of \"foo\" is ${simple_foo.class}")
    */
    echoed_foo = sh(script:"echo 'foo'", returnStdout: true).trim()
    println "echoed foo is [$echoed_foo], params foo is [${params.foo_the_parameter}]";
    echo_foo_length = echoed_foo.length()
    dqs_foo_length = "${params.foo_the_parameter}".length()
    println "their lengths are: echo: [$echo_foo_length] and dqs: [$dqs_foo_length]";
    if (echoed_foo.equals("${params.foo_the_parameter}")) {
        println "SUCCESS they are equals()"
    }
    else {
        println "FAIL they are not equals()" //this one fires
    }
    if (echoed_foo.equals("${params.foo_the_parameter}".trim())) {
        println "SUCCESS they are equals() after the dqs gets a trim()" //this one fires
    }
    else {
        println "FAIL they are not equals()after the dqs gets a trim()"
    }
    if (echoed_foo.equals("${params.foo_the_parameter}".toString())) {
        println "SUCCESS they are equals() after the dqs gets a toString()" //this one fires
    }
    else {
        println "FAIL they are not equals()after the dqs gets a toString()"
    }

    if ("foo".equals("${params.foo_the_parameter}")) {
        println "SUCCESS at least a simple literal \"foo\" works"
    }
    else {
        println "FAIL even a simple literal \"foo\" fails to be .equals() with the interpolated parameter" //this one fires
    }
}

Jenkins 输出:

Started by user Michael South
[Office365connector] No webhooks to notify
Obtained jenkins.groovy from git git@github.redacted.com:msouth/test_groovy_equals.git
Running in Durability level: MAX_SURVIVABILITY
[Pipeline] node
Running on subnet_mon_02 in /opt/jenkins/m1/workspace/field-analytics-org/test_string_equals
[Pipeline] {
[Pipeline] properties
[Pipeline] sh
[test_string_equals] Running shell script
+ echo foo
[Pipeline] echo
echoed foo is [foo], params foo is [foo]
[Pipeline] echo
their lengths are: echo: [3] and dqs: [3]
[Pipeline] echo
FAIL they are not equals()
[Pipeline] echo
SUCCESS they are equals() after the dqs gets a trim()
[Pipeline] echo
SUCCESS they are equals() after the dqs gets a toString()
[Pipeline] echo
FAIL even a simple literal "foo" fails to be .equals() with the interpolated parameter
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
[Office365connector] No webhooks to notify
Finished: SUCCESS

【问题讨论】:

  • 在寻找“为什么 .equals() 会做这种可怕的事情”;) 我也找到了这个答案:stackoverflow.com/a/9682610/411177
  • 不要使用Java的equals...试试if (echoed_foo == "${params.foo_the_parameter}") {
  • @tim_yates 我刚刚在进一步搜索中了解到这一点,并回到这里作为第二个答案。如果你把它变成一个答案,我会支持它。这是一个很好的参考:objectpartners.com/2018/11/07/….

标签: groovy jenkins-pipeline


【解决方案1】:

在 Groovy 中,您可以简单地使用 == 来比较字符串

在 Java 中,您使用 String.equals() 是因为 str1 == str2 不会按照您的预期进行:Java 比较的是引用而不是值。

在 Groovy 中,您只需编写 str1 == str2 即可,它会执行您期望它执行的操作。 Groovy 使用String.compareTo() 比较值,当结果为0 时返回true

GString g = "${'foo'}"
String s = "foo"

assert g == "foo" && s == "foo"
assert g instanceof GString && s instanceof String
assert !s.equals(g) && !g.equals(s)
assert g.compareTo(s) == 0 && s.compareTo(g) == 0
assert g == s && s == g 

【讨论】:

    【解决方案2】:

    不确定这是否是您所击中的,但请考虑以下 groovy 代码:

    def x = 'World'
    def gstr = "Hello ${x}!"
    def str  = 'Hello World!'
    
    println "class of gstr: ${gstr.class}"
    println "class of str:  ${str.class}"
    
    println(gstr.equals(str))
    println(gstr.toString().equals(str))
    

    运行时打印:

    ~> groovy solution.groovy
    class of gstr: class org.codehaus.groovy.runtime.GStringImpl
    class of str:  class java.lang.String
    false
    true
    
    ~> 
    

    换句话说,字符串插值将导致groovy GString 的实例不一定等于具有相同内容的字符串。使用.toString() 强制评估解决了这个特殊问题。

    引用groovy documentation on string interpolation

    任何 Groovy 表达式都可以插入到所有字符串文字中,除了单引号和三引号字符串。插值是在评估字符串时用其值替换字符串中的占位符的行为。占位符表达式由 ${} 包围,或以 $ 为前缀以表示点分表达式。当 GString 通过在该表达式上调用 toString() 传递给以 String 作为参数的方法时,占位符内的表达式值被评估为其字符串表示形式。

    换句话说,您必须使用以下变体将 GString 实例分配给计划 java String:

    String str1 = gstr
    def str2 = gstr as String
    def str3 = (String) gstr
    

    ,调用一个接受字符串的方法(这会将 GString 强制转换为字符串)或调用gstr.toString() 来强制转换。

    希望对您有所帮助。

    【讨论】:

    • 这正是我一直在寻找的——为什么 .toString() 在它真的、真的、真的看起来不应该的情况下会有所作为的答案。作为一个完全的 groovy 新手,你还教了我 .class 并且我可以使用 println 而不是炮弹回声。很棒的答案!谢谢!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-23
    • 1970-01-01
    相关资源
    最近更新 更多