Directory

使用修改時間 (mtime) 和大小而不是內容來區分目錄

  • March 11, 2021

是否可以選擇讓 diff (-q) 不查看文件內容而只查看大小和 mtime?如果沒有,是否有類似的工具可以選擇?

使用rsync,但告訴它不要復製或刪除任何文件。

rsync -a -nv --delete a/ b/

下一個腳本是對此處答案的改進:

#!/bin/sh

# diffm.sh

# DIFF with Modification date - a .sh (dash; bash; zsh - compatible) 
# "diff utility"-like script that can compare files in two directory 
# trees by path, size and modification date

GetOS () {
   
   OS_kernel_name=$(uname -s)
   
   case "$OS_kernel_name" in
       "Linux")
           eval $1="Linux"
       ;;
       "Darwin")
           eval $1="Mac"
       ;;
       "CYGWIN"*|"MSYS"*|"MINGW"*)
           eval $1="Windows"
       ;;
       "")
           eval $1="unknown"
       ;;
       *)
           eval $1="other"
       ;;
   esac
   
}

DetectShell () {
   eval $1=\"\";
   if [ -n "$BASH_VERSION" ]; then
       eval $1=\"bash\";
   elif [ -n "$ZSH_VERSION" ]; then
       eval $1=\"zsh\";
   elif [ "$PS1" = '$ ' ]; then
       eval $1=\"dash\";
   else
       eval $1=\"undetermined\";
   fi
}

PrintInTitle () {
   printf "\033]0;%s\007" "$1"
}

PrintJustInTitle () {
   PrintInTitle "$1">/dev/tty
}

trap1 () {
   CleanUp
   printf "\nAborted.\n">/dev/tty
}

CleanUp () {
   
   #Restore "INTERRUPT" (CTRL-C) and "TERMINAL STOP" (CTRL-Z) signals:
   trap - INT
   trap - TSTP
   
   #Restore Initial Directory:
   cd "$initial_dir"
   
   #Clear the title:
   PrintJustInTitle ""
   
   #Restore initial IFS:
   #IFS=$old_IFS
   unset IFS
   
   #Set shell flags (enable globbing):
   set +f
}

DisplayHelp () {
   printf "\n"
   printf "diffm - DIFF by Modification date\n"
   printf "\n"
   printf "    What it does:\n"
   printf "        - compares the files in the two provided directory tree paths (<dir_tree1> and <dir_tree2>) by:\n"
   printf "            1. Path\n"
   printf "            2. Size\n"
   printf "            3. Modification date\n"
   printf "    Syntax:\n"
   printf "        <caller_shell> '/path/to/diffm.sh' <dir_tree1> <dir_tree2> [flags]\n"
   printf "        - where:\n"
   printf "            - <caller_shell> can be any of the shells: dash, bash, zsh, or any other shell compatible with the \"dash\" shell syntax\n"
   printf "            - '/path/to/diffm.sh' represents the path of this script\n"
   printf "            - <dir_tree1> and <dir_tree2> represent the directory trees to be compared\n"
   printf "            - [flags] can be:\n"
   printf "                --help or -h\n"
   printf "                    Displays this help information\n"
   printf "    Output:\n"
   printf "        - lines starting with '<' signify files from <dir_tree1>\n"
   printf "        - lines starting with '>' signify files from <dir_tree2>\n"
   printf "    Notes:\n"
   printf "        - only the files in the two provided directory tree paths are compared, not also the folders\n"
   printf "\n"
}

Proc1 () {
   {\
       {\
           cd "$initial_dir"
           [ -n "$dir1" ] && { cd "$dir1"; PrintJustInTitle "Loading files in directory 1, please wait..."; }
           eval $command1
           cd "$initial_dir"
           [ -n "$dir2" ] && { cd "$dir2"; PrintJustInTitle "Loading files in directory 2, please wait..."; }
           eval $command2
           cd "$initial_dir"
       }|eval $sort_command;
   }|eval $uniq_command;
}

Proc2 () {
   cd "$initial_dir"
   [ -n "$dir1" ] && { cd "$dir1"; PrintJustInTitle "Loading files in directory 1, please wait..."; }
   eval $command1
   cd "$initial_dir"
   [ -n "$dir2" ] && { cd "$dir2"; PrintJustInTitle "Loading files in directory 2, please wait..."; }
   eval $command2
   cd "$initial_dir"
}

GetOS OS

DetectShell current_shell

OS_CASE=""
if [ "$OS" = "Linux" ]; then
   OS_CASE="1"; # = use Linux OS commands
elif [ "$OS" = "Mac" ]; then
   OS_CASE="2"; # = use Mac OS commands
