在 shell 中获取文件大小(以字节为单位)的可移植方法

在 Linux 上,我使用 stat --format="%s" FILE,但是我可以访问的 索拉里斯机器没有 stat命令。那我该用什么?

我正在编写 Bash 脚本,无法在系统上安装任何新软件。

我已经考虑过使用:

perl -e '@x=stat(shift);print $x[7]' FILE

甚至:

ls -nl FILE | awk '{print $5}'

但是这些看起来都不明智——运行 Perl只是为了得到文件大小?或者运行两个程序来做同样的事情?

198635 次浏览

您的第一个 Perl 示例在我看来并非不合理。

正是出于这样的原因,我从编写 shell 脚本(使用 Bash、 sh 等)转向编写佩尔最琐碎的脚本。我发现我不得不根据特定的需求启动 Perl,而且随着我越来越多地这样做,我意识到用 Perl 编写脚本可能是一种更强大的(就语言和通过 CPAN提供的大量库而言)和更有效的方法来实现我想要的。

请注意,其他 shell 脚本语言(例如 巨蟒露比)无疑也有类似的工具,您可能希望根据自己的需要对这些工具进行评估。我只讨论 Perl,因为它是我使用和熟悉的语言。

在 Linux 上你可以使用 du -h $FILE,也可以在 Solaris 上使用。

如果使用来自 GNU fileutils 的 find:

size=$( find . -maxdepth 1 -type f -name filename -printf '%s' )

遗憾的是,find的其他实现通常不支持 -maxdepth,也不支持 -printf。Solaris 和 macOS find就是这种情况。

wc -c < filename(字计数的缩写,-c打印字节计数)是一种可移植的 POSIX解决方案。只有输出格式在不同平台之间可能不统一,因为可能预先添加了一些空格(Solaris 就是这种情况)。

不要忽略输入重定向。当文件作为参数传递时,在字节计数之后打印文件名。

我担心它不能用于二进制文件,但它在 Linux 和 Solaris 上都能正常工作。你可以用 wc -c < /usr/bin/wc试试。此外,除非另外明确指定,否则 POSIX 实用程序是 保证处理二进制文件

如果您的 Solaris 上有 Perl,那么就使用它。否则,带有 AWK是的将是您的下一个最佳选择,因为您没有 立刻,或者您的 找到不是 GNU find。

试试 du -ks | awk '{print $1*1024}'也许能行。

最后,我决定使用 是的和 Bash 数组展开:

TEMP=( $( ls -ln FILE ) )
SIZE=${TEMP[4]}

虽然不是很好,但至少它只有一个 fork + Executive,而且不依赖于辅助编程语言(Perl露比巨蟒或其他)。

我在索拉里斯用过一个小把戏。如果你要求多个文件的大小,它只返回没有名称的总大小-所以包括一个空文件,如 /dev/null作为第二个文件:

比如说,

command fileyouwant /dev/null

我不记得这个命令适用于哪个大小-是的WC,等等-不幸的是,我没有一个 Solaris 框来测试它。

最后我写了我自己的程序(真的很小)来显示大小。更多信息在 一个 href = “ http://fwhacking.blogspot.com/2011/03/bfsize-print-file-size-in-bytes-and.html”rel = “ nofollow noReferrer”> bfsize-print file size in bytes (and just that) 中。

在我看来,使用普通 Linux 工具最干净的两种方式是:

stat -c %s /usr/bin/stat


50000




wc -c < /usr/bin/wc


36912

但是我只是不想输入参数或管道输出只是为了得到一个文件大小,所以我使用我自己的 Bfsize

尽管 du通常打印的是磁盘使用量而非实际数据大小,但 GNU核心工具组 du可以打印文件的“表观大小”(以字节为单位) :

du -b FILE

但是在 BSD索拉里斯MacOS等情况下不起作用。

跨平台的最快解决方案(它只对 是的使用一个 fork () ,不尝试计算实际字符数,不会产生不需要的 awk、 perl 等)。

它在 Mac OS X 和 Linux 上进行了测试,可能需要对 Solaris 进行一些小的修改:

__ln=( $( ls -Lon "$1" ) )
__size=${__ln[3]}
echo "Size is: $__size bytes"

如果需要,简化 是的参数,并调整 ${ _ _ ln [3]}中的偏移量。

注意: 它将遵循符号链接。

您可以使用 find命令获取一些文件集(这里提取临时文件)。然后,您可以使用 du命令使用 -h开关以人类可读的形式获取每个文件的文件大小。

find $HOME -type f -name "*~" -exec du -h {} \;

产出:

4.0K    /home/turing/Desktop/JavaExmp/TwoButtons.java~
4.0K    /home/turing/Desktop/JavaExmp/MyDrawPanel.java~
4.0K    /home/turing/Desktop/JavaExmp/Instream.java~
4.0K    /home/turing/Desktop/JavaExmp/RandomDemo.java~
4.0K    /home/turing/Desktop/JavaExmp/Buff.java~
4.0K    /home/turing/Desktop/JavaExmp/SimpleGui2.java~

在处理 ls -n输出时,作为可移植性差的 shell 数组的替代方案,您可以使用位置参数,这些参数形成唯一的数组,并且是标准 shell 中唯一的局部变量。在函数中包装位置参数的覆盖,以保留脚本或函数的原始参数。

