bashスクリプトのcmp - 既存のファイルには「ファイルまたはディレクトリがありません」

bashスクリプトのcmp - 既存のファイルには「ファイルまたはディレクトリがありません」

ディレクトリを読み、内容が重複する可能性があるファイルを識別しようとしています。

簡略化されたバージョン:

#!/bin/bash

IFS=$'\n'

for FILEA in $(find "$1" -type f -print)
do
    echo "$FILEA"
    for FILEB in $(find "$1" -type f -print)
    do
        echo cmp \"${FILEA}\" \"${FILEB}\"
        cmp \"${FILEA}\" \"${FILEB}\"
    done
done

ただし、すべてのcmpインスタンスが文句を言いますNo such file or directory

cmpコマンドのエコーされたバージョンを取得すると、期待どおりに機能します。

cmpがスクリプトコンテキストで実行されたときにファイルを表示できないのはなぜですか?

ベストアンサー1

主な問題は、使用時にファイル名にリテラル二重引用符を追加することですcmp(そして、ファイル名自体には実際に引用符がありません)。これがファイルが見つからない理由です(名前に引用符がありません)。出力を繰り返すこともできますfindこれは理想的ではありません。

もしあなたなら本物使いたくないfdupes次のようにして、方法をより効率的に(速度の観点から)作成できます。

#!/bin/bash

# enable the "**" globbing pattern,
# remove non-matched patterns rather than keeping them unexpanded, and
# allow the matching of hidden names:
shopt -s globstar nullglob dotglob

pathnames=("${1:-.}"/**)  # all pathnames beneath "$1" (or beneath "." if "$1" is empty)

# loop over the indexes of the list of pathnames
for i in "${!pathnames[@]}"; do
    this=${pathnames[i]}  # the current pathname

    # skip this if it's not a regular file (or a symbolic link to one)
    [[ ! -f "$this" ]] && continue

    # loop over the remainder of the list
    for that in "${pathnames[@]:i+1}"; do

        # skip that if it's not a regular file (or a symbolic link to one)
        [[ ! -f "$that" ]] && continue

        # compare and print if equal
        if [[ "$this" -ef "$that" ]] || cmp -s "$this" "$that"; then
            printf '"%s" and "%s" contains the same thing\n' "$this" "$that"
        fi
    done
done

これにより、ファイルごとにディレクトリ構造全体を一度だけ見ることが防止され(これは内部ループで実行されます)、ペアを複数回比較することも防ぎます。まだ非常にcmpディレクトリ階層全体のすべてのファイルの組み合わせで実行する必要があるため、遅くなります。

代わりに、より簡単なアプローチを試してみることもできます。

#!/bin/bash

tmpfile=$(mktemp)

find "${1:-.}" -type f -exec md5sum {} + | sort -o "$tmpfile"
awk 'FNR == NR && seen[$1]++ { next } seen[$1] > 1' "$tmpfile" "$tmpfile"

rm -f "$tmpfile"

すべてのファイルのMD5チェックサムを計算し、リストをソートして一時ファイルに保存します。その後、出力はすべての重複ファイルをawk抽出するために使用されます。md5sum

出力は次のとおりです。

$ bash ~/script.sh
01b1688f97f94776baae85d77b06048b  ./QA/StackExchange/.git/hooks/pre-commit.sample
01b1688f97f94776baae85d77b06048b  ./Repositories/password-store.git/hooks/pre-commit.sample
036208b4a1ab4a235d75c181e685e5a3  ./QA/StackExchange/.git/info/exclude
036208b4a1ab4a235d75c181e685e5a3  ./Repositories/password-store.git/info/exclude
054f9ffb8bfe04a599751cc757226dda  ./QA/StackExchange/.git/hooks/pre-applypatch.sample
054f9ffb8bfe04a599751cc757226dda  ./Repositories/password-store.git/hooks/pre-applypatch.sample
2b7ea5cee3c49ff53d41e00785eb974c  ./QA/StackExchange/.git/hooks/post-update.sample
2b7ea5cee3c49ff53d41e00785eb974c  ./Repositories/password-store.git/hooks/post-update.sample
3c5989301dd4b949dfa1f43738a22819  ./QA/StackExchange/.git/hooks/pre-push.sample
3c5989301dd4b949dfa1f43738a22819  ./Repositories/password-store.git/hooks/pre-push.sample

上記の出力には重複した内容がいくつかあります。ファイル数。

ファイル名に改行文字が含まれている場合、プレフィックスmd5sumに文字が付いた行が出力されます\

$ touch $'hello\nworld'
$ md5sum *
\d41d8cd98f00b204e9800998ecf8427e  hello\nworld

これを正しく処理するには(行の先頭からバックスラッシュを削除して)、スクリプトの最初のパイプを次のように変更する必要があります。

find "${1:-.}" -type f -exec md5sum {} + | sed 's/^\\//' | sort -o "$tmpfile"

おすすめ記事