检查shell脚本中是否存在带有通配符的文件

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

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

我也试过不加双引号。

433433 次浏览

解决方案:

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


> Exists

试试这个

fileTarget="xorg-x11-fonts*"


filesFound=$(ls $fileTarget)


case ${filesFound} in
"" ) printf "NO files found for target=${fileTarget}\n" ;;
* ) printf "FileTarget Files found=${filesFound}\n" ;;
esac

测试

fileTarget="*.html"  # Where I have some HTML documents in the current directory


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*

注意,这只适用于当前目录,或者变量fileTarget包含你想要检查的路径。

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

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

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

< a href = " https://stackoverflow.com/a/34195247/430062 " > < / >来源


最简单的方法应该是依赖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的回答非常相似,但它避免了在整个列表中进行不必要的迭代。

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

这将适用于多个文件,文件名中有空白。

如果你的shell有一个nullglob选项并且它被打开,一个不匹配任何文件的通配符模式将从命令行中完全删除。这将使ls看不到路径名参数,列出当前目录的内容并成功,这是错误的。GNU 统计,如果没有参数或参数命名一个不存在的文件,它总是失败,它将更加健壮。另外,>重定向操作符是bashism。

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

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

if test -n "$(find /dir/to/search -maxdepth 1 -name 'files*' -print -quit)"
then
echo found
else
echo not found
fi

非gnu版本的找到可能没有这里使用的maxdepth选项,以使找到只搜索/ dir / /搜索,而不是整个目录树。

man test

if [ -e file ]; then
...
fi

将工作目录和文件。

使用:

files=(xorg-x11-fonts*)


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

使用:

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

我用这个:

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

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
}

我认为这是有帮助的,因为最初的问题的概念包括“壳”;一般不只是Bash或Linux,也适用于有相同问题的PowerShell用户。

恕我直言,在测试文件、glob或目录时,最好总是使用find。这样做的绊脚石是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再次失败,因为find只打印到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是打印到标准输出的命令时要小心。在这种情况下,您可能需要使用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- branches。要区分大小写,请使用$?

我使用的Bash代码:

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

严格来说,如果你只想打印“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

这里有一个解决您的具体问题的解决方案,它不需要for循环或诸如lsfind之类的外部命令。

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

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

如果我们推广这个过程,但是,我们应该考虑到这样一个事实,即文件可能包含空格在它们的名称和/或路径中,并且glob char可以正确地扩展为零(在你的例子中,这将是一个名为完全 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展开的结果,以及一个虚假的形参)将合并为一个。另一种方法是,如果有多个实参,就引发错误,还有一种方法是忽略除第一个实参以外的所有实参。

函数体中的第二行将files变量设置为由glob扩展到的所有文件名组成的数组,每个数组元素一个文件名。如果文件名包含空格,则没问题,每个数组元素将包含名称就像,包括空格。

函数体中的第三行做了两件事:

  1. 它首先检查数组中是否有多个元素。如果是这样,这意味着glob 肯定被扩展为某些内容(由于我们在第一行所做的工作),这反过来意味着至少有一个匹配glob的文件存在,这就是我们想知道的。

  2. 如果step 1;我们发现数组中的元素少于2个,然后检查是否有一个,如果有,我们检查是否存在,通常的方法。我们需要做这个额外的检查,以考虑函数参数没有 glob字符,在这种情况下,数组只包含一个unexpand ,元素。

您可以执行以下操作:

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

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

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

使用:

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

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

最后我得到了:

if [ -n "$(find somedir/that_may_not_exist_yet -maxdepth 1 -name \*.ext -print -quit)" ] ; then
echo Such file exists
fi
if [ `ls path1/* path2/* 2> /dev/null | wc -l` -ne 0 ]; then echo ok; else echo no; fi

您还可以删除其他文件

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

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。我很高兴我没有踩进去;)

我发现了一些值得分享的巧妙解决方案。第一个仍然遭受“如果有太多匹配,这将会打破”;问题:

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

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

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

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

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

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