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

时间:2018-11-01 19:05:11

标签: bash shell unix ksh

我正在寻找一种方法来递归获取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使用其stdin中提供的文件名,并且没有内置递归功能,因此您必须提供文件路径列表。

丑陋的方式

我决定尽可能避免出现此问题,并这样做:

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

这有效。老师似乎很不高兴,称这种方法凌乱而丑陋,但他接受了我的报告。我对应该如何做感到好奇。

断路

与老师纠缠了一个多月后,他同意向我展示可以做到这一点的正确方式。

他打了这个字

cat `ls -R $PWD`

这似乎只会导致错误,并且不会创建任何与所需结果类似的东西。

然后他想出了:

cat $PWD/`ls -R`

这件事至少起到了一定作用,但仍然-甚至没有达到要求的结果。
老师然后告诉我,这是他上这门课程的第一年,这是很久以前由uni的不同部门设计的,并且作为UNIX用户,他将使用find和< strong> 他不知道解决方案
但是他发誓一定在课程的设计文档中某个地方看到了它,或者在某个地方看到了它。

因此,有没有一种方法可以在没有找到的情况下获取文件路径的递归列表? UNIX技巧和心理体操中哪一个巧妙的技巧是关键?

3 个答案:

答案 0 :(得分:2)

———使用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 '$^'

-Rgrep递归应用于所有文件。 -E '$^'是一个永远不会匹配的正则表达式。 -L打印所有不匹配的文件。

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

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

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

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

———结束语———

我认为,此作业很糟糕,并不是因为它可能无法解决,而是因为它教给了不良做法(例如,使用假设不正确的工具,...)。

答案 1 :(得分:1)

TL; DR:您只能使用外壳程序,而无需外部工具。在下面您也可以仅使用ls -R加上一些shell或仅使用工具来完成此操作。看看我的other answer.

  

我对如何正确地执行此操作非常感兴趣。

“正确”的方式是find。那是完成这项工作的工具。它是在POSIX中定义的:

  

find实用程序应从路径指定的每个文件中递归地从目录层次结构中降级,为遇到的每个文件评估由OPERANDS部分中描述的原语组成的布尔表达式。

我会让您的老师从疑惑中受益,并认为这不是一些琐碎的学术活动。我假设该作业具有一些实用性,例如:

  

“您已经被放入一个损坏的UNIX系统中,该系统已删除了大部分工具集,包括find命令。您需要对目录结构进行分类。您所拥有的只是{{1} },ls和经典的Bourne shell。您知道文件名是常规名称:文件名中没有空格,文件中没有前导破折号,文件中没有控制字符,等等。您将如何做? (1)

(到目前为止还没有实现。我曾经对一个由于错误的grep指令而丢失的/usr/bin的系统进行了分类。我只能使用像shell这样的内置程序来诊断和恢复它mount。)

为此:

echo

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

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

因此,有没有一种方法可以在没有找到的情况下获取文件路径的递归列表?

是的。我们可以在以下条件下使用内置的shell来解决

$ 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 之类的程序,而无需回显匹配项。由于全部为外壳,因此您可以跟踪变量以求和,等等。

但是,这几乎没有表现力,并且要排除“怪异”文件名。另外,它与find解决方案也不相同:wc的输出按inode顺序排列,而我的shell解决方案按语言环境排列。这些可能与我的示例不同。

这也不是进行递归下降的唯一方法,而只是显而易见的方法。有关没有find的递归下降的替代版本,请参见Rich's POSIX sh tricks


(1)如果您的讲师认为可以通过包含空格,控制字符,破折号等深奥的文件名来正确完成此操作,那么建议您的讲师阅读David Wheeler的treatise (rant)关于这个问题。

答案 2 :(得分:0)

如果您正在寻找一个纯工具解决方案(与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

后一个是我能最接近您的讲师给定参数的一个