如何在 UNIX 中递归获取完整路径?

How to get full paths recursively in UNIX?

我正在寻找一种递归获取 UNIX 给定目录中所有文件路径的方法。 (不使用查找)

示例:

给定这样一棵树

lab_assignment:
file1.txt
file2.txt
subdir1
subdir2
./subdir1:
file11.txt
./subdir2:
file21.txt

我需要一个命令来递归地列出 lab_assignment 中包含的所有文件的路径。

./file1.txt
./file2.txt
./subdir1/file11.txt
./subdir2/file21.txt

我在作业中发现了这个,所以工具集是有意限制的。我知道您可以使用 find 命令轻松完成,但此作业不允许使用 find,因此必须有一种方法可以不用 find,但我想不出一个。

老师告诉我们只使用 ls、引语,也许还有管道和 grep.

就可以实现这一点

更新:

我在最近的一次作业中遇到了这个问题,尽管这不是它的主要重点。因此,我设法完全避免了这个问题,但后来发现自己很好奇正确的解决方案是什么。

此问题的解决方案用于以下任务:
递归输出以.txt结尾的文件内容
递归统计所有f开头文件的行数

实用程序 like cat 和 wc 使用其标准输入中提供的文件名,并且没有内置递归功能,因此您必须提供文件路径列表。

丑陋的方式

我决定尽可能避免这个问题并这样做:

cat *.txt */*.txt */*/*.txt  
wc -l f* */f* */*/f*`  

这成功了。老师似乎很不高兴,说这种方法又脏又丑,但他接受了我的报告。我很好奇我应该怎么做。

断路

在烦扰了老师一个多月之后,他同意向我展示正确的方法。

他输入了这个:

cat `ls -R $PWD`

这似乎只会导致错误,而不会产生任何类似于所需结果的东西。

然后他想出了:

cat $PWD/`ls -R`

这件事至少做了一些事情,但仍然 - 甚至没有接近要求的结果。
老师然后告诉我,这是他第一年开设这门课程,这是很久以前由 uni 的不同部门设计的,作为 UNIX 用户,他只会用 find他不知道解法
但他发誓一定在课程设计文档的某处或某处看到过它...

那么,有没有办法在不查找的情况下获取文件路径的递归列表? 什么巧妙的 UNIX 技巧和思维体操是关键?

————使用globstar————

I need a command which would list paths to all files [...] recursively.
[...]
The command should be as simple as possible.

当你有bash > 4.0且当前目录至少有一个文件时,你可以使用

shopt -s globstar
printf ./%s\n **

当工作目录可以为空时,使用

shopt -s globstar nullglob
a=(**)
(( ${#a[@]} > 0 )) && printf ./%s\n "${a[@]}"

并解决显式赋值

Recursively output contents of files, names of which end with .txt

shopt -s globstar
cat **/*.txt

Recursively count the amount of lines in all files, names of which start with f

shopt -s globstar
wc -l **/f*

