我试图检查一个文件是否存在,但与通配符。以下是我的例子:

if [ -f "xorg-x11-fonts*" ]; then
    printf "BLAH"
fi

我也试过不加双引号。


当前回答

您可以执行以下操作:

set -- xorg-x11-fonts*
if [ -f "$1" ]; then
    printf "BLAH"
fi

这适用于sh和衍生品:KornShell和Bash。它不会创建任何子壳。$(..)和'…在其他解决方案中使用的命令会创建一个子shell:它们会派生一个进程,而且效率很低。当然,它可以处理多个文件,而且这个解决方案可能是最快的,或者仅次于最快的。

当没有任何匹配时,它也能工作。没有必要像某位评论员所说的那样使用nullglob。$1将包含原始的测试名称,因此test -f $1将不会成功,因为$1文件不存在。

其他回答

我发现了一些值得分享的巧妙解决方案。第一个仍然存在“如果匹配太多就会破坏”的问题:

pat="yourpattern*" matches=($pat) ; [[ "$matches" != "$pat" ]] && echo "found"

(回想一下,如果你使用一个没有[]语法的数组,你会得到数组的第一个元素。)

如果你在你的脚本中有"shop -s nullglob",你可以简单地这样做:

matches=(yourpattern*) ; [[ "$matches" ]] && echo "found"

现在,如果一个目录中可能有大量的文件,你很可能会使用find:

find /path/to/dir -maxdepth 1 -type f -name 'yourpattern*' | grep -q '.' && echo 'found'

在KornShell, Bash和Z shell 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 first 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

是的,这闻起来确实像Perl。我很高兴我没有踩进去;)

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用户。

恕我直言,最好在测试文件、glob或目录时使用find always。这样做的绊脚石是find的退出状态:如果成功遍历所有路径,则为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不存在时,grep再次失败,因为查找只打印到stderr:

$ 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脚本中工作得很好。最初的问题是寻找glob 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遇到错误,就会到达else-branched。使用$?来区分大小写。

对于Bash脚本,最直接和最有效的方法是:

if compgen -G "${PROJECT_DIR}/*.png" > /dev/null; then
    echo "pattern exists!"
fi

即使在有数百万个文件的目录中,这也会非常快地工作,而且不涉及新的子shell。


最简单的应该是依赖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输出,使其完全静音。


下面是一个同样依赖于glob展开的优化,但避免使用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的答案非常相似,但它避免了在整个列表中进行不必要的迭代。