UNIX 文件元数据:便携式可解析解决方案例如 POSIX / ubiquitous

问题描述

我正在编写一个 shell 脚本,并且很惊讶我找不到一种可移植的跨平台方法获取基本的文件元数据,例如:类型、修改时间、权限、链接路径等。基本上是一样的ls 输出的东西,但以友好的可解析方式。

参考这篇文章,了解为什么不应该解析 lsWhy not parse ls (and what to do instead)?

似乎每个人都说使用 statfind 来完成类似的最终结果,但我再次惊讶地发现,在我的两台计算机(一台 Ubuntu 18.04,一台 MacOS X Catalina ),我想不出任何适用于两个系统的通用语法。我相信这两个实用程序都是 GNU 扩展。参考这篇文章How can I get the size of a file in a bash script?

对于 stat,Ubuntu 使用 --printf=FORMAT 来指定字段。在 MacOS X(基于 BSD)中,语法是 -f format。字段的名称和顺序也不同,使用 RegEx 解析不实用。

对于 find,Ubuntu 有一个可用于 -printf format 的“操作”字段。 MacOS X 根本没有这个选项或我知道的任何类似的东西。

所以我的问题是: 如果 lsstatfind 没有提供可移植的解决方案来获取可解析的文件元数据,我该怎么做?把它吸起来然后解析 ls??? 这看起来太基本了,我不敢相信没有什么可以跨平台工作的......它本身不必是 POSIX,只是对于'Nix OS'es 来说基本上是无处不在的东西。

到目前为止,我发现了两个有点垃圾解决方案,但我会记下它们以供参考...:

  1. 使用 rsync 作为辅助工具,并使用其 --out-format 标志。使用 --dry-run 选项,因此它实际上不执行任何文件传输。我在我的 Mac 和 Linux 机器上都试过这个,它似乎有效,但它很慢/很脏,我不知道 rsync 是否被认为无处不在。我从我忘记添加书签的 StackOverflow 帖子中找到了这个。 ;-)

  2. 使用 tar 作为辅助工具(我知道 pax 是新的 POSIX 标准,但它不在我的 Ubuntu 机器上),通过管道传输输出并解析其标头只要。丢弃其余的(例如通过管道将其发送到 /dev/null)。我在这文章中发现了这个想法:Getting file modification time in POSIX shell。所以......我绝对认为 tar 实用程序无处不在(它曾经是 POSIX 的一部分)。标题非常适合解析。不过,我有点担心它可能会读取比我想要的更多的文件内容

我使用包含一些大文件(例如 70GB)的目录测试了这两种方法tar 方法肯定不会读取整个文件,尽管您可以稍微注意到它比 ls / stat / find代码需要一些花哨的步法...

再次重申我的问题,是否有一种更简单的方式以可移植的方式获取文件元数据,至少适用于 OSX 和 Ubuntu 以及大多数“Nix”?


参考资料 - 仅供有兴趣的读者使用

以下是上述方法 1 和方法 2 的代码片段。我从参考帖子开始,然后将其带到下一步。出于演示目的,我在下面发布的代码使用每种方法输出来打印看起来像 ls 的目录列表。只是为了演示...

两种方法:肮脏,肮脏

方法 1 (rsync):

ALT_STAT() {
    ALT_STAT_NAME="${1}"
    set -- $(rsync --dry-run --dirs --ignore-times --links --specials --out-format='%i %B %l %U %G' "${1}" "${1}")
    ALT_STAT_TYPE="${1:1:1}"
    ALT_STAT_PERMS="${2}"
    ALT_STAT_EXEC="$(echo "${2}" | sed -n $'/[xt]/i\\\nexe')"
    ALT_STAT_LABEL="$(echo $'freg\nddir\nLlnk\nDdev\nsspe' | sed -n '/^${ALT_STAT_TYPE}/s/^.//p')"
    [ "${ALT_STAT_LABEL}"="reg" -a "${ALT_STAT_EXEC}"="exe" ] && ALT_STAT_LABEL="exe"
    ALT_STAT_SIZE="${3}"
    ALT_STAT_USER="$(id -un ${4})"
    ALT_STAT_GROUP="${5}"
    ALT_STAT_LINK=$(readlink "${ALT_STAT_NAME}")
    [ "${ALT_STAT_LINK}" -a "${ALT_STAT_LINK:1:1}" != "/" ] && ALT_STAT_LINK="$(PATH="$(pwd):${PATH}" which ${ALT_STAT_LINK})"
    ALT_STAT_MTIME=$(date -r "${ALT_STAT_LINK:-${ALT_STAT_NAME}}" +%s)
    [ "${ALT_STAT_LINK}" ] && ALT_STAT_LINK="--> ${ALT_STAT_LINK}"
}

ALT_LS() {
    for f in *; do
        ALT_STAT "${f}"
        printf "%3.3s | %9.9s | %12.12s | %10.10s | %9.9s | %50.50s | %s\n" \
        "${ALT_STAT_LABEL}" "${ALT_STAT_PERMS}" "${ALT_STAT_USER}" "${ALT_STAT_GROUP}"\
        "${ALT_STAT_SIZE}" "${ALT_STAT_NAME} ${ALT_STAT_LINK}" $(date -r "${ALT_STAT_MTIME}" +%D_%T)
    done
}

方法 2 (tar):

cat > fileMetadata.sh <<\ENDSCRIPT
#!/bin/bash

readTarHeader() {
    read -n 100; name="${REPLY}"
    read -n 8; mode="${REPLY}"
    read -n 8; uid=$((8#${REPLY}))
    read -n 8; gid=$((8#${REPLY}))
    read -n 12; size=$((8#${REPLY}))
    read -n 12; mtime=$((8#${REPLY}))
    read -n 8; checksum=$((8#${REPLY}))
    read -n 1; typeflag="${REPLY}"
    read -n 100; linkname="${REPLY}"
    read -n 6; magic="${REPLY}"
    read -n 2; version="${REPLY}"
    read -n 32; uname="${REPLY}"
    read -n 32; gname="${REPLY}"
    read -n 8; devmajor="${REPLY}"
    read -n 8; devminor="${REPLY}"
    read -n 155; prefix="${REPLY}"
    read -n 12; # Padding

    # Flush buffers (tested on a 78GB file; it was so fast that it can't be reading it)
    cat > /dev/null
}

writeBackHeaders() {
    printf "%6.6s | %1.1s | %16.16s | %8.8s | %9.9s | %19.19s | %10.10s | %35.35s" \
        "${mode}" "${typeflag}" "${uname}" "${gname}" "${size}" $(date -r "${mtime}" +%Y/%m/%d_%H:%M%:%s) "${prefix}" "${name} ${linkname}"
    echo
}

printMetaData() {
    for file in "${@}"; do
        tar --use-compress-program="${0} -t" -c "${file}" | cat 
    done
}

[ "${1}" = "-t" ] && { readTarHeader; writeBackHeaders; exit 0; }

[ "${1}" = "-p" ] || exit -1

[ -d ${2} ] && printMetaData "${2}"* || printMetaData "${2}"

ENDSCRIPT

用法

chmod 777 fileMetadata.sh
./fileMetadata.sh -p testDir2/

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)