else
   #################################################################################
   ##                  IN CASE YOUR OS IS NOT LINUX OR MAC:                       ##
   ##    MODIFY THE NEXT VARIABLE ACCORDING TO YOUR SYSTEM REQUIREMENTS (e.g.:    ##
   ##    "1" (use Linux OS commands) or "2" (use Mac OS commands)):               ##
   #################################################################################
   OS_CASE="3"
fi

if [ "$current_shell" = "undetermined" ]; then
   printf "\nWarning: This script was designed to work with dash, bash and zsh shells.\n\n">/dev/tty
fi

#Get the program parameters into the array "params":
params_count=0
for i; do
   params_count=$((params_count+1))
   eval params_$params_count=\"\$i\"
done
params_0=$((params_count))

if [ "$params_0" = "0" ]; then #if no parameters are provided: display help
   DisplayHelp
   CleanUp && exit 0
fi


#Create a flags array. A flag denotes special parameters:
help_flag="0"
i=1;
j=0;
while [ "$i" -le "$((params_0))" ]; do
   eval params_i=\"\$\{params_$i\}\"
   case "${params_i}" in
   "--help" | "-h" )
       help_flag="1"
   ;;
   * )
       j=$((j+1))
       eval selected_params_$j=\"\$params_i\"
   ;;
   esac
   
   i=$((i+1))
done
selected_params_0=$j

#Rebuild params array:
for i in $(seq 1 $selected_params_0); do
   eval params_$i=\"\$\{selected_params_$i\}\"
done
params_0=$selected_params_0

if [ "$help_flag" = "1" ]; then
   DisplayHelp
