【问题标题】:How to recursively list subdirectories in Bash without using "find" or "ls" commands?如何在不使用“find”或“ls”命令的情况下递归列出 Bash 中的子目录?
【发布时间】:2010-01-28 11:42:37
【问题描述】:

我知道您可以使用find 命令来完成这项简单的工作,但我得到了一项任务,即不使用findls 来完成这项工作。我该怎么做?

【问题讨论】:

  • 你应该用什么来做到这一点?您自己的 shell 脚本、C 程序、Java ......?如果您让我们知道您可以使用什么,帮助应该会更及时:-)
  • 您或许可以使用echo * 或其变体来模拟ls

标签: bash


【解决方案1】:

你可以只用外壳来做

#!/bin/bash
recurse() {
 for i in "$1"/*;do
    if [ -d "$i" ];then
        echo "dir: $i"
        recurse "$i"
    elif [ -f "$i" ]; then
        echo "file: $i"
    fi
 done
}

recurse /path

或者如果你有 bash 4.0

#!/bin/bash
shopt -s globstar
for file in /path/**
do
    echo $file
done

【讨论】:

  • 你为什么不也用树命令否决那个?或带有回声的那个,这些也是有效的答案。
  • 如果目录为空,这将失败,即它将打印dir: bla/*,因为bla/* 被视为文字,因为它没有后代。您可以使用pushd/popd,也可以在do 之后的第一行添加if [[ "$1/*" = "$i" ]]; then continue; fi
  • 这也可以通过在循环内使用shopt -s nullglob[ -e "$i" ] || [ -L "$i" ] || continue来修复(不发出bla/*)。
【解决方案2】:

尝试使用

tree -d

【讨论】:

  • 为什么我得到-1?请发表评论。
  • 我没有给你-1,但我很清楚你为什么得到它。为什么不给出答案alias bla ls ; bla?提出的问题是编写算法,而不是找到一些与ls 执行相同操作但不称为lscommand
  • @Vlad Romascanu:哦……好吧,但是从“我得到了一项任务,不使用 find 或 ls 来完成这项工作”,我了解到一切都很好,除了 find 和 ls ^^
  • 另一方面,他的任务可能必须在 Solaris、AIX 或 HPUX 上运行,而不是在 Linux 上运行。 :) 让我向你保证,在非 Linux 等系统上默认没有 tree 命令可用。
【解决方案3】:

以下是一种可能的实现方式:

# my_ls -- recursively list given directory's contents and subdirectories
# $1=directory whose contents to list
# $2=indentation when listing
my_ls() {
  # save current directory then cd to "$1"
  pushd "$1" >/dev/null
  # for each non-hidden (i.e. not starting with .) file/directory...
  for file in * ; do
    # print file/direcotry name if it really exists...
    test -e "$file" && echo "$2$file"
    # if directory, go down and list directory contents too
    test -d "$file" && my_ls "$file" "$2  "
  done
  # restore directory
  popd >/dev/null
}

# recursively list files in current
#  directory and subdirectories
my_ls .

作为练习,您可以考虑如何修改上述脚本以打印文件的完整路径(而不仅仅是缩进的文件/目录名),可能摆脱 pushd/popd(以及第二个参数$2)。

顺便提一下,注意test XYZ && command 的使用完全等同于if test XYZ ; then command ; fi(即,如果test XYZ 成功,则执行command)。还要注意test XYZ 等价于[ XYZ ],即上面也等价于if [ XYZ ] ; then command ; fi。另请注意,任何分号; 都可以替换为换行符,它们是等价的。

删除test -e "$file" && 条件(只留下echo),看看会发生什么。

删除"$file" 周围的双引号,看看当您列出其内容的目录包含其中包含空格的文件名时会发生什么。在脚本顶部添加set -x(或将其调用为sh -x scriptname.sh)以打开调试输出并查看详细情况(要将调试输出重定向到文件,请运行sh -x scriptname.sh 2>debugoutput.txt)。

同时列出隐藏文件(例如.bashrc):

...
for file in * .?* ; do
  if [ "$file" != ".." ] ; then
    test -e ...
    test -d ...
  fi
done
...

注意使用!=(字符串比较)而不是-ne(数字比较。)

另一种技术是生成子shell,而不是使用pushd/popd

my_ls() {
  # everything in between roundbrackets runs in a separatly spawned sub-shell
  (
    # change directory in sub-shell; does not affect parent shell's cwd
    cd "$1"
    for file in ...
      ...
    done
  )
}

请注意,在某些 shell 实现中,可以作为参数传递给 for(或任何内置或外部命令)的字符数有硬性限制(~4k)。 shell expands, inline, * to a list of all matching filenames 在实际执行 for 之前,如果 * 在包含大量文件的目录中展开,您可能会遇到麻烦(运行时会遇到同样的麻烦,例如 ls *目录,例如得到类似Command too long的错误。)

【讨论】:

  • 您可以使用shopt -s dotglob 列出隐藏文件。
  • @ghostdog -- 不可移植。 .*(或.?* 直接跳过.)可以很好地解决问题并且也可以移植。
【解决方案4】:

既然是针对 bash 的,令人惊讶的是还没有说过:
(globstar 从 bash 4.0+ 开始有效)

shopt -s globstar nullglob dotglob
echo **/*/

就是这样。
尾部斜杠 / 仅用于选择目录。

选项globstar 激活**(递归搜索)。 选项nullglob 在不匹配任何文件/目录时删除*。 选项dotglob 包括以点开头的文件(隐藏文件)。

【讨论】:

    【解决方案5】:

    du 命令将递归列出子目录。

    我不确定 empty 目录是否会被提及

    【讨论】:

      【解决方案6】:

      就像 Mark Byers 所说,您可以使用 echo * 获取当前目录中所有文件的列表。

      test[] 命令/内置选项可以测试文件是否为目录。

      应用递归,你就完成了。

      【讨论】:

      • 作为echo 的替代品,for 将 glob,因此 OP 可以使用 for file in * ; do ... ; done
      【解决方案7】:
      $ function f { for i in $1/*; do if [ -d $i ]; then echo $i; f $i; fi; done }
      $ mkdir -p 1/2/3 2/3 3
      $ f .
      ./1
      ./1/2
      ./1/2/3
      ./2
      ./2/3
      ./3
      

      【讨论】:

      • -1 如果您要为家庭作业问题提供完整答案,请将其设为正确答案。目录条目名称中的空格怎么办?
      • 而且不进目录也不列出文本文件
      • 它确实进入了目录,并且 OP 对文本文件(或除目录之外的任何文件)只字未提。我不会解决“带有空格的目录”问题。
      • 太糟糕了。这几乎是一个很好的答案,但缺乏我需要的信息(作为一个 n00b)来修补它。在我看来,这只是另一串 bash-foo。
      • 所以你不喜欢用 bash 写的答案来回答一个要求 bash 的问题,因为它太害羞了?这可能不适合您。
      【解决方案8】:

      从技术上讲,find2perl|perlFile::Find 都不会直接使用 findls

      $ find2perl -type d | perl $ perl -MFile::Find -e'find(sub{-d&&print"$File::Find::name\n"},".")'

      【讨论】:

      • 但是,如果是这样的话,使用 Python/Ruby/PHP 等任何可以列出文件的语言在技术上都不会使用 lsfind
      • @ghostdog74 显然。但是由于所有严肃的答案都没有得到惊人的反馈,所以这件事……不那么严重。 (我实际上打算用 C 语言编写一个 find 的克隆,并编写一个编译 + 运行它的 shell 脚本,但我认为这对于一个笑话来说太过分了。)
      【解决方案9】:

      基于this answer;使用 shell options 获得所需的通配行为:

      • 启用 **globstar(Bash 4.0 或更高版本)
      • 包含带有dotglob 的隐藏目录
      • 如果与nullglob 不匹配,则扩展为空字符串而不是**/*/

      然后使用printf with the %q formatting directive 引用其中包含特殊字符的目录名称:

      shopt -s globstar dotglob nullglob
      printf '%q\n' **/*/
      

      所以如果你有像has space 这样的目录,甚至包含换行符,你会得到类似的输出

      $ printf '%q\n' **/*/
      $'has\nnewline/'
      has\ space/
      

      每行一个目录。

      【讨论】:

        【解决方案10】:

        我写了另一个迭代而不是递归的解决方案。

        iterativePrintDir() {    
            dirs -c;
            currentd=$1
            if [ ! -d $currentd ];then
                    echo "Diretorio não encontrado. \"$currentd\""
            fi
            pushd $(readlink -f $currentd)
        
            while popd 2>&-; do
                    echo $currentd
                    for d in $(dir $currentd); do
                            test -d "${currentd}/${d}" && pushd  "${currentd}/${d}"
                    done
                    currentd=$(dirs -l +0)
            done
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-01-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-12-30
          • 2012-04-25
          相关资源
          最近更新 更多