我正在编写一个(单行)脚本,它应该通过子目录递归。查找包含超链接的 .txt 文件。使用 wget 获取内容并将其下载到文本文件所在的同一目录中。
假设找到的所有文本文件仅包含有效的超链接。
要测试这一点:
创建一个子目录./s1
创建一个文本文件./s1/s1.txt
内容./s1/s1.txt
: www.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
)。
使用 GNUfind
和xargs
,您可以并行运行一些:
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
实例,最多在并行中。4
wget
10
P
在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 是用这些参数调用的:
find
.
-type
f
-name
*.txt
-exec
bash
-cx
wget -i "{}" -P .
;
并将find
运行bash
这些参数:
bash
-cx
wget -i "./path/to/the/file.txt" -P .
对于每个找到的文件,bash 将依次运行wget
:
wget
-i
./path/to/the/file.txt
-P
.
但只有当文件路径不包含\
、 、"
,`
也不"
包含如果包含则可能造成灾难性后果的字符(例如,如果有一个名为 的文件$(rm -rf ~).txt
)。
使用单引号而不是双引号时:
find . -type f -name "*.txt" -exec bash -cx 'wget -i "{}" -P "$(dirname "{}")"' \;
本来可以修复它,但由于上述原因,它仍然是非常错误的。{}
应该绝不嵌入到作为代码计算的参数中。看@吉尔斯的回答如何正确地做到这一点。
1 -execdir
AFAIK 来自 OpenBSD,1996 年添加到 FreeBSD,1997 年添加到 FreeBSD,2002 年添加到 NetBSD,find
2005 年添加到 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