注意 **/* 也匹配工作目录中的文件。扩展列表可能包含也可能不包含包含 / 的路径。


————使用ls/grep————

Teacher told us it was possible to achieve this using only ls, quotation, and maybe pipes and grep

我不这么认为,至少不可靠。如果任何 file/directory 名称包含换行符,则无法仅使用 提到的机制使其工作。

如果您可以做出类似 »没有路径包含换行符« 甚至 »没有路径包含空格« 这样的假设,那么赋值就变成了可解。但是,我找不到使用 ls 的解决方案,因为 ls 从不输出完整路径,而且我们缺少用于构建的工具(例如 sed、递归或循环)其输出的完整路径。

列出所有文件(但不包括目录)的路径

grep -RLE '$^'

-R 递归地将 grep 应用于所有文件。 -E '$^' 是一个从不匹配的正则表达式。 -L 打印所有不匹配的文件。

打印所有以 .txt 结尾的文件的内容

cat $(grep -RLE '$^' | grep -E '\.txt$')

计算以 f 开头的所有文件的行数

wc -l $(grep -RLE '$^' | grep -E '(^|/)f[^/]*$')

———— 结束语————

在我看来,这个作业很糟糕,与其说是因为它可能无法解决,不如说是因为它教导了不好的做法(例如,没有使用正确的工具,依赖假设,...)。

总结:您可以仅使用 shell,无需外部工具。就在下面。您也可以仅使用 ls -R 加上一些 shell,或仅使用工具。看我的

I'm genuinely interested in how would one do this the correct way.

“正确”的方式是find。这就是这项工作的工具。它在 POSIX:

中定义

The find utility shall recursively descend the directory hierarchy from each file specified by path, evaluating a Boolean expression composed of the primaries described in the OPERANDS section for each file encountered.

我会相信你的老师,并假设这不是一些微不足道的学术练习。我假设作业具有一定的实用性,例如:

"You've been dropped into a damaged UNIX system that has had most of its toolset removed, including its find command. You need to triage the directory structure. All you've got is ls, grep and a classic Bourne shell. You know that file names are conventional: no spaces in them, no leading dash in them, no control characters in them, etc. How would you do this?" (1)

(这并非遥不可及。我曾经对一个系统进行分类,该系统由于错误的 mount 指令而丢失了 /usr/bin。我不得不仅使用 [=65 来诊断和恢复它=] 内置函数,例如 echo.)

鉴于此:

$ tree
.
├── file1.txt
├── file2.txt
├── subdir1
│   ├── file11.txt
│   ├── file12.c
│   └── subdira
│       ├── file1a1.c
│       └── file1a1.txt
├── subdir2
│   └── file21.txt

首先,“正确”的方式。这是我们的目标输出:

$ find . -name '*.txt'
./file2.txt
./file1.txt
./subdir1/file11.txt
./subdir1/subdira/file1a1.txt
./subdir2/file21.txt

So, is there a way to get a recursive list of filepaths without find?

是的。我们可以 在这些条件下 仅使用 shell 内置函数:

$ r() {
    d=${1:-.}
    for f in *
    do
        if test -f "$f"; then
            case "$f" in *.txt)
                echo $d/$f
                ;;
            esac
        elif test -d "$f"; then
            ( cd "$f"; r "$d/$f" )
        fi
    done
}
$ r
./file1.txt
./file2.txt
./subdir1/file11.txt
./subdir1/subdira/file1a1.txt
./subdir2/file21.txt

没有外部程序,只有 shell 内置程序。它很容易扩展:你可以调用像 wc 这样的程序,而不是回显匹配。既然都是shell,可以一直跟踪变量求和等

但是,这几乎没有性能,并且它会排除“奇怪”的文件名。此外,它与 find 解决方案不同:find 输出按 inode 顺序排列,而我的 shell 解决方案按区域设置顺序排列。这些可能有所不同,如我的示例所示。

这也不是唯一的递归下降方式,它只是一种显而易见的方式。对于没有 find 的递归下降的替代版本,请参阅 Rich's POSIX sh tricks


(1) 如果您的导师认为使用包含空格、控制字符、破折号等的深奥文件名可以正确完成此操作,我建议您的导师阅读 David Wheeler 的treatise (rant) 关于这个主题。

如果您正在寻找纯工具解决方案(与 中的纯 shell 解决方案相比),那么有几个选择:

tar cvf /dev/null . | grep '\.txt$'
du -a | grep '.txt$' | cut -f2

如果您正在寻找工具和 shell 的混合解决方案,那么:

ls -R . | while read l; do case $l in *:) d=${l%:};; "") d=;; *.txt) echo "$d/$l";; esac; done

后一个是我能得到的最接近你的导师给的参数。

警告!
在上面的回答 中:
注意事项:

echo "" | grep -Ec '$^'  
1

这不是0! “解决方案”需要此零值:

 grep -RLE '$^'

的确,正如所见,这种说法是天真的错误:

-E '$^' is a regex that never matches.

事实上,它没有为 L列出的文件提供消除歧义的可能性。
比较:

echo -e "$^"    | grep -Ec '$^'  
0
echo -e "$^\n"  | grep -Ec '$^'  
1

但是,进一步挥手可以通过制作两个文件列表来挽救该技术;那些有匹配的和那些没有匹配的。 (据推测,cat 将两个列表与以下 sort 结合起来。)
使用环境:

uname -a  
Linux ubuntu 4.15.0-74-generic #84-Ubuntu SMP Thu Dec 19 08:06:00 UTC 2019 i686 i686 i686 GNU/Linux

grep --version
grep (GNU grep) 3.1

大脑疼痛训练的教学法虽然迂腐,但有其优点。

具体来说,ls 确实提供了明确解密的文件名和路径名。

ls --help
-D, --dired                generate output designed for Emacs' dired mode

详见man ls

'-D'
'--dired'
     With the long listing ('-l') format, print an additional line after
     the main output:

          //DIRED// BEG1 END1 BEG2 END2 ...

     The BEGN and ENDN are unsigned integers that record the byte
     position of the beginning and end of each file name in the output.
     This makes it easy for Emacs to find the names, even when they
     contain unusual characters such as space or newline, without fancy
     searching.

除了 emacs(编辑宏)之外,其他实用程序 sed 也可以解析它。我严重缺乏这样做的动力。


来自socowi的评论:

这个脚本很有潜力

 ls -R | sed -n -E '/:$/h;/[^:]$/{G;s|(.*)\n(.*):|/|p}'

虽然排除病理病例,但仍需要按照规定进行过滤。

值得注意(或者我相信,待测试)文件名中唯一不允许的字节代码是 \x0/.

不使用 --dired 的技术可能涉及 ls -p -Q 和传统的古老名称通配,man -s 7 glob

待完成(可能未成功)...敬请期待,同一时间,同一频道...