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

Dan*_*nny 264 shell wildcard sh

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

if [ -f "xorg-x11-fonts*" ]; then
    printf "BLAH"
fi
Run Code Online (Sandbox Code Playgroud)

我也尝试过没有双引号.

Cos*_*atu 393

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

if ls /path/to/your/files* 1> /dev/null 2>&1; then
    echo "files do exist"
else
    echo "files do not exist"
fi
Run Code Online (Sandbox Code Playgroud)

我重定向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
Run Code Online (Sandbox Code Playgroud)

这与@ grok12的答案非常相似,但它避免了整个列表中不必要的迭代.

  • `ls`在包含许多文件的目录上可能会很慢(可能是由于排序).您可能希望至少使用`-U`关闭排序. (18认同)
  • 一句警告:在Debian Almquist Shell(`dash`) - 安装在Debian和Ubuntu的`/ bin/sh`中 - `&>`似乎丢弃了退出代码,这打破了这个解决方案.解决方法是使用`>/dev/null 2>&1`重定向. (7认同)
  • @Izzy,你应该把它放在双引号中,但在`/ path/to/your files'*中保留`*`from:`for f. (5认同)
  • 警告。如果目录包含大量文件,这将失败。“*”的扩展将超出命令行长度限制。 (2认同)

fla*_*let 59

如果您的shell具有nullglob选项并且已打开,则将完全从命令行中删除不匹配任何文件的通配符模式.这将使ls看不到路径名参数,列出当前目录的内容并成功,这是错误的.GNU stat,如果没有参数或命名不存在的文件的参数,总是会失败,会更强大.此外,&>重定向运算符是一种基础.

if stat --printf='' /path/to/your/files* 2>/dev/null
then
    echo found
else
    echo not found
fi
Run Code Online (Sandbox Code Playgroud)

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

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

非GNU版本的发现可能没有-maxdepth这里用来做选择查找搜索只/ /目录为/搜索,而不是扎根在那里的整个目录树.

  • 让`find`处理通配符是最好的,因为`bash`,因为它扩展了模式,试图对匹配文件名的列表进行排序,这是浪费的,而且可能很昂贵. (7认同)
  • @musiphil 如果目录中只有几个文件(或没有),则启动诸如“find”之类的外部进程会更加浪费。 (2认同)
  • @dolmen:按照@flabdablet的帖子中的描述使用`-quit`运行`find`不会受到大量文件的困扰,因为它会在找到第一个匹配项后立即退出,因此不会列出所有文件。因此,这并没有您建议的那样浪费资源。此外,“ find”并不会像shell那样简单地“扩展”通配符,而是对照模式检查它找到的每个文件以查看是否匹配,因此对于大量文件而言,它不会失败。 (2认同)

Pan*_*har 37

这是我的答案 -

files=(xorg-x11-fonts*)

if [ -e "${files[0]}" ];
then
    printf "BLAH"
fi
Run Code Online (Sandbox Code Playgroud)

  • 和'shopt -s nullglob`为`bash` (6认同)

gro*_*k12 21

for i in xorg-x11-fonts*; do
  if [ -f "$i" ]; then printf "BLAH"; fi
done
Run Code Online (Sandbox Code Playgroud)

这将适用于多个文件和文件名中的空格.

  • 如果有多个匹配,它将打印多个"BLAH".也许在第一场比赛后添加一个`break`来退出循环. (4认同)
  • 这(@tripleee 的休息时间)得到了我的投票。通过仅使用本机通配符和文件测试运算符,它甚至避免了使用 ls 或 find 或转发通配符等命令带来的极端情况问题。我认为它没有为其他一些答案提出的所有问题,例如带有空格的名称、nullglob 设置和 bashisms。我做了一个函数:`existsAnyFile() { for file; do [ -f "$file" ] && return 0; 完毕; 错误的; }` (2认同)

jos*_*uis 21

您可以执行以下操作:

set -- xorg-x11-fonts*
if [ -f "$1" ]; then
    printf "BLAH"
fi
Run Code Online (Sandbox Code Playgroud)

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

当没有匹配时它也可以工作.没有必要使用nullglob作为其中一个comentatators说.$ 1将包含原始测试名称,因此测试-f $ 1将不会成功,因为$ 1文件不存在.


Swi*_*ift 14

更新:

好的,现在我肯定有解决方案:

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

> Exists
Run Code Online (Sandbox Code Playgroud)

  • ls在包含许多文件的目录上可能会非常慢(可能是由于排序)。您可能至少要关闭使用-U的排序。 (2认同)

Mar*_*ian 13

也许这会对某人有所帮助:

if [ "`echo xorg-x11-fonts*`" != "xorg-x11-fonts*" ]; then
    printf "BLAH"
fi
Run Code Online (Sandbox Code Playgroud)

  • @SergeStroobandt不确定我同意.命令替换在这里可能是必要的,但它会刺激我的畏缩反射. (3认同)
  • 这是**最简单、最简单、最优雅的答案**,而且确实有效! (2认同)
  • 是的......就像文件名为`xorg-x11-fonts\*`的文件存在一样? (2认同)
  • 根本不优雅,因为它要求子shell运行echo命令. (2认同)

小智 10

我使用的 Bash 代码:

if ls /syslog/*.log > /dev/null 2>&1; then
   echo "Log files are present in /syslog/;
fi
Run Code Online (Sandbox Code Playgroud)


Jer*_*jek 9

这个问题并不是特定于Linux/Bash,所以我想我会添加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
}
Run Code Online (Sandbox Code Playgroud)

我认为这很有用,因为原始问题的概念通常涵盖"shell",而不仅仅是Bash或Linux,并且也适用于具有相同问题的Powershell用户.


Vou*_*uze 6

严格来说,如果您只想打印“废话”,这里是解决方案:

find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 'BLAH' -quit
Run Code Online (Sandbox Code Playgroud)

这是另一种方式:

doesFirstFileExist(){
    test -e "$1"
}

if doesFirstFileExist xorg-x11-fonts*
then printf "BLAH"
fi
Run Code Online (Sandbox Code Playgroud)

但我认为最优化如下,因为它不会尝试对文件名进行排序:

if [ -z `find . -maxdepth 1 -name 'xorg-x11-fonts*' -printf 1 -quit` ]
then printf "BLAH"
fi
Run Code Online (Sandbox Code Playgroud)


Fab*_* A. 6

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

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

正如您所看到的,它只是比您希望的要复杂一点,并且依赖于这样一个事实:如果 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]}" ]
}
Run Code Online (Sandbox Code Playgroud)

回到你的例子,它可以像这样被调用。

if doesAnyFileExist "xorg-x11-fonts*"; then
    printf "BLAH"
fi
Run Code Online (Sandbox Code Playgroud)

Glob 扩展应该发生在函数本身内才能正常工作,这就是为什么我将参数放在引号中,这就是函数体中第一行的用途:以便任何多个参数(这可能是 glob 的结果)函数外的扩展以及虚假参数)将合并为一个。如果有多个参数,另一种方法可能是引发错误,而另一种方法可能是忽略除第一个参数之外的所有参数。

函数体中的第二行将filesvar设置为一个数组,该数组由 glob 扩展到的所有文件名构成,每个数组元素一个。如果文件名包含空格就可以了,每个数组元素都将按原样包含名称,包括空格。

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

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

  2. 如果在第1步中,我们发现数组中的元素少于 2 个,那么我们检查是否有一个,如果有,则按照通常的方式检查该元素是否存在。我们需要做这个额外的检查,以考虑没有glob 字符的函数参数,在这种情况下,数组只包含一个未扩展的元素。