简体   繁体   English

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

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

I know you can use the find command for this simple job, but I got an assignment not to use find or ls and do the job.我知道你可以使用find命令来完成这个简单的工作,但是我得到了一个不使用findls完成这项工作的任务。 How can I do that?我怎样才能做到这一点?

you can do it with just the shell你可以只用外壳来做

#!/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

OR if you have bash 4.0或者如果你有 bash 4.0

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

尝试使用

tree -d

Below is one possible implementation:下面是一种可能的实现:

# 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 .

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 && command which is fully equivalent to if test XYZ ; then command ; fi顺便提一下,请注意test XYZ && command的使用,它完全等同于if test XYZ ; then command ; fi if test XYZ ; then command ; fi if test XYZ ; then command ; fi (ie execute command if test XYZ is successful). if test XYZ ; then command ; fi (即如果test XYZ成功则执行command )。 Also note that test XYZ is equivalent to [ XYZ ] , ie the above is also equivalent to if [ XYZ ] ; then command ; fi还要注意test XYZ等价于[ XYZ ] ,即上面也等价于if [ XYZ ] ; then command ; fi if [ XYZ ] ; then command ; fi if [ XYZ ] ; then command ; fi . if [ XYZ ] ; then command ; fi Also note that any semicolon ;还要注意任何分号; can be replaced with a newline, they are equivalent.可以用换行符代替,它们是等价的。

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.去掉"$file"周围的双引号,看看当你列出的目录包含带有空格的文件名时会发生什么。 Add set -x at the top of the script (or invoke it as sh -x scriptname.sh instead) 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 ).在脚本顶部添加set -x (或将其作为sh -x scriptname.sh调用)以打开调试输出并查看发生的详细情况(将调试输出重定向到文件,运行sh -x scriptname.sh 2>debugoutput.txt )。

To also list hidden files (eg .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 :另一种技术是生成子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
  )
}

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 filenames before actually performing for on 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, eg get an error like Command too long .)请注意,在某些 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:由于它是用于 bash 的,令人惊讶的是还没有说:
(globstar valid from bash 4.0+) (globstar 从 bash 4.0+ 开始有效)

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

That's all.就这样。
The trailing slash / is there to select only dirs.尾部斜杠/仅用于选择目录。

Option globstar activates the ** (search recursivelly).选项globstar激活** (递归搜索)。 Option nullglob removes an * when it matches no file/dir.选项nullglob在不匹配任何文件/目录时删除* Option dotglob includes files that start wit a dot (hidden files).选项dotglob包括以点dotglob文件(隐藏文件)。

The du command will list subdirectories recursively. du命令将递归列出子目录。

I'm not sure if empty directories get a mention, though不过,我不确定是否提及目录

Like Mark Byers said you can use echo * to get a list of all files in the current directory.就像 Mark Byers 所说,您可以使用echo *获取当前目录中所有文件的列表。

The test or [] command/builtin has an option to test if a file is a directory. test[]命令/内置有一个选项来测试文件是否是目录。

Apply recursion and you're done.应用递归,你就完成了。

$ 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

Technically, neither find norls are used by find2perl |从技术上讲, find2perl既不使用find也不使用ls | perl or File::Find directly. perlFile::Find直接。

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

Based on this answer ;基于这个答案 use shell options for the desired globbing behaviour:对所需的通配行为使用shell 选项

  • enable ** with globstar (Bash 4.0 or newer)使用globstar启用** (Bash 4.0 或更新版本)
  • include hidden directories with dotglob使用dotglob包含隐藏目录
  • expand to the empty string instead of **/*/ if there is no match with nullglob如果与nullglob不匹配,则扩展为空字符串而不是**/*/

and then useprintf with the %q formatting directive to quote directory names with special characters in them:然后使用带有%q格式化指令的printf来引用包含特殊字符的目录名称:

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

so if you have directories like has space or even containing a newline, you'd get output like所以如果你有像has space甚至包含换行符这样的目录,你会得到类似的输出

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

with one directory per line.每行一个目录。

I wrote a another solution iterative instead of recursive.我写了另一个解决方案迭代而不是递归。

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
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM