Qual é a diferença entre "du -sh *" e "du -sh ./*"?

26

Qual é a diferença entre du -sh *e du -sh ./*?

Nota: O que me interessa é o *e ./*peças.

Biswanath
fonte
2
a saída ? um vai mostrar-lhe ./ na frente do nome do arquivo
kiwy

Respostas:

69
$ touch ./-c $ 'a \ n12 \ tb' foo
$ du -hs *
0 a
12 b
0 foo
0 total

Como você pode ver, o -carquivo foi usado como uma opção due não é relatado (e você vê a totallinha por causa disso du -c). Além disso, o arquivo chamado a\n12\tbestá nos fazendo pensar que existem arquivos chamados ae b.

$ du -hs -- *
0       a
12      b
0       -c
0       foo

Isso é melhor. Pelo menos esse tempo -cnão é considerado uma opção.

$ du -hs ./*
0       ./a
12      b
0       ./-c
0       ./foo

Isso é ainda melhor. O ./prefixo impede que -cseja tomado como uma opção e a ausência de ./antes bna saída indica que não há barquivo, mas há um arquivo com um caractere de nova linha (mas veja abaixo 1 para mais digressões sobre isso).

É uma boa prática usar o ./prefixo quando possível e, se não, e para dados arbitrários, você sempre deve usar:

cmd -- "$var"

ou:

cmd -- $patterns

Se cmdnão houver suporte --para marcar o final das opções, você deve denunciá-lo como um bug ao autor (exceto quando for por opção e documentado como para echo).

Há casos em que ./*resolve problemas que --não resolvem . Por exemplo:

awk -f file.awk -- *

falhará se houver um arquivo chamado a=b.txtno diretório atual (define a variável awk apara em b.txtvez de pedir para processar o arquivo).

awk -f file.awk ./*

Não tem o problema porque ./anão é um nome de variável awk válido, portanto ./a=b.txtnão é considerado uma atribuição de variável.

cat -- * | wc -l

falhará se houver um arquivo chamado -no diretório atual, conforme indicado catna leitura do stdin ( -é especial para a maioria dos utilitários de processamento de texto e para cd/ pushd).

cat ./* | wc -l

está OK porque ./-não é especial cat.

Coisas como:

grep -l -- foo *.txt | wc -l

contar o número de arquivos que fooestão errados porque pressupõe que os nomes dos arquivos não contenham caracteres de nova linha ( wc -lconta os caracteres de nova linha, os que são gerados por grepcada arquivo e os nomes dos mesmos). Você deve usar:

grep -l foo ./*.txt | grep -c /

(contar o número de /caracteres é mais confiável, pois só pode haver um por nome de arquivo).

Para recursivo grep, o truque equivalente é usar:

grep -rl foo .//. | grep -c //

./* pode ter alguns efeitos colaterais indesejados.

cat ./*

adiciona mais dois caracteres por arquivo, para que você atinja o limite do tamanho máximo de argumentos + ambiente mais rapidamente. E às vezes você não quer que isso ./seja relatado na saída. Gostar:

grep foo ./*

Saída:

./a.txt: foobar

ao invés de:

a.txt: foobar

Outras digressões

1 . Sinto que tenho que expandir isso aqui, seguindo a discussão nos comentários.

$ du -hs ./*
0       ./a
12      b
0       ./-c
0       ./foo

Acima, ./marcar o início de cada arquivo significa que podemos identificar claramente onde cada nome de arquivo começa (em ./) e onde termina (na nova linha antes do próximo ./ou do final da saída).

O que isso significa é que a saída de du ./*, ao contrário da de du -- *), pode ser analisada de maneira confiável, embora não tão facilmente em um script.

Quando a saída vai para um terminal, existem muitas outras maneiras pelas quais um nome de arquivo pode enganar você:

  • Controlar caracteres, seqüências de escape podem afetar a maneira como as coisas são exibidas. Por exemplo, \rmove o cursor para o início da linha, \bmove o cursor para trás, \e[Cpara frente (na maioria dos terminais) ...
  • muitos caracteres são invisíveis em um terminal, começando pelo mais óbvio: o caractere de espaço.
  • Existem caracteres Unicode com a mesma aparência da barra na maioria das fontes

    $ printf '\u002f \u2044 \u2215 \u2571 \u29F8\n'
    /    

    (veja como funciona no seu navegador).

Um exemplo:

$ touch x 'x ' $'y\bx' $'x\n0\t.\u2215x' $'y\r0\t.\e[Cx'
$ ln x y
$ du -hs ./*
0       ./x
0       ./x
0       ./x
0       .∕x
0       ./x
0       ./x

Muitos x, mas yestá faltando.

Algumas ferramentas como GNUls substituiriam os caracteres não imprimíveis por um ponto de interrogação (observe que (U + 2215) é imprimível) quando a saída passa para um terminal. GNU dunão.

Existem maneiras de fazê-los se revelar:

$ ls
x  x   x?0?.∕x  y  y?0?.?[Cx  y?x
$ LC_ALL=C ls
x  x?0?.???x  x   y  y?x  y?0?.?[Cx

Veja como nos mudamos ???depois que dissemos lsque nosso conjunto de caracteres era ASCII.

$ du -hs ./* | LC_ALL=C sed -n l
0\t./x$
0\t./x $
0\t./x$
0\t.\342\210\225x$
0\t./y\r0\t.\033[Cx$
0\t./y\bx$

$marca o final da linha, para que possamos identificar o "x"vs "x ", todos os caracteres não imprimíveis e caracteres não ASCII são representados por uma sequência de barra invertida (a barra invertida seria representada com duas barras invertidas), o que significa que é inequívoco. Era o GNU sed, deveria ser o mesmo em todas as sedimplementações compatíveis com POSIX, mas observe que algumas sedimplementações antigas não são tão úteis.

$ du -hs ./* | cat -vte
0^I./x$
0^I./x $
0^I./x$
0^I.M-bM-^HM-^Ux$

(não padrão, mas bastante comum, também cat -Acom algumas implementações). Essa é útil e usa uma representação diferente, mas é ambígua ( "^I"e <TAB>é exibida da mesma forma, por exemplo).

$ du -hs ./* | od -vtc
0000000   0  \t   .   /   x  \n   0  \t   .   /   x      \n   0  \t   .
0000020   /   x  \n   0  \t   . 342 210 225   x  \n   0  \t   .   /   y
0000040  \r   0  \t   . 033   [   C   x  \n   0  \t   .   /   y  \b   x
0000060  \n
0000061

Esse é padrão e inequívoco (e consistente de implementação para implementação), mas não é tão fácil de ler.

Você notará que ynunca apareceu acima. Isso é um completamente alheios problema com du -hs *que não tem nada a ver com nomes de arquivos, mas devem ser observados: porque duo uso do disco relatórios, não relatar outros links para um arquivo já listadas (nem todas as duimplementações se comportam como que, embora quando os links de disco rígido são listados na linha de comando).

Stéphane Chazelas
fonte
+1, agradável e completo (tanto quanto eu posso dizer ^^). Eu amo especialmente a vantagem do "grep -c /". Também vale a pena notar: a vantagem de "./*" sobre "*" aparece em uma das (muitas) boas respostas das Perguntas frequentes do Unix (provavelmente no faqs.org. Iirc, está na pergunta sobre arquivos rm-ing começando com uma "-").
Olivier Dulac
... e é não má prática ter arquivos com novas linhas e tabs em seus nomes? Eu sei que tento limitar nomes a [a-z0-9.+-].
Blacklight Shining
5
@ BlacklightShining, é muito ruim roubar carros, mas é ruim deixar seu carro destrancado (ignorar novas linhas), especialmente quando é um carro caro (script rodando como usuário privilegiado, em um servidor com dados confidenciais ...) ou quando você estacione-o em uma área irregular ( /tmp) ou em uma área com muitos carros caros ( $HOME) e é ainda pior ir a um site de perguntas e respostas e dizer que sempre é bom não trancar o carro sem especificar em quais condições (em uma garagem trancada, script você escreveu executar por si mesmo apenas em uma máquina não está ligado a qualquer rede ou armazenamento removível ...)
Stéphane Chazelas
1
@BlacklightShining, as novas linhas são incomuns, mas os espaços incorporados são muito comuns atualmente, principalmente para arquivos criados via GUI.
Alexis
2
@BlacklightShining, sim, embora alguém (goste "b "ou "a\bb") engane um usuário em um terminal, mas não um script que analise a saída du ./*. Eu provavelmente deveria adicionar uma nota sobre isso. Vai fazer amanhã. Note que anteriormente eu quis dizer privilegiado no sentido geral, não root(embora isso se aplique ainda mais, é rootclaro). novas linhas são permitidas, ignorá-las é um bug. os bugs costumam ser explorados. Você precisa medir o risco caso a caso. Boas práticas de codificação podem evitar os problemas em muitos casos. Certamente no SE, devemos conscientizar.
Stéphane Chazelas
6

Não há diferença entre um *e ./*em termos de quais arquivos quer lista de vontade. A única diferença seria com o 2º formulário, cada arquivo teria uma barra ./prefixada na frente deles, o que normalmente significa o diretório atual.

Lembre-se de que o .diretório é uma notação abreviada para o diretório atual.

$ ls -la | head -4
total 28864
drwx------. 104 saml saml    12288 Jan 23 20:04 .
drwxr-xr-x.   4 root root     4096 Jul  8  2013 ..
-rw-rw-r--.   1 saml saml      972 Oct  6 20:26 abcdefg

Você pode se convencer de que essas duas listas são essencialmente a mesma coisa, usando echopara ver o que o shell as expandiria.

$ echo *
$ echo ./*

Esses 2 comandos listarão todos os arquivos em seu diretório atual.

Exemplos

Podemos criar alguns dados falsos como:

$ touch file{1..5}
$ ll
total 0
-rw-rw-r--. 1 saml saml 0 Jan 24 07:14 file1
-rw-rw-r--. 1 saml saml 0 Jan 24 07:14 file2
-rw-rw-r--. 1 saml saml 0 Jan 24 07:14 file3
-rw-rw-r--. 1 saml saml 0 Jan 24 07:14 file4
-rw-rw-r--. 1 saml saml 0 Jan 24 07:14 file5

Agora, quando usamos os echocomandos acima , vemos a seguinte saída:

$ echo *
file1 file2 file3 file4 file5
$ echo ./*
./file1 ./file2 ./file3 ./file4 ./file5

Essa diferença pode parecer desnecessária, mas há situações em que você deseja garantir às várias ferramentas de linha de comando do Unix que você está passando nomes de arquivos para elas através da linha de comando e nada mais!

Então, por que usar ./*?

Como a resposta de @ Stephane aponta , devido à natureza de quais caracteres são legais ao nomear arquivos e diretórios no Unix, nomes de arquivos perigosos podem ser construídos, com efeitos colaterais inesperados quando transmitidos a vários comandos do Unix na linha de comando.

Com frequência, o uso de ./será usado para ajudar a garantir que os nomes de arquivos expandidos sejam considerados como nomes de arquivo quando passados ​​como argumentos para os vários comandos do Unix.

slm
fonte