Maneira portátil de obter o tamanho do arquivo (em bytes) no shell?

121

No Linux, eu uso stat --format="%s" FILE, mas o Solaris ao qual tenho acesso não tem comando stat. O que devo usar então?

Estou escrevendo scripts Bash e não consigo instalar nenhum software novo no sistema.

Já considerei usar:

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

ou mesmo:

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

Mas nenhum desses parece sensato - executando Perl apenas para obter o tamanho do arquivo? Ou executando 2 comandos para fazer o mesmo?


fonte
1
bem, um script bash é um software e, se você puder colocá-lo no sistema, poderá instalar o software.
apenas alguém
4
Tecnicamente - verdade. Eu quis dizer que não tenho privilégios de root e não posso instalar novos pacotes. Claro que a instalação no diretório doméstico é possível. Mas não realmente quando tenho que fazer o script que é portátil e a instalação em máquinas "X", novos pacotes adicionais se tornam complicados.

Respostas:

207

wc -c < filename(abreviação de contagem de palavras, -cimprime a contagem de bytes) é uma solução POSIX portátil . Apenas o formato de saída pode não ser uniforme entre as plataformas, pois alguns espaços podem ser prefixados (que é o caso do Solaris).

Não omita o redirecionamento de entrada. Quando o arquivo é passado como argumento, o nome do arquivo é impresso após a contagem de bytes.

Eu estava preocupado que não funcionasse com arquivos binários, mas funciona bem no Linux e no Solaris. Você pode tentar com wc -c < /usr/bin/wc. Além disso, os utilitários POSIX têm garantia de lidar com arquivos binários , a menos que especificado de outra forma explicitamente.

Carl Smotricz
fonte
67
Ou apenas wc -c < filese você não quiser que o nome do arquivo apareça.
caf
34
Se não me engano, porém, wcem um pipeline deve read()todo o fluxo para contar os bytes. As soluções ls/ awk(e semelhantes) usam uma chamada de sistema para obter o tamanho, que deve ser tempo linear (versus O (tamanho))
jmtd
1
Lembro-me de wcser muito lento na última vez que fiz isso em um disco rígido cheio. Foi lento o suficiente para que eu pudesse reescrever o roteiro antes que o primeiro acabasse, vim aqui para lembrar como fiz rs.
Camilo Martin
6
Eu não usaria wc -c; parece muito mais limpo, mas ls+ awké melhor para velocidade / uso de recursos. Além disso, gostaria apenas de salientar que você realmente precisa pós-processar os resultados wctambém porque em alguns sistemas haverá um espaço em branco antes do resultado, que você pode precisar remover antes de fazer comparações.
Haravikk
3
wc -cé ótimo, mas não funcionará se você não tiver acesso de leitura ao arquivo.
Silas
41

Acabei escrevendo meu próprio programa (muito pequeno) para exibir apenas o tamanho. Mais informações aqui: http://fwhacking.blogspot.com/2011/03/bfsize-print-file-size-in-bytes-and.html

As duas maneiras mais limpas, na minha opinião, com ferramentas comuns do Linux são:

$ stat -c %s /usr/bin/stat
50000

$ wc -c < /usr/bin/wc
36912

Mas eu simplesmente não quero digitar parâmetros ou canalizar a saída apenas para obter o tamanho do arquivo, então estou usando meu próprio bfsize.

fwhacking
fonte
2
A primeira linha da descrição do problema afirma que stat não é uma opção e wc -c é a melhor resposta há mais de um ano, então não tenho certeza de qual é o objetivo desta resposta.
22
A questão está em pessoas como eu, que encontram essa pergunta SO no Google e stat é uma opção para elas.
yo '22 de
3
Estou trabalhando em um sistema embarcado onde wc -cleva 4090 mseg em um arquivo de 10 MB versus "0" mseg stat -c %s, então concordo que é útil ter soluções alternativas, mesmo quando elas não respondem exatamente à pergunta feita.
Robert Calhoun,
3
"stat -c" não é portátil / não aceita os mesmos argumentos no MacOS que no Linux. "wc -c" será muito lento para arquivos grandes.
Orwellophile de
2
stat também não é portátil. stat -c %s /usr/bin/stat stat: illegal option -- c usage: stat [-FlLnqrsx] [-f format] [-t timefmt] [file ...]
27

