从Shell脚本检查目录是否包含文件


116

从Shell脚本中,如何检查目录是否包含文件?

与此类似

if [ -e /some/dir/* ]; then echo "huzzah"; fi;

但是如果目录包含一个或多个文件,则该方法有效(以上一个仅适用于0或1个文件)。


对于您的示例而言,值得做的事就是在ksh和zsh中做您想要的事
Dave Webb

2
@DaveWebb不,不是。如果glob扩展为多个单词,则zsh测试将报告“测试:参数过多”。
詹斯2012年

“ huzzah”表示“目录不为空”。
funroll

如果该目录仅包含一个空子目录,该目录是否算作“包含文件”?
基思·汤普森

Answers:


72

到目前为止,使用的解决方案ls。这是一个全bash解决方案:

#!/bin/bash
shopt -s nullglob dotglob     # To include hidden files
files=(/some/dir/*)
if [ ${#files[@]} -gt 0 ]; then echo "huzzah"; fi

8
只要您记得在脚本末尾将这些选项设置回其原始值即可:)
Jean Jean

这是一个稍有改进的版本,用于重置bash设置:shopt -q nullglob || resetnullglob=1; shopt -s nullglob; shopt -q dotglob || resetdotglob=1; shopt -s dotglob; files=(/some/dir/*); [ "$files" ] && echo "wowzers"; [ "$resetdotglob" ] && shopt -u dotglob; [ "$resetnullglob" ] && shopt -u nullglob;
user123444555621 2011年

12
为什么不使用子外壳重置设置:files=$(shopt -s nullglob;shopt -s dotglob;echo /some/dir/*)
teambob 2012年

7
@teambob如果使用子shell:files=$(shopt -s nullglob;shopt -s dotglob;echo /some/dir/*) 那么if语句应该更改为,if [ ${#files} -gt 0 ];或者您只是忘记了sub-shell命令周围的()?files=($(shopt -s nullglob;shopt -s dotglob;echo /some/dir/*))
stoutyhk

2
@stoutyhk $(...)恢复设置,不需要单独的子shell。使用$(...)产生外壳的新实例。命令完成后,将丢弃此新实例的环境。编辑:找到了参考tldp.org/LDP/abs/html/commandsub.html“命令替换调用了子外壳程序”。
teambob

139

最佳三招


shopt -s nullglob dotglob; f=your/dir/*; ((${#f}))

这个技巧是100%,bash并调用(产生)一个子shell。这个想法来自Bruno De Fraine,并根据teambob的评论进行了改进。

files=$(shopt -s nullglob dotglob; echo your/dir/*)
if (( ${#files} ))
then
  echo "contains files"
else 
  echo "empty (or does not exist or is a file)"
fi

注意:空目录和不存在的目录(即使提供的路径是文件)之间也没有区别。

#bash IRC频道“官方”常见问题解答中也有类似的替代方法和更多详细信息(以及更多示例):

if (shopt -s nullglob dotglob; f=(*); ((${#f[@]})))
then
  echo "contains files"
else 
  echo "empty (or does not exist, or is a file)"
fi

[ -n "$(ls -A your/dir)" ]

该技巧的灵感来自nixCraft在2007年发布的文章。添加2>/dev/null以抑制输出错误"No such file or directory"
另请参阅安德鲁·泰勒Andrew Taylor)的答案(2008)和gr8can8dian的答案(2011)。

if [ -n "$(ls -A your/dir 2>/dev/null)" ]
then
  echo "contains files (or is a file)"
else
  echo "empty (or does not exist)"
fi

或单行Bashism版本:

[[ $(ls -A your/dir) ]] && echo "contains files" || echo "empty"

注意: 目录不存在时ls返回$?=2。但是文件和空目录之间没有区别。


[ -n "$(find your/dir -prune -empty)" ]

这最后绝招是从灵感gravstar的答案在那里-maxdepth 0被替换-prune和改进菲尔斯的评论。

if [ -n "$(find your/dir -prune -empty 2>/dev/null)" ]
then
  echo "empty (directory or file)"
else
  echo "contains files (or does not exist)"
fi

使用-type d以下变体:

if [ -n "$(find your/dir -prune -empty -type d 2>/dev/null)" ]
then
  echo "empty directory"
else
  echo "contains files (or does not exist or is not a directory)"
fi

说明:

  • find -prune类似于少find -maxdepth 0用字符
  • find -empty 打印空目录和文件
  • find -type d 仅打印目录

注意:您也可以[ -n "$(find your/dir -prune -empty)" ]只用下面的简短版本替换:

if [ `find your/dir -prune -empty 2>/dev/null` ]
then
  echo "empty (directory or file)"
else
  echo "contains files (or does not exist)"
fi

最后的代码在大多数情况下都适用,但是请注意,恶意路径可能会表达命令...


2
我认为您可以将find选项简化为[ -n "$(find "your/dir" -prune -empty)" ],以避免目录路径重复。
phils

if [ ``ls -A your/dir`` ]在脚本中使用时,我得出的结论是,它对于具有0、1或2个子目录的目录运行良好,但是对于具有2个以上子目录的目录则无效。line 17: [: 20150424-002813: unary operator expected这里20150424-002813是目录名称之一。使用的外壳是/bin/bash/。我终于将其更改为ls "$destination" | tail -1
Christophe De Troyer

嗨@ChristopheDeTroyer,我无法重现您的问题。在我这边if [ ``ls -A my/dir`` ]错误退出bash: [: -A: binary operator expected。在bash版本4.1.2和4.2.53上测试。
olibre

1
好吧,我主要是因为感觉到不一致而提出评论(因为该-n变体出现在标题中,但随后的段落中没有)。所以,是的,很好。
maxschlepzig's

1
我正在使用-n解决方案,但我的脚本仍然对此有所抱怨ls: cannot access '/path': No such file or directory。有什么办法可以禁止显示此消息?我想在那里默默失败。
Freedo

48

怎么样:

if find /some/dir/ -maxdepth 0 -empty | read v; then echo "Empty dir"; fi

这样,就无需生成目录内容的完整列表。该read既是丢弃输出,使表达时,才读一些评估为真(即/some/dir/被发现空find)。


3
或者干脆find /some/dir/ -maxdepth 0 -empty -exec echo "huzzah" \;
doubleDown

5
+1这是最优雅的解决方案。它不涉及ls输出解析,也不依赖于非默认的shell功能。

7
但是,它确实依赖于非标准-maxdepth和主要对象-empty
chepner

21

尝试:

if [ ! -z `ls /some/dir/*` ]; then echo "huzzah"; fi

这对我有用,但是我无法格式化此d * mn消息!!!<br> export tmp = /bin/ls <some_dir>/* 2> /dev/null如果[!-z“ $ tmp”]; 然后回声某事
-AndrewStone

1
对于像我这样的Bash笨蛋,如果要检查相反的内容-目录为空-只需使用[-z ls /some/dir/*]; 然后回声“ huzzah”;fi
Chris Moschini 2013年

4
您可以使用-n代替! -z(它们都是等效的,但是为什么不使用较短的形式(如果存在)。
2014年

ZSH这个例子抛出一个错误,这个想法是不抛出错误BigGray% if [ ! -z 。然后回声“ huzzah”;fi zsh:未找到匹配项:/ some / dir / *`
Hvisage

ls...bash中没有双引号的情况下对我不起作用
letmutx

15
# Works on hidden files, directories and regular files
### isEmpty()
# This function takes one parameter:
# $1 is the directory to check
# Echoes "huzzah" if the directory has files
function isEmpty(){
  if [ "$(ls -A $1)" ]; then
    echo "huzzah"
  else 
    echo "has no files"
  fi
}

15

注意包含大量文件的目录!评估该ls命令可能需要一些时间。

IMO最好的解决方案是使用

find /some/dir/ -maxdepth 0 -empty


6

你能比较一下这个输出吗?

 ls -A /some/dir | wc -l

4
#检查目录是否包含任何非隐藏文件。
#
#用法:如果为空“ $ HOME”;然后回显“欢迎回家”;科幻
#
是空的() {
    为$ 1 / *中的_ief; 做
        如果[-e“ $ _ief”]; 然后
            返回1
        科幻
    做完了
    返回0
}

一些实施说明:

  • for循环避免了对外部ls进程的调用。仍然会一次读取所有目录条目。这只能通过编写明确使用readdir()的C程序来进行优化。
  • test -e循环内捕获了一个空的目录,在此情况下,可变的情况下,_ief将被指定为值“somedir / *”。仅当该文件存在时,该函数才会返回“ nonempty”
  • 此功能将在所有POSIX实现中使用。但是请注意,Solaris / bin / sh不在该类别中。它的test实现不支持该-e标志。

1
如果dotglob未设置,将忽略目录中的点文件shopt -s dotglob
l0b0 2012年

4

这告诉我目录是否为空,如果不是,则为其中包含的文件数。

directory="/some/dir"
number_of_files=$(ls -A $directory | wc -l)

if [ "$number_of_files" == "0" ]; then
    echo "directory $directory is empty"
else
    echo "directory $directory contains $number_of_files files"
fi

谁能解释我为什么被否决?如果正在写胡扯,我想知道为什么;)
Daishi

1
我没有对此进行投票,但是猜测是因为您正在解析的输出ls
Toby Speight

链接的文章不清楚吗?如果是这样,给作者发电子邮件(不是我)。
Toby Speight 2015年

1
@TobySpeight我明白了。但是在这种情况下,我正在计算没有枚举它们的行数。仅当文件名包含换行符时,它才应该给出错误的结果。而且,如果文件名包含换行符,则必须在某处添加更重要的内容;)
Daishi 2015年

3

这可能是一个很晚的响应,但这是一个可行的解决方案。该行仅识别文件的存在!如果目录存在,它不会给您带来误报。

if find /path/to/check/* -maxdepth 0 -type f | read
  then echo "Files Exist"
fi

2
dir_is_empty() {
   [ "${1##*/}" = "*" ]
}

