【问题标题】:Bash: Check all files in a location against another for existenceBash:检查一个位置中的所有文件是否存在
【发布时间】:2012-08-26 17:20:36
【问题描述】:

我正在寻求一些 Bash 脚本的帮助(在 OSX 上)。我想创建一个带有两个参数的脚本——源文件夹和目标文件夹——并检查源层次结构中的所有文件以查看它们是否存在于目标层次结构中。即,给定一张数据 DVD,检查其中包含的文件是否已经在内部驱动器上。

到目前为止,我想出的是

#!/bin/bash

if [ $# -ne 2 ]
then
        echo "Usage is command sourcedir targetdir"
        exit 0
fi

source="$1"
target="$2"

for f in "$( find $source -type f -name '*' -print )"
do

我现在不确定如何最好地获取没有路径的文件名,然后查看它是否存在。我真的是脚本的初学者。

编辑:到目前为止给出的答案在紧凑代码方面都非常有效。但是,我需要能够在目标层次结构中的任何位置查找在整个源层次结构中找到的文件。如果找到,我想比较校验和和最后修改日期等并发表评论,或者,如果没有找到,我想注意这一点。目的是检查外部媒体上的文件是否已经上传到文件服务器。

【问题讨论】:

  • 你有没有想过获取两个文件夹的内容然后运行一个差异?根据您正在谈论的文件数量,这种方法可能会更快。
  • 另外,你也需要下到子目录吗?
  • jedwards - 下降到子目录是至关重要的。为了给您一些背景信息,以及为什么我采用循环方法,我有一系列数据 DVD,我希望根据我的文件服务器检查它们的内容,看看我是否可以将它们装箱。第 1 步是检查文件是否存在,第 2 步可能是查看校验和、日期、大小等,然后在以下位置输出有用的信息:1. 未找到文件,2. 找到文件但未找到校验和匹配。

标签: macos file bash loops


【解决方案1】:

这应该会给你一些想法:

#!/bin/bash

DIR1="tmpa"
DIR2="tmpb"

function sorted_contents
{
    cd "$1"
    find . -type f | sort
}

DIR1_CONTENTS=$(sorted_contents "$DIR1")
DIR2_CONTENTS=$(sorted_contents "$DIR2")

diff -y  <(echo "$DIR1_CONTENTS") <(echo "$DIR2_CONTENTS")

在我的测试目录中,输出是:

[user@host so]$ ./dirdiff.sh ./address-book.dat ./address-book.dat ./passwords.txt ./passwords.txt ./some-song.mp3 ./victory.wav ./zzz.wad ./zzz.wad

如果不清楚,“some-song.mp3”仅在第一个目录中,而“victory.wav”仅在第二个目录中。其余文件都是通用的。

请注意,这仅比较文件名,而不是内容。如果你喜欢它的发展方向,你可以使用diff 选项(如果你想要更清晰的输出,可以使用--suppress-common-lines)。

但这可能是我的处理方式——将大量工作转移到diff

编辑:我还应该指出一些简单的事情:

[user@host so]$ diff tmpa tmpb

也可以:

仅在 tmpa 中:some-song.mp3 仅在 tmpb 中:victory.wav

...但感觉不如自己编写脚本那么令人满意。 :-)

【讨论】:

  • diff 命令还会向您显示两个地方存在的文件内容的差异,如果您关心的话。
【解决方案2】:

仅列出$source_dir 中不存在于$target_dir 中的文件:

 comm -23 <(cd "$source_dir" && find .|sort) <(cd "$target_dir" && find .|sort)

您可以在find 命令中使用-f 将其限制为常规文件,

comm 命令(“common”的缩写)查找两个文本文件之间的共同行并输出三列:仅在第一个文件中的行,仅在第二个文件中的行,以及两者共有的行。数字抑制了相应的列,因此comm -23 的输出只是第一个文件中没有出现在第二个文件中的行。

进程替换语法&lt;(command) 被连接到给定命令输出的命名管道的路径名替换,这使您可以在任何可以放置文件名的地方使用“管道”,而不仅仅是标准输入和标准输出。

在这种情况下,命令会生成两个目录下的文件列表 - cd 使输出相对于被比较的目录,因此相应的文件以相同的字符串出现,sort 确保 @987654331 @ 不会被两个文件夹中以不同顺序列出的相同文件混淆。

【讨论】:

    【解决方案3】:

    关于for f in "$( find $source -type f -name '*' -print )"这一行的几点说明:

    • 使"$source"。始终在变量替换周围使用双引号。否则,结果将被拆分为被视为通配符模式的单词(shell 解析规则中的一个历史奇点);特别是,如果变量的值包含空格,这将失败。
    • 您不能以这种方式迭代find 的输出。由于双引号,循环中将有一次迭代,$f 包含来自find 的完整输出。如果没有双引号,包含空格和其他特殊字符的文件名会使脚本出错。
    • -name '*' 是无操作的,它匹配所有内容。

    据我了解,您希望独立于文件的位置按名称查找文件,即您认为 /dvd/path/to/somefile/internal-drive/different/path-to/somefile 匹配。因此,在每边按名称索引的文件列表。您可以通过稍微按摩find 的输出来做到这一点。下面的代码可以处理文件名中除换行符以外的任何字符。

    list_files () {
      find . -type f -print |
      sed 's:^\(.*\)/\(.*\)$:\2/\1/\2:' |
      sort
    }
    source_files="$(cd "$1" && list_files)"
    dest_files="$(cd "$2" && list_files)"
    join -t / -v 1 <(echo "$source_files") <(echo "$dest_files") |
    sed 's:^[^/]*/::'
    

    list_files 函数生成带有路径的文件名列表,并在文件前面添加文件名,例如/mnt/dvd/some/dir/filename.txt 将显示为 filename.txt/./some/dir/filename.txt。然后对文件进行排序。

    当源层次结构中有一个名为filename.txt 的文件但目标层次结构中没有文件时,join 命令会打印出类似filename.txt/./some/dir/filename.txt 的行。我们终于稍微调整了它的输出,因为我们不再需要行首的文件名。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-09-18
      • 2014-01-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-05-26
      相关资源
      最近更新 更多