Ruby:我能否使用无连接符的多行字符串?

498

有没有办法让这个看起来更好一些?

conn.exec 'select attr1, attr2, attr3, attr4, attr5, attr6, attr7 ' +
          'from table1, table2, table3, etc, etc, etc, etc, etc, ' +
          'where etc etc etc etc etc etc etc etc etc etc etc etc etc'

比如说,有没有一种方式可以暗示字符串拼接?

17个回答

699

这个答案的一些部分帮助我得到了所需的东西(易于多行连接而没有额外的空格),但由于实际答案中没有,所以我在这里整理它们:

str = 'this is a multi-line string'\
  ' using implicit concatenation'\
  ' to prevent spare \n\'s'

=> "this is a multi-line string using implicit concatenation to eliminate spare
\\n's"

作为额外奖励,这里有一个使用有趣的HEREDOC语法的版本(通过这个链接):
p <<END_SQL.gsub(/\s+/, " ").strip
SELECT * FROM     users
         ORDER BY users.id DESC
END_SQL
# >> "SELECT * FROM users ORDER BY users.id DESC"

后者主要是为需要更灵活处理的情况而准备的。个人不太喜欢它,因为它将处理放在了字符串的奇怪位置(即在字符串前面,但使用通常在其之后的实例方法),但它确实存在。请注意,如果您缩进了最后一个END_SQL标识符(这很常见,因为这可能是在函数或模块内部),则需要使用连字符语法(即p <<-END_SQL而不是p <<END_SQL)。否则,缩进的空格会导致标识符被解释为字符串的连续部分。

这并没有节省太多打字时间,但我觉得它比使用加号看起来更好。

另外(我在编辑中说,几年后),如果您使用的是Ruby 2.3+,运算符<<~ 也可用,它会从最终字符串中删除额外的缩进。在这种情况下,您应该能够删除.gsub调用(尽管它可能取决于起始缩进和您的最终需求)。

编辑:再添加一个:

p %{
SELECT * FROM     users
         ORDER BY users.id DESC
}.gsub(/\s+/, " ").strip
# >> "SELECT * FROM users ORDER BY users.id DESC"

5
这是一个老问题,但是答案中可能存在错误或者自那时以来语法已经发生了改变。 p <<END_SQL 应该是 p <<-END_SQL 。否则这就是答案。你可以使用波浪线 HEREDOC 运算符 <<~END_SQL 来去掉前导空格(可选)。 - user483040
只有当结尾标识符缩进时才会出现错误(连字符告诉 Ruby 解释器在确定结尾标识符之前修剪空格)。我可以添加一条注释来提醒。另外,波浪线是不必要的,gsub \ s +和strip已经删除了前导空格。 - A. Wilson
1
当我写下这个答案时(九年前,天哪!),Ruby版本是1.9,而<<~(显然)直到2.3才被引入。无论如何,除了古老的历史,我会把它放进去,感谢你提出来。 - A. Wilson
这个解决方案导致了 FrozenError 错误:无法修改已冻结的字符串。 - cegprakash
.gsub.strip会改变它们的目标对象,而冻结字符串是不可变的;这可能意味着您正在Rails 2.3+项目中使用frozen_string_literal编译指示(并对字面字符串执行此操作)。使用其他答案之一依赖于连接而不是突变应该适用于您(删除额外的空格仍需要解冻字符串)。 - A. Wilson
显示剩余2条评论

227
在 Ruby 2.0 中,您现在可以直接使用 %
例如:
    SQL = %{
      SELECT user, name
      FROM users
      WHERE users.id = #{var}
      LIMIT #{var2}
    }

14
Ruby 1.9.3也可以使用这个功能。 - Andy Stewart
50
使用这种语法创建的字符串将包括换行符和后续行添加的任何缩进。 - James
2
@Nasser 一个heredoc也可以进行插值。 - anon
6
如果使用Rails,在输出上调用squish应该会很有帮助。 - Jignesh Gohel
说话要准确。不是百分号,而是百分号加大括号{}。 - undefined
显示剩余2条评论

181

如果您不介意插入额外的换行符,那么是可以的:

 conn.exec 'select attr1, attr2, attr3, attr4, attr5, attr6, attr7
            from table1, table2, table3, etc, etc, etc, etc, etc,
            where etc etc etc etc etc etc etc etc etc etc etc etc etc'

或者你可以使用heredoc

conn.exec <<-eos
   select attr1, attr2, attr3, attr4, attr5, attr6, attr7
   from table1, table2, table3, etc, etc, etc, etc, etc,
   where etc etc etc etc etc etc etc etc etc etc etc etc etc
