bash迭代文件列表,除非为空


33

我以为这很简单-但事实证明它比我预期的要复杂。

我想遍历目录中特定类型的所有文件,所以我这样写:

#!/bin/bash

for fname in *.zip ; do
   echo current file is ${fname}
done

只要目录中至少有一个匹配文件,此方法就起作用。但是,如果没有匹配的文件,我会得到:

current file is *.zip

然后,我尝试:

#!/bin/bash

FILES=`ls *.zip`
for fname in "${FILES}" ; do
    echo current file is ${fname}
done

虽然没有文件时循环主体不执行,但是我从ls收到错误:

ls: *.zip: No such file or directory

如何编写一个干净地处理没有匹配文件的循环?


7
shopt -s nullglob在运行for循环之前添加。
cuonglm

@cuolnglm:怪异的是,如果我执行FILES=ls * .zip ; for fname in "${FILES}"...,这会导致ls返回执行脚本的名称,而不是此RHEL5框上的空列表(bash 3.2.25),但它确实可以正常工作for fname in *.zip ; do....
symcbean 2015年

3
使用for file in *.zip,而不是`ls ...`。@cuonglm的建议是,*.zip当模式与任何文件都不匹配时,它扩展为空。ls不带参数列出当前目录。
斯特凡Chazelas

1
这个问题讨论为什么ls通常避免解析输出:为什么解析ls; 还请参见该页面顶部附近的BashGuide的ParsingLs文章链接。
下午

Answers:


34

在中bash,您可以设置nullglob选项,以使不匹配任何内容的模式“消失”,而不是视为文字字符串:

shopt -s nullglob
for fname in *.zip ; do
   echo "current file is ${fname}"
done

在POSIX shell脚本中,只需验证fname存在(并与[ -f ],同时检查它是常规文件(或符号链接到常规文件),而不是目录/ fifo / device ...等其他类型):

for fname in *.zip; do
    [ -f "$fname" ] || continue
    printf '%s\n' "current file is $fname"
done

如果要遍历名称以任何类型结尾的所有(非隐藏)文件,请替换[ -f "$fname" ]为。[ -e "$fname" ] || [ -L "$fname ].zip

如果您还想考虑名称以结尾的隐藏文件,请替换*.zip为。.*.zip .zip *.zip.zip


1
shopt -s nullglob在Ubuntu 17.04上对我不起作用,但[ -f "$fname" ] || continue效果很好。
koppor

@koppor听起来您实际上并没有使用bash
chepner's

2
set ./*                               #set the arg array to glob results
${2+":"} [ -e "$1" ] &&               #if more than one result skip the stat "$1"
printf "current file is %s\n" "$@"    #print the whole array at once

###or###

${2+":"} [ -e "$1" ] &&               #same kind of test
for    fname                          #iterate singly on $fname var for array
do     printf "file is %s\n" "$fname" #print each $fname for each iteration
done                                  

在这里的评论中,您提到了调用函数...

file_fn()
    if     [ -e "$1" ] ||               #check if first argument exists
           [ -L "$1" ]                  #or else if it is at least a broken link
    then   for  f                       #if so iterate on "$f"
           do : something w/ "$f"
           done
    else   command <"${1-/dev/null}"    #only fail w/ error if at least one arg
    fi

 file_fn *

2

使用查找

export -f myshellfunc
find . -mindepth 1 -maxdepth 1 -type f -name '*.zip' -exec bash -c 'myshellfunc "$0"' {} \;

export -f为此,您必须导出shell函数。现在find执行bash将执行您的shell函数,并且仅保留在当前目录级别。


通过子目录递归,我想为匹配调用bash函数(不是脚本)。
symcbean 2015年

@symcbean我已编辑为限制为单个目录并处理bash函数
Dani_l 2015年

-3

代替:

FILES=`ls *.zip`

尝试:

FILES=`ls * | grep *.zip`

这样,如果ls失败(在您的情况下这样做),它将grep失败的输出并作为空白变量返回。

current file is      <---Blank Here

您可以为此添加一些逻辑,以使其返回“找不到文件”

#!/bin/bash

FILES=`ls * | grep *.zip`
if [[ $? == "0" ]]; then
    for fname in "$FILES" ; do
        echo current file is $fname
    done
else
    echo "No Files Found"
fi

这样,如果前一个命令成功(以0值退出),它将打印当前文件,否则将打印“找不到文件”


1
我认为这是一个坏主意,再添加一个过程(grep),而不是试图通过使用更好的工具(来解决问题find),或者改变了当前解决方案(有关设置shopt -s nullglob
托马斯巴吕谢尔

1
根据OP在其原始帖子上的评论,该链接shopt -s nullglob无效。我find在验证答案时尝试过,但一直失败。我认为是因为丹妮说的出口话。
Kip K
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.