遍历所有具有特定扩展名的文件

187
for i in $(ls);do
    if [ $i = '*.java' ];then
        echo "I do something with the file $i"
    fi
done

我想循环遍历当前文件夹中的每个文件,并检查它是否与特定扩展名匹配。上面的代码不起作用,你知道为什么吗?


5
对于 for i in $(ls *.java); do echo "do something with file $i"; done 呢?(该命令会遍历当前目录下所有扩展名为 .java 的文件,并对每个文件执行一些操作) - speakr
如果语句出了问题,没有办法修复吗? - AR89
1
你正在将 $i 与字面字符串“*.java”进行比较;这里不执行模式扩展。 - chepner
要修复您的if语句,请使用if [[ $i == *.java ]]; then..(请注意双重[[]]和未引用的*.java)。 - that other guy
2
不要解析 ls -- 接受 @chepner 的答案。 - glenn jackman
@glennjackman 是的,但这并不仅是由于糟糕的文件名导致失败。请参见我下面的答案。 - user000001
7个回答

296

不需要花哨的技巧:

for i in *.java; do
    [ -f "$i" ] || break
    ...
done

守卫确保在没有匹配文件的情况下,循环将退出而不会尝试处理不存在的文件名*.java

在(或支持类似功能的shell)中,您可以使用nullglob选项来忽略匹配失败并且不进入循环体。

shopt -s nullglob
for i in *.java; do
    ...
done

针对评论中breakcontinue的讨论,以下提供更多细节。我认为使用break还是continue并不重要,因为第一个循环试图区分两种情况:

  1. *.java没有匹配项,因此视为字面文本。
  2. *.java至少有一个匹配项,该匹配项可能包括一个名为*.java的条目。

在情况#1中,使用break是可以的,因为没有其他的$i值即将到来,而breakcontinue是等效的(尽管我认为break更显式;你正在退出循环,而不仅仅是等待循环被动退出)。

在情况#2中,您仍需要对任何可能的匹配进行必要的过滤。因此,选择breakcontinue的选择比适用于$i的哪个测试(-f-d-e等)不那么相关,而根据我个人经验,这种选择方法并不正确,因为这首先是确定是否“错误地”进入循环的方法。

也就是说,在情况#1中,我不希望检查$i的值,并且在情况#2中,您对该值所做的操作更多地依赖于每个文件的业务逻辑,而不是首先选择要处理的文件的逻辑。我更愿意将这种逻辑留给各自的用户,而不是在问题中表达一种选择或另一种选择。


另外,zsh提供了一种在glob本身中执行此类过滤的方法。您可以只匹配以.java结尾的常规文件(并禁用默认行为,即将未匹配的模式视为错误,而不是字面文本),如下:

for f in *.java(.N); do
  ...
done

有了上述内容,您可以确保如果您到达循环体,则$f会扩展为常规文件的名称。点号(.)使*.java仅匹配常规文件,而N会导致匹配失败时不会产生错误,而是扩展为空。

还有其他类似的通配符限定词用于对文件名扩展进行各种过滤。 (我喜欢开玩笑说,zsh的通配符扩展完全取代了使用find的需要。)


6
最简单的方法是添加另一个模式:for i in *.java *.cpp; do。如果你在 bash 中启用了扩展模式 (shopt -s extglob),你可以写成 for i in *.@(java|cpp); do - chepner
8
如果实际匹配到任何文件,它就能起作用。你需要使用shopt -s nullglob命令,这样不匹配的模式会扩展为空序列而不是按字面处理。 - chepner
3
对于非专业人士,将此指示包含在答案中可能会很有用,因为某些人可能会复制粘贴代码并发现它无法正常工作。一个完美的例子就是某个人在执行重要功能之前将所有“*.jpg”文件转换为“*.png”。 - puk
5
“Instead of [ -f "$i" ] || break, we need [ -f "$i" ] || continue, right?” 可以翻译为:我们需要将“[ -f "$i" ] || break”改为“[ -f "$i" ] || continue”,对吗? - codeforester
4
@codeforester 是正确的,continue 是必需的。如果有一个名为 b.java 的普通文件和一个名为 a.java 的目录,那么在循环到达 b.java 之前可以通过 break 终止循环。 - nekketsuuu
显示剩余12条评论

