百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

Shell如何检查一个目录是否为空?

wptr33 2024-12-10 21:20 15 浏览


如何检查一个目录是否为空?如何检查是否存在任何 *.mpg 文件,或者计算有多少个这样的文件?

在 Bash 中,你可以通过使用 nullglob 和 dotglob 选项(这会改变 globbing 的行为),以及一个 array,安全而简便地计算文件数量:

# Bash
shopt -s nullglob dotglob
files=(*)
(( ${#files[*]} )) || echo 目录为空
shopt -u nullglob dotglob

当然,你可以使用任何你喜欢的 glob,而不仅仅是 。比如,.mpg 或者 /my/music/*.mpg 都可以正常工作。

请注意,你需要对目录拥有读取权限,否则它将始终显示为空。

一些人不喜欢 nullglob,因为没有匹配的 glob 会完全消失,这会让像 ls 这样的程序感到困惑。如果将 ls *.zip 错误地输入为 ls *.zpi,可能会导致显示所有文件(对于这种情况,考虑设置 failglob)。在子 shell 中设置 nullglob 可以避免意外地改变其余部分的 shell 设置,但代价是额外的 fork()。如果你想避免设置和取消设置 shell 选项,你可以将所有内容放入一个子 shell:

# Bash
if (shopt -s nullglob dotglob; f=(*); ((! ${#f[@]}))); then
    echo "当前目录为空。"
fi

这种方法的另一个缺点(除了额外的 fork())是,当子 shell 退出时,数组会丢失。如果你计划稍后使用这些文件名,那么它们必须再次检索。

这两个示例都会扩展 glob 并将结果文件名存储到一个数组中,然后检查数组中元素的数量是否为 0。如果你实际上想要查看有多少个文件,只需打印数组的大小,而不是检查它是否为 0:

# Bash
shopt -s nullglob dotglob
files=(*)
echo "当前目录包含 ${#files[@]} 个文件。"

如果你不介意在数组中放入一个不存在的文件名(而不是一个空数组),你也可以避免使用 nullglob:

# Bash
shopt -s dotglob
files=(*)
if [[ -e ${files[0]} || -L ${files[0]} ]]; then
    echo "当前目录不为空。它包含以下文件:"
    printf '%s\n' "${files[@]}"
fi

如果目录中没有文件,则没有 nullglob,这个 glob 将被添加为数组中的唯一元素。由于 * 是一个有效的文件名,我们无法简单地检查数组是否包含一个字面上的 *。因此,我们检查数组中的内容是否作为文件存在。需要注意的是,-L 测试是必需的,因为 -e 如果第一个文件是一个 dangling symlink,将失败。

如果您不关心有多少匹配的文件,并且不想将结果存储在数组中,您可以使用Bash的compgen命令。不幸的是,由于一个bug,您需要使用一个技巧让其识别dotglob:

# Bash
if (shopt -s dotglob; : *; compgen -G '*' >/dev/null); then
    echo "当前目录不为空。"
else
    echo "当前目录为空。"
fi

或者您可以使用扩展的glob:

# Bash
# 可以通过为整个脚本启用extglob来避免子shell。
# 这样做是安全的。
if (shopt -s extglob; compgen -G '@(*|.[!.]*|..?*)' >/dev/null); then
    echo "当前目录不为空。"
else
    echo "当前目录为空。"
fi

您还可以使用failglob:

# Bash
if (shopt -s dotglob failglob; : ./*) 2>/dev/null; then
    echo "当前目录不为空。"
else
    echo "当前目录为空。"
fi

但是,请注意,如果您使用failglob,则需要子shell;下面的代码无法运行,因为failglob会引发一个shell错误,导致Bash停止运行当前命令(包括if命令,任何外部复合命令以及运行此代码的整个函数,如果它是函数的一部分),因此这仅适用于true的情况,else分支永远不会运行:

# 错误的代码!
shopt -s dotglob failglob
if { : ./*; } 2>/dev/null; then
    echo "当前目录不为空。"
else
    echo "当前目录为空。"
fi

如果您真的想避免使用子shell并且想要全局设置failglob,您可以使用命令eval来“捕获”shell错误,或者您可以编写一个间接展开glob的函数:

shopt -s dotglob failglob
if command eval ': ./*' 2>/dev/null; then
    echo "当前目录不为空。"
else
    echo "当前目录为空。"
fi
# 或者
shopt -s dotglob failglob
any_match () { local IFS=; { : "$@"; } 2>/dev/null; }
if any_match './*'; then
    echo "当前目录不为空。"
else
    echo "当前目录为空。"
fi

如果您的脚本需要在各种非Bash shell实现中运行,您可以尝试使用外部程序如Python、Perl或find;或者您可以尝试以下方法。请注意“magic 3 globs”^[1],因为POSIX没有dotglob选项。

# POSIX
# 这会破坏位置参数,所以确保您不需要它们。
set -- * .[!.]* ..?*
for f in "$@"; do
  if test -e "$f" || test -L "$f"; then
    echo "目录不为空"
    break
  fi
done

在这个阶段,位置参数已经加载了目录的内容,并且可以用于处理。

如果你只想计算文件数量:

# POSIX
n=0
for f in * .[!.]* ..?*; do
  if test -e "$f" || test -L "$f"; then n=$((n+1)); fi
done
printf "共有 %d 个文件。\n" "$n"

在 Bourne shell 中,情况更糟糕,因为没有 test -e 或 test -L?:

# Bourne
# (当然,系统必须有 printf(1)。)
if test "`printf '%s %s %s' .* *`" = '. .. *' && test ! -f '*'
then
    echo "目录为空"
fi

当然,如果 *? 存在于普通文件以外的其他内容(如目录或 FIFO),这种方法会失败。缺少 -e? 测试真的很痛苦。

这里还有另一种使用 find? 的解决方案:

# POSIX
# 打印每个文件的一个 `.` 并计算打印的字符数。
# 这个解决方案将递归。如果不想递归,请参见下面的内容。
n=$(find . -type f -exec printf %.0s. {} + | wc -m)
printf "共有 %d 个文件。\n" "$n"

如果你不希望递归,那么你需要告诉 find? 不要递归进入目录。这变得非常棘手和丑陋。GNU find? 有一个 -maxdepth? 选项来实现此功能。对于标准的 POSIX find?,你只能使用 -prune?。这留给读者作为练习。

永远不要尝试解析 ls? 的输出。即使是 ls -A? 的解决方案也可能会出错(例如,在 HP-UX 上,如果你是 root 用户,ls -A? 与非 root 用户的行为正好相反--不,我不能编造出这么愚蠢的东西)。

实际上,你可能希望完全避免直接的 *问题。通常人们想知道一个目录是否为空是因为他们想在其中的文件中进行一些操作等。要看到更大的问题。例如,这些基于 find? 的例子可能是一个合适的解决方案:

# Bourne / POSIX
find "$somedir" -type f -exec echo 发现意外的文件 {} \;
find "$somedir" -prune -empty -exec printf '%s 是空的。\n' {} \;  # GNU/BSD
find "$somedir" -type d -empty -exec cp /my/configfile {} \;   # GNU/BSD

最常见的情况是只需要像这样:

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

换句话说,提问者可能认为需要显式的空目录测试来避免出现错误消息,比如 mympgviewer: ./*.mpg: No such file or directory?,当实际上并不需要这样的测试。

对于类似 nullglob 的特性的支持是不一致的。在 ksh93 中,可以通过在模式前加上 ~(N)? 来实现每个模式的基础:

# ksh93
for f in ~(N)*; do
    ....
done

如果您觉得文章内容对你有一点帮助可以关注我,我在头条平台会持续分享更多实用的shell技巧和最佳实践,如果想系统的快速学习shell的各种高阶用法和生产环境避坑指南可以看看《shell脚本编程最佳实践》专栏,专栏里有更多的实用小技巧和脚本代码分享。

相关推荐

每天一个编程技巧!掌握这7个神技,代码效率飙升200%

“同事6点下班,你却为改BUG加班到凌晨?不是你不努力,而是没掌握‘偷懒’的艺术!本文揭秘谷歌工程师私藏的7个编程神技,每天1分钟,让你的代码从‘能用’变‘逆天’。文末附《Python高效代码模板》,...

Git重置到某个历史节点(Sourcetree工具)

前言Sourcetree回滚提交和重置当前分支到此次提交的区别?回滚提交是指将改动的代码提交到本地仓库,但未推送到远端仓库的时候。...

git工作区、暂存区、本地仓库、远程仓库的区别和联系

很多程序员天天写代码,提交代码,拉取代码,对git操作非常熟练,但是对git的原理并不甚了解,借助豆包AI,写个文章总结一下。Git的四个核心区域(工作区、暂存区、本地仓库、远程仓库)是版本控制的核...

解锁人生新剧本的密钥:学会让往事退场

开篇:敦煌莫高窟的千年启示在莫高窟321窟的《降魔变》壁画前,讲解员指着斑驳色彩说:"画师刻意保留了历代修补痕迹,因为真正的传承不是定格,而是流动。"就像我们的人生剧本,精彩章节永远...

Reset local repository branch to be just like remote repository HEAD

技术背景在使用Git进行版本控制时,有时会遇到本地分支与远程分支不一致的情况。可能是因为误操作、多人协作时远程分支被更新等原因。这时就需要将本地分支重置为与远程分支的...

Git恢复至之前版本(git恢复到pull之前的版本)

让程序回到提交前的样子:两种解决方法:回退(reset)、反做(revert)方法一:gitreset...

如何将文件重置或回退到特定版本(怎么让文件回到初始状态)

技术背景在使用Git进行版本控制时,经常会遇到需要将文件回退到特定版本的情况。可能是因为当前版本出现了错误,或者想要恢复到之前某个稳定的版本。Git提供了多种方式来实现这一需求。...

git如何正确回滚代码(git命令回滚代码)

方法一,删除远程分支再提交①首先两步保证当前工作区是干净的,并且和远程分支代码一致$gitcocurrentBranch$gitpullorigincurrentBranch$gi...

[git]撤销的相关命令:reset、revert、checkout

基本概念如果不清晰上面的四个概念,请查看廖老师的git教程这里我多说几句:最开始我使用git的时候,我并不明白我为什么写完代码要用git的一些列指令把我的修改存起来。后来用多了,也就明白了为什么。gi...

利用shell脚本将Mysql错误日志保存到数据库中

说明:利用shell脚本将MYSQL的错误日志提取并保存到数据库中步骤:1)创建数据库,创建表CreatedatabaseMysqlCenter;UseMysqlCenter;CREATET...

MySQL 9.3 引入增强的JavaScript支持

MySQL,这一广泛采用的开源关系型数据库管理系统(RDBMS),发布了其9.x系列的第三个更新版本——9.3版,带来了多项新功能。...

python 连接 mysql 数据库(python连接MySQL数据库案例)

用PyMySQL包来连接Python和MySQL。在使用前需要先通过pip来安装PyMySQL包:在windows系统中打开cmd,输入pipinstallPyMySQL ...

mysql导入导出命令(mysql 导入命令)

mysql导入导出命令mysqldump命令的输入是在bin目录下.1.导出整个数据库  mysqldump-u用户名-p数据库名>导出的文件名  mysqldump-uw...

MySQL-SQL介绍(mysql sqlyog)

介绍结构化查询语言是高级的非过程化编程语言,允许用户在高层数据结构上工作。它不要求用户指定对数据的存放方法,也不需要用户了解具体的数据存放方式,所以具有完全不同底层结构的不同数据库系统,可以使用相同...

MySQL 误删除数据恢复全攻略:基于 Binlog 的实战指南

在MySQL的世界里,二进制日志(Binlog)就是我们的"时光机"。它默默记录着数据库的每一个重要变更,就像一位忠实的史官,为我们在数据灾难中提供最后的救命稻草。本文将带您深入掌握如...