如何转义触摸命令的文件夹名称?

如何转义触摸命令的文件夹名称?

我创建了两个脚本。首先读取所有文件夹的 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"

这适用于类似 的文件夹Folder1Folder2但不适用于名为 的文件夹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 语法。相反,这些字符按字面意思处理。因此,如果$line1650819409 Foo\ \&\ Bar,它将被分词为1650819409, Foo\, \&\, 和Bar。这与以下问题类似我们如何运行存储在变量中的命令?(您可以使用evalshell 来解析扩展值,但这有其自身的一系列问题,尤其是对数据的不可信修改。)


如果您愿意假设文件名不能包含换行符,则可以直接存储它们。这将创建类似 的行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"

相关内容