31

递归添加子文件夹,

for i in `find . -name "*.java" -type f`; do
    echo "$i"
done

为了避免对“find”输出的误解,建议使用以下命令代替:“find . -name“*.java”-type f -exec echo {} ;” - umläute
1
如果你不这样做,至少你应该在循环内引用 "$i" - tripleee
6
如果文件名中有空格,这种方法就行不通。 - codeforester
2
@codeforester 我曾经遇到过这个问题,我使用了这个答案中的方法来解决它:https://askubuntu.com/a/343753/551184 - fsinisi90
1
如果文件名中有空格,代码就会完全崩溃。 - Jamie Hutber

24
循环遍历所有以以下后缀结尾的文件:.img.bin.txt,并打印文件名:
for i in *.img *.bin *.txt;
do
  echo "$i"
done

或以递归方式(也在所有子目录中查找):

for i in `find . -type f -name "*.img" -o -name "*.bin" -o -name "*.txt"`;
do
  echo "$i"
done

1
根据 man find-o(表示逻辑或) - Timo
1
我喜欢你的顶级解决方案并给它点了赞,但如果我没有一个特定扩展名的文件,我会收到一个错误。如果找不到某个扩展名,有什么办法可以忽略它吗? - Matt Cremeens

14

正确答案是@chepner的

EXT=java
for i in *.${EXT}; do
    ...
done

不过,这里有一个小技巧可以检查文件名是否具有给定的扩展名:

EXT=java
for i in *; do
    if [ "${i}" != "${i%.${EXT}}" ];then
        echo "I do something with the file $i"
    fi
done

如果使用变量ext代替.java会怎样呢? - AR89

3

正如@chepner在他的评论中所说,您正在将$i与一个固定字符串进行比较。

为了扩展和纠正这种情况,您应该使用[[ ]]与正则表达式运算符=~

例如:

for i in $(ls);do
    if [[ $i =~ .*\.java$ ]];then
        echo "I want to do something with the file $i"
    fi
done

在=~右侧的正则表达式将针对左操作数的值进行测试,并且不应该被引用。 (引用不会出错,但将与固定字符串进行比较,因此很可能会失败)

但是,@chepner上面的答案使用glob是一个更有效的机制。


使用变量ext代替.java会怎样? - AR89
2
ack,不需要正则表达式:if [[ $i == *.java ]]if [[ $i == *.$ext ]]。但是不要解析ls - glenn jackman
一个美妙的解决方案,因为我可以通过简单地使用for i in $(ls -lR); do ...在子目录中进行搜索,或者如果您想要文件的相对路径:for i in $(find -L .);do ...借助另一个好答案的帮助:https://dev59.com/03VD5IYBdhLWcg3wDXF3#105249 - Mahdad Baghani

3
我同意其他答案关于循环遍历文件的正确方式。然而,OP问道:
“上面的代码无法工作,你知道为什么吗?”
是的!
一个出色的文章 什么是test、[和[[之间的区别? 详细解释了不能在test命令(它是[的简称)中使用表达式匹配或模式匹配等不同之处。
特征                    新的test [[            旧的test [                   示例
模式匹配 =(或==) (不可用) [[ $name = a* ]] || echo "name does not start with an 'a': $name"
正则表达式匹配 =~ (不可用) [[ $(date) =~ ^Fri\ ...\ 13 ]] && echo "It's Friday the 13th!"
因此这就是您的脚本失败的原因。如果OP对使用[[语法(缺点是没有像[命令一样被多个平台支持)的答案感兴趣,我很乐意编辑我的答案来包括它。
编辑:如何将答案中的数据格式化为表格的任何提示都将有所帮助!

3

我发现这个解决方案非常方便,它使用了find命令中的-or选项:

find . -name \*.tex -or -name "*.png" -or -name "*.pdf"

此命令将查找扩展名为texpngpdf的文件。


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