0

我在 python 中使用 rsync 命令,如下所示:

rsync_out = subprocess.Popen(['sshpass', '-p', password, 'rsync', '--recursive', source], 
                     stdout=subprocess.PIPE)
        command = subprocess.Popen(('grep', '\.'), stdin=rsync_out.stdout, stdout=subprocess.PIPE).communicate()[0]

使用 grep 显示文件的目的如下:

rathi/20090209.02s1.2_sequence.txt

rathi/20090729.02s4.2_sequence.txt.gz

rathi/Homo_sapiens_UCSC_hg19.tar.gz

    rathi/hello/ok.txt

代替

rathi

rathi/20090209.02s1.2_sequence.txt

rathi/20090729.02s4.2_sequence.txt.gz

rathi/Homo_sapiens_UCSC_hg19.tar.gz

hello

rathi/hello/ok.txt

它工作正常,除非目录名称'.'上有。

如果有一个目录名称 hello.v1 那么输出将是:

rathi/hello.v1
rathi/hello.v1/ok.txt

由于 hello.v1 是一个目录名,我只想这样显示:

rathi/hello.v1/ok.txt

我怎样才能做到这一点?

4

1 回答 1

1

就我个人而言,我不会费心使用grep,我只是使用 Python 自己的字符串过滤 - 但是,这不是你问的问题。

由于文件名是远程的并且 Python 将它们视为简单的字符串,因此我们不能使用任何 Python 自己的文件操作例程(例如os.path.isdir())。所以,我认为你有三种基本方法:

  1. 用斜杠分割每个字符串,并使用它在内存中构建您自己的文件系统树表示。然后,遍历树并只显示叶节点(即文件)。

  2. 如果您可以假设一个目录中的文件总是在该目录之后立即列出,那么您可以快速检查以前的条目以查看该条目是否是其中一个目录中的文件。

  3. 使用来自 的元信息rsync

我会建议第三种选择。我的经验rsync是,它通常会为您提供完整的文件信息,如下所示:

drwxr-xr-x        4096 2013/06/14 17:19:13 tmp/t
-rwxrwxr-x       14532 2013/06/14 17:17:23 tmp/t/a.out
-rwxrwxr-x       14539 2013/06/14 17:19:13 tmp/t/static-order

在您的示例中,我看不到任何删除此附加信息的代码,您可以通过查找以 ad而不是 a开头的任何行来轻松地使用它来过滤目录-

如果您没有此扩展信息,则需要执行另外两个之一。第一个选项非常简单 - 只需用斜杠分割,然后下降一个标准的树结构,为尚未看到的目录和文件添加条目。解析完所有条目后,您可以遍历树并打印出任何没有子节点的节点。

第二个选项更复杂,但内存效率更高,您可以在其中维护父目录列表并检查它们是否是列表中当前项目的前缀。如果是这样,您可以确定前一个是目录,当前是一个文件,因此您可以将前一个标记为不显示的内容。一旦您从该目录中递归“退出”,您也可以将项目从该列表中删除,前提rsync是以可预测的顺序返回它们。您必须确保仅检查斜线边界处的前缀(因此foo/dir不是 的父级foo/dir-bar,但它是 的父级foo/dir/bar)。通常,这种方法相当繁琐,除非您正在处理非常大的目录树,否则其他方法之一可能更可取。

顺便说一句,纯基于字符串的方法也有一个缺点,即空目录与文件无法区分,因为只有目录中存在或不存在文件才能区分它们。这是我建议使用来自rsync.

编辑

根据要求,使用rsync元数据的示例:

import subprocess

cmdline = ["rsync", "-e", "ssh", "-r", "user@host:/dir"]
proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE)
for entry in proc.stdout:
    items = entry.strip().split(None, 4)
    if not items[0].startswith("d") and "." in items[4]:
        print items[4]

在此示例中,我rsync直接调用并使用它ssh,假设设置了适当的 SSH 密钥。我强烈建议使用 SSH 密钥而不是sshpass实用程序 - 从安全角度来看,以明文形式存储密码是一个非常糟糕的主意。如果您不担心密钥被盗,您可以随时设置没有密码的密钥。有很多页面解释了如何创建 SSH 密钥(例如这个)。

替换user,host/dir使用您在远程计算机上的用户名、远程计算机的主机名和您希望在远程计算机上列出的父目录(/dir如果您想列出用户的主目录,可以省略)。否则代码应该不加修改地运行。如果将打印它找到的每个文件的路径名,跳过不包含点的目录和项目。如果您的点过滤器也只是试图跳过目录,您可以省略 ' 和 "." 在项目[4]'中。

编辑 2

此示例仅打印条目,但您当然可能想做其他事情。如果你想变得非常聪明,你可以把它写成一个生成器,yield当它们出现时调用它们。我在下面有一个例子,它也打印项目,但你可以看到它如何用于做其他事情。我还添加了一些更好的错误处理来确保使用subprocess不能死锁:

编辑 3: 我已经更新了这个例子,还包括文件大小和修改时间。这是基于我从我那里得到的rsync——如果你的格式不同,你可能需要使用不同的成员,items或者可能更改格式字符串strptime()以匹配你返回的格式rsync

from datetime import datetime
import os
import subprocess

def find_remote_files(hostspec):
    cmdline = ["rsync", "-e", "ssh", "-r", hostspec]
    with open(os.devnull, "w") as devnull:
        proc = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=devnull)
        try:
            for entry in proc.stdout:
                items = entry.strip().split(None, 4)
                if not items[0].startswith("d"):
                    dt = datetime.strptime(" ".join(items[2:4]),
                                           "%Y/%m/%d %H:%M:%S")
                    yield (int(items[1]), dt, items[4])
            proc.wait()
        except:
            # On any exception, terminate process and re-raise exception.
            proc.terminate()
            proc.wait()
            raise

for filesize, filedate, filename in find_remote_files("user@host:/dir"):
    print "Filename: %s" % (filename,)
    print "(%d bytes, modified %s)" % (filesize, filedate.strftime("%Y-%m-%d"))

如果您愿意,您应该能够将整个find_remote_files()函数粘贴到您的代码中并直接使用它。

于 2013-07-02T11:06:43.037 回答