使用Bash在每个子目录中执行操作


193

我正在处理一个脚本,该脚本需要在特定文件夹的每个子目录中执行操作。

最有效的写法是什么?


1
请考虑通过回来,重新评估答案的正确性-你有一个公认的答案得到了很多的意见,尽管主要的bug(F / E,它运行在一个目录,有人以前运行mkdir 'foo * bar'会造成foobar即使要遍历它们不存在,并且*将被所有文件名(甚至非目录文件名)的列表替换)。
查尔斯·达菲

1
...甚至更糟的是,如果有人运行了mkdir -p '/tmp/ /etc/passwd /'-如果有人按照这种做法运行脚本/tmp,例如找到要删除的目录,他们最终可能会删除/etc/passwd
查尔斯·达菲

Answers:


177
for D in `find . -type d`
do
    //Do whatever you need with D
done

81
这将在空白处打破
ghostdog74

2
另外,请注意,find如果要递归或非递归行为,则需要调整参数。
克里斯·汤金森

32
上面的答案也给了我自己的目录,所以以下内容对我来说更好一些: find . -mindepth 1 -type d
jzheaux

1
@JoshC,这两种变体都会将创建的目录mkdir 'directory name with spaces'分为四个单独的词。
查尔斯·达菲

4
我需要添加-mindepth 1 -maxdepth 1或者它太深了。
ScrappyDev

281

避免创建子流程的版本:

for D in *; do
    if [ -d "${D}" ]; then
        echo "${D}"   # your processing here
    fi
done

或者,如果您的操作是单个命令,则此方法更为简洁:

for D in *; do [ -d "${D}" ] && my_command; done

或更简洁的版本(感谢@enzotib)。请注意,在此版本中,的每个值都D将带有斜杠:

for D in */; do my_command; done

48
您可避免if[用:for D in */; do
enzotib

2
+1是因为目录名不是以./开头,而不是接受的答案
HayriUğurKoltuk 2014年

3
即使目录名称+1中的空格也是如此,这是正确的
Alex Reinking 2014年

5
最后一个命令有一个问题:如果您所在的目录中没有子目录;它将返回“ * /”。因此,最好使用第二个命令for D in *; do [ -d "${D}" ] && my_command; done或两个最新命令的组合:for D in */; do [ -d $D ] && my_command; done
Chris Maes 2014年

5
请注意,此答案将忽略隐藏目录。要包含隐藏目录,请for D in .* *; do改用for D in *; do
patryk.beza

105

最简单的非递归方法是:

for d in */; do
    echo "$d"
done

/在年底告诉,只使用目录。

不需要

  • awk
  • ...

11
注意:这将不包括点目录(这可能是一件好事,但重要的是要知道)。
wisbucky

有用的提示是,shopt -s dotglob在扩展通配符时可以用来包含dotfiles / dotdirs。参见:gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
斯蒂恩舒特

我觉得你的意思/*,而不是*//代表您要使用的路径。
Brōtsyorfuzthrāx

2
@Shule /*用于绝对路径,*/但包括当前位置的子目录
Dan G

3
有用的提示:如果您需要从$ d中${d%/*}
删除

18

使用find命令。

在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


有关更多示例,请参见:如何转到每个目录并执行命令?在SO


1
-exec {} +是POSIX指定的,-exec sh -c 'owd=$PWD; for arg; do cd -- "$arg" && pwd -P; cd -- "$owd"; done' _ {} +是另一个合法选项,并且调用的shell少于-exec sh -c '...' {} \;
查尔斯·达菲

13

方便的单线

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

10

find . -type d -print0 | xargs -0 -n 1 my_command


我可以将该发现的返回值输入for循环吗?这是一个较大的脚本的一部分...
mikewilliamson 2010年

@迈克,不太可能。$?可能会获得find或xargs命令的状态,而不是my_command
Paul Tomblin

7

这将创建一个子shell(这意味着当while循环退出时变量值将丢失):

find . -type d | while read -r dir
do
    something
done

这不会:

while read -r dir
do
    something
done < <(find . -type d)

如果目录名称中有空格,则任何一种都将起作用。


为了更好地处理奇怪的文件名(包括以空格结尾和/或包含换行符的名称),请使用find ... -print0while IFS="" read -r -d $'\000' dir
Gordon Davisson,2010年

@GordonDavisson,...的确,我什至认为这-d ''对bash语法和功能的误导性较小,因为-d $'\000'隐含(错误地)$'\000'有所不同''-实际上,一个人可以轻易地(再一次错误地)推断出bash支持Pascal样式的字符串(指定长度,能够包含NUL文字),而不是C字符串(NUL分隔,不能包含NUL)。
查尔斯·达菲

5

您可以尝试:

#!/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(不包括当前文件夹.)的目录


这很麻烦-尝试使用带空格的目录名称。请参阅BashPitfalls#1DontReadLinesWithFor
查尔斯·达菲

带空格的目录名称用引号引起来,因此可以使用,并且OP不会尝试从文件中读取行。
亨利·多布森

它适用于cd "$f"。当from的输出find是字符串拆分时,它不起作用,因此您将名称的不同部分作为中的单独值$f,从而可以更好地理解或不引用$f的扩展模拟。
查尔斯·达菲

我没有说他们正在尝试从文件中读取行。find的输出是面向行的(从理论上讲,它是行的名称,但请参见下文),且具有默认-print操作。
查尔斯·达菲

面向行的输出,例如from find -print,不是传递任意文件名的安全方法,因为一个人可以运行某些文件mkdir -p $'foo\n/etc/passwd\nbar'并获得/etc/passwd名称中带有单独一行的目录。毫不费力地处理文件/upload/tmp目录中文件的名称是获得特权升级攻击的好方法。
查尔斯·达菲

4

如果目录名称包含空格,则可接受的答案将在空格上中断,并且首选语法是$()bash / ksh。使用GNU find -exec 选项,+;例如

find .... -exec mycommand +; #this is same as passing to xargs

或使用while循环

find .... |  while read -r D
do
  ...
done 

什么参数将保存目录名称?例如,chmod + x $ DIR_NAME(是的,我知道只有目录才有chmod选项)
Mike Graf

find -exec和传递给之间有一个微妙的区别xargsfind将忽略正在执行的命令的退出值,而xargs在非零退出时失败。可能是正确的,具体取决于您的需求。
杰森·沃迪卡

find ... -print0 | while IFS= read -r d更安全-支持以空格开头或结尾的名称,以及包含换行文字的名称。
查尔斯·达菲
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.