Embora dugeralmente imprima o uso do disco e não o tamanho real dos dados, GNU coreutils dupode imprimir o "tamanho aparente" do arquivo em bytes:

du -b FILE

Mas não funcionará em BSD, Solaris, macOS, ...

fwhacking
fonte
3
No MacOS X, brew install coreutilse gdu -bterá o mesmo efeito
Jose Alban,
1
Prefiro esse método porque wcprecisa ler todo o arquivo antes de dar um resultado, dué imediato.
CousinCocaine
2
POSIX menciona du -bem um contexto completamente diferente na dulógica .
Palec
lstatEle usa apenas a chamada, portanto, seu desempenho não depende do tamanho do arquivo. Mais curto stat -c '%s', mas menos intuitivo e funciona de forma diferente para pastas (tamanho de impressão de cada arquivo dentro).
Palec
O FreeBSDdu pode chegar perto usando du -A -B1, mas ainda imprime o resultado em múltiplos de blocos de 1024B. Não conseguiu fazê-lo imprimir a contagem de bytes. Mesmo a configuração BLOCKSIZE=1no ambiente não ajuda, porque o bloco 512B é usado então.
Palec
13

Finalmente, decidi usar ls e expansão de matriz bash:

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

não é muito bom, mas pelo menos faz apenas 1 fork + execve e não depende de linguagem de programação secundária (perl / ruby ​​/ python / qualquer que seja)


fonte
Apenas um aparte - o 'l' em '-ln' não é necessário; '-n' é exatamente igual a '-ln'
barrado
Não, não é. Basta comparar os resultados.
1
Alguém poderia adivinhar que o portátil ls -ln FILE | { read _ _ _ _ size _ && echo "$size"; }não precisa de um fork para a segunda etapa do pipeline, já que usa apenas built-ins, mas o Bash 4.2.37 no Linux faz o fork duas vezes (ainda apenas um execve).
Palec
read _ _ _ _ size _ <<<"$(exec ls -ln /usr/bin/wc)" && echo "$size"funciona com single fork e single exec, mas usa um arquivo temporário para a here-string. Pode ser tornado portátil substituindo o here-string por here-document compatível com POSX . BTW, observe o execno subshell. Sem isso, o Bash executa uma bifurcação para o subshell e outro para o comando executado dentro. Esse é o caso do código que você fornece nesta resposta. também.
Palec
1
O -lé supérfluo na presença de -n. Citando POSIX lsmanpage : -n: Ligue a -lopção (ell), mas quando se escreve proprietário ou grupo do arquivo, escreva UID numérico do arquivo ou GID ao invés do nome do usuário ou grupo, respectivamente. Desativar os -C, -me -xopções.
Palec
8

Solução mais rápida de plataforma cruzada (usa apenas um único fork () para ls , não tenta contar caracteres reais, não gera awk desnecessário, perl, etc).

Testado em MacOS, Linux - pode exigir pequenas modificações para Solaris:

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

Se necessário, simplifique o ls argumentos e ajuste o deslocamento em $ {__ ln [3]}.

Nota: seguirá links simbólicos.

Orwellophile
fonte
1
Ou coloque-o em um script de shell: ls -Lon "$ 1" | awk '{print $ 4}'
Luciano
1
@Luciano Acho que você perdeu totalmente o ponto de não fazer bifurcação e fazer uma tarefa no bash em vez de usar o bash para encadear vários comandos do Unix de uma maneira ineficiente.
Orwellophile
8

Os BSDs possuem statopções diferentes do GNU coreutils, mas recursos semelhantes.

stat -f %z <file name> 

Isso funciona no macOS (testado em 10.12), FreeBSD , NetBSD e OpenBSD .

user7504315
fonte
Solaris não tem statutilidade alguma.
Palec
6

Ao processar a ls -nsaída, como uma alternativa aos arrays de shell mal portáveis, você pode usar os argumentos posicionais, que formam o único array e são as únicas variáveis ​​locais no shell padrão. Envolva a substituição de argumentos posicionais em uma função para preservar os argumentos originais em seu script ou função.

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

