为什么 ls 与 awk 结合不能显示准确尺寸?

为什么 ls 与 awk 结合不能显示准确尺寸?

我正在尝试以精确的字节数找到硬盘上文件的大小,但每当文件大小过大时,数字就会变得很奇怪(例如 1.98329e+12)。我可以阻止它这样做吗,或者将其转换为精确的字节数?

命令是:

ls -lR | grep -v '^d' | awk '{total += $5} END {print "Total:", total}'

精确字节的图片:

图片

奇怪数字的图片:

图片

  • 停止显示确切字节数的临界点似乎在 500gb 左右
  • du -sb无论目录有多大,该命令都能正确显示精确的字节数。
  • 我尝试过 Ubuntu Gnome 15.10 64 位(日语和英语)和 Linux Mint 17.3 Cinnamon 64 位(日语)
  • 我的驱动器是ntfs这样的,所以我尝试将其格式化为 ext4 并复制我的文件。结果与 ntfs 相同。

答案1

问题在于 MAWK(安装在 Ubuntu 上的 AWK 变体)默认以科学计数法打印大于2147483647(2 31 -1) 的整数:

% awk -W version
mawk 1.3.3 Nov 1996, Copyright (C) Michael D. Brennan

compiled limits:
max NF             32767
sprintf buffer      2040
% printf '2147483647\n' | awk '{x += $1; print x}'
2147483647
% printf '2147483648\n' | awk '{x += $1; print x}'
2.14748e+09

您可以使用printf格式说明符代替print*:

printf '2147483648\n' | awk '{x += $1; printf "%.0f\n", x}'
% printf '2147483648\n' | awk '{x += $1; printf "%.0f\n", x}'
2147483648

就你的情况而言:

ls -lR | grep -v '^d' | awk '{total += $5} END {printf "Total:%.0f\n", total}'
ls -lR |
    grep -v '^d' |
    awk '
        {
            total += $5
        }
        END {
            printf "Total:%.0f\n", total
        }
    '

total这将强制 AWK以十进制表示法而不是科学计数法打印。

然而,另一方面,你永远不应该解析ls

更敏感的方法是使用find+ stat

find . -type f -exec stat -c '%s' {} + | awk '{total += $1} END {printf "Total:%.0f\n", total}'
find . -type f -exec stat -c '%s' {} + |
    awk '
        {
            total += $1
        }
        END {
            printf "Total:%.0f\n", total
        }
    '

*是使打印数字大于(2 31 -1)%.0f的技巧,当用作格式说明符时,将始终打印为。 的限制是,如果担心的话,在(2 53 )之后将开始失去精度(感谢 Rotsor 提供的有用信息)。printf2147483647%d2147483647%.0f9007199254740992

答案2

总结lsawk对于您的目的来说不是必需的。在要分析的目录上使用du -cb或。du -bs

你的目的是

  1. 查找所有文件
  2. 找到它们的大小(以字节为单位)
  3. 计算出所有这些的总数

所有这些操作都可以通过 来执行du

$ du -bs $HOME 2>/dev/null                                                                 
76709521942 /home/xieerqi

值得注意的是,它du有两种“模式”——它可以显示文件的大小或实际占用的磁盘空间(实际的物理空间)。由于您对所有文件的总大小感兴趣,因此您需要的是文件的实际大小。-bflag 给出了确切的信息(-b是 的别名--apparent-size --block-size=1)。

也许更简洁、更合适的解决方案是du -bc直接在你想要的目录上使用。例如,我的主目录大小约为 76 GB

$ du -bc $HOME 2> /dev/null  | tail -1                    
76694582570 total

由于某种原因,您担心文件夹大小和文件大小的差异。您在评论中说:

我更喜欢 ls,因为目录大小会变化,而文件大小是恒定的

du是递归的,并计算文件大小的总和。此外,目录确实具有 4096 字节 ( 4k ) 的静态大小,但du它将包含在 的结果中du -bs directory_name。考虑一下:

$ du -b suse/openSUSE-Leap-42.1-DVD-x86_64.iso                                             
4648337408  suse/openSUSE-Leap-42.1-DVD-x86_64.iso

$ du -b suse/                                                                              
4648341504  suse/

$ bc <<< "4648337408+4096" 
4648341504

$ mkdir suse/another_dir  

$ du -b suse/another_dir                                                                   
4096    suse/another_dir

$ du -bs suse/                                                                             
4648345600  suse/

答案3

在底层,awk所有计算都使用双精度浮点数。默认情况下,它使用以下方式打印它们:printf(3)格式说明符%.6g,这意味着如果数字超过六位数字,它将切换到电子记法,这就是您所看到的。您可以通过设置变量来解决此问题OFMT

ls -lR |
    awk 'BEGIN { OFMT = "%d" }  
         /^-/  { total += $5 } 
         END   { print "Total:", total }'

但有一个上限,超过这个上限不能给出确切的字节数;它将开始对总和的低位进行四舍五入。500 GB = 500 * 1024 * 1024 * 1024 = 536870912000 ≈ 2 39。使用通常的 IEEE 浮点数,这安全地低于该限制(即大致2 52)。但是,这个数字已经足够大了,我个人觉得使用具有适当“大数”(无限制大小的整数)的编程语言会更好。例如,Python:

#! /usr/bin/python
import os
import sys

space = 0L  # L means "long" - not necessary in Python 3
for subdir, dirs, files in os.walk(sys.argv[1]):
    for f in files:
        space += os.lstat(os.path.join(subdir, f)).st_size

sys.stdout.write("Total: {:d}\n".format(space))

这也完全不受文件名中含有不寻常字符的问题的影响。并且它计算隐藏文件占用的空间。

这将计算每个文件中可见的字节数,这与打印的内容相同ls -l。如果您想要磁盘上实际占用的字节数而是(du打印的内容),.st_size用替换.st_blocks * 512。(是的,乘数始终是 512,即使st_blksize是不同的数字。)

答案4

您在此处看到的是一种显示大数字的方法。例如:

1.23e+3 = 1.23*10^3 = 1230

据我所知,您无法关闭此功能,但正如您在问题中所写,du它确实表现不同,所以我建议使用此功能。否则,您必须转换数字。

相关内容