tim*_*c22 5 find shell-script quoting command-substitution
我正在尝试使用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" \;
Run Code Online (Sandbox Code Playgroud)
但它似乎不起作用。但是,此脚本确实有效(为我计算机上的所有 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
Run Code Online (Sandbox Code Playgroud)
知道为什么这不起作用吗?我尝试了几种不同的方法(包括使用find $PROCESS_DIRECTORY
而不是cd $PROCESS_DIRECTORY
)。谢谢
你有两个问题,都与事情发生的顺序有关。
\n\nfind . -name *.md -exec ln -vs $(dirname {}) "$OUTPUT_DIR" \\;
是一个单一的命令。shell 在执行之前对其进行解析。解析步骤中:
$(\xe2\x80\xa6)
是一个命令替换,因此dirname {}
被执行,产生.
(没有目录部分{}
)。$OUTPUT_DIR
是一个变量替换,因此它被它的值替换。*.md
是一个全局模式,因此它被匹配文件列表替换。如果没有匹配项,则模式保持不变。这就完成了确定要执行什么命令所需的工作。
\n\n*.md
则使用给定参数执行以下命令:find
, .
, -name
, *.md
, -exec
, , ln
, -vs
, .
, /Users/me/OfflineFolder
。;
*.md
匹配bar.md
,foo.md
则命令为find
, .
, -name
, foo.md
, -exec
, \xe2\x80\xa6 (并且find
将错过something-other-than-foo.md
子目录中调用的任何文件)。*.md
匹配bar.md
,foo.md
则命令为find
, .
, -name
, bar.md
, foo.md
, -exec
, \xe2\x80\xa6 (并且find
会抱怨语法错误)。您想要执行dirname
查找结果。这意味着您需要指示find
运行dirname
。您可以这样做,但 find 没有任何机制来收集输出dirname
并将其作为参数传递给ln
. 为此,您需要一个工具,例如 shell,它是命令替换。
因此,策略如下:告诉 find 调用 shell,并告诉该 shell 运行涉及ln
和 的命令dirname
。您需要注意引用。将 shell 命令放在单引号中以避免其特殊字符被外壳解释。还将模式放在-name
引号中,以便它传递到外壳find
而不是由外壳扩展。
find . -name \'*.md\' -exec sh -c \'ln -vs \xe2\x80\xa6\' \\;\n
Run Code Online (Sandbox Code Playgroud)\n\n下一步是完成\xe2\x80\xa6
. 不要{}
在 shell 命令内部使用:这只会将文件名作为 shell 代码片段,任何特殊字符都将由内 shell 进行解析。相反,将由 给出的文件名find
作为参数传递给 shell 脚本。之后的第一个参数是 shell 实例的名称 ( ),但您可以将其用于任何您喜欢的目的;后续参数是位置参数 ( ,sh -c CODE
$0
$1
$2
, \xe2\x80\xa6)。
find . -name \'*.md\' -exec sh -c \'ln -vs "$(dirname "$0")" "$1"\' {} "$OUTPUT_DIR" \\;\n
Run Code Online (Sandbox Code Playgroud)\n\n我已$OUTPUT_DIR
作为参数传递给脚本。这里并不重要,因为该值不包含任何 shell 特殊字符,但这是一个好习惯,你永远不知道什么时候有人可能会更改路径,例如包含空格。另一种可能性是将其通过环境传递:
export OUTPUT_DIR\nfind . -name \'*.md\' -exec sh -c \'ln -vs "$(dirname "$0")" "$OUTPUT_DIR"\' {} \\;\n
Run Code Online (Sandbox Code Playgroud)\n\ndirname
您可以使用文本替换来代替:删除最后一个斜杠之后的所有内容。您不必担心没有目录部分的特殊情况,因为通过 传递的文件名不会发生这种情况find
。
export OUTPUT_DIR\nfind . -name \'*.md\' -exec sh -c \'ln -vs "${0%/*}" "$OUTPUT_DIR"\' {} \\;\n
Run Code Online (Sandbox Code Playgroud)\n\n您可以使用+
的形式-exec
来加快速度。我传递_
as ,后续参数是循环迭代的$0
文件名。for
export OUTPUT_DIR\nfind . -name \'*.md\' -exec sh -c \'for x; do ln -vs "${x%/*}" "$OUTPUT_DIR"; done\' _ {} +\n
Run Code Online (Sandbox Code Playgroud)\n