使用'find'命令返回没有扩展名的文件名

22
我有一个目录(包括子目录),我想找到所有扩展名为".ipynb"的文件。但是我希望'find'命令只返回这些文件名而不带扩展名。
我知道第一部分:
find . -type f -iname "*.ipynb" -print    

那么我如何获取没有“ipynb”扩展名的名称呢? 非常感谢您的回复...

9个回答

29

尝试只返回没有扩展名的文件名:

find . -type f -iname "*.ipynb" -execdir sh -c 'printf "%s\n" "${0%.*}"' {} ';'

(现在省略-type f

find "$PWD" -iname "*.ipynb" -execdir basename {} .ipynb ';'

或:
find . -iname "*.ipynb" -exec basename {} .ipynb ';'

或:
find . -iname "*.ipynb" | sed "s/.*\///; s/\.ipynb//"

不过,对每个文件都调用basename可能会效率低下,因此@CharlesDuffy的建议是:

find . -iname '*.ipynb' -exec bash -c 'printf "%s\n" "${@%.*}"' _ {} +

或:
find . -iname '*.ipynb' -execdir basename -s '.sh' {} +

使用 + 的意思是我们将多个文件传递给每个bash实例,因此如果整个列表适合单个命令行,则只调用一次bash。


要在同一行中打印完整路径和文件名(无扩展名),可以尝试:

find . -iname "*.ipynb" -exec sh -c 'printf "%s\n" "${0%.*}"' {} ';'

或:

find "$PWD" -iname "*.ipynb" -print | grep -o "[^\.]\+"

打印文件路径和文件名,分别显示在不同的行上:


find "$PWD" -iname "*.ipynb" -exec dirname "{}" ';' -exec basename "{}" .ipynb ';'

应用“basename”也会丢弃目录组件。 - user1934428
每个文件执行一次 basename 看起来相当低效。使用 find . -name '*.ipynb' -exec bash -c 'printf "%s\n" "${@%.*}"' _ {} + 可以在每批文件中只调用一个 shell,因此开销要小得多。 - Charles Duffy
@CharlesDuffy已添加到列表中。对每个文件都调用bash会不会效率低呢?还是因为'+'的原因而在所有文件上执行? - kenorb
1
@kenorb,+ 表示我们将多个文件传递给每个 bash 实例 -- 如果整个列表适合单个命令行,则只调用一次 bash。 - Charles Duffy
1
@IMTheNachoMan,...在这种情况下,"-exec ... {} +"会多次运行命令(每次使用文件列表的子集),就像"xargs"一样。 - Charles Duffy
显示剩余2条评论

13

这里有一个简单的解决方案:

find . -type f -iname "*.ipynb" | sed 's/\.ipynb$//1'

不需要使用“/1”,因为模式不能匹配多次(假设文件名中没有嵌入换行符)。 - Toby Speight
2
我使用这个,因为它不像bash或basename那样为每个文件分叉一个进程。有点定制,但更快。 - Pysis

5
我发现了一个Bash单行命令,可以简化过程而不使用find命令。
for n in *.ipynb; do echo "${n%.ipynb}"; done

2
只有当文件在当前目录中时,这才有效。OP的原始代码可以在子目录中找到文件。 - Charles Duffy

1
如果您需要带有目录但不带扩展名的名称:
find .  -type f -iname "*.ipynb" -exec sh -c 'f=$(basename $1 .ipynb);d=$(dirname $1);echo "$d/$f"' sh {} \;

更加正确的做法是引用您的扩展:f=$(basename "$1" .ipynb);d=$(dirname "$1"); echo "$d/$f" -- 这样文件名中带有空格或通配符的情况就不容易出现问题。 - Charles Duffy
话虽如此,目前这种方法非常低效——对于每个文件,您都要启动一个新的sh副本,在其中生成子shell并运行非内置程序/bin/basename,然后再生成另一个子shell调用/bin/dirname。使用-exec ... {} +可以让您在多个文件名之间共享单个sh副本(尽管您需要迭代它们而不是硬编码$1);更好的方法是将所有名称流式传输到执行工作的单个子进程中,根本不会启动任何新的每个名称子进程。 - Charles Duffy

0
另一种使用basename的简单方法是:
find . -type f -iname '*.ipynb' -exec basename -s '.ipynb' {} +

使用+将减少命令的调用次数(manpage):

-exec 命令 {} +
这个 -exec 操作的变体在所选文件上运行指定的命令,但是命令行是通过将每个所选文件名附加到末尾来构建的;命令的调用总数将远少于匹配的文件数。命令行的构建方式与 xargs 构建其命令行的方式非常相似。命令中只允许一个 '{}' 实例,并且(当从 shell 调用 find 时)应该对其进行引用(例如,'{}'),以保护它免受 shell 的解释。命令在起始目录中执行。如果任何使用“+”形式的调用返回非零值作为退出状态,则 find 返回非零退出状态。如果 find 遇到错误,这有时会导致立即退出,因此某些挂起的命令可能根本不会运行。出于这个原因,-exec my-command ... {} + -quit 可能不会导致 my-command 实际运行。这个 -exec 的变体总是返回 true。

使用basename-s选项可以接受多个文件名并删除指定的后缀(manpage):

-a, --multiple

支持多个参数,并将每个参数视为名称

-s, --suffix=SUFFIX

删除尾随的SUFFIX;意味着-a


0

Perl一行代码
你想要的内容
find . | perl -a -F/ -lne 'print $F[-1] if /.*.ipynb/g'

排除你不想要的代码
你不想要的内容
find . | perl -a -F/ -lne 'print $F[-1] if !/.*.ipynb/g'

注意
Perl 中,需要多加一个.。所以您的模式应该是.*.ipynb


0
如果您不知道扩展名是什么或有多个可用选项,可以使用以下代码:
find . -type f -exec basename {} \;|perl -pe 's/(.*)\..*$/$1/;s{^.*/}{}'

并且返回一个没有重复文件的列表(最初在路径或扩展名上有所不同)

find . -type f -exec basename {} \;|perl -pe 's/(.*)\..*$/$1/;s{^.*/}{}'|sort|uniq

0
find . -type f -iname "*.ipynb" | grep -oP '.*(?=[.])'

-o标志仅输出匹配的部分。-P标志根据Perl正则表达式进行匹配。这是使lookahead (?=[.])工作的必要条件。


-1
如果除了后缀以外的任何文件名中都没有出现“.ipynb”字符串,则可以尝试使用tr这种更简单的方法:
find . -type f -iname "*.ipynb" -print | tr -d ".ipbyn"

大多数情况下,最简单的答案是最有用的。 - Leo
1
这个答案不好,因为 tr 不关心字符是否有序,它将删除任何一个这些字符的所有出现。例如:echo snipsnap | tr -d ".ipbyn" => ssa - stefansundin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接