281

如果我想检查单个文件是否存在,我可以使用test -e filenameor来测试它[ -e filename ]

假设我有一个 glob,我想知道是否存在名称与 glob 匹配的文件。glob 可以匹配 0 个文件(在这种情况下我不需要做任何事情),或者它可以匹配 1 个或多个文件(在这种情况下我需要做一些事情)。如何测试 glob 是否有任何匹配项?(我不在乎有多少匹配项,如果我可以用一个if语句而不是循环来做到这一点,那将是最好的(因为我发现它最易读)。

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

4

20 回答 20

243

Bash特定的解决方案:

compgen -G "<glob-pattern>"

转义模式,否则它将被预先扩展为匹配项。

退出状态为:

  • 1 表示不匹配,
  • 0 表示“一个或多个匹配”

stdout与 glob 匹配的文件列表。我认为就简洁性和最大程度地减少潜在副作用而言,这是最佳选择。

例子:

if compgen -G "/tmp/someFiles*" > /dev/null; then
    echo "Some files exist."
fi
于 2015-12-10T06:36:29.147 回答
189

nullglob shell 选项确实是一个bashism。

为了避免繁琐的保存和恢复 nullglob 状态,我只将它设置在扩展 glob 的子 shell 中:

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操作用于find而不是默认的隐式-print操作,以便find在找到与搜索条件匹配的第一个文件后立即退出。echo glob*在许多文件匹配的情况下,这应该比or运行得更快,ls glob*并且它还避免了扩展命令行的可能性(某些 shell 有 4K 长度限制)。

如果find感觉有点矫枉过正,并且可能匹配的文件数量很少,请使用 stat:

if stat -t glob* >/dev/null 2>&1
then
    echo found
else
    echo not found
fi
于 2010-11-24T07:24:42.860 回答
26

我喜欢

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

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

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

编辑:可能有误报,见评论

于 2013-03-20T04:09:49.380 回答
26
#!/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
于 2010-05-30T03:39:07.167 回答
11

如果你设置了 globfail,你可以使用这个疯狂的(你真的不应该这样做)

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

或者

q=( * ) && echo 0 || echo 1
于 2014-06-28T14:04:35.380 回答
8

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

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

if globexists glob*; then
    echo found
else
    echo not found
fi
于 2013-11-08T16:16:37.690 回答
6

我还有另一个解决方案:

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

这对我很有效。我可能错过了一些极端情况。

于 2015-11-06T14:39:30.280 回答
4

根据他的想法,为了稍微简化miku 的回答:

M=(*py)
if [ -e ${M[0]} ]; then
  echo Found
else
  echo Not Found
fi
于 2010-05-30T04:07:42.607 回答
4

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

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
于 2013-03-01T09:05:24.577 回答
3

在 Bash 中,您可以 glob 到一个数组;如果 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 操作。

于 2018-05-03T11:23:34.100 回答
2
#!/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
于 2010-05-30T03:33:22.580 回答
2

像这样在Bash中(包含 的测试文件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: 好得多,因为我们可以更准确地区分更多案例。

它只能使用一个通配符*

于 2020-06-09T16:56:09.660 回答
2
set -- glob*
if [ -f "$1" ]; then
  echo "It matched"
fi

解释

当 不匹配时glob*$1将包含'glob*'。测试-f "$1"不会是真的,因为该glob*文件不存在。

为什么这比替代品更好

这适用于 sh 和衍生产品:KornShell和 Bash。它不会创建任何子外壳。$(..)`...`命令创建一个子外壳;他们分叉了一个进程,因此比这个解决方案慢。

于 2018-07-18T12:24:35.683 回答
1

This abomination seems to work:

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

It probably requires bash, not sh.

This works because the nullglob option causes the glob to evaluate to an empty string if there are no matches. Thus any non-empty output from the echo command indicates that the glob matched something.

于 2010-08-30T00:37:16.790 回答
0

普遍认为这[ -f file* ]是行不通的。事实上,它确实有效,而且我个人发现它在某些情况下非常有用——当我想在特定位置捕获一个且只有一个文件的名称时。例如,名称中包含版本号的文件。考虑这段代码:

if [ -f "$ROOT"/lib64/libc-*.so ] ;then
  LIBC=$(basename -- "$ROOT"/lib64/libc-*.so .so)
else
  echo "libc ??" ; exit 1
fi

顺便说一句,ShellCheck在看到这样的使用时会大喊大叫。:-) 我希望他们能解决这个问题!

SC2144。-e 不适用于 glob。使用 for 循环。

于 2021-08-11T04:34:21.267 回答
-1
(ls glob* &>/dev/null && echo Files found) || echo No file found
于 2010-05-30T03:37:48.550 回答
-1

extglobBash中扩展 glob ( ) 的解决方案:

bash -c $'shopt -s extglob \n /bin/ls -1U <ext-glob-pattern>'

如果至少有一个匹配项,则退出状态为 0,如果没有匹配项,则退出状态为非零 (2)。标准输出包含一个以换行符分隔的匹配文件列表(以及包含空格的文件名,它们被引用)。

或者,略有不同:

bash -c $'shopt -s extglob \n compgen -G <ext-glob-pattern>'

ls基于 - 的解决方案的差异:可能更快(未测量),输出中未引用空格的文件名,不匹配时退出代码 1(不是 2 :shrug:)。

示例用法:

没有匹配:

$ bash -c $'shopt -s extglob \n /bin/ls -1U @(*.foo|*.bar)'; echo "exit status: $?"
/bin/ls: cannot access '@(*.foo|*.bar)': No such file or directory
exit status: 2

至少一场比赛:

$ bash -c $'shopt -s extglob \n /bin/ls -1U @(*.ts|*.mp4)'; echo "exit status: $?"
'video1 with spaces.mp4'
video2.mp4
video3.mp4
exit status: 0

使用的概念:

  • ls' 退出代码行为(增加-U效率输出-1控制)。
  • 不在extglob当前 shell 中启用(通常不需要)。
  • 使用$前缀以便\n解释 ,以便扩展 glob 模式与 -- 位于不同的行上,shopt -s extglob否则扩展 glob 模式将是语法错误!

注意 1:我致力于此解决方案,因为compgen -G "<glob-pattern>"其他答案中建议的方法似乎不适用于大括号扩展;但我需要一些更高级的通配符功能。

注 2:扩展 glob 语法的可爱资源:extglob

于 2020-12-09T13:00:06.950 回答
-2
if ls -d $glob > /dev/null 2>&1; then
  echo Found.
else
  echo Not found.
fi

请注意,如果有很多匹配项或文件访问速度很慢,这可能会非常耗时。

于 2010-05-30T03:43:42.950 回答
-2

ls | grep -q "glob.*"

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

于 2016-04-08T10:58:14.270 回答
-4
[ `ls glob* 2>/dev/null | head -n 1` ] && echo true
于 2017-07-27T12:03:03.963 回答