关于ruby:从rakefile执行bash命令

Execute bash commands from a Rakefile

我想从一个Rakefile执行一些bash命令。

我在我的电脑里试过以下方法

1
2
3
task :hello do
  %{echo"World!"}
end

但在执行rake hello时,没有输出?如何从rakefile执行bash命令?

注意:这不是重复的,因为它专门询问如何从rakefile执行bash命令。


我认为rake希望这种情况发生的方式是:http://rubydoc.info/gems/rake/fileutils sh-instance_方法例子:

1
2
3
task :test do
  sh"ls"
end

内置rake函数sh负责命令的返回值(如果命令的返回值不是0,则任务失败),此外,它还输出命令输出。


有几种方法可以在Ruby中执行shell命令。一个简单的方法(可能也是最常见的方法)是使用反勾号:

1
2
3
task :hello do
  `echo"World!"`
end

在shell命令的标准输出成为返回值时,反勾号具有很好的效果。例如,您可以通过执行

1
shell_dir_listing = `ls`

但是有很多其他方法可以调用shell命令,它们都有优点/缺点,并且工作方式也不同。这篇文章详细地解释了这些选择,但这里有一个简短的总结可能性:

  • stdout=%x cmd-背景符号的替代语法它也在做同样的事情

  • exec(cmd)-用新的cmd进程完全替换正在运行的进程

  • success=system(cmd)-运行子进程并返回true/false成功/失败时(基于命令退出状态)

  • io popen(cmd)io-运行子进程并连接stdout和对IO的STDR

  • stdin,stdout,stderr=open3.popen3(cmd)-运行子进程并连接到所有管道(输入、输出、错误)


考虑到共识似乎更喜欢Rake的#sh方法,但OP明确要求bash,这个答案可能有一些用处。

这是相关的,因为Rake#sh使用Kernel#system调用来运行shell命令。Ruby硬编码到/bin/sh,忽略用户在环境中配置的shell或$SHELL

这里有一个从/bin/sh调用bash的解决方法,允许您仍然使用sh方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
task :hello_world do
  sh <<-EOS.strip_heredoc, {verbose: false}
    /bin/bash -xeuo pipefail <<'BASH'
    echo"Hello, world!"
    BASH
  EOS
end

class String
  def strip_heredoc
    gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, ''.freeze)
  end
end

#strip_heredoc是从Rails借来的:

https://github.com/rails/rails/blob/master/active support/lib/active_支持/core_ext/string/strip.rb

您可能需要主动的_支持来获得它,或者在Rails项目中自动加载它,但是我在Rails外部使用它,所以必须自己定义它。

这里有两个文档,一个带有标记EOS的外部文档和一个带有标记BASH的内部文档。

它的工作方式是在bash标记之间输入内部heredoc到bash的stdin。注意,它是在/bin/sh的上下文中运行的,所以它是posix-heredoc而不是ruby-one。通常,这要求结束标记位于第1列中,但由于缩进,这里不是这样。

但是,由于它被包装在Ruby Heredoc中,因此在此处应用了strip_heredoc方法,在/bin/sh看到它之前,将内部Heredoc的整个左侧放置在第1列中。

/bin/sh通常也会在heredoc中扩展变量,这可能会干扰脚本。开始标记"bash"周围的单引号告诉/bin/sh,在传递给bash之前,不要扩展Heredoc中的任何内容。

然而,在将字符串传递给bash之前,/bin/sh仍然对该字符串应用转义。这就意味着反斜杠必须加倍才能通过/bin/sh进行重击,即\变成\\

bash选项-xeuo pipefail是可选的。

-euo pipefail参数告诉bash在严格模式下运行,在任何失败或引用未定义变量(甚至管道中的命令)时都会停止执行。这将向rake返回一个错误,从而停止rake任务。通常这就是你想要的。如果需要正常的bash行为,可以删除这些参数。

-x选择bash和{verbose: false}#sh的参数协同工作,以便rake只打印实际执行的bash命令。如果您的bash脚本不打算完整地运行,例如,如果它有一个允许它在脚本的早期优雅地退出的测试,那么这很有用。

如果不希望rake任务失败,请注意不要设置除0以外的退出代码。通常这意味着您不想在不显式设置退出代码的情况下使用任何|| exit构造,即|| exit 0

如果你在路上碰到了什么怪事,就把-o pipefail关掉。我看到了一些与之相关的错误,特别是当管道连接到grep时。


%{echo"World!"}定义了一个字符串。我想你要的是%x{echo"World!"}

%x{echo"World!"}执行命令并返回输出(stdout)。您将看不到结果。但你可以这样做:

1
puts %x{echo"World!"}

有更多方法可以调用系统命令:

  • Backticks:
  • system( cmd )
  • popen
  • Open3#popen3