如何模仿tree
命令并迭代目录的所有文件和子目录并回显所有文件名?
我认为目录中的子目录仍然算作目录,所以我这样做了:
for FILE in *; do
if [ -d $FILE ]
then echo "$FILE is a directory";
else echo "$FILE is a file"
fi
done
如何使其递归并在子目录内循环并迭代所有文件并打印它们的文件名+路径?
这是一个使用 bash 的 shell 脚本。谢谢!
答案1
下面是作为命令实现的 terdon shell 函数的等效项find
:
find . -exec sh -c 'for f; do
[ -d "$f" ] && is_dir="" || is_dir="not "
printf "\"%s\" is %sa directory\n" "$f" "$is_dir"
done' sh {} +
或者使用 find & perl (每个目录级别缩进两个空格并使用 NUL 作为文件名分隔符):
find . -print0 | \
perl -0ne '$is_dir = -d $_ ? "" : "not ";
$f = $_; $indent = ($f =~ s=/==g);
printf " " x $indent . "%s is %sa directory\n", $_, $is_dir'
大多数 Perl 一行代码都是直截了当且显而易见的,但其中一行代码可能需要一些解释:
$f = $_; $indent = ($f =~ s=/==g);
这会将当前输入记录(即文件名$_
)复制到变量 中$f
。然后修改 $f 以删除所有/
字符,同时将更改计数存储在变量中$indent
。$f
不再使用,它只是一个一次性变量,以避免更改$_
。
该$indent
变量与重复运算符(x
- 类似于乘法,但对于字符串,请参阅man perlop
)一起使用来构造 printf 格式字符串。
要对文件和目录进行计数,每行使用 3 位数字宽编号:
$ find . -print0 | perl -0ne '
if ( -d $_ ) {
$is_dir = "";
$dirs++
} else {
$is_dir = "not ";
$files++
};
$f = $_; $indent = ($f =~ s=/==g);
#printf " " x $indent . "%s is %sa directory\n", $_, $is_dir;
printf "%3i: " . " " x $indent . "%s is %sa directory\n", $., $_, $is_dir;
END {
# simple method to determine singular or plural word forms
# without using the Lingua::EN::Inflect module
# (see https://metacpan.org/release/Lingua-EN-Inflect)
my $d_word = ($dirs != 1) ? "directories" : "directory";
my $f_word = ($files != 1) ? "files" : "file";
print "\n$dirs $d_word, $files $f_word\n";
}'
这一行的 Perl 部分已经到了在命令行上编辑不再合理的地步,因为处理荒谬的长行是一个完整的 PITA - 使用文本编辑器要容易得多。它应该保存到 $PATH 中某处的脚本文件中(例如,如果它不存在,~/bin/
则添加到您的 PATH 中)作为第一行,并使用./usr/local/bin
#!/usr/bin/perl -0ne
chmod +x
答案2
如果您只是对将目录与其他所有内容分开感兴趣,您可以使用递归函数执行类似的操作:
#!/bin/bash
## Do not expand globs to themselves if they don't match
shopt -s nullglob
list_files(){
## if a target has been passed as an argument, use that; if not,
## default to '.', the current directory.
target=${1:-.}
for i in "$target"/*; do
if [ -d "$i" ]; then
list_files "$i"
else
echo "$i is not a directory"
fi
done
}
list_files "$@"
给定一个像这样的目录结构:
$ tree
.
├── 1.txt
├── 2.txt
├── 3.txt
├── dir1
│ ├── subdir1
│ │ ├── file
│ │ ├── subsubdir1
│ │ └── subsubdir2
│ └── subdir2
│ ├── file
│ ├── subsubdir1
│ └── subsubdir2
├── dir2
│ ├── subdir1
│ │ ├── subsubdir1
│ │ └── subsubdir2
│ └── subdir2
│ ├── subsubdir1
│ └── subsubdir2
└── symlink -> 2.txt
15 directories, 6 files
上面的代码产生:
$ bar.sh
"./1.txt" is not a directory
"./2.txt" is not a directory
"./3.txt" is not a directory
"./dir1" is a directory
"./dir1/subdir1" is a directory
"./dir1/subdir1/file" is not a directory
"./dir1/subdir1/subsubdir1" is a directory
"./dir1/subdir1/subsubdir2" is a directory
"./dir1/subdir2" is a directory
"./dir1/subdir2/file" is not a directory
"./dir1/subdir2/subsubdir1" is a directory
"./dir1/subdir2/subsubdir2" is a directory
"./dir2" is a directory
"./dir2/subdir1" is a directory
"./dir2/subdir1/subsubdir1" is a directory
"./dir2/subdir1/subsubdir2" is a directory
"./dir2/subdir2" is a directory
"./dir2/subdir2/subsubdir1" is a directory
"./dir2/subdir2/subsubdir2" is a directory
"./symlink" is not a directory
您还可以通过添加偏移量来指示目录树中的深度,并首先处理所有目录以获得更有序的输出,从而使其更易于阅读:
#!/bin/bash
## Do not expand globs to themselves if they don't match
shopt -s nullglob
list_files(){
## if a target has been passed as an argument, use that; if not,
## default to '.', the current directory.
target=${1:-.}
for i in "$target"/*; do
offset=${2:-""}
if [ -d "$i" ]; then
printf '%s"%s" is a directory\n' "$offset" "$i"
for subdir in "$i"/*/; do
new_offset="$offset "
list_files "$i" "$new_offset"
new_offset=""
done
else
printf '%s"%s" is not a directory\n' "$offset" "$i"
fi
done
}
list_files "$@"
这将在上面的示例输入上产生以下输出:
$ foo.sh
"./1.txt" is not a directory
"./2.txt" is not a directory
"./3.txt" is not a directory
"./dir1" is a directory
"./dir1/subdir1" is a directory
"./dir1/subdir1/file" is not a directory
"./dir1/subdir1/subsubdir1" is a directory
"./dir1/subdir1/subsubdir2" is a directory
"./dir1/subdir1/subsubdir2/file" is not a directory
"./dir1/subdir2" is a directory
"./dir1/subdir2/file" is not a directory
"./dir1/subdir2/subsubdir1" is a directory
"./dir1/subdir2/subsubdir2" is a directory
"./dir2" is a directory
"./dir2/subdir1" is a directory
"./dir2/subdir1/subsubdir1" is a directory
"./dir2/subdir1/subsubdir2" is a directory
"./dir2/subdir2" is a directory
"./dir2/subdir2/subsubdir1" is a directory
"./dir2/subdir2/subsubdir2" is a directory
"./symlink" is not a directory
请注意,此方法将下降到符号链接目录。例如,如果我添加以下内容:
$ ln -s dir2 dirsym
$ ls -ld dirsym
lrwxrwxrwx 1 terdon terdon 4 Apr 8 16:03 dirsym -> dir2
然后dirsym
将被视为一个目录,其内容将像其目标一样被报告,dir2
有效地重复dir2
输出。我不知道这是否是期望的行为。如果不是,请使用卡兹的find
方法相反。
答案3
(
find . -type f -print0 | xargs -0 printf '%s is a file\0'
find . -type d -print0 | xargs -0 printf '%s is a directory\0'
) | sort -z | tr '\0' '\n'
我不确定我是否认为这是一个好的答案,有点像“我想知道我是否可以……”
生成文件和目录的列表。
使用适当的后缀打印它们。
然后对整个结果进行排序,使项目出现在正确的上下文中。
需要一些额外的摆弄来处理名称中带有换行符的项目。