【问题标题】:Recursively differentiating files and directories in bash在bash中递归区分文件和目录
【发布时间】:2018-04-10 01:42:15
【问题描述】:

在这里猛击。希望编写一个脚本:

  • 将绝对/相对目标目录作为其唯一输入参数并循环遍历其内容
  • 如果其直接子项之一是文件,我希望它回显“Found a file called $file!”(其中$file 是文件名)
  • 否则,如果它的一个直接子级本身就是一个目录,我希望它回显“Found a directory named $dir”(其中$dir 是目录的名称),然后我想要它在该目录中以相同的确切逻辑递归执行,最后我希望它继续循环遍历目标目录的其余部分

因此给出以下目标目录结构:

~/testDir/
    1.txt
    2.txt
    childDirA/
        foo.png
    3.txt
    childDirB/
        buzz.gif
        childDirC/
            foo.bar
    4.txt

脚本的输出是:

Found a file called 1.txt!
Found a file called 2.txt!
Found a directory named childDirA
Found a file called foo.png!
Found a file called 3.txt!
Found a directory named childDirB
Found a file called buzz.gif!
Found a directory named childDirC
Found a file called foo.bar!
Found a file called 4.txt!

到目前为止,我能想到的最好的是:

#!/bin/bash
for file in $1;
  do echo "Found a file called $file";
done

但是,如果我将其指向我的 testDir,我得到的唯一输出是:

Found a file called testDir

有什么想法会出错吗?

【问题讨论】:

  • 我会尝试使用find。这就是它的设计目的。
  • 我可以使用 find 但我的脚本实际上不仅仅是回显文件名和目录,我只是想先找出递归问题。
  • find 可以做的不仅仅是回显名称。重新发明轮子没有意义。
  • 具体看find-exec参数。
  • ls -R 可以打印您想要的相同信息

标签: bash recursion


【解决方案1】:

这是一个可以做到这一点的脚本。

function listDir() {
    for file in "$1"/*;
    do
        if [ -f "$file" ]; then
            echo "Found file $(basename "$file")"
        elif [ -d "$file" ]; then
            echo "Found directory $(basename "$file")"
            listDir "$file"
        fi
    done
}

listDir "$1"

【讨论】:

  • 纯 bash 解决方案,速度快
  • 你应该引用任何变量扩展
  • @wjandrea 确实 - 已修复。
  • 这不包括以“.”开头的文件和目录-- 要包含它们,在循环之前使用shopt -s dotglob,之后使用shopt -u dotglob。并确保循环内的命令不会因为打开 dotglob 选项而混淆。
【解决方案2】:

正如 Tripp 在 cmets 中提到的,find 可以为您完成“递归”工作;那么您只需将输出调整为您想要的格式。

假设您只需要担心文件和目录:

#!/usr/bin/bash

while IFS= read -d "" -r tgt
do
        # for printing purposes, strip off directory info; basically simulate
        # `basename` without the overhead of spawning an expensive sub-process

        tgtname=${tgt##*/}

        # determine tgt's type : 'file' or 'directory'

        tgttype='file'
        [ -d "${tgt}" ] && tgttype='directory'

        echo "Found a ${tgttype} named ${tgtname}"

done < <(find "$1" -print0)

注意事项:

  • 单独运行find $1 以查看输入循环的数据格式
  • 添加额外的测试以确定tgttype 是否在处理链接、设备等(也许使用case 语句?)
  • 考虑将额外的命令行参数/参数添加到find 以微调输入循环的列表

【讨论】:

  • 为了防止文件名中出现奇怪字符的问题,最好使用空分隔的文件列表。如果列表中的任何内容可能从 stdin 读取,最好向列表发送 stdin 以外的其他内容(我通常使用 FD #3)。组合如下所示:while IFS= read -d "" -r tgt &lt;&amp;3done 3&lt; &lt;(find "$1" -print0)
  • @GordonDavisson:关于奇怪角色的观点很好,我已经用你的IFS/-d/print0 建议更新了答案;虽然我理解您对标准输入和 FD#3 的看法,但提议的实现有点令人困惑(对我而言),所以我很犹豫是否将其粘贴到我的答案中......
【解决方案3】:

查找

#!/bin/sh
find "$1" -mindepth 1 -type f -printf 'Found a file called %f!\n' -o -type d -printf 'Found a directory named %f\n'

说明:

默认情况下,find 通过子目录递归。

find 的参数:

  • -mindepth 1排除基目录
  • -type f ...如果找到正常文件,请执行以下操作
  • -o -type d ... 或者,如果找到目录,请执行以下操作
  • -printf ...按照给定的格式字符串打印
  • %f 文件的基本名称(dirs 是此上下文中的文件)

【讨论】:

  • 虽然此代码可能会回答问题,但提供有关此代码为何和/或如何回答问题的额外上下文可提高其长期价值
  • @VishalChhodwani 我已经添加了解释。如果还需要什么,请告诉我。
猜你喜欢
  • 2013-02-07
  • 2011-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多