我想从一个Rakefile执行一些bash命令。
我在我的电脑里试过以下方法
1 2 3
| task :hello do
%{echo"World!"}
end |
但在执行rake hello时,没有输出?如何从rakefile执行bash命令?
注意:这不是重复的,因为它专门询问如何从rakefile执行bash命令。
- 它不是%{,它是%x(,返回stdout作为字符串而不是打印它。
- 它不是如上所述的副本,因为它特别要求在rake文件中执行,而不是在常规的ruby程序中执行。
- 当它特别询问Rake而不是Ruby时,为什么它被标记为副本?!
我认为rake希望这种情况发生的方式是:http://rubydoc.info/gems/rake/fileutils sh-instance_方法例子:
1 2 3
| task :test do
sh"ls"
end |
内置rake函数sh负责命令的返回值(如果命令的返回值不是0,则任务失败),此外,它还输出命令输出。
- 你的意思是Rake希望这种情况发生的方式?
- 其他的解决方案,如使用backticks执行等,不是rake方法,而是ruby方法(参见zhangxh.net/programming/ruby/…,了解6种不同的方法)。rake在"sh"函数中的作用是自动处理命令的返回值,这样,如果sh中的一个命令失败,rake任务就会失败。大多数时候这正是你想要的。从Ruby执行命令的其他方法不应该像从Rake执行命令那样经常使用。
- sh对系统的另一个好处是:默认情况下,sh似乎对echo ing命令更加冗长。
- 我在rake文件而不是系统中使用sh取得了最好的成功。
- 这应该是公认的答案,问题明确提到了Rake
- 考虑到RubyForge已经不复存在,截至2014年7月,该链接已经失效。
- @Artobendiken更新了评论,以包含到RDOC的最新链接。
有几种方法可以在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"some_task"将命令打印到终端,就好像直接从终端运行命令一样。
考虑到共识似乎更喜欢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时。
- 谢谢。这有助于我朝着正确的方向前进。我在Ruby脚本中使用它,我需要调用一些需要bash的方法。我还使用了一个长字符串和一些"奇怪"的字符。这是我的方法,也许它能帮助别人。system(%[xx=$(cat <。不过,最简单的方法是将所有内容放在bash脚本中,并将其命名为system("path/my_bash_script.sh")。
%{echo"World!"}定义了一个字符串。我想你要的是%x{echo"World!"}。
%x{echo"World!"}执行命令并返回输出(stdout)。您将看不到结果。但你可以这样做:
有更多方法可以调用系统命令:
- Backticks:
- system( cmd )
- popen
- Open3#popen3
- 应该注意的是,%x和backticks实际上正在调用等价的ruby内核方法,它们是相同精确方法的可选语法。
- 埃多克斯为我工作。我试图用wkhtmltopdf编写一个PDF生成器脚本。