Bash:检查某个位置的所有文件是否存在

时间:2012-08-31 22:02:36

标签: macos file bash loops

我在使用一些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

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

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

3 个答案:

答案 0 :(得分: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                              <
./the-holy-grail.info                          ./the-holy-grail.info
                                             > ./victory.wav
./zzz.wad                                      ./zzz.wad

如果不清楚,“some-song.mp3”仅在第一个目录中,而“victory.wav”仅在第二个目录中。其余文件很常见。

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

但这可能是我接近它的方法 - 将大量工作卸载到diff

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

[user@host so]$ diff tmpa tmpb

也会有效:

    Only in tmpa: some-song.mp3
    Only in tmpb: victory.wav

...但不像自己编写脚本那样令人满意。 : - )

答案 1 :(得分:1)

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

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

您可以在-f命令,上将其限制为只有find的常规文件。

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

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

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

答案 2 :(得分:0)

关于第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/./some/dir/filename.txt的文件但目标层次结构中没有文件时,join命令会打印出filename.txt之类的行。我们最后按下它的输出,因为我们不再需要行开头的文件名。