测试glob是否在bash中有任何匹配项


223

如果要检查是否存在单个文件,可以使用test -e filename或进行测试[ -e filename ]

假设我有一个glob,我想知道是否存在名称与该glob匹配的任何文件。这个glob可以匹配0个文件(在这种情况下,我什么都不要做),也可以匹配1个或多个文件(在这种情况下,我需要做些什么)。我如何测试一个glob是否有匹配项?(我不在乎有多少个匹配项,最好是我可以只用一条if语句就可以做到这一点,并且没有循环(这是因为我发现它最易读)。

test -e glob*如果全局匹配多个文件,则失败。)


3
我怀疑我在下面的回答“很明显是正确的”,这与其他所有问题一样。这是一个单行的shell内置代码,已经存在了很长时间,并且似乎是“完成此特定工作的目标工具”。我担心用户会在这里错误地引用已接受的答案。任何人都可以随时纠正我,我将在这里撤回我的评论,我很乐意犯错并从中学到东西。如果差异看起来没有那么大,我不会提出这个问题。
Brian Chrisman

1
我最喜欢这个问题的解决方案是find命令这在任何壳(甚至非的Bourne shell)的作品,但需要GNU发现,和compgen命令这显然是一个Bashism。太糟糕了,我不能接受两个答案。
肯·布鲁姆

注意:此问题自被询问以来已被编辑。原始标题是“测试glob是否在bash中有任何匹配项”。我发布答案后,从问题中删除了特定的shell“ bash”。问题标题的编辑使我的答案似乎有误。我希望有人可以修改或至少解决此更改。
布莱恩·克里斯曼

Answers:


178

重击特定的解决方案:

compgen -G "<glob-pattern>"

退出模式,否则它将被预先展开为匹配项。

退出状态为:

  • 1表示不匹配,
  • “一次或多次比赛”为0

stdout与glob匹配的文件列表。
我认为,从简明性和最小化潜在副作用的角度来看,这是最佳选择。

更新:请求示例用法。

if compgen -G "/tmp/someFiles*" > /dev/null; then
    echo "Some files exist."
fi

9
请注意,这compgen是特定于bash的内置命令,不是POSIX标准Unix shell指定的内置命令的一部分。pubs.opengroup.org/onlinepubs/9699919799 pubs.opengroup.org/onlinepubs/9699919799/utilities/...因此,避免在脚本哪里移植到其他炮弹是一个问题使用它。
Diomidis Spinellis

