一,简介:
Win10 系统不会根据深层目录文件更新主目录的修改时间.
一般解决办法是关闭 Winodws 搜索引擎。
二,本脚本通过递归遍历子目录和子文件,来更新根目录的时间。
使用内层目录和当前目录下的最新文件的修改时间,更新根目录的修改时间。
#! /bin/bash
# 版本:
# bash --version: GNU bash,版本 4.4.12(3)-release-(x86_64-unknown-cygwin)
# ls --version: ls (GNU coreutils) 9.0
# touch --version: touch (GNU coreutils) 9.0
#
# 作者:
# blog.csdn.net/tiandyoin 2022.10.18
#
# 简介:
# Win10 系统不会根据深层目录文件更新主目录的修改时间.
# 一般解决办法是关闭 Winodws 搜索索引。
# https://zhidao.baidu.com/question/1970249248433264740.html
# 本脚本通过递归遍历子文件,来更新根目录的时间。
# 使用内层目录和当前目录下的最新文件的修改时间,更新主目录的修改时间。
# Usage:
# [./]update_dir_time[.sh] -h
# [./]update_dir_time[.sh] [-q] .
# [./]update_dir_time[.sh] [-qs] ./
# [./]update_dir_time[.sh] [-sv] ../
# [./]update_dir_time[.sh] [-st] .\\
# [./]update_dir_time[.sh] [-stv] /usr/bin/
# [./]update_dir_time[.sh] [-stv] "/usr/bin/tiandyoin text dir - 副本"
# [./]update_dir_time[.sh] [-stv] C:\\Cygwin64\\bin\\tiandyoin text dir - 副本\\
# [./]update_dir_time[.sh] [-stv] "C:\Cygwin64\bin\tiandyoin text dir - 副本"
# [./]update_dir_time[.sh] [-stv] "C:\Cygwin64\bin\tiandyoin text dir - 副本\\"
#
# -h 查看帮助
# -q 安静模式,不输出任何消息。
# -s 安全模式,当父目录时间比子目录或子文件晚时,不更新父目录时间。
# -t 统计各函数的执行时间
# -v 输出详细的文件遍历信息
#
# 备注:
# 不比较时间,如果子级文件的修改时间 与 当前父级目录的修改时间相同,仍会覆盖父级修改时间。
# 更改文件名,不会改变文件的修改时间,但会改变当前父级目录的修改时间。
# update_dir: 会把当前父级目录的修改时间 回退为 更名文件的修改时间。
# update_dir_s: 如果当前父级目录的修改时间 大等于 内层内容的修改时间,则保持父级修改时间。
#
# FAQ:
# 1.总用时 35s(27个文件,20个目录)
# 正常处理 18s(包括touch)
# 打印处理 17s
# touch 5s
# 2.为什么使用"Break 2"无效? ---
# 3.很多地方要判断文件或目录是否合法?
# Hacker 可能创建空字符(空格、制表符、换页符等)文件或目录,会造成程序死循环。
# 4.以下两种方式都无法阻止 touch 失败时强制退出
# command || true
# if a command exits with a non-zero status, ignore that and continue.
# 5.$IFS Shell预置变量
# 字符串分隔符,识别字符串或单词边界,默认值是空格,脚本中根据需要可以修改此变量的值。
#
set +e
function update_dir_main()
{
# 获取参数
get_opt $*
# 转换路径
case "`uname`" in CYGWIN*)
MAIN_DIR=`cygpath -U "$MAIN_DIR"`;;
esac
[[ $MODE_QUIET == 0 ]] && echo -e "________________________________________________________________________________"
[[ $MODE_QUIET == 0 ]] && echo -e "FUNC=$FUNC \nMAIN_DIR=\"$MAIN_DIR\" \nDealing..."
[[ $MODE_VERBOSE == 1 ]] && printf "\n[ T K C ]: TraveL, Keep, Change.\n\n"
start_time 'update_dir_main()'
# 调用主体
update_dir $MAIN_DIR
end_time 'update_dir_main()'
[[ $MODE_VERBOSE == 1 ]] && printf "\n[ T K C ]: TraveL, Keep, Change.\n\n"
# 打印非法路径
print_invalid_list
# 统计 目录 和 文件 总个数
print_total_files $MAIN_DIR
# 打印各流程总时间
print_time_spans
[[ $MODE_QUIET == 0 ]] && echo -e "\nDone!"
return 0
}
function update_dir()
{
LEVEL=`expr $LEVEL + 1`
start_time 'Folder Expand'
local dir="$*"
[[ $MODE_VERBOSE == 1 ]] && printf "[ T ]: " && print_info "$dir" || true
# 务必把目录排在最前面,递归优先处理叶子层目录。
# 每个路径一行,按时间倒序,最新的最前。
# read 命令在读取数据时会把 \t 替换成空格,以及行末尾的 \t 舍弃,其它无法识别的字符也丢弃。
#
# ls -1tA --indicator-style=none --group-directories-first "$dir" | \
# while read fd
local OLDIFS="$IFS"
IFS=$'\n'
for fd in `ls -1taA --indicator-style=none --group-directories-first "$dir"`
do
invalid "$dir" "$fd"; [[ $? -ne 0 ]] && continue
if [ -d "$dir/$fd" ]
then
update_dir "$dir/$fd" || true
fi
done
IFS="$OLDIFS"
end_time 'Folder Expand'
start_time 'Folder Collapse'
# 按间倒序,重新排序当前目录,用目录下内容的最新修改时间更新当前目录。
# update_dir => ls -1tA
# update_dir_s => ls -1ta (会出现 ".", ".." 目录)
#
# ls -1tA --indicator-style=none "$dir" | \
# while read fd
local OLDIFS="$IFS"
IFS=$'\n'
for fd in `ls $LS_OPTION --indicator-style=none "$dir"`
do
invalid "$dir" "$fd"; [[ $? -ne 0 ]] && continue
if [[ "$fd" == "." || "$fd" == ".." ]] # 当前目录或上级目录最新,不用修改
then
[[ $MODE_VERBOSE == 1 ]] && printf "[ K ]: " && print_info "$dir/$fd" || true
else
start_time 'touch()'
touch -mr "$dir/$fd" "$dir" || true
end_time 'touch()'
[[ $MODE_VERBOSE == 1 ]] && printf "[ C ]: " && print_info "$dir/$fd" || true
fi
break
done
IFS="$OLDIFS"
end_time 'Folder Collapse'
LEVEL=`expr $LEVEL - 1`
return 0
}
function get_opt()
{
MODE_QUIET=0
MODE_STAT_TIME=0
MODE_VERBOSE=0
MAIN_DIR=
LEVEL=0
FUNC=update_dir
LS_OPTION="-1tA"
__SPACE_REPLACER__='?' # 用 '?' 替换 空字符,以便存入 TIME_SPAN_LIST
declare -Ag INVALID_LIST # -g 全局变量。-a 顺序数组;-A 关联数组,类似 C++ Map.
declare -Ag TIME_SPAN_LIST
declare -Ag TIME_START_LIST
declare -Ag TIME_END_LIST
while getopts ":hqstv" opt; do
case ${opt} in
h )
echo "Usage:"
echo " update_dir_time [options] [dir]"
echo ""
echo "General Options:"
echo " -h show help."
echo " -q suppress all normal output."
echo " -s safe mode, reserve parent directory's update-time when it's latest."
echo " -t statistics time of some modules."
echo " -v verbosely list files processed."
exit 0
;;
q )
MODE_QUIET=1
MODE_VERBOSE=0
MODE_STAT_TIME=0
;;
s )
FUNC=update_dir_s
LS_OPTION="-1ta"
;;
t )
MODE_STAT_TIME=1
MODE_QUIET=0
;;
v )
MODE_VERBOSE=1
MODE_QUIET=0
;;
\? )
echo "Invalid Option: -$OPTARG" 1>&2
exit 1
;;
: )
echo "Miss Option Argument: -$OPTARG requires an argument" 1>&2
exit 2
;;
esac
done
shift $((OPTIND -1)) # remove options
MAIN_DIR="$*"
[[ -z "$MAIN_DIR" || ! ( -e "$MAIN_DIR" ) ]] && echo -e "Path not found!\nType 'update_dir_time -h' for help." && exit 1
return 0
}
function invalid()
{
local dir="$1"
local file="$2"
if [[ -z "$file" || ! ( -e "$dir/$file" ) ]]
then
if [[ ! -z "$file" && "$file" != "." && "$file" != ".." ]]
then
# collect invalid list
echo "LINENO=$LINENO invalid $dir/$file"
local level_time_dir=`get_time_path "$dir/$file"` || true
local level_time_dir_ind=echo "$level_time_dir" | tr "[:space:]" "$__SPACE_REPLACER__"
INVALID_LIST["$level_time_dir_ind"]="$level_time_dir"
fi
return 1
fi
return 0
}
function get_time_path()
{
local fd="$*"
[[ -e "$fd" ]] && fd=$(realpath -es "$fd")
[[ -d "$fd" ]] && fd="$fd/"
local time_path=`ls -ldQ --indicator-style=none --time-style="+///%Y-%m-%d %H:%M:%S///" "$fd" | awk -F"///" '{print $2,$3}'` || true
local level_time_dir=$(printf "%s %3d: %s\n" Level $LEVEL "$time_path")
echo "$level_time_dir"
}
function print_info()
{
if [ $MODE_VERBOSE == 1 ]
then
local level_time_dir=`get_time_path "$*"`
printf "$level_time_dir\n"
fi
return 0
}
function print_total_files()
{
if [ $MODE_QUIET == 0 ]
then
local dir="$*"
# 递归统计指定目录下的文件数(包括子层)
local total_dirs=`ls -AlR "$dir" | grep "^-" | wc -l`
# 递归统计指定目录下的目录(文件夹)数(包括子层)
local total_files=`ls -AlR "$dir" | grep "^d" | wc -l`
printf "\nDealed Totals : %5d\n DIRs : %5d\n Files : %5d\n" \
$(($total_dirs + $total_files)) $total_dirs $total_files
fi
return 0
}
function print_invalid_list()
{
if [ $MODE_VERBOSE == 1 ]
then
echo -e "\nInvalid paths as follow:"
echo "${INVALID_LIST[@]}"
fi
return 0
}
function start_time()
{
if [ $MODE_STAT_TIME == 1 ]
then
local key=`echo -e "$@"`
key=`echo -e "$key"|tr "[:space:]" "$__SPACE_REPLACER__"`
# echo "start_time key=\"$key\""
# TIME_START_LIST[$key]=$(date +%s)
TIME_START_LIST[$key]=$[$(date +%s%N)/1000000]
fi
return 0
}
function end_time()
{
if [ $MODE_STAT_TIME == 1 ]
then
local key=`echo -e "$@"`
key=`echo -e "$key"|tr "[:space:]" "$__SPACE_REPLACER__"`
# echo " end_time key=\"$key\""
# TIME_END_LIST[$key]=$(date +%s)
TIME_END_LIST[$key]=$[$(date +%s%N)/1000000]
local time_span=$[ ${TIME_END_LIST[$key]} - ${TIME_START_LIST[$key]} ]
TIME_SPAN_LIST[$key]=$(( ${TIME_SPAN_LIST[$key]} + $time_span ))
fi
return 0
}
function print_time_spans()
{
if [ $MODE_STAT_TIME == 1 ]
then
local key=`echo -e "update_dir_main()"|tr "[:space:]" "$__SPACE_REPLACER__"`
printf "\nTime Span %-18s: %6.1f sec\n" "Totals" `awk 'BEGIN{printf "%.2f\n",'${TIME_SPAN_LIST["$key"]}'/'1000.0'}'`
for key in ${!TIME_SPAN_LIST[*]}
do
local org_key=`echo -e "$key" | tr "$__SPACE_REPLACER__" " "`
printf " %-18s: %6.1f sec\n" "$org_key" `awk 'BEGIN{printf "%.2f\n",'${TIME_SPAN_LIST["$key"]}'/'1000.0'}'`
done
fi
return 0
}
# 全局主函数调用
update_dir_main $*
测试图例:
Windows 安装 Cygwin 或 Linux Shell 里输入:
---------------------------------------------------------------------------------------------------------------------------------
老是要手动打开 bash 窗口再输入,麻烦!
三,再写个 bat 调用 sh.
把 *.sh 和 *.bat 拷贝到"发送到"的目录里,选择要处理的目录,鼠标右键 点发送到,选择那个 bat
@REM https://blog.csdn.net/onlyAngel521/article/details/121268315
@REM https://blog.csdn.net/themagickeyjianan/article/details/127487205
@REM 把 "同步目录树修改时间.bat",
@REM "update_dir_time(更新文件夹修改时间) v2.0.sh"
@REM 复制到 %AppData%\Microsoft\Windows\SendTo
@REM sh文件改名为 "update_dir_time_v2.sh"
@REM 鼠标右键点击要处理的目录,发送到(N) ...
@echo off
setlocal EnableDelayedExpansion
cd.
call :yes_or_no "是否更新整个目录树的修改时间?" && (
REM echo 是
) || (
if %errorlevel% EQU 3 (echo "取消选择。不做任何修改。" & goto :EOF)
REM echo 否
goto :EOF
)
if exist "%~1\" (
set "$=%~1"
call set "$=%%$:\=/%%"
call echo DIR="%%$%%/"
call echo CD="%CD%/"
set "@=%AppData%\Microsoft\Windows\SendTo\update_dir_time_v2.sh"
call set "@=%%@:\=/%%"
call echo .SH="%%@%%"
set "OLD_LANG=%LANG%"
set "OLD_LC_ALL=%LC_ALL%"
call set "LANG=zh_CN.GBK"
call set "LC_ALL=zh_CN.GBK"
call "E:/Cygwin64/bin/bash.exe" --login -i -c " ""%%@%%"" -st ""%%$%%"" ;bash"
call set "LANG=%%OLD_LANG%%"
call set "LC_ALL=%%OLD_LC_ALL%%"
) else (
echo 不是目录!
)
echo. & pause
@goto :EOF
@rem Usage:
rem 功能: 判断是否按要求执行命令
:yes_or_no <RefOfPrompt>
@echo OFF
choice /C YNC /T 3 /D Y /M "%~1 (3秒后默认按:Yes)"
if %errorlevel%==1 goto :YES
if %errorlevel%==2 goto :NO
if %errorlevel%==3 goto :CANCEL
@rem 返回后延迟值 !errorlevel!=1
type 2>nul&goto :EOF
:YES
cd.
@rem 返回后延迟值 !errorlevel!=0
goto :EOF
:NO
echo "选择否,不做任何修改。"
@rem 返回后延迟值 !errorlevel!=2
goto :EOF
:CANCEL
echo "取消选择。不做任何修改。"
@rem 返回后延迟值 !errorlevel!=3
REM ver
@goto :EOF
REM call chcp 65001 >nul 2>&1
REM call chcp 936 >nul 2>&1
REM call "E:/Cygwin64/bin/bash.exe" --login -i -c "source ""${HOME}/.charset.bashrc"" && ""%%@%%"" -st ""%%$%%"" ;bash"