【问题标题】:Portable way to get file size (in bytes) in the shell在shell中获取文件大小(以字节为单位)的便携式方法?
【发布时间】:2010-12-21 09:09:46
【问题描述】:

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

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

我已经考虑过使用:

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

甚至:

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

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

【问题讨论】:

  • 一个bash脚本软件,如果你能把它放到系统上,你就可以安装软件了。
  • 技术上是真的。我的意思是我没有root权限,也不能安装新的包。当然可以安装在主目录中。但是当我必须制作可移植的脚本并在“X”机器上安装时,新的附加包变得很棘手。

标签: linux bash shell solaris


【解决方案1】:

我会使用 ls 以获得更好的速度而不是 wc 它将读取管道中的所有流

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

  • 使用标志 --b M--b G 以兆字节或千兆字节为单位的输出[按说法:不可移植@ Andrew Henle 在 cmets 上 ]

顺便说一句,如果你打算去:du cut

du -b <filename> | cut -f -1
  • 使用 -h 以获得更好的人工阅读效果

或者,du awk

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

stat 怎么样?

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

【讨论】:

【解决方案2】:

我不知道 gnu gawk 的 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-byte 方法处理二进制文件 的速度要快得多,而1-byte 方法是一个以换行符结尾的典型文本文件。对于二进制文件,1 字节的 1 字节可能会过于频繁地进行行拆分并减慢速度。

但我们在这里已经接近于吹毛求疵了,因为 mawk2 读取 1.83GB txt 文件的每个字节都是 0.95 secs,所以除非你正在处理大量数量,可以忽略不计。

尽管如此,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

(mp4更新cuz awk方法的文件权限需要它)