1
在我看来,没有bash内置函数的类似效果将是使用对glob起作用的任何其他命令,如果没有文件匹配则失败,例如ls:if ls /tmp/*Files 2>&1 >/dev/null; then echo exists; fi-可能对代码高尔夫有用吗?如果存在一个与glob相同的文件,而glob不应该匹配,则失败,但是如果那样的话,您可能会遇到更大的问题。
Dewi Morgan's

4
@DewiMorgan这更简单:if ls /tmp/*Files &> /dev/null; then echo exists; fi
Clay Bridges

有关的详细信息compgen,请参阅man bash或与help compgen
el-teedee's

2
是的,引用它,否则文件名通配符将被预先扩展。compgen“ dir / *。ext”
Brian Chrisman'1

169

nullglob shell选项确实是一种bashism。

为了避免对nullglob状态进行繁琐的保存和恢复,我只将其设置在扩展glob的子外壳中:

if test -n "$(shopt -s nullglob; echo glob*)"
then
    echo found
else
    echo not found
fi

为了更好的可移植性和更灵活的通配,请使用find:

if test -n "$(find . -maxdepth 1 -name 'glob*' -print -quit)"
then
    echo found
else
    echo not found
fi

显式-print -quit操作用于查找,而不是默认的隐式-print操作,以便查找到第一个符合搜索条件的文件后,查找将立即退出。在匹配大量文件的情况下,此文件的运行速度应比echo glob*或快得多ls glob*,并且还避免了过度填充扩展命令行的可能性(某些外壳程序的长度限制为4K)。

如果发现感觉有点过头了,并且可能匹配的文件数量很少,请使用stat:

if stat -t glob* >/dev/null 2>&1
then
    echo found
else
    echo not found
fi

10
find似乎是完全正确的。它没有极端的情况,因为外壳程序不进行扩展(并将未扩展的glob传递给其他命令),它可以在外壳程序之间移植(尽管显然不是您使用的所有选项都是由POSIX指定的),并且比ls -d glob*(先前接受的答案),因为它在到达第一个比赛时就停止了。
肯·布鲁姆

1
请注意,shopt -u failglob由于这些选项似乎存在某种冲突,因此可能需要提供答案。
Calimo 2014年

find解决方案将匹配任何水珠字符的文件名也。在这种情况下,这就是我想要的。只是一些需要注意的事情。
我们都是莫妮卡(Monica)

1
显然,由于其他人决定编辑我的答案以使之如此。
flabdablet

1
unix.stackexchange.com/questions/275637/…讨论了如何替换-maxdepthPOSIX查找的选项。
肯·布鲁姆'18

25
#!/usr/bin/env bash

# If it is set, then an unmatched glob is swept away entirely -- 
# replaced with a set of zero words -- 
# instead of remaining in place as a single word.
shopt -s nullglob

M=(*px)

if [ "${#M[*]}" -ge 1 ]; then
    echo "${#M[*]} matches."
else
    echo "No such files."
fi

2
为了避免设置可能的错误“不匹配”,nullglob而不是检查单个结果是否等于模式本身。某些模式可以匹配与模式本身完全相同的名称(例如a*b;但不能例如a?b[a])。
克里斯·约翰森

我想这失败的可能性很小,因为实际上存在一个名为glob的文件。(例如有人跑了touch '*py'),但这确实为我指明了另一个方向。
肯·布鲁姆

我最喜欢这个版本。
肯·布鲁姆

而且最短。如果您只希望获得一场比赛,则可以"$M"用作的简写"${M[0]}"。否则,您已经在数组变量中具有了glob扩展,因此您可以通过gtg将其作为列表传递给其他东西,而不是让它们重新扩展glob。
彼得·科德斯

真好 您可以[使用if [[ $M ]]; then ...
Tobia 2015年

22

我喜欢

exists() {
    [ -e "$1" ]
}

if exists glob*; then
    echo found
else
    echo not found
fi

这既可读又高效(除非有大量文件)。
主要缺点是它比看起来要微妙得多,有时我不得不添加一个冗长的评论。
如果存在匹配项,"glob*"则由shell进行扩展,并将所有匹配项传递给exists(),以检查第一个匹配项,而忽略其余匹配项。
如果没有匹配项,"glob*"则传递给exists()并发现那里也不存在。

编辑:可能有误报,请参阅评论


13
如果glob类似于*.[cC](可能不是cC文件,而是一个名为的文件*.[cC]),则它可能返回假肯定;如果从中扩展的第一个文件是例如符号链接到不存在的文件或文件中的某个文件,则它可能返回假否定。您无权访问的目录(您想添加|| [ -L "$1" ])。
Stephane Chazelas 2013年

有趣。Shellcheck报告说,只有-e0或1个匹配项时,通配符才适用。它不适用于多个匹配项,因为那样会变成这样,[ -e file1 file2 ]并且会失败。另请参阅github.com/koalaman/shellcheck/wiki/SC2144了解基本原理和建议的解决方案。
Thomas Praxl

10

如果您设置了globfail,则可以使用这个疯狂选项(您实际上不应该这样做)

shopt -s failglob # exit if * does not match 
( : * ) && echo 0 || echo 1

要么

q=( * ) && echo 0 || echo 1

2
完美使用noop失败。永远不要使用...但是真的很漂亮。:)
Brian Chrisman

您可以将购物篮放在括号内。这样,它只会影响测试:(shopt -s failglob; : *) 2>/dev/null && echo exists
flabdablet

8

test -e有一个不幸的警告,它认为不存在断开的符号链接。因此,您可能也要检查这些内容。

function globexists {
  test -e "$1" -o -L "$1"
}

if globexists glob*; then
    echo found
else
    echo not found
fi

4
但这仍不能解决其中包含glob特殊字符的文件名的假肯定,例如Stephane Chazelas指出了Dan Bloch的答案。(除非您与nullglob一起玩)。
彼得·科德斯

3
您应该避免使用/ -o和。举例来说,在这里,它如果失败是大多数的实现。使用代替。-atest[$1=[ -e "$1" ] || [ -L "$1" ]
Stephane Chazelas

4

为了简化MYYN的答案,基于他的想法:

M=(*py)
if [ -e ${M[0]} ]; then
  echo Found
else
  echo Not Found
fi

4
关闭,但是如果要匹配[a],有一个名为的文件[a],但没有名为的文件a怎么办?我还是喜欢nullglob这个。有些人可能认为这是古怪的,但我们也可能在合理的情况下完全正确。
克里斯·约翰森

@ sondra.kinsey错了;glob [a]应该只匹配a,而不是文字文件名[a]
Tripleee

4

根据flabdablet的答案,对我来说,看起来最简单(不一定是最快)只是使用find本身,而将glob扩展留在shell上,例如:

find /some/{p,long-p}ath/with/*globs* -quit &> /dev/null && echo "MATCH"

或者if像:

if find $yourGlob -quit &> /dev/null; then
    echo "MATCH"
else
    echo "NOT-FOUND"
fi

这与我已经使用stat展示的版本完全一样。不知道如何找到比统计“容易”。
flabdablet 2014年

3
请注意,&>重定向是一种卑鄙的行为,它将在其他shell中悄悄地做错事。
flabdablet 2014年

这似乎比flabdablet的find回答要好,因为它接受glob中的路径,并且更简洁(不需要-maxdepth等)。它似乎也比他的stat回答更好,因为它不会继续stat在每个附加的glob匹配中执行额外的操作。如果有人可以提供一些无法解决的极端情况,我将不胜感激。
drwatsoncode

1
经过进一步考虑,我将添加,-maxdepth 0因为它允许在添加条件方面更大的灵活性。例如,假设我想将结果限制为仅匹配文件。我可以尝试find $glob -type f -quit,但是如果glob与文件不匹配,但是与包含文件的目录(甚至递归)匹配,则返回true 。相反find $glob -maxdepth 0 -type f -quit,只有当glob本身至少匹配一个文件时,才返回true。请注意,maxdepth这不会阻止glob具有目录组件。(FYI 2>足以不需要。&>
drwatsoncode

2
首先,使用find的目的是避免让shell生成和排序潜在的庞大匹配列表。find -name ... -quit最多匹配一个文件名。如果脚本依赖于将shell生成的glob匹配列表传递给find,则调用只会产生find不必要的进程启动开销。直接对结果列表进行非空性测试将更加快捷,清晰。
flabdablet

4

我还有另一个解决方案:

if [ "$(echo glob*)" != 'glob*' ]

这对我来说很好。我想念一些极端的情况吗?


2
有效,除非文件实际上名为“ glob *”。
伊恩·凯灵

确实可以将glob作为变量传递-当存在多个匹配项时,会给出“参数过多”错误。“ $(echo $ GLOB)”未返回单个字符串,或者至少未将其解释为单个单个字符串,因此参数过多错误
DKebler

@DKebler:它应该解释为单字符串,因为它用双引号引起来。
user1934428

3

在Bash中,您可以遍历数组;如果glob不匹配,则您的数组将包含一个与现有文件不对应的条目:

#!/bin/bash

shellglob='*.sh'

scripts=($shellglob)

if [ -e "${scripts[0]}" ]
then stat "${scripts[@]}"
fi

注意:如果已nullglob设置,scripts将是一个空数组,应该使用[ "${scripts[*]}" ]或进行测试[ "${#scripts[*]}" != 0 ]。如果您要编写必须使用或不使用的库,则nullglob需要

if [ "${scripts[*]}" ] && [ -e "${scripts[0]}" ]

这种方法的优势在于,您可以拥有要使用的文件列表,而不必重复进行glob操作。


为什么在设置了nullglob且数组可能为空的情况下,仍然无法进行测试 if [ -e "${scripts[0]}" ]...?您是否也允许设置shell选项名词集?
johnraff '18

@johnraff,是的,我通常认为nounset是活动的。另外,测试字符串非空可能比检查文件是否存在(稍微)便宜。但是,鉴于我们刚刚执行了Glob,这不太可能,这意味着目录内容应该在OS的缓存中是最新的。
Toby Speight,

1

这种可恶似乎有效:

#!/usr/bin/env bash
shopt -s nullglob
if [ "`echo *py`" != "" ]; then
    echo "Glob matched"
else
    echo "Glob did not match"
fi

它可能需要bash,而不是sh。

之所以有效,是因为nullglob选项使glob在没有匹配项的情况下求值为空字符串。因此,echo命令的任何非空输出都表明该glob与某些内容匹配。


您应该使用if [ "`echo *py`" != "*py"]
yegle 2014年

1
如果存在名为的文件,则无法正常工作*py
Ryan C. Thompson

如果没有文件结尾py`echo *py`将计算为*py
yegle 2014年

1
是的,但是如果有一个名为的文件*py,它也会这样做,这是错误的结果。
瑞安·汤普森

如果我错了,请纠正我,但是如果没有匹配的文件*py,您的脚本将回显“ Glob匹配”吗?
yegle 2014年

1

我没有看到这个答案,所以我想把它放在那里:

set -- glob*
[ -f "$1" ] && echo "found $@"

1
set -- glob*
if [ -f "$1" ]; then
  echo "It matched"
fi

说明

如果没有匹配项glob*$1则将包含'glob*'。测试-f "$1"不正确,因为glob*文件不存在。

为什么这比替代品更好

这适用于sh及其派生词:ksh和bash。它不会创建任何子外壳。$(..)`...`命令创建一个子外壳;他们分叉了一个过程,因此比该解决方案要慢。


1

像这样 (包含的测试文件pattern):

shopt -s nullglob
compgen -W *pattern* &>/dev/null
case $? in
    0) echo "only one file match" ;;
    1) echo "more than one file match" ;;
    2) echo "no file match" ;;
esac

它比compgen -G:更好,因为我们可以区分更多情况,并且更精确。

只能使用一个通配符 *


0
if ls -d $glob > /dev/null 2>&1; then
  echo Found.
else
  echo Not found.
fi

请注意,如果存在很多匹配项或文件访问速度很慢,那么这可能会很耗时。


1
如果[a]在文件[a]存在且文件a不存在时使用了类似模式,则将给出错误的答案。即使a实际上它不应该匹配的唯一文件,它也会说“找到” 。
克里斯·约翰森

这个版本应该可以在普通的POSIX / bin / sh中使用(没有bashisms),并且在我需要它的情况下,无论如何,glob都没有括号,并且我不必担心出现这种情况十分病理。但是我想没有一种很好的方法来测试是否有任何文件与glob相匹配。
肯·布鲁姆

0
#!/bin/bash
set nullglob
touch /tmp/foo1 /tmp/foo2 /tmp/foo3
FOUND=0
for FILE in /tmp/foo*
do
    FOUND=$((${FOUND} + 1))
done
if [ ${FOUND} -gt 0 ]; then
    echo "I found ${FOUND} matches"
else
    echo "No matches found"
fi

2
当只有一个文件匹配时,此版本将失败,但是您可以通过使用nullglobshell选项避免FOUND = -1错误。
肯·布鲁姆

@肯:嗯,我不会打电话nullglob给克鲁格。将单个结果与原始模式进行比较会产生错误(并且容易产生错误的结果),而使用nullglobnot则不是。
克里斯·约翰森

@克里斯:我认为你读错了。我没有nullglob吵架。
肯·布鲁姆

1
@肯:确实,我确实读错了。请接受我对无效批评的歉意。
克里斯·约翰森

-1
(ls glob* &>/dev/null && echo Files found) || echo No file found

5
如果存在匹配的目录,glob*并且例如您没有写操作列出这些目录,则也会返回false 。
Stephane Chazelas 2013年

-1

ls | grep -q "glob.*"

不是最有效的解决方案(如果目录中有很多文件,它可能会变慢),但是它简单,易读,并且具有正则表达式比普通bash glob模式更强大的优点。


-2
[ `ls glob* 2>/dev/null | head -n 1` ] && echo true

1
为了获得更好的答案,请尝试在代码中添加一些说明。
先生
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.