本问题已经有最佳答案,请猛点这里访问。
我正在尝试检查文件是否存在,但使用通配符。下面是我的例子:
if [ -f"xorg-x11-fonts*" ]; then
printf"BLAH"
fi
我也试过没有双引号。
您的代码有两个错误:(1)星号必须在双引号之外(带引号的星号失去了它特殊的通配符含义),以及(2)如果多个文件与模式匹配,多个参数将传递给[命令,很可能导致[退出并出现错误,因此被解释为没有文件匹配。
最简单的应该是依赖ls返回值(当文件不存在时返回非零):
if ls /path/to/your/files* 1> /dev/null 2>&1; then
echo"files do exist"
else
echo"files do not exist"
fi
我重新定向了ls输出,使其完全静音。
编辑:由于这个答案引起了人们的注意(以及非常有用的评论评论),这里有一个优化,它也依赖于全局扩展,但避免了使用ls:
for f in /path/to/your/files*; do
## Check if the glob gets expanded to existing files.
## If not, f here will be exactly the pattern above
## and the exists test will evaluate to false.
[ -e"$f" ] && echo"files do exist" || echo"files do not exist"
## This is all we needed to know, so we can break after the first iteration
break
done
这与@grok12的答案非常相似,但它避免了整个列表中不必要的迭代。
警告:在Debian Almquist shell(dash)中,安装在Debian和Ubuntu-&>中的/bin/sh上,似乎丢弃了退出代码,从而破坏了此解决方案。一个解决办法是改为使用> /dev/null 2>&1进行重定向。
在有许多文件的目录中(可能是由于排序),ls的速度可能相当慢。您可能希望至少使用-U关闭排序。
如果你要处理大量的搜索,把这些文件列成文本文件并查看它会更好吗?
@Musiphil感谢您的输入。我编辑了答案,添加了一个优化的替代方案,消除了使用ls。
@Costiciudatu您检查过当目录名中有空格时,这个选项是如何工作的吗?例如,for f in /path/to/your files*不是被解释为两个论点,/path/to/your和files*吗?我试过用双引号括起来,但没用(即使有文件也找不到)。
@伊兹,你应该把这句话用双引号引起来,但把*放在外面:for f in"/path/to/your files"*应该管用。
@科斯蒂丘杜图确认,谢谢!你可能希望把它和你的答案结合起来,以便更容易找到:)我同时同意了潘卡吉的答案——但很高兴知道另一个答案:)
警告。如果目录包含大量文件,这将失败。"*"的扩展将超过命令行长度限制。
@但是,如果文件本身在其名称中有空格,它仍然会失败,不是吗?
我发布了一个不需要任何for循环,也不需要任何外部工具的解决方案,并且使用包含空格的文件名:stackoverflow.com/a/40808284/566849
单侧注释…并非所有的Linux/Unix设备都支持glob。例如,我工作的一个Unix服务器不支持glob。
如果您的shell有一个nullglob选项,并且它已经打开,那么一个不匹配任何文件的通配符模式将从命令行中完全删除。这将使ls看不到路径名参数,列出当前目录的内容并成功,这是错误的。gnu stat,如果没有参数或者参数命名一个不存在的文件,它总是会失败,这将更加健壮。此外,&;>重定向运算符是一种bashim。
if stat --printf='' /path/to/your/files* 2>/dev/null
then
echo found
else
echo not found
fi
更好的是gnu find,它可以在内部处理通配符搜索,并在找到一个匹配的文件时立即退出,而不是浪费时间处理由shell扩展的可能巨大的文件列表;这还避免了shell可能溢出其命令行缓冲区的风险。
if test -n"$(find /dir/to/search -maxdepth 1 -name 'files*' -print -quit)"
then
echo found
else
echo not found
fi
非GNU版本的find可能没有-maxdepth选项用于使find search仅为/dir/to/search,而不是根目录树的整个目录树。
让find处理通配符是最好的,因为bash在扩展模式时,试图对匹配文件名的列表进行排序,这是浪费的,而且成本高昂。
@如果目录中只有几个文件(或没有),那么启动外部进程(如find)的musiphil会更加浪费。
@多尔曼:你说得对。我想这完全取决于情况;另一方面,如果有大量的文件,那么bash的通配符扩展可能比启动find花费更多的时间。
@更糟的是,如果有大量的通配符扩展,只会失败。但在这种情况下,扩大find的巨大产出也可能失败。还要注意,列出所有带有find just to then测试输出是否为空的文件是对资源的极大浪费。
如果找不到文件,find命令将创建一条难看的错误消息:find: '/dir/to/search': No such file or directory;您可以使用-quit 1> /dev/null 2>&1抑制此错误。
@Dolmen:运行find和-quit,如@flabdablet's post中所述,不会受到大量文件的影响,因为它一旦找到第一个匹配就退出,因此不会列出所有文件。所以这并不像你建议的那样浪费资源。此外,find并不像shell那样简单地"扩展"通配符,而是根据模式检查它找到的每个文件,看它是否匹配,因此对于大量文件来说,它不会失败。
这是我的答案-
files=(xorg-x11-fonts*)
if [ -e"${files[0]}" ];
then
printf"BLAH"
fi
如果zsh报告错误,您应该添加unsetopt nomatch。
和shopt -s nullglob代表bash。
也许应该更清楚地指出,使用一个数组可以使它明显地非posix sh。
for i in xorg-x11-fonts*; do
if [ -f"$i" ]; then printf"BLAH"; fi
done
这将适用于多个文件和文件名中的空白。
如果有多个匹配项,它将打印多个"blah"。可能在第一场比赛后添加一个break退出循环。
这个(有@triplee的休息)得到了我的选票。通过只使用本机globbing和文件测试操作符,它甚至避免了使用ls、find或forwarding globs等命令时出现的角情况问题。我认为它没有为其他答案而提出的所有问题,比如带空格的名称、nullglob设置和bashims。我做了一个函数:existsAnyFile () { for file; do [ -f"$file" ] && return 0; done; false; }。
注意,如果XORG-X11-FONTS*不存在,这将导致stat失败,这可能不是您想要的。
您可以执行以下操作:
set -- xorg-x11-fonts*
if [ -f"$1" ]; then
printf"BLAH"
fi
这适用于sh和派生词:ksh和bash。它不会创建任何子shell。$(…)和`…`命令创建一个子shell:它们分叉一个进程,而且效率低下。当然,它可以处理多个文件,而且这个解决方案可以是最快的,也可以是第二快的。
在没有火柴的情况下也能用。正如一位演讲人所说,不需要使用nullglob。$1将包含原始测试名称,因此测试-f$1不会成功,因为$1文件不存在。
最便携的解决方案!
唉,没有火柴就不行。$1将包含原始测试名称,包括*。您可以在bash中设置"nullglob",这样它将空白。不过,那不是便携式的:)
克里斯,如果没有匹配的话,$1将包含原始测试名称,包括*如您所说。然后测试:[-f"$1"]将无法成功完成,因为文件"*"不存在。因此,您不需要nullglob或其他技巧。它是100%便携式的。
更新:
好吧,现在我有了解决方案:
files=$(ls xorg-x11-fonts* 2> /dev/null | wc -l)
if ["$files" !="0" ]
then
echo"Exists"
else
echo"None found."
fi
> Exists
在我的shell(zsh)中,如果只有一个与glob匹配,那么它就可以工作,否则它将扩展所有文件,测试将失败(参数太多)。
更新我的代码。我确信这是可行的,我刚安装了zsh并测试过。
重新更新。我的错。
在有许多文件的目录中(可能是由于排序),ls的速度可能相当慢。您可能希望至少使用-U关闭排序。
如果globbing匹配一个目录名,ls将吐出该目录的内容,这可能导致误报。
运行ls和wc需要启动两个外部程序。一点效率都没有。
也许这对某人有帮助:
if ["`echo xorg-x11-fonts*`" !="xorg-x11-fonts*" ]; then
printf"BLAH"
fi
这是一个最简单、最简单、最优雅的答案。
@Sergestroobant不确定我是否同意。在这里命令替换可能是必要的,但它会刺激我的畏缩反射。
赞成。。。比如,如果文件的字面名称为xorg-x11-fonts\*,会怎么样?
一点也不优雅,因为它分叉一个子shell来运行echo命令。
不优雅,丑陋的AF
这个问题并不是针对Linux/bash的,所以我想我应该添加PowerShell方法——它处理通配符的方式不同——您将它放在引号中,如下所示:
If (Test-Path"./output/test-pdf-docx/Text-Book-Part-I*"){
Remove-Item -force -v -path ./output/test-pdf-docx/*.pdf
Remove-Item -force -v -path ./output/test-pdf-docx/*.docx
}
我认为这是有帮助的,因为原始问题的概念一般包括"shell",而不仅仅是bash或linux,并且也适用于具有相同问题的PowerShell用户。
严格来说,如果你只想打印"blah",下面是解决方案:
find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 'BLAH' -quit
另一种方法是:
doesFirstFileExist(){
test -e"$1"
}
if doesFirstFileExist xorg-x11-fonts*
then printf"BLAH"
fi
但我认为最理想的方法是如下,因为它不会尝试对文件名进行排序:
if [ -z `find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 1 -quit` ]
then printf"BLAH"
fi
您也可以使用-exec选项,这样查找:find . -maxdepth 1 -name '*.o' -exec rm {} \;
我使用的bash代码
if ls /syslog/*.log > /dev/null 2>&1; then
echo"Log files are present in /syslog/;
fi
谢谢!
这里有一个针对您的特定问题的解决方案,它不需要for循环或外部命令,如ls、find等。
if ["$(echo xorg-x11-fonts*)" !="xorg-x11-fonts*" ]; then
printf"BLAH"
fi
正如你所看到的,这只是比你所期望的要复杂一点,它依赖于这样一个事实:如果shell不能扩展glob,它就意味着不存在具有该glob的文件,并且echo将按原样输出glob,这允许我们做一个简单的字符串比较来检查这些文件是否存在。
但是,如果我们要推广该过程,我们应该考虑到这样一个事实:文件的名称和/或路径中可能包含空格,并且glob char可以正确地扩展为Nothing(在您的示例中,该文件的名称正好是xorg-x11-fonts)。
这可以通过bash中的以下函数来实现。
function doesAnyFileExist {
local arg="$*"
local files=($arg)
[ ${#files[@]} -gt 1 ] || [ ${#files[@]} -eq 1 ] && [ -e"${files[0]}" ]
}
回到您的示例,可以这样调用它。
if doesAnyFileExist"xorg-x11-fonts*"; then
printf"BLAH"
fi
glob扩展应该发生在函数本身中,以便它正常工作,这就是为什么我将参数放在引号中的原因,这就是函数体中第一行的作用:这样,任何多个参数(可能是函数外部glob扩展的结果,也可能是伪参数)都可以合并为一个参数。另一种方法可能是,如果有多个参数,则会引发错误;另一种方法可能是忽略除第一个参数以外的所有参数。
函数体中的第二行将filesvar设置为一个数组,该数组由全局扩展到的所有文件名组成,每个数组元素一个。如果文件名包含空格,那么每个数组元素都将包含原样的名称,包括空格。
函数体中的第三行有两个功能:
它首先检查数组中是否有多个元素。如果是这样的话,那就意味着地球肯定扩展到了某个地方(由于我们在第一行所做的),这反过来意味着至少存在一个与地球匹配的文件,这就是我们想要知道的。
如果在步骤1。我们发现数组中的元素少于2个,然后我们检查是否有一个元素,如果有,我们检查这个元素是否存在,通常的方法。我们需要做这个额外的检查,以说明没有glob字符的函数参数,在这种情况下,数组只包含一个未展开的元素。
这是低效的,因为$(..)启动了一个子shell。
@像其他任何一个过程一样,Dolmen A Sub Shell也只是一个过程。接受的答案启动了ls命令,无论出于何种目的,它都和子shell一样高效(或低效)。
我从来没有写过接受的答案更好,如果我是提交者,我会接受的。
注意,我在这个非常相同的答案中解释的广义方法根本不使用任何子shell。
我用这个:
filescount=`ls xorg-x11-fonts* | awk 'END { print NR }'`
if [ $filescount -gt 0 ]; then
blah
fi
对于这个任务,wc -l比awk更有效。
计算结果的数量无论如何都是一个反模式。通常,您只想查看ls是否返回成功(或者更好地避免ls,并使用shell的内置功能)。
if [ `ls path1/* path2/* 2> /dev/null | wc -l` -ne 0 ]; then echo ok; else echo no; fi
imho最好在测试文件、全局或目录时始终使用find。这样做的绊脚石是find的退出状态:如果所有路径都成功遍历,则为0;否则为0。您传递给find的表达式在其退出代码中不创建回声。
以下示例测试目录是否有条目:
$ mkdir A
$ touch A/b
$ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . && echo 'not empty'
not empty
当A没有文件时,grep失败:
$ rm A/b
$ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . || echo 'empty'
empty
当A不存在时,由于find只打印到stderr,grep再次失败:
$ rmdir A
$ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . && echo 'not empty' || echo 'empty'
find: 'A': No such file or directory
empty
用任何其他find表达式替换-not -empty,但如果您使用-exec命令打印到stdout,则要小心。在这种情况下,您可能希望grep用于更具体的表达式。
这种方法在shell脚本中很好地工作。最初的问题是寻找环球xorg-x11-fonts*:
if find -maxdepth 0 -name 'xorg-x11-fonts*' -print | head -n1 | grep -q .
then
: the glob matched
else
: ...not
fi
注意,如果xorg-x11-fonts*不匹配,或者find遇到错误,则到达另一个分支。为了区分案件,使用$?。
当使用-name时,可能意味着-maxdepth 1,因为-maxdepth 0将查看当前目录而不是其内容。
找到了几个值得分享的解决方案。第一个问题仍然是"如果匹配太多,这将中断"问题:
pat="yourpattern*" matches=($pat) ; [["$matches" !="$pat" ]] && echo"found"
(回想一下,如果使用不带[ ]语法的数组,则会得到数组的第一个元素。)
如果脚本中有"shopt-s nullglob",您只需执行以下操作:
matches=(yourpattern*) ; [["$matches" ]] && echo"found"
现在,如果一个目录中可能有大量的文件,那么使用find非常困难:
find /path/to/dir -maxdepth 1 -type f -name 'yourpattern*' | grep -q '.' && echo 'found'
怎么样
if ls -l | grep -q 'xorg-x11-fonts.*' # grep needs a regex, not a shell glob
then
# do something
else
# do something else
fi
不,不要在脚本中使用ls,.*通配符是多余的(您可能是指grep -q '^xorg-x1-fonts')。
为什么不分析ls。
如果使用通配符的网络文件夹上有大量文件,则存在问题(速度或命令行参数溢出)。
我的结局是:
if [ -n"$(find somedir/that_may_not_exist_yet -maxdepth 1 -name \*.ext -print -quit)" ] ; then
echo Such file exists
fi
您还可以剪切其他文件
if [ -e $( echo $1 | cut -d"" -f1 ) ] ; then
...
fi
这会很慢,因为子壳。如果文件名包含空格怎么办?
在ksh、bash和zsh shell中使用新的花式shmancy特性(本例不处理文件名中的空格):
# Declare a regular array (-A will declare an associative array. Kewl!)
declare -a myarray=( /mydir/tmp*.txt )
array_length=${#myarray[@]}
# Not found if the 1st element of the array is the unexpanded string
# (ie, if it contains a"*")
if [[ ${myarray[0]} =~ [*] ]] ; then
echo"No files not found"
elif [ $array_length -eq 1 ] ; then
echo"File was found"
else
echo"Files were found"
fi
for myfile in ${myarray[@]}
do
echo"$myfile"
done
是的,这个闻起来像珍珠。很高兴我没有插手;)
试试这个
fileTarget="xorg-x11-fonts*"
filesFound=$(ls $fileTarget) # 2014-04-03 edit 2: removed dbl-qts around $(...)
编辑2014-04-03(删除dbl引号并添加测试文件"charlie 22.html"(2个空格)
case ${filesFound} in
"" ) printf"NO files found for target=${fileTarget}
" ;;
* ) printf"FileTarget Files found=${filesFound}
" ;;
esac
试验
fileTarget="*.html" # where I have some html docs in the current dir
FileTarget Files found=Baby21.html
baby22.html
charlie 22.html
charlie21.html
charlie22.html
charlie23.html
fileTarget="xorg-x11-fonts*"
NO files found for target=xorg-x11-fonts*
注意,这只在当前目录中有效,或者var fileTarget包含您要检查的路径。
如果fileTarget包含空白(如fileTarget="my file*"),代码将失败。
@理查德汉森有空白的时候,解决办法是什么?
@罗斯:用公认的答案:if ls"my file"* >/dev/null 2>&1; then ...。
@理查德汉森,谢谢,对不起-不为我工作。现在就把它修好。
@罗斯,我添加了一个编辑到我的应该与文件与空间。基本上是case"${filesFound}" in ....。祝大家好运。
根据case的posix规范,case后面的单词不受字段拆分或路径名扩展的影响,因此您添加的双引号无效。问题在于ls $fileTarget中的场分裂。
@罗斯,理查德汉森在研究一些事情,DBL的引语并不重要,也许这就是为什么我一开始没有这些引语。很久以前了,我不确定。对于这个问题,我回到了我的测试目录,并创建了一个链接,名为charlie 22.html,我已经编辑了上面的结果。这能处理每一个案件和find ... -print0 | xargs -0 ..一样吗?可能不会。它是否在您控制创建哪些文件名的环境中工作?它对我有好处,我的意思是我经常使用这个构造(在我控制创建的文件名的环境中;-)。祝大家好运。
人体试验
if [ -e file ]; then
...
fi
适用于dirfile。
当做
这不适用于通配符(这是这个问题中的问题)。如果它与多个文件匹配,您将得到bash: [: too many arguments。
有点不公平,因为这在Solaris上非常有效……
嘿,老博文,感谢克里斯的支持——我那时确实也在和Solaris合作。