检查目录是否包含所有(且仅)列出的文件

check whether a directory contains all (and only) the listed files

我正在编写单元测试来测试文件 IO 功能。 在我的目标语言中没有正式的测试框架,所以我的想法是 运行 一个小测试程序,以某种方式操作测试目录中的文件,然后在一个小 shell 脚本中检查结果.

为了评估输出,我想检查给定目录是否存在所有预期文件,并且在测试期间是否未创建其他文件。

我的第一次尝试是这样的:

set -e

test -e "${test_dir}/a.txt"
test -e "${test_dir}/b.txt"
test -d "${test_dir}/dir"
find "${test_dir}" -mindepth 1 \
     -not -wholename "${test_dir}/a.txt" \
     -not -wholename "${test_dir}/b.txt" \
     -not -wholename "${test_dir}/dir" \
    | grep . && exit 1 || true

这正确检测是否有两个文件 a.txtb.txt,以及 ${test_dir} 中的子目录 dir/。 如果碰巧有一个文件 c.txt,测试应该并且将会失败。

但是,这并不能很好地扩展。 有几十个单元测试,每个都有一组不同的files/directories,所以我发现自己一次又一次地重复与上面非常相似的行。

所以我宁愿将上面的内容包装成这样的函数调用:

if checkdirectory "${test_dir}" a.txt b.txt dir/ dir/subdir/ dir/.hidden.txt; then
  echo "ok"
else
  echo "ko"
fi

不幸的是,我不知道如何实现 checkdirectory(尤其是 find 调用多个 -not -wholename ... 节让我很头疼)。

为了增加一点乐趣,限制条件是:

编辑

更多限制条件(这些并没有成为我的深夜问题的原始问题;因此只需将它们视为“奖励积分的额外限制条件”)

我不知道你所说的区分文件和目录是什么意思,因为你最后的 if 语句不知何故是二进制的。以下是对我有用的方法:

#! /bin/bash

function checkdirectory()
{
        test_dir=""
        shift
        content="$@"

        for file in ${content}
        do
                [[ -z "${test_dir}/${file}" ]] && return 1
        done

        # -I is meant to be appended to "ls" to ignore the files in order to check if other files exist.  
        matched=" -I ${content// / -I } -I ${test_dir}"

        [[ -e `ls $matched` ]] && return 1

        return 0
}


if checkdirectory /some/directory a.txt b.txt dir; then
        echo "ok"
else
        echo "ko"
fi
 

一种简单快捷的方法是将 find 的输出与参考字符串进行比较:

让我们从预期的目录和文件结构开始:

d/FolderA/filexx.csv
d/FolderA/filexx.doc
d/FolderA/Sub1
d/FolderA/Sub2

testassert

#!/usr/bin/env bash

assertDirContent() {
  read -r -d '' s < <(find "" -printf '%y %p\n')
  [ "" = "$s" ]
}

testref='d d/FolderA/
f d/FolderA/filexx.csv
f d/FolderA/filexx.doc
d d/FolderA/Sub1
d d/FolderA/Sub2'

if assertDirContent 'd/FolderA/' "$testref"; then
  echo 'ok'
else
  echo 'Directory content assertion failed'
fi

正在测试:

$ ./testassert
ok
$ touch d/FolderA/unwantedfile
$ ./testassert
Directory content assertion failed
$ rm d/FolderA/unwantedfile
$ ./testassert
ok
$ rmdir d/FolderA/Sub1
$ ./testassert
Directory content assertion failed
$ mkdir d/FolderA/Sub1
$ ./testassert
ok
$ rmdir d/FolderA/Sub2
# Replace with a file instead of a directory
touch d/FolderA/Sub2
$ ./testassert
Directory content assertion failed

现在,如果您将时间戳和权限、所有者、组等其他信息添加到 find -printf 输出,您还可以检查所有这些是否与断言的字符串输出相匹配。

假设我们有一个目录树作为例子:

$test_dir/a.txt
$test_dir/b.txt
$test_dir/dir/c.txt
$test_dir/dir/"d e".txt
$test_dir/dir/subdir/

那你试试看:

#!/bin/sh

checkdirectory() {
    local i
    local count
    local testdir=
    shift
    for i in "$@"; do
        case "$i" in
            */) [ -d "$testdir/$i" ] || return 1 ;;     # check if the directory exists
            *)  [ -f "$testdir/$i" ] || return 1 ;;     # check if the file exists
        esac
    done
    # convert each filename to just a newline, then count the lines
    count=`find "$testdir" -mindepth 1 -printf "\n" | wc -l`
    [ "$count" -eq "$#" ] || return 1
    return 0
}

if checkdirectory "$test_dir" a.txt b.txt dir/ dir/c.txt "dir/d e.txt" dir/subdir/; then
    echo "ok"
else
    echo "ko"
fi

这是我昨晚想到的一个可能的解决方案。

它会破坏测试数据,因此在很多情况下可能无法使用(尽管它可能只适用于单元测试期间即时生成的路径):

checkdirectory() {
    local i
    local testdir=
    shift
    # try to remove all the listed files
    for i in "$@"; do
        if [ "x${i}" = "x${i%/}" ]; then
            rm "${testdir}/${i}" || return 1
        fi
    done

    # the directories should now be empty,
    # so try to remove those dirs that are listed
    for i in "$@"; do
        if [ "x${i}" != "x${i%/}" ]; then
            rmdir "${testdir}/${i}" || return 1
        fi
    done

    # finally ensure that no files are left
    if find "${testdir}" -mindepth 1 | grep . >/dev/null ; then
        return 1
    fi
    return 0
}

调用checkdirectory函数时,更深层次的目录必须在前(即checkdirectory foo/bar/ foo/而不是checkdirectory foo/ foo/bar/)。