【问题标题】:How to find/list the directories where a particular sub-directory is not present如何查找/列出不存在特定子目录的目录
【发布时间】:2016-02-18 16:30:58
【问题描述】:

我正在编写一个 shell 脚本,它检查 bin 目录是否存在于 /home 目录下的所有用户目录下。 bin目录可以直接存在于用户目录下,也可以存在于用户目录的子目录下。
我的意思是说我在/home 下有一个用户作为amit。所以 bin 目录可以直接作为 /amit/bin 出现,也可以作为 /amit/jash/bin 出现

现在我的要求是我应该有一个用户目录列表,其中 bin 目录不直接存在于用户目录下或用户目录的子目录下。我尝试了以下命令: 找到 /home -type d ! -exec 测试 -e '{}/bin' \; -打印 但它不工作。但是,当我用某个文件替换 bin 目录时,该命令运行正常。看起来这个命令特别适用于文件。目录有类似的命令吗?对此的任何帮助将不胜感激。

【问题讨论】:

    标签: linux bash


    【解决方案1】:

    你在正确的轨道上。问题是,您对“此目标中是否不存在以下目录”的测试无法在 find 的条件中以仅返回顶级目录的方式表示。所以你需要以一种或另一种方式嵌套。

    一种策略是在 bash 中使用 for 循环:

    $ mkdir foo bar baz one two
    $ mkdir bar/bin baz/bin
    $ for d in /home/*/; do find "$d" -type d -name bin | grep -q . || echo "$d"; done
    foo/
    one/
    two/
    

    这使用路径名扩展(globbing)来生成要测试的目录列表,然后检查“bin”是否存在。如果该检查失败(即 find 没有输出),则打印该目录。请注意/home/*/ 上的尾部斜杠,它确保您只会在目录中搜索,而不是在/home/ 中可能意外存在的文件。


    如果您不想依赖 bash,另一种可能是使用嵌套的 finds:

    $ find /home/ -type d -depth 1 -not -exec sh -c "find {}/ -type d -name bin -print | grep -q . " \; -print
    /home/foo
    /home/one
    /home/two
    

    这大致复制了上面 bash for 循环的效果,但是通过将 find 嵌套在 find -exec 中。它使用grep -q .find 的输出转换为可用作外部 find 的条件的退出状态。

    请注意,由于您正在寻找 bin 目录,我们希望使用 test -d 而不是 test -e(它也会检查 bin 文件 ,这对你来说可能无关紧要。)


    另一种选择是使用 bash 进程重定向。多行以便于阅读:

    cd /home/
    comm -3 \
      <(printf '%s\n' */ | sed 's|/.*||' | sort) \
      <(find */ -type d -name bin | cut -d/ -f1 | uniq)
    

    不幸的是,这需要您在运行前更改到/home 目录,因为它会剥离子目录。如果您愿意,当然可以将其折叠成一条大而长的单线。

    这个comm 解决方案还存在在名称中包含特殊字符(如换行符)的目录上失败的风险。


    最后一个选项是 bash-only,但不仅仅是单线。它涉及从完整列表中减去包含“bin”的目录。它使用关联数组和globstar,因此它依赖于 bash 版本 4。

    #!/usr/bin/env bash
    
    shopt -s globstar
    
    # Go to our root
    cd /home
    
    # Declare an associative array
    declare -A dirs=()
    
    # Populate the array with our "full" list of home directories
    for d in */; do dirs[${d%/}]=""; done
    
    # Remove directories that contain a "bin" somewhere inside 'em
    for d in **/bin; do unset dirs[${d%%/*}]; done
    
    # Print the result in reproducible form
    declare -p dirs
    
    # Or print the result just as a list of words.
    printf '%s\n' "${!dirs[@]}"
    

    请注意,我们将目录存储在数组 index 中,这 (1) 使我们可以轻松查找和删除项目,以及 (2) 确保唯一的条目,即使一个用户拥有其主目录下有多个“bin”目录。

    【讨论】:

    • 对迟到的回复表示歉意,并非常感谢您提供多种解决方案。我选择了第二种解决方案,它奏效了。但是我最初收到错误:find: warning: you have specified the -depth option after a non-option argument -type, but options are not positional (-depth 影响在它之前指定的测试以及在它之后指定的测试)。请在其他参数之前指定选项但是在交换 -type d 和 -depth 1 的位置并使用 maxdepth 修改深度之后它起作用了。
    • 有趣。这不是我在 OSX 或 FreeBSD 中运行命令时遇到的错误。奇怪的是,关于选项不是位置的警告是通过更改选项的位置来解决的! :-) 实际上,事实证明-depth 是唯一的问题,切换到-maxdepth 会使订单变得不重要。结果在 FreeBSD(我测试过的地方)中,-depth n 等同于 -maxdepth n,但 Linux 没有这个功能。感谢您提及它,我很高兴能提供帮助。请随时单击答案左侧的复选标记以“关闭”此问题。
    【解决方案2】:
    cd /home
    find . -maxdepth 1 -type d ! -name . | sort > a
    find . -type d -name bin | cut -d/ -f1,2 | sort > b
    comm -23 a b
    

    在这里,我正在制作两个排序列表。第一个包含所有主目录,第二个包含任何 bin 子目录的顶级父目录。最后,我输出第一个列表中不存在于第二个列表中的所有项目。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-08-07
      • 2018-12-19
      • 2011-04-29
      • 2012-11-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多