1

我正在尝试编写一个脚本,该脚本列出目录中的文件,然后逐个搜索其他目录中的每个文件。为了处理空格和特殊字符,如“[”或“]”,我将$(printf %q "$FILENAME")其用作 find 命令的输入:find /directory/to/search -type f -name $(printf %q "$FILENAME"). 它对每个文件名都像一个魅力,除了在一种情况下:当有多字节字符(UTF-8)时。在这种情况下,printf 的输出是一个外部引用的字符串,即:$'文件名,带有空格和 \NNN\NNN' 形式的引用字符,并且如果没有 $'' 引用,该字符串不会被扩展,所以find 搜索名称包含该引号的文件:«$'filename'»。

是否有替代解决方案可以通过查找任何类型的文件名?

我的脚本如下(我知道有些行可以删除,比如“RESNAME=”):

#!/bin/bash

if [ -d $1 ] && [ -d $2 ]; then
    IFSS=$IFS
    IFS=$'\n'
    FILES=$(find $1 -type f )
    for FILE in $FILES; do
        BASEFILE=$(printf '%q' "$(basename "$FILE")")
        RES=$(find $2 -type f -name "$BASEFILE" -print )
        if [ ${#RES} -gt 1 ]; then
            RESNAME=$(printf '%q' "$(basename "$RES")")
        else
            RESNAME=
        fi
        if [ "$RESNAME" != "$BASEFILE" ]; then
            echo "FILE NOT FOUND: $FILE"
        fi
    done

else
    echo "Directories do not exist"
fi

IFS=$IFSS

正如答案所说,我使用了关联数组,但没有运气,也许我没有正确使用数组,但回显它 (array[@]) 什么也不返回。这是我写的脚本:

#!/bin/bash
if [ -d "$1" ] && [ -d "$2" ]; then
    declare -A files
    find "$2" -type f -print0 | while read -r -d $'\0' FILE;
    do
        BN2="$(basename "$FILE")"
        files["$BN2"]="$BN2"
    done

    echo "${files[@]}"

    find "$1" -type f -print0 | while read -r -d $'\0' FILE;
    do
        BN1="$(basename "$FILE")"
        if [ "${files["$BN1"]}" != "$BN1" ]; then
            echo "File not found: "$BN1""  
        fi
    done
fi
4

5 回答 5

1

不要使用for循环。首先,它比较慢。您find必须在程序的其余部分运行之前完成。其次,有可能使命令行超载。enterfor命令必须适合命令行缓冲区。

最重要的是,for处理时髦的文件名很糟糕。你正在运行 conniptions 试图解决这个问题。然而:

find $1 -type f -print0 | while read -r -d $'\0' FILE

会工作得更好。它处理文件名——甚至是包含\n字符的文件名。-print0告诉用findNUL 字符分隔文件名。FILE 会将每个while read -r -d $'\0文件名(由 NUL 字符分隔)读入$FILE.

如果在命令中为文件名加上引号find,则不必担心文件名中的特殊字符

对于找到的每个文件,您的脚本都会运行find一次。如果您的第一个目录中有 100 个文件,那么您将运行find100 次。

你知道 BASH 中的关联(散列)数组吗?使用关联数组可能会更好。在第一个目录上运行find,并将这些文件名存储在关联数组中。

然后,为您的第二个目录运行 find(再次使用find | while read语法)。对于您在第二个目录中找到的每个文件,查看您的关联数组中是否有匹配的条目。如果这样做,您就知道该文件在两个数组中。


附录

我一直在看find命令。似乎没有真正的方法可以阻止它使用模式匹配,除非通过大量工作(就像你正在做的那样printf。我已经尝试使用-regex匹配和使用\Q\E删除模式字符的特殊含义。我没有成功的。

有时你需要比 shell 更强大、更灵活的东西来实现你的脚本,我相信现在是时候了。

Perl、Python 和 Ruby 是几乎在所有 Unix 系统上都可以找到的三种相当普遍的脚本语言,并且在其他非 POSIX平台上也可用(咳!...Windows!...咳!)。

下面是一个 Perl 脚本,它采用两个目录,并在它们中搜索匹配的文件。它使用该find命令一次并使用关联数组(在 Perl 中称为哈希)。我将哈希值键入我的文件名。在hash的value部分,我存储了我找到此文件的目录数组。

我只需要find每个目录运行一次命令。完成后,我可以打印出散列中包含多个目录的所有条目。

我知道这不是外壳,但这是您可以花费更多时间试图弄清楚如何让外壳做您想做的事情而不是其价值的情况之一。

#! /usr/bin/env perl

use strict;
use warnings;
use feature qw(say);

use File::Find;
use constant DIRECTORIES => qw( dir1 dir2 );


my %files;
#
# Perl version of the find command. You give it a list of
# directories and a subroutine for filtering what you find.
# I am basically rejecting all non-file entires, then pushing
# them into my %files hash as an array.
#
find (
    sub {
        return unless -f;
        $files{$_} = [] if not exists $files{$_};
        push @{ $files{$_} }, $File::Find::dir;
    },  DIRECTORIES
);

#
# All files are found and in %files hash. I can then go
# through all the entries in my hash, and look for ones
# with more than one directory in the array reference.
# IF there is more than one, the file is located in multiple
# directories, and I print them.
#

for my $file ( sort keys %files ) {
    if ( @{ $files{$file} } > 1 ) { 
        say  "File: $file: " . join ", ", @{ $files{$file} };
    }
}
于 2013-10-27T18:27:34.413 回答
0

尝试这样的事情:

find "$DIR1" -printf "%f\0" | xargs -0 -i find "$DIR2" -name \{\}
于 2013-10-27T17:15:13.060 回答
0

由于您仅将find其用于其递归目录,因此只需globstar使用bash. (你正在使用关联数组,所以你bash的足够新)。

#!/bin/bash
shopt -s globstar
declare -A files
if [[ -d $1 && -d $2 ]]; then
    for f in "$2"/**/*; do
        [[ -f "$f" ]] || continue
        BN2=$(basename "$f")
        files["$BN2"]=$BN2
    done

    echo "${files[@]}"

    for f in "$1"/**/*; do
        [[ -f "$f" ]] || continue
        BN1=$(basename $f)
        if [[ ${files[$BN1]} != $BN1 ]]; then
            echo "File not found: $BN1"
        fi
    done
fi

**将匹配零个或多个目录,因此$1/**/*将匹配 中的所有文件$1和目录,这些目录中的所有文件和目录,依此类推,一直沿树向下。

于 2013-10-28T18:39:16.433 回答
0

这个单排怎么样?

find dir1 -type f -exec bash -c 'read < <(find dir2 -name "${1##*/}" -type f)' _ {} \; -printf "File %f is in dir2\n" -o -printf "File %f is not in dir2\n"

对于名称中带有有趣符号、换行符和空格的文件,绝对 100% 安全。

它是如何工作的?

find(主要)将扫描目录dir1并为每个文件(-type f)执行

read < <(find dir2 -name "${1##*/} -type f")

参数是 main 给出的当前文件的名称find。这个论点在 position $1${1##*/}删除最后一个之前的所有内容,因此if /is$1语句是:path/to/found/filefind

find dir2 -name "file" -type f

如果找到文件,则会输出一些内容,否则没有输出。这就是readbash 命令读取的内容。read如果它能够读取某些内容,则退出状态为 true,如果没有读取任何内容,则为 false(即,如果没有找到任何内容)。此退出状态变为bash' 退出状态,后者变为-exec' 状态。如果为真,-printf则执行下一条语句,如果为假,则-o -printf执行该部分。

如果您的目录以变量形式给出$dir1$dir2执行此操作,以确保可能出现在以下位置的空格和有趣符号的安全$dir2

find "$dir1" -type f -exec bash -c 'read < <(find "$0" -name "${1##*/}" -type f)' "$dir2" {} \; -printf "File %f is in $dir2\n" -o -printf "File %f is not in $dir2\n"

关于效率:这当然不是一种有效的方法!内部find将执行的次数与在dir1. 这很糟糕,特别是如果目录树dir2很深并且有很多分支(您可以稍微依赖缓存,但有限制!)。

关于可用性:您可以对 的工作方式和输出进行细粒度控制find,并且很容易添加更多测试。


那么,嘿,告诉我如何比较两个目录中的文件?好吧,如果你同意失去一点控制,这将是最短和最有效的答案:

diff dir1 dir2

试试看,你会惊讶的!

于 2013-10-28T12:46:54.620 回答
0

如果你想使用关联数组,这里有一种可能适用于名称中包含各种有趣符号的文件(这个脚本太多了,无法说明这一点,但它可以按原样使用——只需删除你需要的部分不想要并适应您的需求):

#!/bin/bash

die() {
    printf "%s\n" "$@"
    exit 1
}

[[ -n $1 ]] || die "Must give two arguments (none found)"
[[ -n $2 ]] || die "Must give two arguments (only one given)"

dir1=$1
dir2=$2

[[ -d $dir1 ]] || die "$dir1 is not a directory"
[[ -d $dir2 ]] || die "$dir2 is not a directory"

declare -A dir1files
declare -A dir2files

while IFS=$'\0' read -r -d '' file; do
   dir1files[${file##*/}]=1
done < <(find "$dir1" -type f -print0)

while IFS=$'\0' read -r -d '' file; do
   dir2files[${file##*/}]=1
done < <(find "$dir2" -type f -print0)

# Which files in dir1 are in dir2?
for i in "${!dir1files[@]}"; do
   if [[ -n ${dir2files[$i]} ]]; then
      printf "File %s is both in %s and in %s\n" "$i" "$dir1" "$dir2"
      # Remove it from dir2 has
      unset dir2files["$i"]
   else
      printf "File %s is in %s but not in %s\n" "$i" "$dir1" "$dir2"
   fi
done

# Which files in dir2 are not in dir1?
# Since I unset them from dir2files hash table, the only keys remaining
# correspond to files in dir2 but not in dir1

if [[ -n "${!dir2files[@]}" ]]; then
   printf "File %s is in %s but not in %s\n" "$dir2" "$dir1" "${!dir2files[@]}"
fi

评论。文件的识别仅基于它们的文件名,而不是它们的内容。

于 2013-10-28T18:17:46.183 回答