Linuxシェルで2つのディレクトリを再帰的に比較し、両方のディレクトリで同じ場所(名前を含む)と同じ内容を持つが変更時間が異なるすべてのファイルペア(シンボリックリンクとディレクトリを含む)についてどのファイルが古いかを確認する方法?ペアの2つのファイルが同じ期間を持つ場合は、そのペアの出力があってはなりません。ペア内の2つのファイルの内容が異なり、場所が同じ場合は、そのペアの出力があってはなりません。
使用例:
# First, ensure that /tmp/d1, /tmp/d2, ~/d3, and ~/f don't exist. Then:
$ cd /tmp
$ mkdir d1 d2 ~/d3
$ touch d1/f && sleep 1 && touch d2/f ~/d3/f ~/f
$ echo "g1" > d1/g
$ echo "g2" > d2/g
$ echo "g3" > ~/d3/g
$ compare_times.sh d1 d2
d1/f is older than d2/f
$ cd d1
$ compare_times.sh . ../d2
f is older than ../d2/f
$ cd ../d2
$ compare_times.sh . ../d1
../d1/f is older than f
$ cd ..
$ compare_times.sh /tmp/d1 d2
/tmp/d1/f is older than d2/f
$ compare_times.sh d1 /tmp/d2
d1/f is older than /tmp/d2/f
$ compare_times.sh d1 ~/d3
d1/f is older than ~/d3/f
$ cd d1
$ compare_times.sh ~ .
f is older than ~/f
私たちの比較スクリプトcompare_times.sh
(もちろん、compare_times.zsh
扱いにくい変えるスプリントまたは強く打つ)は通常、ディレクトリへの絶対パスまたは相対パス(単純なパスを含む.
)である可能性がある2つの引数を受け入れる必要があります/
。
引用符のない文字列は、~
通常どおり、両方の引数でホームディレクトリとして解釈する必要があります。実際、出力からホームディレクトリを印刷するのはやや複雑かもしれません~
。
出力は通常どおり簡潔でなければなりません。たとえば path
答え1
このスクリプトは、要件に応じて2つのディレクトリツリーを比較します。ファイル名は制限されません。 (GNUcomm
またはNULLで終わるレコードを処理できる他の同等のツールがfind
ない場合は、sort
ファイル名の改行を処理する機能を放棄する必要があります。)
#!/bin/bash
#
d1=$1 d2=$2
# Identify the set of matching file names
LC_ALL=C comm -z -12 \
<( cd -P -- "$d1" && find . -type f -print0 | LC_ALL=C sort -z ) \
<( cd -P -- "$d2" && find . -type f -print0 | LC_ALL=C sort -z ) |
while IFS= read -rd '' fn
do
# Tidy the filenames
fn=${fn#./}
f1="$d1/$fn"
f2="$d2/$fn"
# Compare content
if cmp -s -- "$f1" "$f2"
then
# Report older/newer file pairs (not those the same)
[[ "$f1" -ot "$f2" ]] && printf '%s is older than %s\n' "${f1#./}" "${f2#./}"
[[ "$f2" -ot "$f1" ]] && printf '%s is older than %s\n' "${f2#./}" "${f1#./}"
fi
done
答え2
そしてzsh
:
#! /bin/zsh -
d1=${1?} d2=${2?}
# All regular files in $d1, with $d1/ removed from the start of their
# path in the second step
l1=( ${d1?}/**/*(ND-.) ); l1=( ${l1#$d1/} )
# same for $2
l2=( ${d2?}/**/*(ND-.) ); l2=( ${l2#$d2/} )
# loop over the intersection of the $l1 and $l2 arrays
for f ( ${l1:*l2} ) {
f1=$1/$f f2=$2/$f
cmp -s -- $f1 $f2 || continue
if [[ $f1 -nt $f2 ]] {
print -r -- $f1 is newer than $f2
} elif [[ $f1 -ot $f2 ]] {
print -r -- $f1 is older than $f2
} else {
print -r -- $f1 and $f2 are the same age
}
}
私はORに置き換えようとしません/home/your-home-dir
。~
なぜなら、私の考えでは、これはあいまいさと混乱を引き起こし、コードをより複雑にするからです。./file
file
/path/to/current/dir/foo/bar
foo/bar
それでも好む場合は、取ることができるいくつかのアプローチがあります。d1=${1:P}
inが保存される絶対標準パスを使用してください。を完全なパスとして識別するのは簡単ですが、拡張子にシンボリックリンクコンポーネントが実際に含まれていると競合が発生します。シンボリックリンクが解決されるという事実は、ユーザを容易に混乱させることができる。$1
$d1
~
~user
$HOME
~user
トレードオフは、パスの先頭でおよびを簡素化し//./././/
て/
削除するなど、最も安全なパスの単純化を実行してから使用することです。$PWD/
./
D
パラメータ拡張フラグ~user
パスで識別されます。たとえば、次のように$display_f1
計算できます$f1
(でも同じ$f2
)。
set -o extendedglob
display_f1=${(D)${${f1//\/((.|)\/)##/\/}#$PWD/}#./}
たとえば、
$ f1='///home/././chazelas//foo/bar baz'
$ pwd
/home/chazelas
$ display_f1=${(D)${${f1//\/((.|)\/)##/\/}#$PWD/}#./}
$ typeset display_f1
display_f1='foo/bar\ baz'
$ cd /
$ display_f1=${(D)${${f1//\/((.|)\/)##/\/}#$PWD/}#./}
$ typeset display_f1
display_f1='~/foo/bar\ baz'
$ f1='./~/foo'
$ display_f1=${(D)${${f1//\/((.|)\/)##/\/}#$PWD/}#./}
$ typeset display_f1
display_f1='\~/foo'
しかし:
$ cd /home
$ f1=./chazelas/foo
$ display_f1=${(D)${${f1//\/((.|)\/)##/\/}#$PWD/}#./}
$ typeset display_f1
display_f1=chazelas/foo
(まさか~/foo
)。
このコンテンツを含む完全なソリューションは次のとおりです。
#! /bin/zsh -
set -o extendedglob
d1=${1?} d2=${2?}
# All regular files in $d1, with $d1/ removed from the start of their
# path in the second step
l1=( ${d1?}/**/*(ND-.) ); l1=( ${l1#$d1/} )
# same for $2
l2=( ${d2?}/**/*(ND-.) ); l2=( ${l2#$d2/} )
# loop over the intersection of the $l1 and $l2 arrays
for f ( ${l1:*l2} ) {
f1=$1/$f f2=$2/$f
display_f1=${(D)${${f1//\/((.|)\/)##/\/}#$PWD/}#./}
display_f2=${(D)${${f2//\/((.|)\/)##/\/}#$PWD/}#./}
cmp -s -- $f1 $f2 || continue
if [[ $f1 -nt $f2 ]] {
print -r -- $display_f1 is newer than $display_f2
} elif [[ $f1 -ot $f2 ]] {
print -r -- $display_f1 is older than $display_f2
} else {
print -r -- $display_f1 and $display_f2 are the same age
}
}
答え3
一般ファイル、シンボリックリンク、ディレクトリの場合は、次のものに基づいています。http://stackoverflow.com/a/66468913そしてhttp://unix.stackexchange.com/a/767825(両方ありがとう!):
#!/bin/bash
# Syntax: compare_times.sh directory_1 directory_2
# Semantics: for pairs of files, directories, and symlinks with the same path at any depth in both directory_1 and directory_2 and the same type, compare their modification times and, if they differ, say which file/dir/symlink is older.
# thanks to http://stackoverflow.com/a/66468913. The original is in http://github.com/python/cpython/blob/main/Lib/posixpath.py#L377 .
normpath() {
local IFS=/ initial_slashes='' comp comps=()
if [[ $1 == /* ]]; then
initial_slashes='/'
[[ $1 == //* && $1 != ///* ]] && initial_slashes='//'
fi
for comp in $1; do
if [[ -n ${comp} && ${comp} != '.' ]]; then
if [[ ${comp} != '..' || (-z ${initial_slashes} && ${#comps[@]} -eq 0) || (${#comps[@]} -gt 0 && ${comps[-1]} == '..') ]]; then
comps+=("${comp}")
elif ((${#comps[@]})); then
unset 'comps[-1]'
fi
fi
done
comp="${initial_slashes}${comps[*]}"
printf '%s\n' "${comp:-.}"
}
# thanks to http://unix.stackexchange.com/a/767825
if [[ $# -eq 2 ]]; then
dir1=$1
if [[ -d $dir1 ]]; then
dir2=$2
if [[ -d $dir2 ]]; then
# Identify the set of matching names of regular files:
LC_ALL=C comm -z -12 \
<( cd -P -- "$dir1" && find . -type f -print0 | LC_ALL=C sort -z ) \
<( cd -P -- "$dir2" && find . -type f -print0 | LC_ALL=C sort -z ) |
while IFS= read -rd '' fn; do
# Tidy the file names:
file1="$(normpath "$dir1/$fn")"
file2="$(normpath "$dir2/$fn")"
# Compare file contents:
if cmp -s -- "$file1" "$file2"; then
# Report older/newer regular-file pairs:
if [[ "$file1" -ot "$file2" ]]; then
printf '%s is older than %s\n' "$file1" "$file2"
elif [[ "$file2" -ot "$file1" ]]; then
printf '%s is older than %s\n' "$file2" "$file1"
fi
fi
done
# Identify the set of matching names of symlinks:
LC_ALL=C comm -z -12 \
<( cd -P -- "$dir1" && find . -type l -print0 | LC_ALL=C sort -z ) \
<( cd -P -- "$dir2" && find . -type l -print0 | LC_ALL=C sort -z ) |
while IFS= read -rd '' fn; do
# Tidy the link names:
link1="$(normpath "$dir1/$fn")"
link2="$(normpath "$dir2/$fn")"
# Compare the symlinks:
if cmp -s <(readlink -- "$link1") <(readlink -- "$link2"); then
## Report older/newer symlink pairs:
# if (find "$link2" -prune -newer "$link1" -printf 'a\n' | grep -q a) then
# printf '%s is older than %s\n' "$link1" "$link2"
# elif (find "$link1" -prune -newer "$link2" -printf 'a\n' | grep -q a) then
# printf '%s is older than %s\n' "$link2" "$link1"
# fi
find "$link2" -prune -newer "$link1" -printf "$link1 is older than $link2\n"
find "$link1" -prune -newer "$link2" -printf "$link2 is older than $link1\n"
fi
done
# Identify the set of matching names of directories:
LC_ALL=C comm -z -12 \
<( cd -P -- "$dir1" && find . -type d -print0 | LC_ALL=C sort -z ) \
<( cd -P -- "$dir2" && find . -type d -print0 | LC_ALL=C sort -z ) |
while IFS= read -rd '' fn; do
# Tidy the directory names:
subdir1="$(normpath "$dir1/$fn")"
subdir2="$(normpath "$dir2/$fn")"
# Compare the directory contents:
if cmp -s <(ls -Al --full-time -- "$subdir1") <(ls -Al --full-time -- "$subdir2"); then
# Report older/newer directory pairs:
if [[ "$subdir1" -ot "$subdir2" ]]; then
printf '%s is older than %s\n' "$subdir1" "$subdir2"
elif [[ "$subdir2" -ot "$subdir1" ]]; then
printf '%s is older than %s\n' "$subdir2" "$subdir1"
fi
fi
done
else
printf '%s is not an existing directory.\n' "$dir2"
exit 66 # EX_NOINPUT
fi
else
printf '%s is not an existing directory.\n' "$dir1"
exit 66 # EX_NOINPUT
fi
else
echo "Wrong number of arguments." >&2
exit 64 # EX_USAGE
fi
改善を歓迎します。