else #Run program:
   
   error1="false"
   error2="false"
   error3="false"
   error4="false"
   
   { sort --help >/dev/null 2>/dev/null; } || { error1="true"; }
   { stat --help >/dev/null 2>/dev/null; } || { error2="true"; }
   { find --help >/dev/null 2>/dev/null; } || { error3="true"; }
   { uniq --help >/dev/null 2>/dev/null; } || { error4="true"; }
   if [ "$error1" = "true" -o "$error2" = "true" -o "$error3" = "true" -o "$error4" = "true" ]; then
       {
           printf "\n"
           if [ "$error1" = "true" ]; then printf '%s' "ERROR: Could not run \"sort\" (necessary in order for this script to function correctly)!"; fi
           if [ "$error2" = "true" ]; then printf '%s' "ERROR: Could not run \"stat\" (necessary in order for this script to function correctly)!"; fi
           if [ "$error3" = "true" ]; then printf '%s' "ERROR: Could not run \"find\" (necessary in order for this script to function correctly)!"; fi
           if [ "$error4" = "true" ]; then printf '%s' "ERROR: Could not run \"uniq\" (necessary in order for this script to function correctly)!"; fi
           printf "\n"
       }>/dev/stderr
       exit
   fi
   
   #Check program arguments:
   if [ "$params_0" -lt "2" ]; then
       printf '\n%s\n' "ERROR: To few program parameters provided (expected two: <dir_tree1> and <dir_tree2>)!">/dev/stderr
       exit 1
   elif [ "$params_0" -gt "2" ]; then
       printf '\n%s\n' "ERROR: To many program parameters provided (expected two: <dir_tree1> and <dir_tree2>)!">/dev/stderr
       exit 1
   fi
   
   initial_dir="$PWD" #Store initial dir
       
   #If two program arguments are provided (<dir_tree1> and <dir_tree2>) proceed to checking them:
   
   initial_dir="$PWD" #Store initial dir
   dir1=""
   dir2=""
   file1=""
   file2=""
   error_encountered="false"
   
   error1="false"
   error2="false"
   [ -e "$params_1" ] && {
       if [ -d "$params_1" ]; then
           cd "$params_1" >/dev/null 2>/dev/null && {
               dir1="$PWD"
               cd "$initial_dir"
           } || {
               error1="true"
           }
       elif [ ! -d "$params_1" ]; then
           file1="$params_1"
       fi
   }||{
       error2="true"
   }
   if [ "$error1" = "true" -o "$error2" = "true" ]; then
       printf '\n%s\n' "ERROR: PARAMETER1: \"$params_1\" does not exist as a directory/file or is not accessible!">/dev/stderr
       error_encountered="true"
   fi
   
   printf "\n">/dev/tty
   
   error1="false"
   error2="false"
   [ -e "$params_2" ] && {
       if [ -d "$params_2" ]; then
           cd "$params_2" >/dev/null 2>/dev/null && {
               dir2="$PWD"
               cd "$initial_dir"
           }||{
               error1="true"
           }
       elif [ ! -d "$params_2" ]; then
           file2="$params_2"
       fi
   }||{
       error2="true"
   }
   if [ "$error1" = "true" -o "$error2" = "true" ]; then
       printf '%s\n' "ERROR: PARAMETER2: \"$params_2\" does not exist as a directory/file or is not accessible!">/dev/stderr
       error_encountered="true"
   fi
   
   if [ "$error_encountered" = "true" ]; then
       printf "\n">/dev/stderr
       exit
   fi
   
   ## TYPE ///// PATH ///// SIZE ///// LAST TIME WRITE IN SECONDS ##
   
   if [ "$OS_CASE" = "1" ]; then #Linux OS
       if [ -n "$dir1" ]; then
           command1='find . -not -type d -exec stat -c "< ///// %n ///// %s ///// %Y" {} \;'
       else
           command1_string="$(stat -c "< ///// %n ///// %s ///// %Y" "$file1")"
           command1="printf '%s\n' \"\$command1_string\""
       fi
       if [ -n "$dir2" ]; then
           command2='find . -not -type d -exec stat -c "> ///// %n ///// %s ///// %Y" {} \;'
       else
           command2_string="$(stat -c "> ///// %n ///// %s ///// %Y" "$file2")"
           command2="printf '%s\n' \"\$command2_string\""
       fi
       command3='date -d @'
       cd "$initial_dir"
       
       sort_command="sort -k 3"
       
       uniq_command="uniq -u -f 2"
   elif [ "$OS_CASE" = "2" ]; then #Mac OS
       if [ -n "$dir1" ]; then
           command1='find . -not -type d -exec stat -f "< ///// %N ///// %z ///// %m" {} \;'
       else
           command1_string="$(stat -f "< ///// %N ///// %z ///// %m" "$file1")"
           command1="printf '%s\n' \"\$command1_string\""
       fi
       if [ -n "$dir2" ]; then
           command2='find . -not -type d -exec stat -f "> ///// %N ///// %z ///// %m" {} \;'
       else
           command2_string="$(stat -f "> ///// %N ///// %z ///// %m" "$file2")"
           command2="printf '%s\n' \"\$command2_string\""
       fi
       command3='date -j -f %s '
       cd "$initial_dir"
       
       sort_command="sort -k 3"
       
       uniq_command="uniq -u -f 2"
   else
       printf '\n%s\n\n' "Error: Unsupported OS!">/dev/stderr
       exit 1
   fi
   
   #Trap "INTERRUPT" (CTRL-C) and "TERMINAL STOP" (CTRL-Z) signals:
   trap 'trap1' INT
   trap 'trap1' TSTP
   
   old_IFS="$IFS" #Store initial IFS value
   IFS="
   "
   set -f #Set shell flags (disable globbing):
   found_previous="false"
   count=0
   skip=0
   if [ -n "$dir1" -o -n "$dir2" ]; then
       for line in $(\
           if [ -n "$dir1" -a -n "$dir2" ]; then\
               Proc1;\
           elif [ -z "$dir1" -o -z "$dir2" ]; then\
               Proc2;\
           fi;\
       ); do
           count=$((count+1))
           PrintJustInTitle "Analyzing file $count..."
           if [ -z "$current_line_file_type" ]; then
               current_line="$line"
               current_line_file_type="${line%%" ///// "*}"
               current_line_file_mtime_in_seconds="${line##*" ///// "}"
               current_line_file_type_path_and_size="${line%" ///// "*}"
               current_line_file_size="${current_line_file_type_path_and_size##*" ///// "}"
               current_line_file_type_path="${line%" ///// "*" ///// "*}"
               current_line_file_path="${current_line_file_type_path#*" ///// "}"
           else
               previous_line="$current_line"
               previous_line_file_type="$current_line_file_type"
               previous_line_file_mtime_in_seconds="$current_line_file_mtime_in_seconds"
               previous_line_file_type_path_and_size="$current_line_file_type_path_and_size"
               previous_line_file_size="$current_line_file_size"
               previous_line_file_type_path="$current_line_file_size"
               previous_line_file_path="$current_line_file_path"
               
               current_line="$line"
               current_line_file_type="${line%%" ///// "*}"
               current_line_file_mtime_in_seconds="${line##*" ///// "}"
               current_line_file_type_path_and_size="${line%" ///// "*}"
               current_line_file_size="${current_line_file_type_path_and_size##*" ///// "}"
               current_line_file_type_path="${line%" ///// "*" ///// "*}"
               current_line_file_path="${current_line_file_type_path#*" ///// "}"
               
               if [ ! "$skip" = "$count"  ]; then
                   if [ "$found_previous" = "false" ]; then
                       seconds_difference=$(($current_line_file_mtime_in_seconds - $previous_line_file_mtime_in_seconds))
                       if [ \
                           \( "$current_line" = "$previous_line" \) -o \
                           \( \
                               \( "$current_line_file_path" = "$previous_line_file_path" \) -a \
                               \( "$current_line_file_size" = "$previous_line_file_size" \) -a \
                               \( "$seconds_difference" = "1" -o "$seconds_difference" = "-1" \) \
                           \) \
                       ]; then
                           found_previous="true"
                           skip=$((count+1))
                       else
                           printf '%s\n' "$previous_line_file_type $previous_line_file_path - ""Size: ""$previous_line_file_size"" Bytes"" - ""Modified Date: ""$(eval $command3$previous_line_file_mtime_in_seconds)"
                           found_previous="false"
                       fi
                   else
                       printf '%s\n' "$previous_line_file_type $previous_line_file_path - ""Size: ""$previous_line_file_size"" Bytes"" - ""Modified Date: ""$(eval $command3$previous_line_file_mtime_in_seconds)"
                       found_previous="false"
                   fi
               else
                   found_previous="false"
               fi
           fi
       done
       #Treat last case separately:
       if [ "$count" -gt "0" ]; then
           if [ "$found_previous" = "false" ]; then
               printf '%s\n' "$current_line_file_type $current_line_file_path - ""Size: ""$current_line_file_size"" Bytes"" - ""Modified Date: ""$(eval $command3$current_line_file_mtime_in_seconds)"
           fi
       fi
   else
       line1=""
       line2=""
       for line in $(\
           if [ -n "$dir1" -a -n "$dir2" ]; then\
               Proc1;\
           elif [ -z "$dir1" -o -z "$dir2" ]; then\
               Proc2;\
           fi;\
       ); do
           if [ -z "$line1" ]; then
               line1="$line"
               line1_file_type="${line%%" ///// "*}"
               line1_file_mtime_in_seconds="${line##*" ///// "}"
               line1_file_type_path_and_size="${line%" ///// "*}"
               line1_file_size="${line1_file_type_path_and_size##*" ///// "}"
               line1_file_type_path="${line%" ///// "*" ///// "*}"
               line1_file_path="${line1_file_type_path#*" ///// "}"
           else
               line2="$line"
               line2_file_type="${line%%" ///// "*}"
               line2_file_mtime_in_seconds="${line##*" ///// "}"
               line2_file_type_path_and_size="${line%" ///// "*}"
               line2_file_size="${line2_file_type_path_and_size##*" ///// "}"
               line2_file_type_path="${line%" ///// "*" ///// "*}"
               line2_file_path="${line2_file_type_path#*" ///// "}"
               
               seconds_difference=$(($line2_file_mtime_in_seconds - $line1_file_mtime_in_seconds))
               if [ \
                   \( "$line2_file_size" = "$line1_file_size" \) -a \
                   \( \
                       \( "$line2_file_mtime_in_seconds" = "$line1_file_mtime_in_seconds" \) -o \
                       \( "$seconds_difference" = "1" -o "$seconds_difference" = "-1" \) \
                   \) \
               ]; then
                   :;
               else
                   printf '%s\n' "$line1_file_type $line1_file_path - ""Size: ""$line1_file_size"" Bytes"" - ""Modified Date: ""$(eval $command3$line1_file_mtime_in_seconds)"
                   printf '%s\n' "$line2_file_type $line2_file_path - ""Size: ""$line2_file_size"" Bytes"" - ""Modified Date: ""$(eval $command3$line2_file_mtime_in_seconds)"
               fi
           fi
       done
   fi
   
   CleanUp
fi
  • 它能做什麼:

  • 通過以下方式比較作為參數提供的兩個目錄樹路徑中的文件(遞歸地)(我們將它們表示為:<dir_tree1><dir_tree2>):

    1. 相對路徑
    2. 尺寸
    3. 修改日期
  • 如果發現差異:它列出不同文件的相對路徑及其詳細資訊(大小和修改日期):

    • 文件的相對文件路徑以 ’ ‘<dir_tree1>為前綴<
    • 文件的相對文件路徑以 ’ ‘<dir_tree2>為前綴>
  • 筆記:

    • 只比較文件,不比較目錄
    • 應該在 Linux OS、Mac OS 上工作 - 無需安裝任何額外的工具

引用自:https://unix.stackexchange.com/questions/197677