如何使用Bash脚本测试多个文件是否存在

30

我如何使用带通配符参数的test命令来检查任意数量的文件?

例如:

test -f /var/log/apache2/access.log.* && echo "exists one or more files"

目前,它打印出

error: bash: test: too many arguments

1
http://en.wikipedia.org/wiki/Glob_%28programming%29 - Ignacio Vazquez-Abrams
11个回答

54

这个解决方案对我来说更直观:

if [ `ls -1 /var/log/apache2/access.log.* 2>/dev/null | wc -l ` -gt 0 ];
then
    echo "ok"
else
    echo "ko"
fi

2
对我来说,这是对问题最相关的答案。 - Ken Ingram
这正是我正在寻找的。 - jimh
3
好主意,但可以更简单地实现:如果ˋls -/var/log/apache2/access.log.* 2>/dev/null 1>&2ˋ也能正常工作,则不需要使用test、反引号和wc -l。 - Gnadelwartz
see my example below - Gnadelwartz

12

为避免“参数过多错误”,你需要使用xargs。不幸的是,test -f 不支持多个文件。下面这个单行命令应该可以解决问题:

for i in /var/log/apache2/access.log.*; do test -f "$i" && echo "exists one or more files" && break; done

顺便提一下,/var/log/apache2/access.log.*被称为shell-globbing,而不是正则表达式。请参见Confusion with shell-globbing wildcards and Regex获取更多信息。


9

首先,将文件存储在目录中并以数组形式呈现:

logfiles=(/var/log/apache2/access.log.*)

然后对数组的计数进行测试:

if [[ ${#logfiles[@]} -gt 0 ]]; then
  echo 'At least one file found'
fi

好主意。但是当没有文件存在时,数组内容将为['/var/log/apache2/access.log.*'](包含“*”的一个元素)。 建议:[[ ${#logfiles[@]} -gt 1 || -e ${logfiles[0] ]] 或者简单地使用 [[ -e ${logfiles[0] ]](就像dmaticzka所做的那样)。 - simohe
我建议添加 shopt -s nullglob 以避免如果没有文件存在,则在数组内容中有一个元素。在这种情况下,变量将为空,并且后续测试更容易进行。 - zero0cool
@simohe @skupjoe,是否考虑为常规文件添加测试?if [[ ${#logfiles[@]} -gt 0 ]] && [[ -f ${#logfiles[@]} ]] ; then。对于晚来的派对,抱歉,那么shopt -s nullglob就不必要了吗? - sunta3iouxos
@zero0cool 感谢你的发现!我怎么能错过那个呢? :) 而且 @sunta3iouxos 的建议很好。测试应该是:if [[ ${#logfiles[@]} -gt 0 ]] && [[ -e ${logfiles[@]} ]]; then 来捕捉到 0 个文件的情况!或者使用 -f-d 来仅测试文件/目录 ;)。 - skupjoe

6
这个适用于与非官方Bash严格模式一起使用,当没有找到文件时没有非零退出状态。
数组logfiles =(/ var / log / apache2 / access.log.*)将始终包含至少一个未展开的通配符,因此可以简单地测试第一个元素是否存在:
logfiles=(/var/log/apache2/access.log.*)

if [[ -f ${logfiles[0]} ]]
then 
  echo 'At least one file found'
else
  echo 'No file found'
fi

测试数组中的 [0] 是个好主意。 - ajaaskel

5
如果你想要批处理文件列表,而不是对每个文件执行单独的操作,可以使用find命令,将结果存储在变量中,然后检查该变量是否为空。例如,我使用以下命令编译源目录中的所有.java文件。
SRC=`find src -name "*.java"`
if [ ! -z $SRC ]; then
    javac -classpath $CLASSPATH -d obj $SRC
    # stop if compilation fails
    if [ $? != 0 ]; then exit; fi
fi

3
你只需要测试一下ls是否有东西可以列出:
ls /var/log/apache2/access.log.* >/dev/null 2>&1 && echo "exists one or more files"

为什么这个被踩了?重定向很丑陋吗?最好使用显式的if语句吗?不应该在脚本中使用ls命令吗? - kevmitch

2

主题上的变化:

if ls /var/log/apache2/access.log.* >/dev/null 2>&1
then 
  echo 'At least one file found'
else
  echo 'No file found'
fi

1
ls -1 /var/log/apache2/access.log.* | grep . && echo "One or more files exist."

1

或者使用 find

if [ $(find /var/log/apache2/ -type f -name "access.log.*" | wc -l) -gt 0 ]; then
  echo "ok"
else
  echo "ko"
fi

1
我建议在“-type f”之前添加“-maxdepth 1”,以避免进入子目录。为了加速,使用以下命令:find /var/log/apache2/ -maxdepth 1 -type f -name "access.log.*" -print -quit(在第一个匹配后退出)。 - simohe

1
下面这个条件不会产生stderr。条件中的黑洞(/dev/null)并不能阻止cmd中的stderr。
if [[ $(ls -1 /var/log/apache2/access.log.* | wc -l ) -gt 0 ]] 2> /dev/null

因此,我建议使用这段代码。
if [[ $(ls -1 /var/log/apache2/access.log.* | wc -l ) -gt 0 ]] 2> /dev/null 
then
    echo "exists one or more files."
fi

它也能处理损坏的符号链接吗? - stefcud
如果符号链接的目标发生了变化,您也可以更改损坏的符号链接以连接已更改的目标。假设链接对为{target:symlink}。我使用后缀1更改目标名称。因此,没有目标了,但是有了target1。您的符号链接已经损坏。但是,如果您使用“ln -s -f target1 symlink”(-f是强制写入现有符号链接文件选项),则您的符号链接将继续存在。 - Constantin Hong
另外,我将上面脚本的条件更改为符号链接,效果非常好。 - Constantin Hong
您的回答目前表述不清,请 [编辑] 添加更多细节,以帮助其他人理解此回答如何回答所提出的问题。您可以在 帮助中心 中找到有关编写良好回答的更多信息。 - Community

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