1

ディレクトリ上のファイルを一覧表示し、他のディレクトリ内のすべてのファイルを 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の残りの部分を実行する前に、完了する必要があります。第 2 に、コマンド ラインをオーバーロードする可能性があります。enterforコマンドは、コマンド ライン バッファーに収まる必要があります。

最も重要なことは、forファンキーなファイル名の処理が苦手なことです。あなたはこれを回避しようとしている。でも:

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

はるかにうまく機能します。ファイル名を処理します。文字を含むファイル名も処理します\n。は、ファイル名を NUL 文字で区切るように-print0指示します。findFILE は、while read -r -d $'\0各ファイル名を (NUL 文字で区切って) に読み込みます$FILE

コマンドでファイル名を引用符で囲んだ場合、ファイル名の特殊文字findについて心配する必要はありません。

スクリプトはfind、見つかったファイルごとに 1 回実行されます。最初のディレクトリに 100 個のファイルがある場合、find100 回実行していることになります。

BASH の連想 (ハッシュ) 配列について知っていますか? おそらく、連想配列を使用した方がよいでしょう。最初のディレクトリで実行findし、それらのファイル名を連想配列に格納します。

次に、2 番目のディレクトリに対して (再びfind | while read構文を使用して) find を実行します。2 番目のディレクトリで見つかった各ファイルについて、連想配列に一致するエントリがあるかどうかを確認します。もしそうなら、ファイルが両方の配列にあることがわかります。


補遺

私はfindコマンドを見てきました。多くの作業を除いて、パターンマッチングの使用を防ぐ本当の方法はないようです(あなたがprintf.成功。-regex\Q\E

スクリプトを実装するために、シェルよりも少し強力で柔軟なものが必要になる時が来ます。その時が来たと思います。

Perl、Python、および Ruby は、ほぼすべての Unix システムで見られるかなりユビキタスな 3 つのスクリプト言語であり、他の非 POSIXプラットフォームで使用できます (咳! ...Windows!... 咳!)。

以下は、2 つのディレクトリを取り、一致するファイルを検索する Perl スクリプトです。findコマンドを 1 回使用し、連想配列( Perl ではハッシュと呼ばれます) を使用します。ハッシュをファイルの名前に合わせます。hashの部分には、このファイルを見つけたディレクトリの配列を格納します。

findこのコマンドは、ディレクトリごとに 1 回だけ実行する必要があります。それが完了したら、複数のディレクトリを含むハッシュ内のすべてのエントリを出力できます。

シェルではないことはわかっていますが、これは、シェルに必要なことをさせる方法を理解するのに、その価値よりも多くの時間を費やすことができるケースの1つです。

#! /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 -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。この引数は位置にあり$1ます。ifステートメントが次のようになるように、最後の前の${1##*/}すべてを削除します。/$1path/to/found/filefind

find dir2 -name "file" -type f

ファイルが見つかった場合は何かを出力し、そうでない場合は何も出力しません。readそれがbash コマンドによって読み取られるものです。read何かを読み取ることができた場合、 の終了ステータスは true になり、何も読み取れなかった場合 (つまり、何も見つからない場合) は false になります。この終了ステータスがbashの終了ステータスになり、それが-execのステータスになります。true の場合は次の-printfステートメントが実行され、false の場合はその-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でき、さらに多くのテストを簡単に追加できます。


では、2 つのディレクトリのファイルを比較する方法を教えてください。少しコントロールを失うことに同意するなら、これが最短かつ最も効率的な答えになります。

diff dir1 dir2

それを試してみてください、あなたは驚かれることでしょう!

于 2013-10-28T12:46:54.620 に答える
0

再帰的なディレクトリのフォローのみを使用しているため、 のオプションfindを単純に使用する方が簡単です。(連想配列を使用しているため、十分に新しいです)。globstarbashbash

#!/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

**は 0 個以上のディレクトリ$1/**/*と一致するため、 内$1のすべてのファイルとディレクトリ、それらのディレクトリ内のすべてのファイルとディレクトリ、というようにツリーの下まで一致します。

于 2013-10-28T18:39:16.433 に答える
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 に答える