eos

93
可以使用 %Q(...) - BaroqueBobcat
3
@Zombies:SQL语句通常允许换行,这些换行会被视为普通的空格。 - Mark Byers
2
请参考下面的示例,您现在可以使用%。 - Robbie Guilfoyle
8
你可以使用 %(...) - zero-divisor
1
如果您有意添加尾随空格并使用其中一种解决方案,请记住一件重要的事情:当保存文件时,您的编辑器可能会自动删除尾随空格。虽然我通常喜欢这种行为,但它有时会给我带来意外问题。解决方案是像问题中的 OP 一样编写多行字符串。 - Dennis
1
如果您不介意插入额外的换行符,那么这个问题就不需要换行符了。 - Josh

66
这个问题让我深入了解了HEREDOC的工作原理。如果答案太长,请见谅。
当您想要定义一个带有换行符和适当缩进的多行字符串时,squiggly HEREDOC <<~ 是您要寻找的内容(自Ruby 2.3以来可用)。
conn.exec <<~EOS
            select attr1, attr2, attr3, attr4, attr5, attr6, attr7
            from table1, table2, table3, etc, etc, etc, etc, etc
            where etc etc etc etc etc etc etc etc etc etc etc etc etc
          EOS

# -> "select...\nfrom...\nwhere..."

如果缩进不是一个问题,那么在Ruby中单引号和双引号可以跨越多行:
conn.exec "select attr1, attr2, attr3, attr4, attr5, attr6, attr7 
           from table1, table2, table3, etc, etc, etc, etc, etc, 
           where etc etc etc etc etc etc etc etc etc etc etc etc etc"    

# -> "select...\n           from...\n           where..."
      

如果单引号或双引号很麻烦,因为需要大量的转义,那么百分比字符串字面值符号%是最灵活的解决方案:
conn.exec %(select attr1, attr2, attr3, attr4, attr5, attr6, attr7
            from table1, table2, table3, etc, etc, etc, etc, etc
            where (ProductLine = 'R' OR ProductLine = "S") AND Country = "...")
# -> "select...\n            from...\n            where..."

如果目的是避免换行(这些都会导致波浪线HEREDOC、引号和百分比字符串字面量),那么可以使用行连续符,方法是将反斜杠\放在行的末尾非空格字符处。这将继续该行,并使Ruby将字符串连接在一起(注意引号内的空格):
conn.exec 'select attr1, attr2, attr3, attr4, attr5, attr6, attr7 ' \
          'from table1, table2, table3, etc, etc, etc, etc, etc, ' \
          'where etc etc etc etc etc etc etc etc etc etc etc etc etc'

# -> "select...from...where..."

如果你使用Rails,那么String.squish会将字符串中前后的空格去除,并将所有连续的空白字符(包括换行符、制表符等)压缩成一个空格:
conn.exec "select attr1, attr2, attr3, attr4, attr5, attr6, attr7 
           from table1, table2, table3, etc, etc, etc, etc, etc, 
           where etc etc etc etc etc etc etc etc etc etc etc etc etc".squish

# -> "select...attr7 from...etc, where..."

更多细节:

Ruby HEREDOC 语法

字符串的 Here Document 符号是一种在代码中内联指定长文本块的方法。它以<<开头,后跟用户定义的字符串(字符串结束符)。所有以下行都会被连接起来,直到找到字符串结束符位于行的最开始位置为止:

puts <<HEREDOC 
Text Text Text Text
Bla Bla
HEREDOC
# -> "Text Text Text Text\nBlaBla"

字符串的结束符可以自由选择,但通常使用类似于“EOS”(字符串结束)或与字符串域匹配的内容,例如“SQL”。

HEREDOC默认支持插值,或者当EOS终止符被双引号引用时:

price = 10
print <<"EOS"  # comments can be put here
1.) The price is #{price}.
EOS
# -> "1.) The price is 10."

如果EOS终止符使用单引号,则可以禁用插值:

print <<'EOS' # Disabled interpolation
3.) The price is #{price}.
EOS
# -> "3.) The price is #{price}."

<<HEREDOC 的一个重要限制是字符串终止符需要在行首:

  puts <<EOS 
    def foo
      print "foo"
    end
  EOS
EOS
#-> "....def foo\n......print "foo"\n....end\n..EOS"

为了解决这个问题,创建了<<-语法。它允许将EOS终止符缩进以使代码看起来更美观。在<<-和EOS终止符之间的行仍然完全使用,包括所有缩进。
def printExample
  puts <<-EOS # Use <<- to indent End of String terminator
    def foo
      print "foo"
    end
  EOS
