使用 find 和 -exec 查找文件并创建指向父文件的符号链接

使用 find 和 -exec 查找文件并创建指向父文件的符号链接

我正在尝试使用find查找与特定模式匹配的文件,然后将其父目录符号链接到另一个目录,这是我当前的脚本(我在 mac 上执行此操作,因此 -printf 在这里不起作用):

#!/usr/bin/env bash

PROCESS_DIR="/Users/me/Google Drive/Me"
OUTPUT_DIR="/Users/me/OfflineFolder"

# Copy directory structure and then make symlinks
rm -R "$OUTPUT_DIR";
mkdir "$OUTPUT_DIR";
cd "$PROCESS_DIR";

find . -name *.md -exec ln -vs $(dirname {}) "$OUTPUT_DIR" \;

但它似乎不起作用。然而,这个脚本确实有效(使符号链接到我的计算机上的所有 md 文件:

# Copy directory structure and then make symlinks
rm -R "$OUTPUT_DIR";
mkdir "$OUTPUT_DIR";
cd "$PROCESS_DIR";
find . -type d -exec mkdir -vp "$OUTPUT_DIR/{}" \;
find . -name *.md -exec ln -vs "$(pwd)/{}" "$OUTPUT_DIR/{}" \;
find "$OUTPUT_DIR" -type d -empty -delete

知道为什么这不起作用吗?我尝试了几种不同的方法(包括使用find $PROCESS_DIRECTORY而不是cd $PROCESS_DIRECTORY)。谢谢

答案1

你有两个问题,都与事情发生的顺序有关。

find . -name *.md -exec ln -vs $(dirname {}) "$OUTPUT_DIR" \;是一个单一的命令。 shell 在执行之前对其进行解析。解析步骤中:

  • $(…)是命令替换,因此dirname {}被执行,产生.( 中没有目录部分{})。
  • $OUTPUT_DIR是一个变量替换,所以它被它的值替换。
  • *.md是一个全局模式,因此它被匹配文件列表替换。如果没有匹配项,则模式保持不变。

这就完成了确定要执行什么命令所需的工作。

  • 如果当前目录中没有匹配的文件*.md,则使用给定参数执行以下命令:find, ., -name, *.md, -exec, ln, -vs, ., /Users/me/OfflineFolder, ;
  • 如果*.md匹配bar.mdfoo.md则命令为find, ., -name, foo.md, -exec, … (并且find将错过something-other-than-foo.md子目录中调用的任何文件)。
  • 如果*.md匹配bar.mdfoo.md则命令为find, ., -name, bar.md, foo.md, -exec, … (并且find会抱怨语法错误)。

您想要执行dirname查找结果。这意味着您需要指示find运行dirname。您可以这样做,但 find 没有任何机制来收集输出dirname并将其作为参数传递给ln.为此,您需要一个工具,例如 shell,它是命令替换。

因此,策略如下:告诉 find 调用 shell,并告诉该 shell 运行涉及ln和 的命令dirname。您需要注意引用。将 shell 命令放在单引号中以避免其特殊字符被外壳解释。还要将模式放在-name引号中,以便它传递到外壳find而不是由外壳扩展。

find . -name '*.md' -exec sh -c 'ln -vs …' \;

下一步是完成.不要{}在 shell 命令内部使用:这只会将文件名作为 shell 代码片段,任何特殊字符都将由内 shell 进行解析。相反,将由 给出的文件名find作为参数传递给 shell 脚本。之后的第一个参数是 shell 实例的名称 ( ),但您可以将其用于任何您喜欢的目的;后续参数是位置参数(, , ...)。sh -c CODE$0$1$2

find . -name '*.md' -exec sh -c 'ln -vs "$(dirname "$0")" "$1"' {} "$OUTPUT_DIR" \;

我已将$OUTPUT_DIR其作为参数传递给脚本。这里并不重要,因为该值不包含任何 shell 特殊字符,但这是一个好习惯,你永远不知道什么时候有人可能会更改路径,例如包含空格。另一种可能性是将其通过环境传递:

export OUTPUT_DIR
find . -name '*.md' -exec sh -c 'ln -vs "$(dirname "$0")" "$OUTPUT_DIR"' {} \;

dirname您可以使用文本替换来代替 :删除最后一个斜杠之后的所有内容。您不必担心没有目录部分的特殊情况,因为通过 传递的文件名不会发生这种情况find

export OUTPUT_DIR
find . -name '*.md' -exec sh -c 'ln -vs "${0%/*}" "$OUTPUT_DIR"' {} \;

您可以使用 的+形式-exec来加快速度。我传递_as ,后续参数是循环迭代$0的文件名。for

export OUTPUT_DIR
find . -name '*.md' -exec sh -c 'for x; do ln -vs "${x%/*}" "$OUTPUT_DIR"; done' _ {} +

相关内容