【问题标题】:Efficient way to find paths from a list of filenames从文件名列表中查找路径的有效方法
【发布时间】:2012-08-31 05:10:48
【问题描述】:

从存储在文件f 中的文件名列表中,找到dir 下每个文件名的相对路径并将这个新列表输出到文件p 的最佳方法是什么?我目前正在使用以下内容:

while read name
do
    find dir -type f -name "$name" >> p
done < f

对于大型列表或大型目录树来说太慢了。

编辑:一些数字:

  • dir下的目录数:1870
  • dir下的文件数:80622
  • f 中的文件名数:73487

f 中列出的所有文件确实存在于dir 下。

【问题讨论】:

  • 所以你的文件包含一个没有路径信息的文件名列表?您想如何处理多次出现的文件?我假设您希望将整个目录树加载到内存中以快速执行此操作,因为重复扫描磁盘永远不会足够快,但这是否可行取决于我们正在谈论的大小。恐怕我不知道 bash 式的方式来做到这一点,但如果可以的话,用 PHP 或类似的方式来做会很简单?
  • 是的,只是文件名,结果必须包括所有可能的路径。可以将目录树加载到内存中,但我宁愿不使用 PHP,因为它没有安装。不过 Perl 或 Python 都可以。
  • +1 使用while read name ... done &lt; f

标签: bash shell find


【解决方案1】:

以下 Python 代码可以解决问题。关键是运行一次 find 并将输出存储在 hashmap 中,以提供从 file_name 获取文件名路径列表的 O(1) 方式。

#!/usr/bin/env python
import os

file_names = open("f").readlines()
file_paths = os.popen("find . -type f").readlines()
file_names_to_paths = {}
for file_path in file_paths:
    file_name = os.popen("basename "+file_path).read()
    if file_name not in file_names_to_paths:
        file_names_to_paths[file_name] = [file_path]
    else:
        file_names_to_paths[file_name].append(file_path) # duplicate file

out_file = open("p", "w")
for file_name in file_names:
    if file_names_to_paths.has_key(file_name):
        for path in file_names_to_paths[file_name]:
            out_file.write(path)

【讨论】:

  • 为什么不使用os.path.walkos.path.basename(file_path)
  • os.path.basename,特别是必须使用。当file_path 包含空格时会失败
  • 4 秒,没有丢失文件。如果没有出现更短的内容,我会接受这个答案。
【解决方案2】:

试试这个 perl 单行代码

perl -e '%H=map{chomp;$_=>1}<>;sub R{my($p)=@_;map R($_),<$p/*> if -d$p;($b=$p)=~s|.*/||;print"$p\n" if$H{$b}}R"."' f

1- 创建一个以文件名为键的哈希图:%H=map{chomp;$_=>1}

2- 定义一个递归子程序来遍历目录:sub R{}

2.1- 递归调用目录:map R($_), if -d$p

2.2- 从路径中提取文件名:($b=$p)=~s|.*/||

2.3- 如果 hashmap 包含文件名则打印: print"$p\n" if$H{$b}

3- 使用路径当前目录调用 R:R"."

编辑:遍历隐藏目录 (.*)

perl -e '%H=map{chomp;$_=>1}<>;sub R{my($p)=@_;map R($_),grep !m|/\.\.?$|,<$p/.* $p/*> if -d$p;($b=$p)=~s|.*/||;print"$p\n" if$H{$b}}R"."' f

【讨论】:

  • 又好又快,但由于某种原因它丢失了一些文件
  • 是不是因为文件名可以包含 * 之类的模式?
  • 不。事实上,我看不到丢失的文件名有任何模式,我可以确认它们确实存在并且被列出。
  • 现在运行了两分钟,完全没有匹配到
  • 通常grep !m|/\.\.?$|,过滤...否则进程可能循环
【解决方案3】:

我认为这应该可以解决问题:

xargs locate -b < f | grep ^dir > p

编辑:我想不出一个简单的方法来为文件名列表添加前缀dir/*/,否则你可以直接将它传递给xargs locate

【讨论】:

  • locate 使用参数作为部分匹配,并打印绝对文件名。 awk '{print "\\" $0}' f | xargs -d '\n' locate -b | sed "s|$(pwd)/||g" | grep ^dir &gt; d 可能会成功。
  • 啊,是的,那些部分匹配,我的前缀想法没有那个问题,但它当然有找不到dir/$file的问题。
【解决方案4】:

根据目录树的百分比被认为是匹配的,找到每个文件可能会更快,然后用grep找出匹配的文件:

find "$dir" -type f | grep -f <( sed 's+\(.*\)+/\1$+' "$f" )

sed 命令将您的文件名列表预处理为仅匹配路径末尾的全名的正则表达式。

【讨论】:

  • 让它运行了几分钟,完全没有输出。对每个组件进行计时表明 grep 是罪魁祸首,并且考虑到 grep 找到匹配项后立即输出匹配的事实,我认为这不会比我的幼稚方法更快。
  • 太糟糕了。我认为即使有很长的文件名列表,单个grep 也可能比多次调用find 更快。
【解决方案5】:

这是使用 bash 和 grep 的替代方法

#!/bin/bash

flist(){
for x in "$1"/*; do #*/ for markup
[ -d "$x" ] && flist $x || echo "$x"
done
}

dir=/etc #the directory you are searching
list=$(< myfiles) #the file with file names

#format the list for grep
list="/${list//
/\$\|/}"

flist "$dir" | grep "$list"

...如果您需要完整的 posix shell 合规性(busybox ash、hush 等...)将 $list 子字符串操作替换为 chepner 的 sed 的变体,并将 $(

【讨论】:

  • chepner方法的问题不在于find的速度,而在于grep的速度,这在你的版本中没有修复,并且它本身也包含两个问题:@ 987654324@无缘无故重新实现,grep "$list"会溢出命令行参数长度的限制
猜你喜欢
  • 2019-12-14
  • 1970-01-01
  • 2014-12-31
  • 2011-02-02
  • 2020-02-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多