【问题标题】:Perform an action in every sub-directory using Bash使用 Bash 在每个子目录中执行操作
【发布时间】:2011-04-29 08:58:54
【问题描述】:

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

最有效的写法是什么?

【问题讨论】:

  • 请考虑返回并重新评估答案的正确性 - 尽管存在重大错误,但您已经得到了一个已接受的答案获得了很多视图(f / e,在某人之前运行过的目录上运行 @ 987654321@ 将导致 foobar 被迭代,即使它们不存在,* 将被替换为 all 文件名列表,甚至是非目录文件名)。
  • ...更糟糕的是,如果有人运行mkdir -p '/tmp/ /etc/passwd /' - 如果有人按照/tmp 上的这种做法运行脚本,例如,找到要删除的目录,他们最终可能会删除@987654327 @.

标签: bash command directory-traversal


【解决方案1】:

避免创建子流程的版本:

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

【讨论】:

  • 您可以避免if[ 使用:for D in */; do
  • +1 因为目录名称不以 ./ 开头,而不是接受的答案
  • 这个是正确的,甚至目录名中的空格+1
  • 最后一个命令有一个问题:如果你在一个没有子目录的目录下;它将返回“*/”。所以最好使用第二个命令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
【解决方案2】:
for D in `find . -type d`
do
    //Do whatever you need with D
done

【讨论】:

  • 这会在空白处中断
  • 另外,请注意,如果您想要递归或非递归行为,则需要调整 find 参数。
  • 上面的答案也给了我自己的目录,所以以下对我来说效果更好:find . -mindepth 1 -type d
  • @JoshC,这两种变体都会将mkdir 'directory name with spaces' 创建的目录分成四个单独的单词。
  • 我需要添加-mindepth 1 -maxdepth 1 否则太深了。
【解决方案3】:

最简单的非递归方式是:

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

最后的/ 告诉我们,只使用目录。

不需要

  • 找到
  • awk
  • ...

【讨论】:

  • 注意:这不包括点目录(这可能是一件好事,但重要的是要知道)。
  • 需要注意的是,您可以在扩展通配符时使用shopt -s dotglob 来包含点文件/点目录。另见:gnu.org/software/bash/manual/html_node/The-Shopt-Builtin.html
  • 我认为您的意思是/* 而不是*// 代表您要使用的路径。
  • @Shule /* 用于绝对路径,而*/ 将包含当前位置的子目录
  • 有用的提示:如果您需要从 $d 中删除尾随的 '/',请使用 ${d%/*}
【解决方案4】:

使用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)。


有关更多示例,请参阅: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 '...' {} \; 少。
【解决方案5】:

方便的单线

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

【讨论】:

    【解决方案6】:

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

    【讨论】:

    • 我可以将该 find 的返回值输入到 for 循环中吗?这是一个更大的脚本的一部分...
    • @Mike,不太可能。美元?可能会为您提供 find 或 xargs 命令的状态,而不是 my_command
    【解决方案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
    • @GordonDavisson, ...事实上,我什至认为 -d '' 对 bash 语法和功能的误导性较小,因为 -d $'\000' 暗示(错误地)$'\000' 在某种程度上与'' 不同——事实上,人们可以很容易地(并且再次错误地)从中推断出 bash 支持 Pascal 样式的字符串(指定长度,能够包含 NUL 文字)而不是 C 字符串(NUL 分隔,无法包含NUL)。
    【解决方案8】:

    你可以试试:

    #!/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"。当find 的输出是字符串拆分时,它不起作用,因此您将在$f 中将名称的单独部分作为单独的值,从而决定您是否引用$f' s 扩张没有意义。
    • 我没有说他们正在试图从文件中读取行。 find 的输出是面向行的(理论上一个名称到一行,但见下文),默认为 -print 操作。
    • find -print 开始的面向行的输出不是传递任意文件名的安全 方式,因为可以运行mkdir -p $'foo\n/etc/passwd\nbar' 并获得具有@ 的目录987654334@ 在其名称中作为单独的一行。不小心处理来自/upload/tmp 目录中文件的名称是获得权限提升攻击的好方法。
    【解决方案9】:

    如果目录名称中有空格,则接受的答案将中断,并且首选语法是 $() 用于 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 
    

    【讨论】:

    • 什么参数将保存目录名称?例如,chmod +x $DIR_NAME(是的,我知道只有目录有一个 chmod 选项)
    • find -exec 和传递给xargs 之间有一个细微差别:find 将忽略正在执行的命令的退出值,而xargs 将在非零退出时失败。两者都可能是正确的,这取决于您的需要。
    • find ... -print0 | while IFS= read -r d 更安全——支持以空格开头或结尾的名称,以及包含换行符的名称。
    猜你喜欢
    • 2010-11-21
    • 2019-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-15
    • 1970-01-01
    • 1970-01-01
    • 2018-10-11
    相关资源
    最近更新 更多