[英]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
命令来完成这个简单的工作,但是我得到了一个不使用find
或ls
完成这项工作的任务。 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.
perl或File::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 选项:
**
with globstar
(Bash 4.0 or newer)globstar
启用**
(Bash 4.0 或更新版本)dotglob
dotglob
包含隐藏目录**/*/
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.