在子 shell 中使用 dirname 时出现问题

在子 shell 中使用 dirname 时出现问题

我正在编写一个(单行)脚本,它应该通过子目录递归。查找包含超链接的 .txt 文件。使用 wget 获取内容并将其下载到文本文件所在的同一目录中。

假设找到的所有文本文件仅包含有效的超链接。

要测试这一点:
创建一个子目录./s1
创建一个文本文件./s1/s1.txt
内容./s1/s1.txtwww.google.com

这是一行:

find . -type f -name "*.txt" -exec bash -cx "wget -i \"{}\" -P  $(dirname \"{}\") " \;

问题是$(dirname \"{}\")无法正确扩展。正在执行的 bash 命令是:

+ wget -i ./s1/s1.txt -P .

所以$(dirname \"{}\")返回的.
效果就是一个新的目录 ./s1/s1.txt被建造。所以下载的文件存储为./s1/s1.txt/index.html

当我替换$(dirname \"{}\")$(echo \"{}\")输出时变为:

+ wget -i ./s1/s1.txt -P ./s1/s1.txt

所以参数传递本身是正确的。所以我假设结果dirname没有正确返回到调用 bash shell。或者dirname根本不评价。

当我只执行 bash 命令时

bash -cx "wget -i ./s1/s1.txt -P  $(dirname ./s1/s1.txt)" 

(因此在find命令之外)命令按预期执行:

+ wget -i ./s1/s1.txt -P ./s1

使这一行有效的正确方法是什么?

答案1

在这里你可以这样做:

find . -name '*.txt' -type f -execdir wget -i {} -P . ';'

使用非标准但非常常见的-execdir谓词 offind而不是-exec从找到的文件的目录中运行命令(并{}扩展为文件名而不是完整路径,可能前面加上./一些find实现,包括 GNU find)。

使用 GNUfindxargs,您可以并行运行一些:

xargs -r0 -n4 -P10 -a <(
  find . -name '*.txt' -type f -printf '-i\0%p\0-P\0%h\0'
  ) wget

我们find构建参数列表wget并以 NUL 分隔输出它们(0 是唯一不能出现在文件路径的外部命令行参数中的字节值),一次运行xargs实例,最多在并行中。4wget10P

zsh

for file (**/*.txt(N.)) wget -i $file -P $file:h

(添加D 全局限定符如果您还想像find方法中那样处理隐藏文件)。


在你的

find . -type f -name "*.txt" -exec bash -cx "wget -i \"{}\" -P  $(dirname \"{}\") " \;

位于双引号内,因此在将结果传递给 之前,您输入该命令的 shell$(...)会将其扩展为 的输出。dirname \"{}\"find

dirname \"{}\",在 sh/bash 中与输出dirname '"{}"'相同(当前工作目录的路径)。dirname anything-that-does-not-contain-a-slash-and-does-not-start-with-dash.

所以 find 是用这些参数调用的:

  1. find
  2. .
  3. -type
  4. f
  5. -name
  6. *.txt
  7. -exec
  8. bash
  9. -cx
  10. wget -i "{}" -P .
  11. ;

并将find运行bash这些参数:

  1. bash
  2. -cx
  3. wget -i "./path/to/the/file.txt" -P .

对于每个找到的文件,bash 将依次运行wget

  1. wget
  2. -i
  3. ./path/to/the/file.txt
  4. -P
  5. .

但只有当文件路径不包含\、 、"`也不"包含如果包含则可能造成灾难性后果的字符(例如,如果有一个名为 的文件$(rm -rf ~).txt)。

使用单引号而不是双引号时:

find . -type f -name "*.txt" -exec bash -cx 'wget -i "{}" -P  "$(dirname "{}")"' \;

本来可以修复它,但由于上述原因,它仍然是非常错误的。{}应该绝不嵌入到作为代码计算的参数中。看@吉尔斯的回答如何正确地做到这一点。


1 -execdirAFAIK 来自 OpenBSD,1996 年添加到 FreeBSD,1997 年添加到 FreeBSD,2002 年添加到 NetBSD,find2005 年添加到 GNU,2010 年添加到 sfind,至少 2014 年添加到 toybox。

答案2

正如评论中所说,不要尝试在 bash 部分使用find占位符。{}这是不可靠的并且有可能安全问题(shell注入)

更好地使用这种方式:

 find . -type f -name '*.txt' -exec sh -c '
     for file; do
         wget -i "$file" -P "$(dirname "$file")"
     done
 ' sh {} +

或使用标准参数扩展(除了效率更高之外,如果目录名称以换行符结尾,它的优点仍然可以工作):

 find . -type f -name '*.txt' -exec sh -c '
     for file; do
         wget -i "$file" -P "${file%/*}"
     done
 ' sh {} +

$ tree
.
└── s1
    ├── index.html
    └── s1.txt

1 directory, 2 files

相关内容