end
# -> "....def foo\n......print "foo"\n....end"

自Ruby 2.3以来,我们现在拥有波浪线HEREDOC <<~,它可以去除前导空格:

puts <<~EOS # Use the squiggly HEREDOC <<~ to remove leading whitespace (since Ruby 2.3!)
  def foo
    print "foo"
  end
EOS
# -> "def foo\n..print "foo"\nend"

空行和只包含制表符和空格的行将被 <<~ 忽略。

puts <<~EOS.inspect 
  Hello

    World!
EOS
#-> "Hello\n..World!"

如果同时使用制表符和空格,则制表符被视为等于8个空格。 如果最小缩进的行位于一个制表符的中间,则该制表符不会被移除。

puts <<~EOS.inspect
<tab>One Tab
<space><space>Two Spaces
EOS
# -> "\tOne Tab\nTwoSpaces"

HEREDOC可以做一些疯狂的事情,比如使用反引号执行命令:

puts <<`EOC`            
echo #{price}
echo #{price * 2}
EOC

HEREDOC字符串定义可以“堆叠”,这意味着第一个EOS终止符(下面的EOSFOO)将结束第一个字符串并开始第二个字符串(下面的EOSBAR):

print <<EOSFOO, <<EOSBAR    # you can stack them
I said foo.
EOSFOO
I said bar.
EOSBAR

我认为没有人会像这样使用它,但是<<EOS实际上只是一个字符串字面量,可以放在任何普通字符串可以放置的地方:

def func(a,b,c)
  puts a
  puts b
  puts c
end

func(<<THIS, 23, <<THAT) 
Here's a line
or two.
THIS
and here's another.
THAT

如果你没有 Ruby 2.3,但是有 Rails >= 3.0,那么你可以使用 String.strip_heredoc,它与 <<~ 的作用相同。

# File activesupport/lib/active_support/core_ext/string/strip.rb, line 22
class String
  def strip_heredoc
    gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze)
  end
end

puts <<-USAGE.strip_heredoc # If no Ruby 2.3, but Rails >= 3.0
  This command does such and such.

  Supported options are:
    -h         This message
    ...
USAGE

故障排除

如果在Ruby解析文件时出现错误,那么最有可能的原因是您在HEREDOC中有额外的前导或尾随空格,或者在squiggly HEREDOC中有额外的尾随空格。例如:

您看到的:

    database_yml = <<~EOS
      production:
        database: #{fetch(:user)}
        adapter: postgresql
        pool: 5
        timeout: 5000
    EOS  

Ruby 告诉你的:

