我正在编写一个 shell 脚本,令我惊讶的是我找不到一个可移植的跨平台方法来获取基本的文件元数据,例如:类型、修改时间、权限、链接路径等。基本上,与
ls
输出,但以友好的可解析方式。
请参考这篇文章,了解有关为什么不应该解析的信息
ls
:
为什么不解析ls
(以及该怎么做)?
每个人似乎都说使用
stat
或 find
来完成类似的最终结果,但我再次惊讶地发现在我的两台计算机(一台 Ubuntu 18.04,一台 MacOS X Catalina)上,我无法提出适用于两个系统的任何通用语法。我相信这两个实用程序都是 GNU 扩展。参考这篇文章:如何在 bash 脚本中获取文件的大小?
对于
stat
,Ubuntu 使用 --printf=FORMAT
来指定字段。在 MacOS X(基于 BSD)中,语法为 -f format
。字段的名称和顺序也不同,使得使用正则表达式进行解析不切实际。
对于
find
,Ubuntu 有一个可用于 -printf format
的“操作”字段。据我所知,MacOS X 根本没有这个选项或任何类似的选项。
所以我的问题是: 如果
ls
、stat
和 find
没有提供可移植的解决方案来获取可解析的文件元数据,我该怎么做?只是吸收它并解析 ls??? 这看起来太基本了,我不敢相信没有跨平台的东西......它本身不一定是 POSIX,只是基本上的东西“Nix OS”无处不在。
到目前为止,我发现了两个有点垃圾的解决方案,但我会记下它们以供参考......:
使用
rsync
作为辅助工具,并使用其 --out-format
标志。使用 --dry-run
选项,这样它实际上不会进行任何文件传输。我在我的 Mac 和 Linux 机器上都尝试过这个,它似乎有效,但它非常慢/肮脏,而且我不知道 rsync
是否被认为是普遍存在的。我从 StackOverflow 的一些帖子中发现了这个,但我忘了添加书签。 ;-)
使用
tar
作为辅助工具(我知道 pax
是新的 POSIX 标准,但它不在我的 Ubuntu 机器上),通过管道传输输出,并仅解析其标头。丢弃其余的(例如,通过管道将其传输到 /dev/null
)。我在这篇文章中发现了这个想法:在 POSIX shell 中获取文件修改时间。所以...我绝对认为 tar
实用程序足够普遍(它曾经是 POSIX 的一部分)。并且标头非常适合解析。不过,我有点担心它可能会读取比我想要的更多的文件内容。
我使用包含一些大文件(例如 70GB)的目录测试了这两种方法,并且
tar
方法绝对不会读取整个文件,尽管您可以稍微注意到它比 ls
/ stat
/ find
慢
。而且代码需要一些花哨的步法......
重申我的问题,是否有一种不太肮脏的方式以可移植的方式获取文件元数据,至少适用于 OSX 和 Ubuntu 以及大多数“Nix”?
参考资料 - 仅限感兴趣的读者
这里是上面方法1和方法2的代码片段。我从参考帖子开始,然后进行下一步。出于演示目的,我在下面发布的代码使用每个方法的输出来打印看起来像
ls
的目录列表。只是为了演示...
两种方法:肮脏,肮脏
方法一(
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/
确实,这是目前 POSIX 提供的一个差距。
一个常见的解决方法是编写一个简单的脚本来提取您需要的信息。虽然不是严格的标准,但现代脚本语言(如 Python 和 Perl)相当普遍,并且通常提供足够的附加价值,作为附加依赖项有意义,即使您尝试编写其他可移植脚本也是如此。
Python 和 Perl 都公开了底层
stat
系统调用。这是两个简单的例子。
我没有提供详尽的示例,而是仅演示如何打印文件大小,并链接到文档以获取更多详细信息。
#!/usr/bin/env python3
import os
import sys
for filename in sys.argv[1:]:
st = os.stat(filename)
print(f"{st.st_size}\t{filename}")
有关 Python
os.stat
调用的文档,请参阅
https://docs.python.org/3/library/stat.html 和 https://docs.python.org/3/library/stat.html
(使用
f"..."
字符串需要 Python >= 3.7。)
#!/usr/bin/perl
for $file (@ARGV) {
print(join("\t", (stat($file))[7], $file), "\n")
}
有关 Perl
stat
调用的文档,请参阅 https://perldoc.perl.org/functions/stat