我正在编写一个需要在特定文件夹的每个子目录中执行操作的脚本。
写那个最有效的方法是什么?
mkdir 'foo * bar'
的目录上运行它会导致 foo
和bar
即使它们不存在也会被迭代,并且 *
将被替换为 all 文件名列表,甚至是非目录文件名)。
mkdir -p '/tmp/ /etc/passwd /'
- 如果有人在 /tmp
上按照这种做法运行脚本以查找要删除的目录,他们最终可能会删除 /etc/passwd
。
避免创建子流程的版本:
for D in *; do
if [ -d "${D}" ]; then
echo "${D}" # your processing here
fi
done
或者,如果您的操作是单个命令,则更简洁:
for D in *; do [ -d "${D}" ] && my_command; done
或者更简洁的版本 (thanks @enzotib)。请注意,在此版本中,D
的每个值都将有一个尾部斜杠:
for D in */; do my_command; done
for D in `find . -type d`
do
//Do whatever you need with D
done
find
参数。
find . -mindepth 1 -type d
mkdir 'directory name with spaces'
创建的目录分成四个单独的单词。
-mindepth 1 -maxdepth 1
否则它太深了。
最简单的非递归方式是:
for d in */; do
echo "$d"
done
最后的 /
告诉我们,只使用目录。
没有必要
寻找
awk
...
shopt -s dotglob
来包含点文件/点目录。另请参阅:gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
/*
而不是 */
,其中 /
代表您要使用的路径。
/*
用于绝对路径,而 */
将包含当前位置的子目录
${d%/*}
使用查找命令。
在 GNU find
中,您可以使用 -execdir
参数:
find . -type d -execdir realpath "{}" ';'
或使用 -exec
参数:
find . -type d -exec sh -c 'cd -P "$0" && pwd -P' {} \;
或使用 xargs
命令:
find . -type d -print0 | xargs -0 -L1 sh -c 'cd "$0" && pwd && echo Do stuff'
或使用 for 循环:
for d in */; { echo "$d"; }
对于递归性,请尝试扩展通配符 (**/
)(通过:shopt -s extglob
启用)。
有关更多示例,请参阅:How to go to each directory and execute a command? 在 SO
-exec {} +
是 POSIX 指定的,-exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +
是另一个合法选项,并且调用的 shell 少于 -exec sh -c '...' {} \;
。
方便的单线
for D in *; do echo "$D"; done
for D in *; do find "$D" -type d; done ### Option A
find * -type d ### Option B
选项 A 对于中间有空格的文件夹是正确的。此外,通常更快,因为它不会将文件夹名称中的每个单词作为单独的实体打印。
# Option A
$ time for D in ./big_dir/*; do find "$D" -type d > /dev/null; done
real 0m0.327s
user 0m0.084s
sys 0m0.236s
# Option B
$ time for D in `find ./big_dir/* -type d`; do echo "$D" > /dev/null; done
real 0m0.787s
user 0m0.484s
sys 0m0.308s
find . -type d -print0 | xargs -0 -n 1 my_command
my_command
。
这将创建一个子 shell(这意味着当 while
循环退出时变量值将丢失):
find . -type d | while read -r dir
do
something
done
这不会:
while read -r dir
do
something
done < <(find . -type d)
如果目录名称中有空格,则任何一个都可以使用。
find ... -print0
和 while IFS="" read -r -d $'\000' dir
-d ''
对 bash 语法和功能的误导性较小,因为 -d $'\000'
暗示(错误地)$'\000'
在某种程度上不同于 ''
- 确实,人们可以很容易地(并且再次错误地)从中推断出 bash 支持 Pascal 样式的字符串(指定长度,能够包含 NUL 文字)而不是 C 字符串(NUL 分隔,无法包含 NUL)。
你可以试试:
#!/bin/bash
### $1 == the first args to this script
### usage: script.sh /path/to/dir/
for f in `find . -maxdepth 1 -mindepth 1 -type d`; do
cd "$f"
<your job here>
done
或类似的...
解释:
find . -maxdepth 1 -mindepth 1 -type d
:只查找最大递归深度为 1(仅 $1 的子目录)和最小深度为 1(不包括当前文件夹 .
)的目录
cd "$f"
。当 find
的输出是字符串拆分时,它不起作用,因此您将在 $f
中将名称的单独部分作为单独的值,从而确定您是否引用 $f
' s 扩张辩论。
find
的输出是面向行的(理论上一个名称对应一行,但请参见下文),默认操作为 -print
。
find -print
开始的面向行的输出不是传递任意文件名的安全方式,因为可以运行 mkdir -p $'foo\n/etc/passwd\nbar'
并获得一个目录,其中包含 /etc/passwd
作为单独的行姓名。不小心处理 /upload
或 /tmp
目录中文件的名称是获得权限提升攻击的好方法。
如果目录名称中有空格,则接受的答案将在空格处中断,并且 bash/ksh 的首选语法是 $()
。将 GNU find
-exec
选项与 +;
一起使用,例如
find .... -exec mycommand +;
#this is same as passing to xargs
或使用 while 循环
find .... | while read -r D
do
# use variable `D` or whatever variable name you defined instead here
done
find -exec
和传递给 xargs
之间有一个细微的区别:find
将忽略正在执行的命令的退出值,而 xargs
将在非零退出时失败。两者都可能是正确的,这取决于您的需要。
find ... -print0 | while IFS= read -r d
更安全 - 支持以空格开头或结尾的名称,以及包含换行文字的名称。
不定期副业成功案例分享
if
或[
:for D in */; do
for D in *; do [ -d "${D}" ] && my_command; done
或两个最新的组合:for D in */; do [ -d $D ] && my_command; done
for D in .* *; do
而不是for D in *; do
。