测试一个glob在Bash中是否有匹配项

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

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

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

74323 次浏览
#!/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
(ls glob* &>/dev/null && echo Files found) || echo No file found
#!/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
if ls -d $glob > /dev/null 2>&1; then
echo Found.
else
echo Not found.
fi

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

为了简化miku的回答,基于他的想法:

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

这招似乎很管用:

#!/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匹配了某些内容。

nullglob shell选项实际上是bashism。

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

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

显式安全性辞职操作用于找到,而不是默认的隐式安全性操作,以便找到在找到第一个匹配搜索条件的文件时立即退出。当许多文件匹配时,这应该比echo glob*ls glob*运行得快得多,并且它还避免了过度填充扩展命令行的可能性(一些shell有4K长度限制)。

如果找到感觉有点多余,并且可能匹配的文件数量很少,则使用stat:

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

基于flabdablet的回答,对我来说,最简单的(不一定是最快的)是只使用找到本身,同时在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

我喜欢

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


if exists glob*; then
echo found
else
echo not found
fi
这既可读又有效(除非有大量的文件) 主要的缺点是它比看起来要微妙得多,有时我觉得不得不添加一个很长的注释 如果有匹配项,"glob*"将被shell扩展,所有匹配项都传递给exists(),后者检查第一个匹配项并忽略其余匹配项 如果没有匹配,"glob*"将被传递给exists(),并且发现exists()中也不存在

编辑:可能是假阳性,见评论

Test -e有一个不幸的警告,它认为断开的符号链接不存在。所以你可能也想检查一下。

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


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

如果你有globfail集,你可以使用这个疯狂的(你真的不应该)

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

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

我还有另一个解决方案:

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

这对我来说很有用。我可能漏掉了一些极端情况。

Bash-specific解决方案:

compgen -G "<glob-pattern>"

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

退出状态为:

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

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

例子:

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

ls | grep -q "glob.*"

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

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

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

#!/bin/bash


shellglob='*.sh'


scripts=($shellglob)


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

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

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

这种方法的一个优点是,这样您就有了想要处理的文件列表,而不必重复glob操作。

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

解释

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

为什么这比其他选择更好

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

像这样在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好得多:因为我们可以区分更多的情况,更精确。

它只能使用一个通配符*

在Bash中扩展globs (extglob)的解决方案:

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

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

或者, different:

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

与基于__abc0的解决方案的区别:可能更快(未测量),输出中未引用空格的文件名,不匹配时退出代码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)。
  • 在当前shell中不启用extglob(通常不需要)。
  • 使用$前缀,以解释\n,因此扩展的glob模式与shopt -s extglob在不同的行上——否则扩展的glob模式将是一个语法错误!

注1:我致力于这个解决方案,因为在其他答案中建议的compgen -G "<glob-pattern>"方法似乎不顺利地与支架扩张一起工作;但我需要一些更高级的globing功能。

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

nullglobcompgen只在一些bash shell上有用。

在大多数shell上工作的(非递归)解决方案是:

set -- ./glob*                  # or /path/dir/glob*
[ -f "$1" ] || shift            # remove the glob if present.
if    [ "$#" -lt 1 ]
then  echo "at least one file found"
fi

如果你想在遍历这些文件之前测试它们是否存在,你可以使用这个模式:

  for F in glob*; do
if [[ ! -f $F ]]; then break; fi
    

...


done

如果glob不匹配任何内容,$F将是未展开的glob(在本例中为'glob*'),如果同名文件不存在,则跳过循环的剩余部分。

假设你可能想对文件做些什么,如果它们存在:

mapfile -t exists < <(find "$dirName" -type f -iname '*.zip'); [[ ${#exists} -ne 0 ]] && { echo "Zip files found" ; } || { echo "Zip files not found" ; }

然后,如果需要对文件做一些事情,可以循环遍历存在数组。