如何在不使用“find”或“ls”命令的情况下递归列出Bash中的子目录?

How to recursively list subdirectories in Bash without using “find” or “ls” commands?

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


你只要用贝壳就行了

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/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

1
2
3
4
5
6
#!/bin/bash
shopt -s globstar
for file in /path/**
do
    echo $file
done


试用使用

1
tree -d


下面是一个可能的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 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):

1
2
3
4
5
6
7
8
...
for file in * .?* ; do
  if ["$file" !=".." ] ; then
    test -e ...
    test -d ...
  fi
done
...

注意使用EDOCX1(字符串比较)而不是EDOCX1(数字比较)。

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

1
2
3
4
5
6
7
8
9
10
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在实际对其执行for之前,将*扩展到所有匹配文件名的列表中,因此可以如果在包含大量文件的目录中扩展*,就会遇到问题(运行时会遇到同样的问题,例如在同一目录中扩展ls *,例如遇到类似Command too long的错误)。


Since it is for bash, it is a surprise that this hasn't been already said:
(globstar valid from bash 4.0+)

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

这就是全部。尾随斜线/仅用于选择dir。

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


正如MarkByers所说,您可以使用echo *获取当前目录中所有文件的列表。

test[]命令/builtin可以选择测试文件是否是目录。

应用递归就完成了。


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

不过,我不确定是否有人提到空目录


基于此答案;使用外壳选项实现所需的全局绑定行为:

  • 使用globstar启用**(bash 4.0或更新版本)
  • 使用dotglob包含隐藏目录
  • 如果与nullglob不匹配,则扩展到空字符串而不是**/*/

然后将printf%q格式化指令一起使用,引用其中包含特殊字符的目录名:

1
2
3
shopt -s globstar dotglob nullglob
printf '%q
'
**/*/

因此,如果您有像has space这样的目录,或者甚至包含新行,那么您将得到如下输出:

1
2
3
4
5
$ printf '%q
'
**/*/
$'has
newline/'

has\ space/

每行一个目录。


从技术上讲,find2perl perl或file::find都不直接使用find和ls。

1
2
3
4
5
6
7
8
9
10
11
12
$ find2perl -type d | perl
$ perl -MFile::Find -e'find(sub{-d&&print"$File::Find::name

<div class="suo-content">[collapse title=""]<ul><li>但是,如果是这样的话,使用python/ruby/php等任何可以列出文件的语言在技术上都不会使用<wyn>ls</wyn>或<wyn>find</wyn>。</li><li>@鬼狗74显然。但是,由于所有严肃的回答都得到了不那么惊人的反馈,这是一个…不太严重。(我本来打算用C语言编写一个<wyn>find</wyn>的克隆版本,然后编写一个shell脚本,编译+运行它,但我觉得这太费劲了,不适合开玩笑。)</li></ul>[/collapse]</div><hr>[cc lang="bash"]$ 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