두 디렉터리를 재귀적으로 비교하고 이름, 위치 및 내용이 동일한 각 파일 쌍에 대해 어떤 파일이 더 오래된지 확인하는 방법은 무엇입니까?

두 디렉터리를 재귀적으로 비교하고 이름, 위치 및 내용이 동일한 각 파일 쌍에 대해 어떤 파일이 더 오래된지 확인하는 방법은 무엇입니까?

Linux 셸에서 두 디렉터리를 재귀적으로 비교하고 두 디렉터리에서 동일한 위치(이름 포함)와 동일한 내용을 갖지만 수정 시간이 다른 모든 파일 쌍(심볼릭 링크 및 디렉터리 포함)에 대해 어떤 파일이 더 오래되었는지 확인하는 방법 ? 한 쌍의 두 파일이 동일한 기간을 갖는 경우 해당 쌍에 대한 출력이 없어야 합니다. 한 쌍에 있는 두 파일의 내용이 다르고 위치가 동일한 경우 해당 쌍에 대한 출력이 없어야 합니다.

사용 예:

# 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다루기 힘든바꾸다스프린트또는세게 때리다)는 일반적으로 디렉터리에 대한 절대 경로 또는 상대 경로(간단한 경로 포함 .) 일 수 있는 두 개의 인수를 허용해야 하며 /.

따옴표가 없는 문자열은 ~평소와 같이 두 인수 모두에서 홈 디렉터리로 해석되어야 합니다. 실제로 출력에서 ​​홈 디렉토리를 인쇄하는 것은 다소 복잡할 수 있습니다 ~.

출력은 평소와 같이 간결해야 합니다. 예를 들어 path

답변1

이 스크립트는 요구 사항에 따라 두 개의 디렉터리 트리를 비교합니다. 파일 이름은 제한되지 않습니다. (GNU comm또는 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. ~왜냐하면 제 생각 에는 이것이 모호함과 혼란을 야기하고 코드를 더 복잡하게 만들기 때문입니다../filefile/path/to/current/dir/foo/barfoo/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

개선을 환영합니다.

관련 정보