测试是否有匹配模式的文件以执行脚本


29

我正在尝试编写一条if语句来测试是否有任何文件符合特定模式。如果目录中有文本文件,则应运行给定脚本。

我的代码当前:

if [ -f /*.txt ]; then ./script fi

请给点想法;我只想.txt在目录中有一个脚本时运行该脚本。


3
您确定“目录”应该是/吗?另外,您之前还缺少分号fi
2013年

我遇到的最干净,可靠的解决方案是find按照stackoverflow上的说明使用。
约书亚·戈德堡

Answers:


39
[ -f /*.txt ]

只有在有 一个(只有一个)非隐藏文件/结尾.txt且该文件是常规文件或指向常规文件的符号链接时,。

这是因为通配符在传递给命令(在此处[)之前已被外壳扩展。

所以,如果有一个/a.txt/b.txt[将通过5个参数:[-f/a.txt/b.txt][然后会抱怨-f给出了太多的争论。

如果您要检查 *.txt模式是否扩展到至少一个非隐藏文件(常规文件或非常规文件):

shopt -s nullglob
set -- *.txt
if [ "$#" -gt 0 ]; then
  ./script "$@" # call script with that list of files.
fi
# Or with bash arrays so you can keep the arguments:
files=( *.txt )
# apply C-style boolean on member count
(( ${#files[@]} )) && ./script "${files[@]}"

shopt -s nullglobbash具体的,但炮弹一样ksh93zshyashtcsh具有等效的语句。

请注意,它通过读取目录内容来查找那些文件,它根本不会尝试访问这些文件,这使其比调用诸如 lsstat由shell计算文件的名单上。

标准 sh等效项为:

set -- [*].txt *.txt
case "$1$2" in
  ('[*].txt*.txt') ;;
  (*) shift; script "$@"
esac

问题在于,对于Bourne或POSIX壳,如果模式不匹配,它将扩展为自身。因此,如果*.txt扩展到*.txt,则不知道是由于.txt目录中没有文件还是因为存在一个名为的文件*.txt。使用[*].txt *.txt允许在两者之间进行区分。


[ -f /*.txt ]与相比,速度相当快compgen
DanielBöhmer16年

@DanielBöhmer [ -f /*.txt ]可能是错误的,但是在我对包含3425文件的目录的测试中,该文件94的速度是非隐藏的txt文件,compgen -G "*.txt" > /dev/null 2>&1其速度最快set -- *.txt; [ "$#" -gt 0 ](在我的情况下,如果重复10000次,两者都为20.5秒)。
斯特凡Chazelas

11

您可以随时使用find

find . -maxdepth 1 -type f -name "*.txt" 2>/dev/null | grep -q . && ./script

说明:

  • find . :搜索当前目录
  • -maxdepth 1:不搜索子目录
  • -type f :仅搜索常规文件
  • name "*.txt" :搜索以结尾的文件 .txt
  • 2>/dev/null :将错误消息重定向到 /dev/null
  • | grep -q . :grep表示任何字符,如果未找到任何字符,则将返回false。
  • && ./script./script仅在上一条命令成功执行后执行(&&

2
find仅当找不到文件时返回false,否则找不到任何文件则返回false。您想通过管道将输出grep -q .检查到是否找到了东西。
斯特凡Chazelas

@StephaneChazelas您当然是正确的。不过很奇怪,我已经对其进行了测试,并且似乎可以正常工作。必须做一些奇怪的事情,因为它不再存在了。什么时候会发现“找不到文件”?
terdon

@terdon,例如无法访问某些目录,I / O错误或它进行的任何系统调用返回的任何错误。在这种情况下,请尝试chmod a-x .
斯特凡Chazelas

8

内置的Bash也是可能的解决方案compgen。该命令返回一个globbing模式的所有可能的匹配,并具有一个退出代码,指示是否有任何文件匹配。

compgen -G "/*.text" > /dev/null && ./script

我在寻找更快的解决方案时发现了这个问题。


1
好发现!如果您使用的是多字节语言环境,则可以使用进行一些改进LC_ALL=C compgen -G "*.txt" > /dev/null
斯特凡Chazelas

7

这是一个班轮:

$ ls
file1.pl  file2.pl

文件存在

$ stat -t *.pl >/dev/null 2>&1 && echo "file exists" || echo "file doesn't exist"
file exists

文件不存在

$ stat -t -- *.txt >/dev/null 2>&1 && echo "file exists" || echo "file don't exist"
file don't exist

此方法利用||&&在bash中运算符。这些是“或”和“与”运算符。

因此,如果stat命令返回的值$?等于0,则echo调用第一个命令;如果返回的是1,则调用第二个命令echo调用。

从统计返回结果

# a failure
$ stat -t -- *.txt >/dev/null 2>&1
$ echo "$?"
1

# a success
$ stat -t -- *.pl >/dev/null 2>&1
$ echo "$?"
0

这个问题已在stackoverflow上广泛讨论:


1
为什么在使用非标准statls -d能做到同样?
斯特凡Chazelas

我以为ls -d列出目录?例如,当我尝试列出其中包含文件的目录时,似乎没有用ls -d *.pl
slm

您可以替代语句左侧&&通过ls *.txt,它也能发挥作用。确保/dev/null按照@slm的建议将stdout和stderr发送到。
unxnut

1
如果您使用ls *.txt并且目录中没有文件,则将返回$? = 2,这将如果仍然工作的话,但是这是我的原因选择一个stat以上ls。我想要0表示成功,而1表示失败。
slm

ls -d是列出目录而不是目录。所以ls -d只是做了lstat该文件,就像GNU stat一样。故障时非零退出状态命令返回的内容是系统特定的,因此对其进行假设几乎没有意义。
斯特凡Chazelas

4

正如Chazelas指出的,如果通配符扩展匹配多个文件,则脚本将失败。

但是,我有一个窍门(即使我不太喜欢)也可以解决:

PATTERN=(/*.txt)
if [ -f ${PATTERN[0]} ]; then
...
fi

怎么运行的?

通配符扩展将匹配文件名数组,如果有文件名,则获取第一个文件名;如果不匹配,则返回null。


海事组织,这是最差的答案。它们似乎都非常可怕,好像该语言缺少基本功能。
plugwash

@plugwash是有意的... * nix shell脚本具有一些基本的流程控制和其他一些零碎的东西,但是最终,工作是将其他命令粘合在一起。如果bash很烂...是因为您从中使用的命令很烂
cb88

2
这是错误的逻辑(并且您缺少引号)。这将检查第一个匹配文件是否为常规文件。可能是非常规文件,但可能还有其他.txt一些类型为regular的文件。请在之后尝试mkdir a.txt; mkfifo b.txt; echo regular > c.txt
斯特凡Chazelas


0

我喜欢以前的数组解决方案,但是这可能会浪费大量文件-外壳程序将使用大量内存来构建数组,并且只有第一个元素会被测试。

这是我昨天测试的替代结构:

$ cd /etc; if [[ $(echo * | grep passwd) ]];then echo yes;else echo no;fi yes $ cd /etc; if [[ $(echo * | grep password) ]];then echo yes;else echo no;fi no

grep的退出值似乎在确定通过控制结构的路径。这也将使用正则表达式而不是外壳模式进行测试。我的某些系统具有“ pcregrep”命令,该命令允许进行更复杂的正则表达式匹配。

(在阅读了有关解析它的上述批评之后,我确实编辑了此答案以删除命令替换中的“ ls”。)


-2

如果要使用if子句,请评估计数:

if (( `ls *.txt 2> /dev/null|wc -l` ));then...
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.