关于sh:检查shell脚本中是否存在带通配符的文件

Check if a file exists with wildcard in shell script

本问题已经有最佳答案,请猛点这里访问。

我正在尝试检查文件是否存在,但使用通配符。下面是我的例子:

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

我也试过没有双引号。


最简单的应该是依赖ls返回值(当文件不存在时返回非零):

1
2
3
4
5
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

1
2
3
4
5
6
7
8
9
10
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的答案非常相似,但它避免了整个列表中不必要的迭代。


如果您的shell有一个nullglob选项,并且它已经打开,那么一个不匹配任何文件的通配符模式将从命令行中完全删除。这将使ls看不到路径名参数,列出当前目录的内容并成功,这是错误的。gnu stat,如果没有参数或者参数命名一个不存在的文件,它总是会失败,这将更加健壮。此外,&;>重定向运算符是一种bashim。

1
2
3
4
5
6
if stat --printf='' /path/to/your/files* 2>/dev/null
then
    echo found
else
    echo not found
fi

更好的是gnu find,它可以在内部处理通配符搜索,并在找到一个匹配的文件时立即退出,而不是浪费时间处理由shell扩展的可能巨大的文件列表;这还避免了shell可能溢出其命令行缓冲区的风险。

1
2
3
4
5
6
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,而不是根目录树的整个目录树。


这是我的答案-

1
2
3
4
5
6
files=(xorg-x11-fonts*)

if [ -e"${files[0]}" ];
then
    printf"BLAH"
fi


1
2
3
for i in xorg-x11-fonts*; do
  if [ -f"$i" ]; then printf"BLAH"; fi
done

这将适用于多个文件和文件名中的空白。


您可以执行以下操作:

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

这适用于sh和派生词:ksh和bash。它不会创建任何子shell。$(…)和`…`命令创建一个子shell:它们分叉一个进程,而且效率低下。当然,它可以处理多个文件,而且这个解决方案可以是最快的,也可以是第二快的。

在没有火柴的情况下也能用。正如一位演讲人所说,不需要使用nullglob。$1将包含原始测试名称,因此测试-f$1不会成功,因为$1文件不存在。


更新:

好吧,现在我有了解决方案:

1
2
3
4
5
6
7
8
9
files=$(ls xorg-x11-fonts* 2> /dev/null | wc -l)
if ["$files" !="0" ]
then
   echo"Exists"
else
    echo"None found."
fi

> Exists


也许这对某人有帮助:

1
2
3
if ["`echo xorg-x11-fonts*`" !="xorg-x11-fonts*" ]; then
    printf"BLAH"
fi


这个问题并不是针对Linux/bash的,所以我想我应该添加PowerShell方法——它处理通配符的方式不同——您将它放在引号中,如下所示:

1
2
3
4
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",下面是解决方案:

1
find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 'BLAH' -quit

另一种方法是:

1
2
3
4
5
6
7
doesFirstFileExist(){
    test -e"$1"
}

if doesFirstFileExist xorg-x11-fonts*
then printf"BLAH"
fi

但我认为最理想的方法是如下,因为它不会尝试对文件名进行排序:

1
2
3
if [ -z `find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 1 -quit` ]
then printf"BLAH"
fi


我使用的bash代码

