我创建了两个脚本。首先读取所有文件夹的 mtime 时间戳并将其逐行写入 time_old.txt:
for d in */ ; do
time_old=$(stat --format %Y "$d")
escaped=$(printf %q "$d")
echo "$time_old $escaped" >> time_old.txt
done
第二个读回这些行并touch
使用文件夹上的旧时间戳运行:
input="time_old.txt"
while IFS= read -r line
do
echo "$line"
touch_output=$(touch -m -d @$line)
echo "$touch_output"
done < "$input"
这适用于类似 的文件夹Folder1
,Folder2
但不适用于名为 的文件夹Foo & Bar
。
我的错误在哪里?
答案1
相反,请执行以下操作(假设使用 GNU 工具):
find . ! -name . -prune -type d -printf '@%T@\0%p\0' > time_old.list
并恢复:
xargs -t -a time_old.list -r0 -n2 touch -md
如果您使用printf %q
,则需要将结果解释为 shell 代码。那可能是:
find . ! -name . -prune -type d -printf 'touch -md @%T@ ' \
-exec printf '%q\n' {} \; > time_old.bash
和:
bash -v ./time_old.bash
恢复。
但是您需要一个printf
支持该非标准的独立实用程序%q
,并且它以与 shell 语言兼容的格式引用文件路径。如果不能保证这一点,那可能会变得危险。当您为每个目录分叉和执行一个进程时(GNUstat
方法也是如此),这也会降低效率。如果可以避免的话,我不会让 shell 解释从外部输入生成的代码。
另请注意,它touch
不会在标准输出上写入任何内容,因此output=$(touch...)
毫无意义。
在这里,您还可以使用zsh
它具有安全引用运算符(使用单引号,无论文件名包含什么字符或非字符文件名,它都应该是安全的,并且与任何类似 sh 的 shell(yash 除外)兼容)并且具有内置stat
(它实际上早于 GNU stat
)所以你不必依赖 GNUisms。
zmodload zsh/stat
{
for d (*(ND/))
stat -A t -F @%s.%9. +mtime -- "$d" &&
print -r touch -d $t -- ${(qq)d}
} > time_old.sh
并用 恢复sh -v ./time_old.sh
。
答案2
escaped=$(printf %q "$d") touch_output=$(touch -m -d @$line)
我的错误在哪里?
您已安排在文件中引用文件名,但问题是引号仅在直接位于 shell 命令行上时才有效,扩展的结果是不是解析引号、反斜杠转义符或其他 shell 语法。相反,这些字符按字面意思处理。因此,如果$line
是1650819409 Foo\ \&\ Bar
,它将被分词为1650819409
, Foo\
, \&\
, 和Bar
。这与以下问题类似我们如何运行存储在变量中的命令?(您可以使用eval
shell 来解析扩展值,但这有其自身的一系列问题,尤其是对数据的不可信修改。)
如果您愿意假设文件名不能包含换行符,则可以直接存储它们。这将创建类似 的行1650819409 Foo & Bar
,其中第一个空格之后的所有内容都是文件名。
> time_old.txt
for d in */ ; do
case "$d" in
*$'\n'*) printf >&2 "newlines in filenames not supported, skipping %q\n" "$d"; continue;;
esac
time_old=$(stat --format %Y -- "$d") &&
printf '%s\n' "$time_old $d"
done > time_old.txt
input="time_old.txt"
while IFS= read -r line
do
time=${line%% *} # remove from first newline to end
file=${line#* } # remove up to and incl. the first newline
printf >&2 "Changing time on '%s' to '%s'\n" "$file" "$time"
touch -m -d "@$time" -- "$file"
done < "$input"