【问题标题】:How to get full paths recursively in UNIX? [closed]如何在 UNIX 中递归获取完整路径? [关闭]
【发布时间】:2018-11-01 19:05:10
【问题描述】:

我正在寻找一种方法来递归地获取 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`

这件事至少做了一些事情,但仍然 - 甚至没有接近所需的结果。
然后老师告诉我,这是他第一年教这门课程,这是很久以前由大学不同部门设计的,他作为一个 UNIX 用户,只需使用find 他不知道解决方案
但他发誓一定是在课程设计文档中的某个地方看到过它,或者某个地方……

那么,有没有办法在不查找的情况下获得文件路径的递归列表? 哪一个聪明的 UNIX 技巧和头脑体操是这方面的关键?

【问题讨论】:

  • 事情是这样的,所以不是“请为我做我的家庭作业”类型的服务。
  • 哦,我不是要我做作业!那将是多么奇特的小作业啊!我要求解决一个简单的技术问题,我缺乏专业知识来回答自己。我想知道更多,就是这样。
  • 如果作业不允许您使用find,那么问题的解决方案如何不是作业的一部分?
  • 只是为了明确一点,如果这很重要,我已经完成了任务。我已经把它交给了,以相当愚蠢的方式避免了这个问题。我在 SO 上问过这个问题,因为我真的对如何以正确的方式做到这一点很感兴趣。该解决方案可能会使用一些我错过或误解的概念。归根结底,我只是想更好地了解 UNIX。这是提出问题的不当理由吗?
  • 请将您的老师推荐给mywiki.wooledge.org/ParsingLs

标签: bash shell unix ksh


【解决方案1】:

——— 使用 globstar ———

我需要一个命令,它可以递归地列出所有文件的路径 [...]。
[...]
命令应该尽可能简单。

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

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

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

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

并解决显式分配

递归输出文件内容,文件名以.txt结尾

shopt -s globstar
cat **/*.txt

递归统计所有文件中以f开头的文件行数

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

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


——— 使用 ls/grep ———

老师告诉我们,只使用 ls、引号,也许还有管道和 grep 就可以做到这一点

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

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

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

grep -RLE '$^'

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

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

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

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

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

——— 结束语———

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

【讨论】:

  • 绝妙的答案!谢谢你。我可以要求详细说明您使用 sed 的想法吗?只是为了让你的答案尽可能好。
  • @IvanFedotov 我不想将此添加到答案中,因为与 grep 解决方案相比它真的很糟糕,并且向不熟悉更高级 sed 命令的人解释它需要一些时间。 您可以使用ls -R | sed -n -E '/:$/h;/[^:]$/{G;s|(.*)\n(.*):|\2/\1|p}' 列出工作目录中的所有路径(不包括./)。假设没有路径包含换行符或以: 结尾。请注意,您必须只过滤掉文件会更加困难(可能需要解析ls -Rl)。
  • 我喜欢grep的解决方案,但问题是-R-L都没有在POSIX grep中可用,所以我觉得很难和“设计很久以前”评论——我不得不认为他们在想什么是香草的用途。当然,这一切都可能是教练的诡计。
【解决方案2】:

总结:您可以只使用外壳,无需外部工具。就是在下面。你也可以只使用ls -R 加上一些shell,或者只使用工具。见我的other answer.

我真的很想知道如何以正确的方式做到这一点。

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

查找实用程序应从路径指定的每个文件递归地降低目录层次结构,对遇到的每个文件评估由操作数部分中描述的主元素组成的布尔表达式。

我会让你的导师明白这一点,并假设这不是一些微不足道的学术练习。我假设这个作业有一些实用性,比如:

“您已掉入一个损坏的 UNIX 系统,该系统的大部分工具集已被删除,包括其 find 命令。您需要对目录结构进行分类。您所拥有的只有 ls、@987654329 “ (1)

(这不是很牵强。我曾经对一个由于错误的mount 指令而丢失了/usr/bin 的系统进行分类。我不得不只使用像echo 这样的shell 内置插件来诊断和恢复它。 )

鉴于此:

$ 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

那么,有没有办法在不查找的情况下获得文件路径的递归列表?

是的。我们可以在这些条件下仅使用 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 在主题。

【讨论】:

    【解决方案3】:

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

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

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

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

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

    【讨论】:

      【解决方案4】:

      注意!
      在上面的答案https://stackoverflow.com/a/53109541/16881092
      注意:

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

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

       grep -RLE '$^'
      

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

      -E '$^' 是一个永远不会匹配的正则表达式。

      事实上,它没有为Listing 文件提供消除歧义的潜力。
      比较:

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

      但是,进一步挥手可以通过制作两个文件列表来挽救该技术;匹配的和不匹配的。 (据推测,concatenating 两个列表与以下 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的cmets:
      How to get full paths recursively in UNIX?
      这个脚本很有潜力

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

      虽然消除了病理性病例,但仍需要按所述过滤。

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

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

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

      【讨论】:

        猜你喜欢
        • 2012-01-11
        • 2012-04-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-31
        • 1970-01-01
        相关资源
        最近更新 更多