Isso divide a saída de ln -dnacordo com as IFSconfigurações atuais da variável de ambiente, atribui-a a argumentos posicionais e ecoa o quinto. O -dgarante que os diretórios sejam manipulados corretamente e -ngarante que os nomes de usuários e grupos não precisam ser resolvidos, ao contrário de -l. Além disso, nomes de usuários e grupos contendo espaços em branco podem teoricamente quebrar a estrutura de linha esperada; eles geralmente não são permitidos, mas essa possibilidade ainda faz o programador parar e pensar.

Richard
fonte
5

Se você usar findutilitários de arquivo GNU:

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

Infelizmente, outras implementações de findgeralmente não suportam -maxdepth, nem -printf. Esse é o caso de, por exemplo, Solaris e macOS find.

Pausado até novo aviso.
fonte
FYI maxdepth não é necessário. Pode ser reescrito como size=$(test -f filename && find filename -printf '%s').
Palec
@Palec: O -maxdepthdestina-se a evitar que findseja recursivo (já statque o que o OP precisa substituir não é). Seu findcomando está sem um -namee o testcomando não é necessário.
Pausado até novo aviso.
@DennisWilliamson findpesquisa seus parâmetros recursivamente por arquivos que correspondam a determinados critérios. Se os parâmetros não forem diretórios, a recursão é ... bastante simples. Portanto, primeiro eu testo se filenameé realmente um arquivo comum existente e, em seguida, imprimo seu tamanho usando findaquele que não tem onde recursar.
Palec
1
find . -maxdepth 1 -type f -name filename -printf '%s'funciona apenas se o arquivo estiver no diretório atual e ainda pode examinar cada arquivo no diretório, o que pode ser lento. Melhor uso (ainda mais curto!) find filename -maxdepth 1 -type f -printf '%s'.
Palec
3

Você pode usar o findcomando para obter algum conjunto de arquivos (aqui os arquivos temporários são extraídos). Depois, você pode usar o ducomando para obter o tamanho de cada arquivo em formato legível por humanos usando -hswitch.

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

RESULTADO:

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~
Abhishek Singh
fonte
2

Seu primeiro exemplo em Perl não parece irracional para mim.

É por razões como essa que migrei de escrever scripts de shell (em bash / sh etc.) para escrever todos os scripts, exceto os mais triviais em Perl. Descobri que estava tendo que lançar o Perl para requisitos específicos e, à medida que fazia isso mais e mais, percebi que escrever scripts em Perl era provavelmente uma forma mais poderosa (em termos de linguagem e da grande variedade de bibliotecas disponíveis via CPAN ) e uma maneira mais eficiente de conseguir o que eu queria.

Observe que outras linguagens de script de shell (por exemplo, python / ruby) sem dúvida terão recursos semelhantes e você pode querer avaliá-los para seus propósitos. Eu apenas discuto Perl porque é a linguagem que uso e com a qual estou familiarizado.

Brian Agnew
fonte
Bem, eu mesmo escrevo muito em Perl, mas às vezes a ferramenta é escolhida por mim, não por mim :)
-3

se você tiver Perl em seu Solaris, use-o. Caso contrário, ls com awk é sua próxima melhor aposta, já que você não tem stat ou seu find não é GNU find.

ghostdog74
fonte
-3

Há um truque que usei no Solaris, se você perguntar o tamanho de mais de um arquivo, ele retorna apenas o tamanho total sem nomes - então inclua um arquivo vazio como / dev / null como o segundo arquivo:

por exemplo, arquivo de comando que você deseja / dev / null

Não consigo lembrar qual comando de tamanho isso funciona para ls / wc / etc - infelizmente não tenho uma caixa solaris para testá-lo.

Martin Beckett
fonte
-4

no linux você pode usar du -h $FILE, isso funciona no solaris também?

tricô
fonte
1
Na verdade, as unidades podem ser convertidas, mas isso mostra o uso do disco em vez do tamanho dos dados do arquivo ("tamanho aparente").
Palec
-7

Você tentou du -ks | awk '{print $ 1 * 1024}'. Isso pode funcionar.

Aditya
fonte
1
Mostra o uso do disco em vez do tamanho dos dados do arquivo ("tamanho aparente").
Palec