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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2154166/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-09 18:53:37  来源:igfitidea点击:

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

bash

提问by Raghubansh Mani

I know you can use the findcommand for this simple job, but I got an assignment not to use findor lsand do the job. How can I do that?

我知道你可以使用这个find命令来完成这个简单的工作,但我得到了一个不使用findls完成这项工作的任务。我怎样才能做到这一点?

回答by ghostdog74

you can do it with just the shell

你可以只用外壳来做

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

recurse /path

OR if you have bash 4.0

或者如果你有 bash 4.0

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

回答by Alberto Zaccagni

Try using

尝试使用

tree -d

回答by vladr

Below is one possible implementation:

下面是一种可能的实现:

# my_ls -- recursively list given directory's contents and subdirectories
# =directory whose contents to list
# =indentation when listing
my_ls() {
  # save current directory then cd to ""
  pushd "" >/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 "$file"
    # if directory, go down and list directory contents too
    test -d "$file" && my_ls "$file" "  "
  done
  # restore directory
  popd >/dev/null
}

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

As an exercise you can think of how to modify the above script to print full paths to files (instead of just indented file/dirnames), possibly getting rid of pushd/popd(and of the need for the second parameter $2) in the process.

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

Incidentally, note the use of test XYZ && commandwhich is fully equivalent to if test XYZ ; then command ; fi(i.e. execute commandif test XYZis successful). Also note that test XYZis equivalent to [ XYZ ], i.e. the above is also equivalent to if [ XYZ ] ; then command ; fi. Also note that any semicolon ;can be replaced with a newline, they are equivalent.

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

Remove the test -e "$file" &&condition (only leave the echo) and see what happens.

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

Remove the double-quotes around "$file"and see what happens when the directory whose contents you are listing contains filenames with spaces in them. Add set -xat the top of the script (or invoke it as sh -x scriptname.shinstead) to turn on debug output and see what's happenning in detail (to redirect debug output to a file, run sh -x scriptname.sh 2>debugoutput.txt).

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

To also list hidden files (e.g. .bashrc):

还要列出隐藏文件(例如.bashrc):

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

Note the use of !=(string comparison) instead of -ne(numeric comparison.)

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

Another technique would be to spawn subshells instead of using pushd/popd:

另一种技术是产生子壳而不是使用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 ""
    for file in ...
      ...
    done
  )
}

Note that on some shell implementations there is a hard limit (~4k) on the number of characters which can be passed as an argument to for(or to any builtin, or external command for that matter.) Since the shell expands, inline, *to a list of all matching filenamesbefore actually performing foron it, you can run into trouble if *is expanded inside a directory with a lot of files (same trouble you'll run into when running, say ls *in the same directory, e.g. get an error like Command too long.)

请注意,在某些 shell 实现中,可以作为参数传递给for(或任何内置命令或外部命令)的字符数有硬限制(~4k)。由于 shell扩展,内联,*在实际执行之前所有匹配文件名的列表,for如果*在包含大量文件的目录中展开,您可能会遇到问题(运行时会遇到同样的问题,比如ls *在同一目录中,例如得到一个错误Command too long.)

回答by vladr

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

由于它是用于 bash 的,令人惊讶的是,这还没有被说出来:
(globstar 从 bash 4.0+ 开始有效)

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

That's all.
The trailing slash /is there to select only dirs.

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

Option globstaractivates the **(search recursivelly). Option nullglobremoves an *when it matches no file/dir. Option dotglobincludes files that start wit a dot (hidden files).

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

回答by schot

Like Mark Byers said you can use echo *to get a list of all files in the current directory.

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

The testor []command/builtin has an option to test if a file is a directory.

test[]命令/内建有一个选项来测试,如果一个文件是一个目录。

Apply recursion and you're done.

应用递归,你就完成了。

回答by pavium

The ducommand will list subdirectories recursively.

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

I'm not sure if emptydirectories get a mention, though

不过,我不确定是否提及目录

回答by ephemient

Technically, neither findnor lsare used by find2perl|perlor File::Finddirectly.

从技术上讲,没有发现也不LS被使用find2perl| perlFile::Find直接。

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

回答by Alex Brown

$ function f { for i in /*; 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

回答by Benjamin W.

Based on this answer; use shell optionsfor the desired globbing behaviour:

基于这个答案;对所需的通配行为使用shell 选项

  • enable **with globstar(Bash 4.0 or newer)
  • include hidden directories with dotglob
  • expand to the empty string instead of **/*/if there is no match with nullglob
  • 使**globstar(击4.0或更高版本)
  • 包括隐藏目录 dotglob
  • 扩展到空字符串而不是**/*/如果没有匹配nullglob

and then use printfwith the %qformatting directiveto quote directory names with special characters in them:

然后用printf%q格式化指令与他们特殊字符引用目录名称:

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

so if you have directories like has spaceor even containing a newline, you'd get output like

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

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

with one directory per line.

每行一个目录。