função shell de tamanho de arquivo cross-os eficiente

4

Estou procurando uma maneira mais simples de ter uma verificação de tamanho de arquivo entre os UNIX. Eu poderia usar wc -c, mas eu estou preocupado que o desempenho pode sugar arquivos grandes (eu estou supondo que apenas conta chars e não faz um stat sob as cobertas?)

O abaixo funciona para linux e macos (talvez bsd). Existe uma abordagem mais simples e de bom desempenho?

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
}
    
por Neil McGill 22.08.2014 / 16:24

5 respostas

5

Da origem de wc ( coreutils/src/wc.c ) em GNU coreutils (ou seja, a versão em Linux e Cygwin não incorporados):

 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.

Portanto, usar wc -c para contar os bytes terá um bom desempenho.

Você pode facilmente testar essa otimização em um arquivo grande (ou seja, um que levaria algum tempo de leitura). wc -c em um arquivo 9.9Gb demorou 0,015s de tempo real em um arquivo que está localizado no meu servidor e gostaria de me alegrar se o arquivo inteiro teria sido transferido nesse tempo, mas minha gigabit ethernet infelizmente não é tão rápida leva 21s para copiar esse arquivo para /dev/null pela rede).

    
por 22.08.2014 / 17:30
4

Estou descartando stat e perl , que não são POSIX. Portanto, é mais provável que você esteja ausente do que ls e awk .

Eu estou descartando wc também, enquanto a implementação GNU de wc é otimizada quando a opção -c é usada, você não deve confiar nela para estar presente em um script portátil. Além disso, alguns não compatíveis com o padrão wc -c podem retornar o número de caracteres que não é necessariamente o mesmo que o número de bytes dependendo da localidade.

Aqui está uma solução baseada apenas em utilitários padrão que relatam o tamanho do arquivo fornecido como argumento:

filesize() {
        [ -f "$1" ] && ls -dnL -- "$1" | awk '{print $5;exit}' || { echo 0; return 1; }
}
Observe que o tamanho relatado pode ser maior ou menor que o tamanho real do conteúdo do arquivo no disco, dependendo do sistema de arquivos usado, do suporte a arquivos esparsos e de opções como compactação ou desduplicação.

    
por 22.08.2014 / 17:13
2

Você deveria usar isso, eu acho. Como acabei de descobrir, é um utilitário padrão especificado pelo POSIX.

du

As opções especificadas por POSIX incluem:

O utilitário du deve estar em conformidade com as Diretrizes de Sintaxe do Utilitário XBD.

As seguintes opções devem ser suportadas:

  • %código% Além da saída padrão, relate o tamanho de cada arquivo não do tipo diretório na hierarquia de arquivos com raiz no arquivo especificado. Independentemente da presença da opção -a, os não-diretórios dados como operandos de arquivo devem sempre ser listados.
  • %código% Se um link simbólico é especificado na linha de comando, du deve contar o tamanho do arquivo ou hierarquia de arquivos referenciados pelo link.
  • %código% Escreva os tamanhos dos arquivos em unidades de 1024 bytes, em vez das unidades padrão de 512 bytes.
  • %código% Se um link simbólico for especificado na linha de comando ou encontrado durante a travessia de uma hierarquia de arquivos, du deverá contar o tamanho do arquivo ou da hierarquia de arquivos referenciados pelo link.
  • %código% Em vez da saída padrão, relate apenas a soma total de cada um dos arquivos especificados.
  • %código% Ao avaliar os tamanhos de arquivo, avalie apenas os arquivos que possuem o mesmo dispositivo que o arquivo especificado pelo operando de arquivo. A especificação de mais de uma das opções mutuamente exclusivas -H e -L não será considerada um erro. A última opção especificada deve determinar o comportamento da utilidade.

O problema é que ele não informa sobre tamanho de arquivo, ele reporta sobre uso de disco . Eles são conceitos diferentes e as diferenças dependem do sistema de arquivos. Se você quiser obter o tamanho dos arquivos para um conjunto de arquivos, use algo como o seguinte:

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

É um bit relativamente simples de -a que mantém uma janela endereçável de duas linhas na saída de -H . Ele funciona deslizando por sua entrada - sempre -k rinting, em seguida, -L , eletcando a mais antiga das duas linhas em seu espaço de padrão, em seguida, puxando a linha de entrada -s ext para substituí-la. É uma olhada de uma linha, basicamente.

Tem algumas deficiências importantes como escritas. Por exemplo, para minha conveniência, evitei manipular -x sed e usar a opção ls , o que torna P reportar no destino do link em vez do próprio link. Ele também assume apenas globs de diretório atuais. Depende do D não ocorrendo nos nomes dos arquivos - porque é o separador. Isso é bastante comum para esse tipo de coisa - você N no diretório e ls de volta.

Tudo isso pode ser tratado em algumas linhas ou mais, mas é apenas uma demonstração.

O componente chave aqui - e o motivo da antecipação - é este pouco:

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

Quando a linha mais nova no espaço padrão contiver a string -> linkpath , acrescente um -L ao final da linha mais antiga e injete um ls na cabeça do mais recente. Eu também substituo o / por outro cd ewline e mais duas barras delimitadoras de linha.

E assim:

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

Torna-se assim:

/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/

Mas que uso é esse, certo? Bem, isso faz toda a diferença:

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

OUTPUT

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>
    
por 22.08.2014 / 17:25
2

Há um tempo para tudo, incluindo a análise de ls . Não há maneira de impedir que ls manuseie nomes de arquivos, mas quando você está interessado apenas em alguns dos metadados sobre um arquivo, tudo bem.

filesize () {
  LC_ALL=C ls -dn -- "$1" | awk 'NR==1 {print $5}'
}

Isso funciona em todos os sistemas compatíveis com POSIX com o XSI extensão. Ele funciona com o BusyBox, que entende -n mas não -o . A opção -n faz com que as colunas de usuário e grupo contenham valores numéricos (UID e GID) em vez de nomes (que podem conter espaços em alguns sistemas, tornando a análise de coluna não confiável). Eu não acho que LC_ALL é necessário, já que esta parte da saída não deve ser afetada por localidades, mas pode haver uma implementação lá fora que faça algo estranho como tamanhos de impressão de acordo com LC_NUMERIC , e não pode ferir.

Se você quer apenas portabilidade entre sistemas onde você pode instalar o zsh (que vem com o OSX), você pode usar o zsh's zstat :

zmodload -F zsh/stat b:zstat
filesize () {
  zstat +size -- $1
}
    
por 23.08.2014 / 00:32
2

O mais simples e mais portátil talvez 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
}
    
por 22.08.2014 / 16:54