if dir_is_empty /some/dir/* ; then
   echo "huzzah"
fi

假设你没有命名的文件*/any/dir/you/check,它可以工作在bash dash posh busybox shzsh,但(对于zsh的)要求unsetopt nomatch

性能应该可以与任何ls使用*(glob)的设备相媲美,我想在具有许多节点的目录上速度会很慢(我/usr/bin的3000+个文件的速度并没有那么慢),将至少使用足够的内存来分配所有dirs /文件名(以及更多)由于它们都是作为参数传递(解析)到函数的,因此某些shell可能会对参数数量和/或参数长度有所限制。

一种可移植的快速O(1)零资源检查目录是否为空的方法将是不错的选择。

更新

上面的版本不考虑隐藏文件/显示目录,在一些情况下,更多的测试是必需的,就像is_empty丰富的SH(POSIX壳)窍门

is_empty () (
cd "$1"
set -- .[!.]* ; test -f "$1" && return 1
set -- ..?* ; test -f "$1" && return 1
set -- * ; test -f "$1" && return 1
return 0 )

但是,我正在考虑这样的事情:

dir_is_empty() {
    [ "$(find "$1" -name "?*" | dd bs=$((${#1}+3)) count=1 2>/dev/null)" = "$1" ]
}

当dir为空时,一些关于尾随斜线与参数和find输出之间的差异以及尾随换行符(但这应该很容易处理)的一些担忧,可悲的busybox sh是,在我的演示中,可能是find -> dd管道上的错误,其输出被随机截断了(如果我使用cat的输出始终是相同的,则似乎dd带有参数count)。


