高效的跨操作系统文件大小 shell 功能

高效的跨操作系统文件大小 shell 功能

我正在寻找一种更简单的方法来检查跨操作系统的文件大小。我可以使用 wc -c 但我担心性能可能会影响大文件(我假设它只是计算字符并且不会在幕后进行统计?)

以下适用于 linux 和 macos(也许是 bsd)。有没有更简单、性能良好的方法?

function filesize
{
    local file=$1
    size=`stat -c %s $file 2>/dev/null` # linux
    if [ $? -eq 0 ]; then
        echo $size
        return 0
    fi

    eval $(stat -s $file) # macos
    if [ $? -eq 0 ]; then
        echo $st_size
        return 0
    fi

    echo 0
    return -1
}

答案1

wc来自 GNU coreutils 中( )的来源coreutils/src/wc.c(即非嵌入式 Linux 和 Cygwin 上的版本):

 When counting only bytes, save some line- and word-counting
 overhead.  If FD is a 'regular' Unix file, using lseek is enough
 to get its 'size' in bytes.

所以使用wc -c字节计数会表现良好。

您可以轻松地在大文件(即需要一些时间读取的文件)上测试此优化。wc -c一个 9.9Gb 的文件在我的服务器上的一个文件上花费了 0.015 秒的实时时间,如果整个文件能在这段时间内传输,我会很高兴,但不幸的是我的千兆以太网速度没有那么快(需要 21 秒才能传输)将该文件复制到/dev/null网络上)。

答案2

我排除了statand哪个不是 POSIX,因此比和perl更有可能丢失。lsawk

我也排除了这种可能性,因为虽然在使用该选项时优化了wcGNU 实现,但您不应该依赖它来提供可移植脚本。此外,一些不符合标准的可能会返回数量wc-cwc -c人物这不一定与数量相同字节取决于区域设置。

这是一个仅基于标准实用程序的解决方案,它将报告作为参数提供的文件的大小:

filesize() {
        [ -f "$1" ] && ls -dnL -- "$1" | awk '{print $5;exit}' || { echo 0; return 1; }
}

请注意,报告的大小可能大于或小于文件内容在磁盘上的实际大小,具体取决于所使用的文件系统、稀疏文件支持以及压缩或重复数据删除等选项。

答案3

我想你应该使用这个。正如我刚刚发现的,这是一个POSIX 指定的标准实用程序。

du

POSIX 指定的选项包括:

du 实用程序应符合 XBD 实用程序语法指南。

应支持以下选项:

  • -a 除了默认输出之外,还报告以指定文件为根的文件层次结构中非目录类型的每个文件的大小。无论 -a 选项是否存在,作为文件操作数给出的非目录都应始终列出。
  • -H 如果在命令行上指定了符号链接,du 将计算该链接引用的文件或文件层次结构的大小。
  • -k 以 1024 字节为单位写入文件大小,而不是默认的 512 字节单位。
  • -L 如果在命令行上指定了符号链接或在遍历文件层次结构期间遇到符号链接,du 应计算该链接引用的文件或文件层次结构的大小。
  • -s 仅报告每个指定文件的总和,而不是默认输出。
  • -x 评估文件大小时,仅评估与文件操作数指定的文件具有相同设备的文件。指定多个互斥选项 -H 和 -L 不应被视为错误。指定的最后一个选项应确定实用程序的行为。

但问题是它不报告文件大小,而是报告磁盘使用情况。它们是不同的概念,差异取决于文件系统。如果您想获取一组文件的文件大小,您可以使用如下所示的内容:

{   echo
    /usr/bin/ls -ndL .//*
} | sed '/\n/P;//D;N
\|//|s|\n|/&/|
$s|$|/|;s| .//|/\
/|;2!P;D'

这是一个相当简单的想法,在的输出sed上维护一个两行可寻址窗口。ls它的工作原理是滑动输入 - 总是P打印然后D删除其模式空间中最旧的两行,然后拉入Next 输入行来替换它。基本上,这是一个单行前瞻。

它有一些书面的重大缺陷。例如,为了我自己的方便,我避免了处理并使用该ls选项,该选项对链接目标而不是链接本身进行报告。它还假设仅当前目录全局。这取决于-> linkpath-Lls/ 不是出现在文件名中 - 因为它是分隔符。对于此类内容来说,这实际上相当常见 - 您cd进入目录然后cd -退出。

所有这些都可以用几行或更多行来处理,但这只是一个演示。

这里的关键部分 - 以及前瞻的原因 - 是这一点:

\|//|s|\n|/&/|

当模式空间中最新的行包含字符串时,.//将 a 附加/到最旧的行的尾部,并/在最新的行的头部注入 a 。然后我还.//用另一个\newline 和另外两个行分隔斜杠替换 。

所以这个:

drwxr-xr-x 1 1000 1000        6 Aug  4 14:40 .//dir*
drwxr-xr-x 1 1000 1000        0 Aug  4 14:40 .//dir1
drwxr-xr-x 1 1000 1000        6 Aug  8 17:34 .//dir2
drwxr-xr-x 1 1000 1000       22 Aug 10 18:12 .//dir3
drwxr-xr-x 1 1000 1000       16 Jul 11 21:59 .//new
-rw-r--r-- 1 1000 1000        8 Aug 20 11:32 .//newfile
-rw-r--r-- 1 1000 1000        0 Jul  6 11:24 .//new
file
-rw-r--r-- 1 1000 1000        0 Jul  6 11:24 .//new
file
link

就变成这样了:

/drwxr-xr-x 1 1000 1000        6 Aug  4 14:40/
/dir*/
/drwxr-xr-x 1 1000 1000        0 Aug  4 14:40/
/dir1/
/drwxr-xr-x 1 1000 1000        6 Aug  8 17:34/
/dir2/
/drwxr-xr-x 1 1000 1000       22 Aug 10 18:12/
/dir3/
/drwxr-xr-x 1 1000 1000       16 Jul 11 21:59/
/new/
/-rw-r--r-- 1 1000 1000        8 Aug 20 11:32/
/newfile/
/-rw-r--r-- 1 1000 1000        0 Jul  6 11:24/
/new
file/
/-rw-r--r-- 1 1000 1000        0 Jul  6 11:24/
/new
file
link/

但这有什么用呢,对吧?嗯,这让一切变得不同:

IFS=/; set -f; set $(set +f
{   echo 
/usr/bin/ls -ndL .//*
}| sed '/\n/P;//D;N
\|//|s|\n|/&/|
$s|$|/|;s| .//|/\
/|;2!P;D'
)

unset IFS
while [ -n "$2" ]
do  printf 'Type :\t <%.1s>\tSize :\t %.0s%.0s%.0s<%d>%.0s%.0s%.0s\nFile :\t %s\n' \
        $2 "<$4>"
shift 4; done

输出

Type :   <d>    Size :   <6>
File :   <dir*>
Type :   <d>    Size :   <0>
File :   <dir1>
Type :   <d>    Size :   <6>
File :   <dir2>
Type :   <d>    Size :   <22>
File :   <dir3>
Type :   <d>    Size :   <16>
File :   <new>
Type :   <->    Size :   <8>
File :   <newfile>
Type :   <->    Size :   <0>
File :   <new
file>
Type :   <->    Size :   <0>
File :   <new
file
link>

答案4

也许更简单、更便携perl

filesize() {
  file="$1"
  if [ -e "$file" ]; then
    size="$(perl -e 'print -s shift' "$file")"
    printf '%s\n' "$size"
    return 0
  else
    printf "0\n"
    return -1
  fi
}

相关内容