1
2
3
if ls /syslog/*.log > /dev/null 2>&1; then
   echo"Log files are present in /syslog/;
fi

谢谢!


这里有一个针对您的特定问题的解决方案,它不需要for循环或外部命令,如lsfind等。

1
2
3
if ["$(echo xorg-x11-fonts*)" !="xorg-x11-fonts*" ]; then
    printf"BLAH"
fi

正如你所看到的,这只是比你所期望的要复杂一点,它依赖于这样一个事实:如果shell不能扩展glob,它就意味着不存在具有该glob的文件,并且echo将按原样输出glob,这允许我们做一个简单的字符串比较来检查这些文件是否存在。

但是,如果我们要推广该过程,我们应该考虑到这样一个事实:文件的名称和/或路径中可能包含空格,并且glob char可以正确地扩展为Nothing(在您的示例中,该文件的名称正好是xorg-x11-fonts)。

这可以通过bash中的以下函数来实现。

1
2
3
4
5
function doesAnyFileExist {
   local arg="$*"
   local files=($arg)
   [ ${#files[@]} -gt 1 ] || [ ${#files[@]} -eq 1 ] && [ -e"${files[0]}" ]
}

回到您的示例,可以这样调用它。

1
2
3
if doesAnyFileExist"xorg-x11-fonts*"; then
    printf"BLAH"
fi

glob扩展应该发生在函数本身中,以便它正常工作,这就是为什么我将参数放在引号中的原因,这就是函数体中第一行的作用:这样,任何多个参数(可能是函数外部glob扩展的结果,也可能是伪参数)都可以合并为一个参数。另一种方法可能是,如果有多个参数,则会引发错误;另一种方法可能是忽略除第一个参数以外的所有参数。

函数体中的第二行将filesvar设置为一个数组,该数组由全局扩展到的所有文件名组成,每个数组元素一个。如果文件名包含空格,那么每个数组元素都将包含原样的名称,包括空格。

函数体中的第三行有两个功能:

  • 它首先检查数组中是否有多个元素。如果是这样的话,那就意味着地球肯定扩展到了某个地方(由于我们在第一行所做的),这反过来意味着至少存在一个与地球匹配的文件,这就是我们想要知道的。

  • 如果在步骤1。我们发现数组中的元素少于2个,然后我们检查是否有一个元素,如果有,我们检查这个元素是否存在,通常的方法。我们需要做这个额外的检查,以说明没有glob字符的函数参数,在这种情况下,数组只包含一个未展开的元素。


  • 我用这个:

    1
    2
    3
    4
    filescount=`ls xorg-x11-fonts* | awk 'END { print NR }'`  
    if [ $filescount -gt 0 ]; then  
        blah  
    fi


    1
    if [ `ls path1/* path2/* 2> /dev/null | wc -l` -ne 0 ]; then echo ok; else echo no; fi

    imho最好在测试文件、全局或目录时始终使用find。这样做的绊脚石是find的退出状态:如果所有路径都成功遍历,则为0;否则为0。您传递给find的表达式在其退出代码中不创建回声。

    以下示例测试目录是否有条目:

    1
    2
    3
    4
    $ mkdir A
    $ touch A/b
    $ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . && echo 'not empty'
    not empty

    A没有文件时,grep失败:

    1
    2
    3
    $ rm A/b
    $ find A -maxdepth 0 -not -empty -print | head -n1 | grep -q . || echo 'empty'
    empty

    A不存在时,由于find只打印到stderr,grep再次失败:

    1
    2
    3
    4
    $ 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*

    1
    2
    3
    4
    5
    6
    if find -maxdepth 0 -name 'xorg-x11-fonts*' -print | head -n1 | grep -q .
    then
        : the glob matched
    else
        : ...not
    fi

    注意,如果xorg-x11-fonts*不匹配,或者find遇到错误,则到达另一个分支。为了区分案件,使用$?


    找到了几个值得分享的解决方案。第一个问题仍然是"如果匹配太多,这将中断"问题:

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

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

    如果脚本中有"shopt-s nullglob",您只需执行以下操作:

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

    现在,如果一个目录中可能有大量的文件,那么使用find非常困难:

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

    怎么样

    1
    2
    3
    4
    5
    6
    if ls -l  | grep -q 'xorg-x11-fonts.*' # grep needs a regex, not a shell glob
    then
         # do something
    else
         # do something else
    fi


    如果使用通配符的网络文件夹上有大量文件,则存在问题(速度或命令行参数溢出)。

    我的结局是:

    1
    2
    3
    if [ -n"$(find somedir/that_may_not_exist_yet -maxdepth 1 -name \*.ext -print -quit)" ] ; then
      echo Such file exists
    fi

    您还可以剪切其他文件

    1
    2
    3
    if [ -e $( echo $1 | cut -d"" -f1 ) ] ; then
       ...
    fi


    在ksh、bash和zsh shell中使用新的花式shmancy特性(本例不处理文件名中的空格):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 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

    是的,这个闻起来像珍珠。很高兴我没有插手;)


    试试这个

    1
    2
    3
    fileTarget="xorg-x11-fonts*"

    filesFound=$(ls $fileTarget)  # 2014-04-03 edit 2: removed dbl-qts around $(...)

    编辑2014-04-03(删除dbl引号并添加测试文件"charlie 22.html"(2个空格)

    1
    2
    3
    4
    5
    6
    case ${filesFound} in
     "" ) printf"NO files found for target=${fileTarget}
    " ;;
       * ) printf"FileTarget Files found=${filesFound}
    " ;;
    esac

    试验

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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包含您要检查的路径。


    人体试验

    1
    2
    3
    if [ -e file ]; then
    ...  
    fi

    适用于dirfile。

    当做