Ruby Ruby 多行字符串 heredoc 详解

oneapm · 2015年06月11日 · 最后由 hww 回复于 2016年08月15日 · 11748 次阅读

基础的用法

def print_heredoc
  puts <<EOF
this is the first line
this is the second line
EOF
end

print_heredoc

输出:

this is the first line
this is the second line

如果你觉得代码太难看(这根本不符合 Ruby 的风格),你可能会这样写:

def print_heredoc
  puts <<EOF
    this is the first line
    this is the second line
  EOF
end

print_heredoc

你会发现高亮显示已经不对了,它还会报这样的一个错误:

test.rb:6: can't find string "EOF" anywhere before EOF
test.rb:2: syntax error, unexpected end-of-input, expecting tSTRING_CONTENT or tSTRING_DBEG or tSTRING_DVAR or tSTRING_END

可以缩进的用法

希望代码写的漂亮一点的话,就得多做点工作,在第一个 EOF 前加上一个减号就 OK 了:

def print_heredoc
  puts <<-EOF
    this is the first line
    this is the second line
  EOF
end

print_heredoc

输出:

this is the first line
this is the second line

heredoc 的本质

有下面一个方法:

def a_method(string, integer)
  puts "the string is #{string} and the integer is #{integer}"
end

一般这么用这个方法:

a_method "the string", 1

如果想用 heredoc 呢?这里就需要说下那个<<-EOF到底是什么东西?其实<<-EOF只是个占位符,写上它以后,它就代表将要输入的字符串,这个字符串的判断是从<<-EOF的下一行开始计算,一直碰到只有EOF的一行(这一行只有一个EOF),这个字符串就这样计算出来的。如果上面的这个方法要用 heredoc 的话,就可以这样写:

a_method <<-EOF, 1
this is the first line
this is the second line
EOF

输出:

the string is this is the first line
this is the second line
 and the integer is 1

可以看到,这个<<-EOF就好像一个实参一样,我们也可以把他完全当个实参来对待,它是个字符串,那么就可以调用字符串的方法,像这样:

a_method <<-EOF.gsub("\n", ""), 1
this is the first line
this is the second line
EOF

输出:

the string is this is the first linethis is the second line and the integer is 1

我们把它掰直了,哈哈。换个写法更能体现 heredoc 的本质:

a_method(<<-EOF.gsub("\n", ""), 1)
this is the first line
this is the second line
EOF

heredoc 的小技巧

那个<<-EOF为什么叫EOF,为什么不叫ABC,你可以试试,叫ABC也可以,但是末尾那个也要写 ABC,要保持配对。在有些编辑器(Atom,RubyMine)中甚至会根据占位符将 heredoc 中的内容高亮显示,比如可以写<<-RUBY, <<-HTML等等。

假如前面的那个方法要传入两个字符串该怎么写呢?很简单:

a_method <<-STR1, <<-STR2
this is for STR1
STR1
this is for STR2
STR2

输出:

the string is this is for STR1
 and the integer is   this is for STR2

有一点点晕,解释一下。<<-STR1在前面,它就会一直找到只包含STR1的那一行,并把其中的内容替换掉<<-STR1,而<<-STR2在后面,它不会包括<<-STR1STR1中的部分,会一直找到只包含STR2的那一行,然后替换'<<-STR2'。只需记住是只包含占位符的一行,像this is for STR1中虽然包含STR1,但是这一行还有其他字符,heredoc 就不会在这一行结束,而是接着往下找。同时需要知道,虽然是占位符,但是可以调用字符串的方法,在 Rails5 中最近的一个 pull request 中 DHH 就用了很多 heredoc。不过我找了半天没找到,等以后找到再把链接付在这里。

前面的输出结果中,大家一定发现一个问题,就是字符串的换行和行首的缩进,有时候你想要他们,有时候可不是,我们可以用gsub来替换。在 Rails 中已经有一个好用的方法了:

2.times do
  puts <<-STR.strip_heredoc
    this is the first line
      this is the second line
  STR
end

输出:

this is the first line
  this is the second line
this is the first line
  this is the second line

注意行首是没有空格的,和输入的格式保持了一致,很方便,实现也是很简单的,参考 github 吧,strip.rb

那些太奇怪的写法

其实还有很多奇怪的写法,比如下面这个:

puts <<-"I am the content"
line 1
line 2
line 3
I am the content

这是合法语法,但估计不会有人这么写,甚至有的编辑器都不能正常高亮显示它,比如 ruby-china 就不支持,但是它确实是可以运行的,不信你可以试试。

总结

希望对大家有用吧。


本文由OneAPM工程师原创,欢迎大家来OneAPM做客,共同讨论各种技术问题,OneAPM提供包括Ruby在内的主流 6 种编程语言,以及浏览器端、移动端、服务器软硬件环境的性能监测服务。

我们把它掰直了,哈哈。

不能更欢乐了!

.strip_heredoc 的另一种实现方式

irb(main):001:0>      print <<-`EOC`
irb(main):002:0`              echo right strip here
irb(main):003:0`              echo me too
irb(main):004:0`      EOC
right strip here
me too
=> nil

#3 楼 @jay_li 你的这种方法是执行 shell 命令,shell 里能做的在这里都能做,只是觉得它没有通用性,而且有时候它是不太方便的。

如果想要打印下面的输出:

def m1
  puts "test"
end

如果用strip_heredoc可以这样:

print <<-STR.strip_heredoc
  def m1
    puts "test"
  end
STR

那么用执行 shell 命令的方式就不太方便了吧。还是说有什么更好的方法,希望你能再补充一下,我可以更新到这篇帖子里,谢谢。

#4 楼 @oneapm 其实我也一直在找更好的实现方法 😄 ,在阅读其他 gem 源码时,发现一种比``好的方法,你文中也有提到

irb(main):001:0> print (<<-STR).gsub(/^ {2}/, '')
irb(main):002:1"   def m1
irb(main):003:1"     puts "test"
irb(main):004:1"   end
irb(main):005:1" STR
def m1
  puts "test"
end
=> nil

弊端就是gsub(/^ {2}/, '')中的数字需要指定,不是那么通用。

期待更好的方法出现。

#5 楼 @jay_li 😄 ,很高兴和你交流,只是我不太清楚,你觉得 Rails strip_heredoc的实现有什么问题吗?

def strip_heredoc
  indent = scan(/^[ \t]*(?=\S)/).min.try(:size) || 0
  gsub(/^[ \t]{#{indent}}/, '')
end

支持楼主,建议写些入门书籍,行文方法很不错

#7 楼 @peter 😃 多谢鼓励,争取早日写一本。

#6 楼 @oneapm 自己贱习惯,只使用自己看得懂的代码。但楼主把strip_heredoc源代码一贴...感谢您的回复,相当然的认为 rails 使用了一大套魔法 曾经研究 rails 源代码有些小阴影,平时使用 sinatra 多一些。

#9 楼 @jay_li Rails 的源码还是很多地方可学的,Sinatra 也是好东西,果然大家现在都开始使用轻量级的框架了,我们的 Ruby 客户有一半多都是 Grape 和 Sinatra

ruby2.3 引进了<<~,可以保留 heredoc 的缩进了。

文中的例子: 2.times do puts <<-STR.strip_heredoc this is the first line this is the second line STR end

可以写成: 2.times do puts <<~EOF this is the first line this is the second line EOF end

需要 登录 后方可回复, 如果你还没有账号请 注册新账号