在Capistrano中如何检查文件(在远程服务器上)是否存在?

43

和许多我在谷歌搜索中看到的人一样,我也曾经遭受过File.exists?陷阱的困扰,这个方法实际上检查的是你本地文件系统而不是你要部署到的服务器。

我找到了一种使用shell hack的解决方案,类似于:

if [[ -d #{shared_path}/images ]]; then ...

但我不太满意这种方法,除非它被包装在一个漂亮的Ruby方法中。

有人解决过这个问题吗?

5个回答

61

在 Capistrano 3 中,您可以执行以下操作:

on roles(:all) do
  if test("[ -f /path/to/my/file ]")
    # the file exists
  else
    # the file does not exist
  end
end

这很好,因为它会将远程测试的结果返回到您的本地 Ruby 程序中,您可以使用更简单的 shell 命令进行操作。


非常感谢!我最终得到了类似于 unless test("[ -f " + shared_path.to_s + "/a_shared_file.txt ]" ) 的东西。 - Lightheaded
2
马特,你能否给我链接到 test 的文档吗?这是一个很难搜索的单词。谢谢! - Jared Beck
2
常见问题解答示例:http://capistranorb.com/documentation/faq/how-can-i-check-for-existing-remote-file/ - Jared Beck
请注意,上下文中不起作用,因此您需要使用绝对路径进行测试。如果您想检查目录是否存在,请使用 [ -p ... ] - Evgenia Karunus

49

@knocte是正确的,因为capture存在问题,通常每个人都会将部署目标定位到多个主机上(而capture只获取第一个主机的输出)。 为了在所有主机上进行检查,您需要使用invoke_command(这就是capture在内部使用的方法)。以下是一个示例,我在其中检查一个文件是否存在于所有匹配的服务器上:

def remote_file_exists?(path)
  results = []

  invoke_command("if [ -e '#{path}' ]; then echo -n 'true'; fi") do |ch, stream, out|
    results << (out == 'true')
  end

  results.all?
end

请注意,invoke_command默认使用run -- 查看可传递的选项以获取更多控制。


1
如果你有多个目标,那么你的最终条件不会失败吗?结果数组可能是[true, true, true]。我认为你应该使用"results.all?"。 - Teflon Ted
4
在我的看法中,你应该在“else”情况下执行“echo -n 'false';”... - Richard Cook
@RichardCook:你说得对。我不知道我是怎么得出那个结论的。很抱歉散布了错误信息。同时,也很抱歉一个月才注意到。 - Peter Long
我该如何调用这个函数?如果我在我的 deploy.rb 中包含它,我会得到一个 undefined method 'remote_file_exists' for #<Capistrano::Configuration::Namespaces::Namespace:0x00000003650f88> (NoMethodError) 错误 - 基本上就像在命名空间中的任务内部调用 sudo "…" if remote_file_exists? "/foo/bar" - slhck
@PeterLong,我也是这样 - [].all? == true。我添加了!results.empty?到返回值中来解决这个问题。 - Isaac Betesh
显示剩余6条评论

20

受@bhups回答的启发,以下是测试代码:

def remote_file_exists?(full_path)
  'true' ==  capture("if [ -e #{full_path} ]; then echo 'true'; fi").strip
end

namespace :remote do
  namespace :file do
    desc "test existence of missing file"
    task :missing do
      if remote_file_exists?('/dev/mull')
        raise "It's there!?"
      end
    end

    desc "test existence of present file"
    task :exists do
      unless remote_file_exists?('/dev/null')
        raise "It's missing!?"
      end
    end
  end
end

7
注意!capture() 函数只能从第一台服务器中检索数据,因此请不要基于此构建任何逻辑。Capistrano 可以用于多台服务器。 - knocte
1
@knocte -- 感谢您指出这一点,我能够想出一个适用于所有匹配服务器的解决方案。请查看我的下面的答案。 - Patrick Reagan
酷啊!!大家请踩这个回复并点赞Patrick的!!这是一个彻底的失败。 - knocte

5
也许你想要做的是:
isFileExist = 'if [ -d #{dir_path} ]; then echo "yes"; else echo "no"; fi'.strip
puts "File exist" if isFileExist == "yes"

谢谢。我猜你的意思是用“capture”方法包装它?http://www.capify.org/index.php/Capture - Teflon Ted
在 Ruby 中捕获输出的另一种方法是使用反引号:isFileExist = if [ -d #{dir_path} ]; then echo "yes"; else echo "no"; fi.strip(确保删除反引号内部的额外空格:我已经添加了它们以帮助 SO 显示) - D_K

4

我以前用Capistrano的run命令(在远程服务器上执行shell命令)完成过这个任务。

例如,下面是一个Capistrano任务,它将检查shared/configs目录中是否存在database.yml文件,如果存在,则会将其链接。

  desc "link shared database.yml"
  task :link_shared_database_config do
    run "test -f #{shared_path}/configs/database.yml && ln -sf 
    #{shared_path}/configs/database.yml #{current_path}/config/database.yml || 
    echo 'no database.yml in shared/configs'"
  end

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