我喜欢便携式解决方案。我来给出相同的答案。我只是写了不同的代码(如果不是目录,请返回2)。而且,这些便携式解决方案的优点是不调用任何外部程序,对于受约束或嵌入式系统,这可能算是一个优势。我只担心使用“()”可能会创建一个新的子进程(或至少会强制复制环境和内容,然后再切换回去)。看一下这个:is_empty(){_d =“ $ {1:-。}”; [!-d“ $ {_ d}”] && return 2; set-“ $ {_ d}” / *“ $ {_ d}” /.[!.]*“ $ {_ d}” /..?*; [“ $ { }” =“ $ {_ d} / $ {_ d} /。[!。] * $ {_ d} /..?*”];};
迭戈·奥古斯托·莫利纳

@DiegoAugustoMolina最近在一个项目中,我不得不再次执行此测试,如果目录为空,一开始我使用python,所以由于我实际上是为此而安装python的,所以我在c中实现了测试,因此实现非常极端平庸,我不知道为什么它从未被添加作为测试或作为一个独立的命令
亚历克斯

1
对于(确实)受约束的系统,我最近也一直需要针对此问题的解决方案。我什至优化了先前的代码,根本不使用任何额外的变量。朴实而难看的单缸套,但很棒。为自己服务:is_empty(){ [ ! -d "${1}" ] && return 2;set -- "${1}" "${1}"/* "${1}"/.[!.]* "${1}"/..?*;[ "${*}" = "${1} ${1}/* ${1}/.[!.]* ${1}/..?*" ]; };
迭戈·奥古斯托·莫利纳

2

ZSH

我知道这个问题被标为“ bash”。但是,仅供参考,适用于zsh用户:

测试非空目录

要检查是否foo为非空:

$ for i in foo(NF) ; do ... ; done

如果foo为非空,for则将执行块中的代码。

测试空目录

要检查是否foo为空:

$ for i in foo(N/^F) ; do ... ; done

如果foo为空,for则将执行块中的代码。

笔记

我们不需要引用foo上面的目录,但是如果需要,可以这样做:

$ for i in 'some directory!'(NF) ; do ... ; done

我们也可以测试多个对象,即使它不是目录:

$ mkdir X     # empty directory
$ touch f     # regular file
$ for i in X(N/^F) f(N/^F) ; do echo $i ; done  # echo empty directories
X

任何不是目录的内容都将被忽略。

附加功能

由于我们在进行globe,因此可以使用任何glob(或大括号扩展):

$ mkdir X X1 X2 Y Y1 Y2 Z
$ touch Xf                    # create regular file
$ touch X1/f                  # directory X1 is not empty
$ touch Y1/.f                 # directory Y1 is not empty
$ ls -F                       # list all objects
X/ X1/ X2/ Xf Y/ Y1/ Y2/ Z/
$ for i in {X,Y}*(N/^F); do printf "$i "; done; echo  # print empty directories
X X2 Y Y2

我们还可以检查放置在数组中的对象。使用上面的目录,例如:

$ ls -F                       # list all objects
X/ X1/ X2/ Xf Y/ Y1/ Y2/ Z/
$ arr=(*)                     # place objects into array "arr"
$ for i in ${^arr}(N/^F); do printf "$i "; done; echo
X X2 Y Y2 Z

因此,我们可以测试可能已经在数组参数中设置的对象。

注意for,显然,该块中的代码依次在每个目录上执行。如果不希望这样做,则可以简单地填充一个数组参数,然后对该参数进行操作:

$ for i in *(NF) ; do full_directories+=($i) ; done
$ do_something $full_directories

说明

对于zsh用户,有一个(F)glob限定符(请参阅参考资料man zshexpn),它与“完整”(非空)目录匹配:

$ mkdir X Y
$ touch Y/.f        # Y is now not empty
$ touch f           # create a regular file
$ ls -dF *          # list everything in the current directory
f X/ Y/
$ ls -dF *(F)       # will list only "full" directories
Y/

限定词(F)列出了符合以下条件的对象:是目录,并且不为空。因此,(^F)匹配:不是目录,或者为空。因此,(^F)例如,仅一个人也将列出常规文件。因此,如zshexp手册页中所述,我们还需要(/)glob限定符,该限定符仅列出目录:

$ mkdir X Y Z
$ touch X/f Y/.f    # directories X and Y now not empty
$ for i in *(/^F) ; do echo $i ; done
Z

因此,要检查给定目录是否为空,可以运行:

$ mkdir X
$ for i in X(/^F) ; do echo $i ; done ; echo "finished"
X
finished

并确保不会捕获非空目录:

$ mkdir Y
$ touch Y/.f
$ for i in Y(/^F) ; do echo $i ; done ; echo "finished"
zsh: no matches found: Y(/^F)
finished

糟糕!由于Y不为空,因此zsh找不到与之匹配的内容(/^F)(“目录为空”),并发出一条错误消息,指出未找到与该glob匹配的内容。因此,我们需要使用(N)glob限定符抑制这些可能的错误消息:

$ mkdir Y
$ touch Y/.f
$ for i in Y(N/^F) ; do echo $i ; done ; echo "finished"
finished

因此,对于空目录,我们需要使用限定符(N/^F),您可以将其读为:“不要警告我失败,目录不完整”。

同样,对于非空目录,我们需要使用qualifier (NF),我们也可以将其理解为:“不要警告我失败,完整目录”。


1

我很惊讶没有提到空目录上wootedge指南。对于壳类型问题,该指南以及所有wooleedge都是必读的。

该页面值得注意的是:

切勿尝试解析ls输出。甚至ls -A解决方案也可能会崩溃(例如,在HP-UX上,如果您是root用户,ls -A的作用与如果您不是root用户的作用恰好相反-而且,我无法弥补一些难以置信的问题笨)。

实际上,人们可能希望完全避免这一直接问题。通常,人们想知道目录是否为空,因为他们想做一些涉及其中文件的操作,等等。请看更大的问题。例如,以下基于查找的示例之一可能是合适的解决方案:

   # Bourne
   find "$somedir" -type f -exec echo Found unexpected file {} \;
   find "$somedir" -maxdepth 0 -empty -exec echo {} is empty. \;  # GNU/BSD
   find "$somedir" -type d -empty -exec cp /my/configfile {} \;   # GNU/BSD

通常,真正需要的是这样的东西:

   # Bourne
   for f in ./*.mpg; do
        test -f "$f" || continue
        mympgviewer "$f"
    done

换句话说,提出问题的人可能认为需要显式的空目录测试,以避免出现类似mympgviewer的错误消息:./*.mpg:实际上不需要此类文件或目录,而实际上不需要此类测试。


1

布鲁诺答案的小变化:

files=$(ls -1 /some/dir| wc -l)
if [ $files -gt 0 ] 
then
    echo "Contains files"
else
    echo "Empty"
fi

这个对我有用


1

从olibre的答案中得到一个提示(或几个提示),我喜欢Bash函数:

function isEmptyDir {
  [ -d $1 -a -n "$( find $1 -prune -empty 2>/dev/null )" ]
}

因为尽管它创建了一个子外壳,但正如我所能想象的,它与O(1)解决方案非常接近,并为其命名可以使其可读。然后我可以写

if isEmptyDir somedir
then
  echo somedir is an empty directory
else
  echo somedir does not exist, is not a dir, is unreadable, or is  not empty
fi

至于O(1),则存在异常情况:如果大型目录已删除了除最后一个条目之外的所有条目,则“查找”可能必须阅读整个内容以确定它是否为空。我相信预期性能为O(1),但最坏情况是目录大小呈线性关系。我还没有测量。


0

到目前为止,我还没有看到使用grep的答案,我认为这会给出一个更简单的答案(没有太多怪异的符号!)。这是我使用bourne shell检查目录中是否存在任何文件的方式:

这将返回目录中的文件数:

ls -l <directory> | egrep -c "^-"

您可以填写写入目录的目录路径。管道的前半部分确保每个文件的输出第一个字符为“-”。然后,egrep使用正则表达式计算以该符号开头的行数。现在,您要做的就是存储获得的数字,并使用反引号将其进行比较,例如:

 #!/bin/sh 
 fileNum=`ls -l <directory> | egrep -c "^-"`  
 if [ $fileNum == x ] 
 then  
 #do what you want to do
 fi

x是您选择的变量。


0

混合修剪的东西和最后的答案,我到

find "$some_dir" -prune -empty -type d | read && echo empty || echo "not empty"

也适用于带有空格的路径



0

我会去find

if [ -z "$(find $dir -maxdepth 1 -type f)" ]; then
    echo "$dir has NO files"
else
    echo "$dir has files"

这将检查仅在目录中查找文件的输出,而无需遍历子目录。然后,它使用以下-z选项检查输出man test

   -z STRING
          the length of STRING is zero

看到一些结果:

$ mkdir aaa
$ dir="aaa"

空目录:

$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
empty

只是在其中:

$ mkdir aaa/bbb
$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
empty

目录中的文件:

$ touch aaa/myfile
$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
$ rm aaa/myfile 

子目录中的文件:

$ touch aaa/bbb/another_file
$ [ -z "$(find aaa/ -maxdepth 1 -type f)" ] && echo "empty"
empty

0

通过一些解决方法,我可以找到一种简单的方法来查找目录中是否有文件。使用grep命令可以进一步扩展此功能,以专门检查.xml或.txt文件等。例如:ls /some/dir | grep xml | wc -l | grep -w "0"

#!/bin/bash
if ([ $(ls /some/dir | wc -l  | grep -w "0") ])
    then
        echo 'No files'
    else
        echo 'Found files'
fi


-1

测试特定的目标目录

if [ -d $target_dir ]; then
    ls_contents=$(ls -1 $target_dir | xargs); 
    if [ ! -z "$ls_contents" -a "$ls_contents" != "" ]; then
        echo "is not empty";
    else
        echo "is empty";
    fi;
else
    echo "directory does not exist";
fi;

-1

尝试使用命令查找。指定硬编码或作为参数的目录。然后启动查找以搜索目录中的所有文件。检查查找的返回是否为空。回显查找的数据

#!/bin/bash

_DIR="/home/user/test/"
#_DIR=$1
_FIND=$(find $_DIR -type f )
if [ -n "$_FIND" ]
then
   echo -e "$_DIR contains files or subdirs with files \n\n "
   echo "$_FIND"
else
echo "empty (or does not exist)"
fi

如果目录中包含空目录或非常规文件(套接字,char设备,符号链接等),则此方法将无效。该目录将被报告为空。
迭戈·奥古斯托·莫利纳

@DiegoAugustoMolina如果我在问题中正确理解的是,我们正在检查文件是否存在(作为原义文件),这就是使用-f参数的原因。但您的评论仍然正确。
igiannak

-2

我不喜欢 ls - A发布解决方案。您很可能希望测试目录是否为空,因为您不希望将其删除。下面这样做。但是,如果您只想记录一个空文件,那么确定删除并重新创建它会更快,然后列出可能无限的文件?

这应该工作...

if !  rmdir ${target}
then
    echo "not empty"
else
    echo "empty"
    mkdir ${target}
fi

4
如果用户没有对的写入权限,则此方法不起作用${target}/..
詹斯(Jens)2012年

使测试具有破坏性只是一个坏主意。除其他外,它引入了比赛条件。
4dummies,

-2

这对我来说很好(当目录存在时):

some_dir="/some/dir with whitespace & other characters/"
if find "`echo "$some_dir"`" -maxdepth 0 -empty | read v; then echo "Empty dir"; fi

经过全面检查:

if [ -d "$some_dir" ]; then
  if find "`echo "$some_dir"`" -maxdepth 0 -empty | read v; then echo "Empty dir"; else "Dir is NOT empty" fi
fi
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.