【讨论】:

    【解决方案3】:

    BSD 具有 stat,其选项与 GNU coreutils 不同,但功能相似。

    stat -f %z <file name> 
    

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

    【讨论】:

    • Solaris 根本没有 stat 实用程序。
    • Busybox 不支持该结构:stat: unrecognized option: % BusyBox v1.32.1 () multi-call binary。
    【解决方案4】:

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

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

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

    【讨论】:

      【解决方案5】:

      尽管du 通常会打印磁盘使用情况而不是实际数据大小,但 GNU coreutils du 可以以字节为单位打印文件的“表观大小”:

      du -b FILE
      

      但它在 BSD、Solaris、macOS 下无法运行...

      【讨论】:

      • 在 MacOS X 上,brew install coreutilsgdu -b 会达到同样的效果
      • 我更喜欢这种方法,因为wc 需要读取整个文件才能给出结果,du 是即时的。
      • POSIX 在du rationale 中以完全不同的上下文提及du -b
      • 这仅使用lstat 调用,因此其性能不依赖于文件大小。比 stat -c '%s' 更短,但不太直观,并且对文件夹的工作方式不同(打印内部每个文件的大小)。
      • FreeBSD du 可以使用du -A -B1 接近,但它仍然以 1024B 块的倍数打印结果。没有设法让它打印字节数。即使在环境中设置BLOCKSIZE=1也无济于事,因为那时使用的是512B块。
      【解决方案6】:

      如果您使用 GNU fileutils 中的find

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

      不幸的是,find 的其他实现通常不支持-maxdepth,也不支持-printf。例如,情况就是这样。 Solaris 和 macOS find.

      【讨论】:

      • FYI maxdepth 不需要。可以改写为size=$(test -f filename &amp;&amp; find filename -printf '%s')
      • @Palec:-maxdepth 旨在防止 find 递归(因为 OP 需要替换的 stat 不是)。您的find 命令缺少-name 并且test 命令不是必需的。
      • @DennisWilliamson find 递归搜索其参数以查找匹配给定条件的文件。如果参数不是目录,递归是……非常简单。因此,我首先测试filename 是否真的是一个现有的普通文件,然后我使用find 打印它的大小,它无处递归。
      • find . -maxdepth 1 -type f -name filename -printf '%s' 仅在文件位于当前目录中时有效,并且它仍可能检查目录中的每个文件,这可能会很慢。更好地使用(甚至更短!)find filename -maxdepth 1 -type f -printf '%s'.
      【解决方案7】:

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

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

      我担心它不适用于二进制文件,但它在 Linux 和 Solaris 上都可以正常工作。你可以试试wc -c &lt; /usr/bin/wc。此外,POSIX 实用程序是guaranteed to handle binary files,除非另有明确说明。

      【讨论】:

      • 如果您不想显示文件名,也可以直接使用wc -c &lt; file
      • 如果我没记错的话,管道中的wc 必须read() 整个流来计算字节数。 ls/awk 解决方案(和类似的)使用系统调用来获取大小,应该是线性时间(相对于 O(size))
      • 我不会使用wc -c;它看起来更整洁,但ls + awk 更适合速度/资源使用。另外,我只想指出,您实际上还需要对wc 的结果进行后处理,因为在某些系统上,结果前会有空格,您可能需要先去除空格,然后才能进行比较。跨度>
      • wc -c 很好,但如果您没有文件的读取权限,它将无法工作。
      • statls 实用程序只是执行lstat 系统调用并获取文件长度而不读取文件。因此,它们不需要读取权限,并且它们的性能不依赖于文件的长度。 wc 实际上会打开文件并通常会读取它,这使得它在大文件上的表现要差得多。但是GNU coreutils wc 在只需要常规文件的字节数时进行优化:它使用fstatlseek 系统调用来获取计数。查看源代码中带有(dd ibs=99k skip=1 count=0; ./wc -c) &lt; /etc/group 的评论。
      【解决方案8】:

      您可以使用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~
      

      【讨论】:

        【解决方案9】:

        跨平台最快的解决方案(ls 仅使用单个 fork(),不尝试计算实际字符,不产生不需要的 awk、perl 等)。

        在 MacOS、Linux 上测试 - 可能需要对 Solaris 稍作修改:

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

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

        注意:将遵循符号链接。

        【讨论】:

        • 或者放到shell脚本中:ls -Lon "$1" | awk '{ 打印 $4 }'
        • @Luciano 我认为您完全错过了 不分叉 并在 bash 中执行任务而不是使用 bash 串接很多unix 命令以一种低效的方式组合在一起。
        【解决方案10】:

        我最终编写了自己的程序(非常小)来显示大小。更多信息在这里:http://fwhacking.blogspot.com/2011/03/bfsize-print-file-size-in-bytes-and.html

        在我看来,使用常用 Linux 工具最干净的两种方法是:

        $ stat -c %s /usr/bin/stat
        50000
        
        $ wc -c < /usr/bin/wc
        36912
        

        但我只是不想输入参数或管道输出只是为了获取文件大小,所以我使用自己的 bfsize。

        【讨论】:

        • 问题描述的第一行指出 stat 不是一个选项,并且 wc -c 是一年多来的最佳答案,所以我不确定这个答案的意义何在。
        • 重点在于像我这样在 Google 中找到这个 SO 问题的人,stat 是他们的选择。
        • 我正在开发一个嵌入式系统,其中wc -c 在 10 MB 文件上需要 4090 毫秒,而 stat -c %s 需要“0”毫秒,所以我同意即使他们有替代解决方案也是有帮助的不要回答提出的确切问题。
        • "stat -c" 不可移植/在 MacOS 上不接受与在 Linux 上相同的参数。 "wc -c" 对于大文件会很慢。
        • stat 也不是可移植的。 stat -c %s /usr/bin/statstat: illegal option -- cusage: stat [-FlLnqrsx] [-f format] [-t timefmt] [file ...]
        【解决方案11】:

        我在 Solaris 中使用了一个技巧,如果您要求提供多个文件的大小,它只会返回没有名称的总大小 - 所以包括一个空文件,例如 /dev/null 作为第二个文件:

        例如 命令文件你想要 /dev/null

        我不记得哪个 size 命令适用于 ls/wc/etc - 不幸的是我没有用于测试它的 solaris 盒子。

        【讨论】:

          【解决方案12】:

          最后我决定使用 ls 和 bash 数组扩展:

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

          确实不是很好,但至少它只做了1个fork+execve,而且不依赖辅助编程语言(perl/ruby/python/whatever)

          【讨论】:

          • 顺便说一句 - '-ln' 中的 'l' 不是必需的; “-n”与“-ln”完全一样
          • 不,不是。只需比较输出。
          • 有人会猜测可移植的ls -ln FILE | { read _ _ _ _ size _ &amp;&amp; echo "$size"; } 不需要 fork 管道的第二步,因为它只使用内置插件,但 Linux 上的 Bash 4.2.37 分叉两次(仍然只有一个 @987654325 @,不过)。
          • read _ _ _ _ size _ &lt;&lt;&lt;"$(exec ls -ln /usr/bin/wc)" &amp;&amp; echo "$size" 与单 fork 和单 exec 一起使用,但它使用临时文件作为 here-string。它可以通过将 here-string 替换为符合 POSX 的here-document 来实现可移植性。顺便说一句,注意子shell 中的exec。没有它,Bash 会为 subshel​​l 执行一次 fork,为内部运行的命令执行另一次 fork。您在此答案中提供的代码就是这种情况。也是。
          • -l 在存在-n 时是多余的。引用POSIX ls manpage: -n: 打开-l (ell) 选项,但是在写入文件的所有者或组时,分别写入文件的数字 UID 或 GID 而不是用户或组名。禁用-C-m-x 选项。
          【解决方案13】:

          你试过 du -ks | awk '{打印 $1*1024}'。这可能行得通。

          【讨论】:

          • 这显示磁盘使用情况而不是文件数据大小(“表观大小”)。
          【解决方案14】:

          如果您的 Solaris 上有 Perl,请使用它。否则,带有 awk 的 ls 是您的下一个最佳选择,因为您没有 stat 或者您的 find 不是 GNU find。

          【讨论】:

            【解决方案15】:

            在 linux 上你可以使用 du -h $FILE,在 solaris 上也可以吗?

            【讨论】:

            • 实际上,单位可以转换,但这显示的是磁盘使用情况而不是文件数据大小(“表观大小”)。
            【解决方案16】:

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

            正是出于这样的原因,我从编写 shell 脚本(在 bash/sh 等中)迁移到在 Perl 中编写除了最琐碎的脚本之外的所有脚本。我发现我必须启动 Perl 以满足特定要求,并且随着我越来越多地这样做,我意识到用 Perl 编写脚本可能更强大(就语言和通过 @ 提供的大量库而言) 987654321@) 和更有效的方式来实现我想要的。

            请注意,其他 shell 脚本语言(例如 python/ruby)无疑具有类似的功能,您可能需要评估这些功能以达到您的目的。我只讨论 Perl,因为那是我使用和熟悉的语言。

            【讨论】:

            • 好吧,我自己编写了很多 Perl,但有时该工具是为我选择的,而不是由我选择的 :)
            猜你喜欢
            • 2011-01-26
            • 2013-09-12
            • 1970-01-01
            • 2015-01-24
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-02-17
            • 2016-04-08
            相关资源
            最近更新 更多