getsize() { set -- $(ls -dn "$1") && echo $5; }
getsize FILE

这将根据当前的 IFS环境变量设置分割 ln -dn的输出,将其分配给位置参数,并回应第五个参数。与 -l不同,-d确保正确处理目录,-n确保不需要解析用户名和组名。另外,包含空白的用户名和组名理论上可能会破坏预期的行结构; 它们通常是不允许的,但这种可能性仍然会让程序员停下来思考。

BSD 系统具有与 GNU核心工具组不同的 stat选项,但具有类似的功能。

stat -f %z <file name>

这适用于 MacOS(在10.12上测试)、 FreeBSDNetBSDOpenBSD

我不知道 GNU发呆filefuncs扩展有多便携

time gawk -e '@load "filefuncs"; BEGIN {
fnL[1] = ARGV[ARGC-1];
fts(fnL, FTS_PHYSICAL, arr); print "";


for (fn0 in arr) {
print arr[fn0]["path"] \
" :: "arr[fn0]["stat"]["size"]; };


print ""; }' genieMV_204583_1.mp4




genieMV_204583_1.mp4 :: 259105690
real    0m0.013s




ls -Aln genieMV_204583_1.mp4


----------  1 501  20  259105690 Jan 25 09:31
genieMV_204583_1.mp4

该语法允许一次检查多个文件

time gawk -e '@load "filefuncs"; BEGIN {
stat(ARGV[ARGC-1], arr);
printf("\n%s :: %s\n", arr["name"], \
arr["size"]); }' genieMV_204583_1.mp4


genieMV_204583_1.mp4 :: 259105690
real    0m0.013s

这几乎算不上任何增量节约,但不可否认的是,它略低于 stat的直线节约速度:

time stat -f '%z' genieMV_204583_1.mp4


259105690
real    0m0.006s (BSD-stat)




time gstat -c '%s' genieMV_204583_1.mp4


259105690
real    0m0.009s (GNU-stat)

最后,一个简洁的方法将 每个单字节读入 AWK数组。这种方法适用于二进制文件(前面或后面没有区别) :

time mawk2 'BEGIN { RS = FS = "^$";
FILENAME = ARGV[ARGC-1]; getline;
print "\n" FILENAME " :: "length"\n"; }' genieMV_204583_1.mp4


genieMV_204583_1.mp4 :: 259105690
real    0m0.270s




time mawk2 'BEGIN { RS = FS = "^$";
} END { print "\n" FILENAME " :: " \
length "\n"; }'  genieMV_204583_1.mp4


genieMV_204583_1.mp4 :: 259105690
real    0m0.269

但这并不是最快的方法,因为你把它们都存储在 RAM 里。正常的 AWK 范例在线上运行。问题在于,对于像 MP4这样的二进制文件,如果它们没有精确地在 \n上结束,那么 length + NR方法的和就会多计一个。下面的代码是显式地使用最后一个1或2字节作为行分割器 RS的一种“全部捕获”形式。

我发现对于 二进制文件使用 2字节方法要快得多,而 1字节方法是典型的以换行结束的 文本文件。对于二进制文件,1字节的文件最终可能会过于频繁地进行行分割,从而使其速度变慢。

但是我们接近于吹毛求疵,因为 mawk2只需要读入 1.83 GB的每一个字节。Txt 文件是 0.95秒,所以除非处理大量文件,否则它几乎可以忽略不计。

尽管如此,正如其他人所提到的,stat仍然是最快的,因为它是一个 OS 文件系统调用。

time mawk2 'BEGIN { FS = "^$";
FILENAME = ARGV[ARGC-1];
cmd = "tail -c 2 \""FILENAME"\"";
cmd | getline XRS;
close(cmd);


RS = ( length(XRS) == 1 ) ? ORS : XRS ;


} { bytes += length } END {


print FILENAME " :: "  bytes + NR * length(RS) }' genieMV_204583_1.mp4


genieMV_204583_1.mp4 :: 259105690
real    0m0.092s


m23lyricsRTM_dict_15.txt :: 1961512986
real    0m0.950s




ls -AlnFT "${m3t}" genieMV_204583_1.mp4


-rw-r--r--  1 501  20  1961512986 Mar 12 07:24:11 2021 m23lyricsRTM_dict_15.txt


-r--r--r--@ 1 501  20   259105690 Jan 25 09:31:43 2021 genieMV_204583_1.mp4

(因为 AWK 方法需要,所以 MP4的文件权限被更新了。)

我会使用 是的来获得更好的速度,而不是使用 WC来读取管道中的所有流:

ls -l <filename> | cut -d ' ' -f5
  • 这是以纯字节表示的

  • 使用 —— b M—— b G标志输出兆字节或千兆字节(每个说法: 不便携 by < strong >@Andrew Henle 在评论中)。

顺便说一下,如果你打算去: 杜卡

du -b <filename> | cut -f -1
  • 使用 -h 来更好地阅读人类

或者,通过 你好

du -h <filename> | awk '{print $1}'

立刻:

stat <filename> | grep Size: | awk '{print $2}'