SyntaxError: .../sample.rb:xx: can't find string "EOS" anywhere before EOF
...sample.rb:xx: syntax error, unexpected end-of-input, expecting `end'

问题出在哪里:

Spot the extra spaces after the terminating EOS

找出终止EOS后的额外空格。

百分数字符串文字

有关如何使用带括号对的百分号后跟字符串(例如%(...)%[...]%{...}等)或任何非字母数字字符对的一些信息,请参见RubyDoc

最后的话

最后,回答原始问题“是否有一种方法暗示连接?”:如果发现两个字符串(单引号和双引号)背靠背,则Ruby总是暗示连接:

puts "select..." 'from table...' "where..."
# -> "select...from table...where..."

需要注意的是,这种方法不能跨越换行符使用,因为Ruby会解释语句结束,并且仅由字符串组成的连续行不会执行任何操作。


1
这是正确的答案。感谢您提供的所有细节!它澄清了关于HEREDOC的许多事情。 - Rael Gugelmin Cunha
我认为对许多人来说这是显而易见的,但我花了一些时间才明白%{}%()%[]字符串的处理方式没有任何区别。你只需选择在字符串内部最不可能使用的终止符,从而减少(或几乎消除)需要进行转义的必要性。例如,在SPARQL查询中,{}()是常见的,[]可以用于列表,因此使用%$$是有意义的。在SQL中,[]()也很常见,%{}可能是一个不错的选择(也许除了T-SQL)。我还喜欢选择在Bash中需要转义的字符,\$看起来很自然。 - berezovskyi

50

有多种多行字符串的语法,就像你已经阅读过的一样。我最喜欢的是 Perl 风格:

conn.exec %q{select attr1, attr2, attr3, attr4, attr5, attr6, attr7
      from table1, table2, table3, etc, etc, etc, etc, etc,
      where etc etc etc etc etc etc etc etc etc etc etc etc etc}

多行字符串以%q开头,后跟{、[或(,然后以相应的反向字符结束。%q不允许插值;而%Q允许插值,这样你就可以像这样编写代码:

conn.exec %Q{select attr1, attr2, attr3, attr4, attr5, attr6, attr7
      from #{table_names},
      where etc etc etc etc etc etc etc etc etc etc etc etc etc}

实际上我不知道这种多行字符串被称为什么,所以我们就叫它Perl多行。

请注意,无论您是使用像马克和彼得建议的Perl多行还是heredocs,都可能会产生不必要的空格。在我的示例和他们的示例中,“from”和“where”行包含前导空格,因为它们在代码中的缩进。如果不需要这个空格,则必须像您现在正在做的那样使用连接字符串。


4
在这个例子中,#{table_names}不起作用,因为你使用了%q{},如果你使用%q[]或(),它就能起作用了。 - MatthewFord
2
我在这方面最喜欢的就是只有%{支持插值的超级多行字符串}。 - Duke
来自%q系列的字符串将包含换行符,这与原始代码不等同。 - Josh

31

有时候删除换行符\n是值得的,例如:

conn.exec <<-eos.squish
 select attr1, attr2, attr3, attr4, attr5, attr6, attr7
 from table1, table2, table3, etc, etc, etc, etc, etc,
 where etc etc etc etc etc etc etc etc etc etc etc etc etc
eos

5
这是基于 Rails 而不是 Ruby。 - a14m

25

你也可以使用双引号

x = """
this is 
a multiline
string
"""

2.3.3 :012 > x
 => "\nthis is\na multiline\nstring\n"

如果需要去除换行符"\n",请在每行结尾处使用反斜杠"\"


6
你可以使用单引号来达到相同的效果。 Ruby 中没有三重双引号这样的东西。它只是将其解释为 "" + "带有一些内容的双引号" + "" - rakvium
是的,但 ""+"\nhello\n"" 确实看起来很奇怪。 - juliangonzalez
1
是的,它看起来很奇怪,这就是为什么没有理由添加额外的双引号,因为您可以只使用单个双引号并获得相同的结果。 - rakvium
是的,我指的是加号。没有它的双引号看起来很好,易读且更容易发现,而单引号应该用于单行字符串。 - juliangonzalez
1
我的意思是,只有"x""""x"""(基本上与""+"x"+"")或"""""x"""""(与"" + "" + "x" + "" + ""相同)更好看,更快。这是Ruby,不是Python,在需要多行字符串时使用"""而不是" - rakvium
显示剩余3条评论

17

其他选项:

#multi line string
multiline_string = <<EOM
This is a very long string
that contains interpolation
like #{4 + 5} \n\n
EOM

puts multiline_string

#another option for multiline string
message = <<-EOF
asdfasdfsador #{2+2} this month.
asdfadsfasdfadsfad.
EOF

puts message

1
应该将 <<EOM 改为 <<-EOM,对吧? - kingPuppy
也许,对于我的 <<-EOF 示例似乎可以工作。我猜任何一种方式都可以。 - Alex Cohen
heredoc将包含换行符,这与原始代码不等同。 - Josh

16

最近,随着Ruby 2.3的新功能,新的squiggly HEREDOC可以让您以一种美观简洁的方式编写多行字符串,而只需进行最小程度的更改。如果您正在使用Rails,则可以将其与.squish结合使用,以便以美观的方式编写多行字符串! 如果仅使用Ruby,则可以执行<<~SQL.split.join(" "),这几乎是相同的。

[1] pry(main)> <<~SQL.squish
[1] pry(main)*   select attr1, attr2, attr3, attr4, attr5, attr6, attr7
[1] pry(main)*   from table1, table2, table3, etc, etc, etc, etc, etc,
[1] pry(main)*   where etc etc etc etc etc etc etc etc etc etc etc etc etc
[1] pry(main)* SQL
=> "select attr1, attr2, attr3, attr4, attr5, attr6, attr7 from table1, table2, table3, etc, etc, etc, etc, etc, where etc etc etc etc etc etc etc etc etc etc etc etc etc"

参考: https://infinum.co/the-capsized-eight/multiline-strings-ruby-2-3-0-the-squiggly-heredoc


1
Squish是Rails,而不是Ruby。 - Josh

15
conn.exec = <<eos
  select attr1, attr2, attr3, attr4, attr5, attr6, attr7
  from table1, table2, table3, etc, etc, etc, etc, etc,
  where etc etc etc etc etc etc etc etc etc etc etc etc etc
eos

3
使用heredoc时不带'-',例如'<<-eos',将包括额外的前导空格。请参见Mark Byers的回答。 - ives
1
heredoc将包含换行符,这与原始代码不等同。 - Josh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接