Existe uma alternativa mais sucinta ao canalizar para o wc para contar arquivos em um diretório

12

Se assim for ls -1 target_dir | wc -l, recebo uma contagem de arquivos em um diretório. Acho isso um pouco complicado. Existe uma maneira mais elegante ou sucinta?

codecowboy
fonte
2
Você não precisa do "-1" ao canalizar para o wc.
Steve Steve
lsjá dá a contagem total, e daí ls -l | head -1? Crie um alias se você quiser algo mais curto.
Daniel Wagner
2
@DanielWagner A saída "total: nnn" ls -lindica o tamanho total dos arquivos, não o número de arquivos.
precisa saber é o seguinte
2
Lembre-se de que ls | wc -lvocê receberá a contagem errada se algum nome de arquivo contiver novas linhas.
Chepner
Isso depende do sistema de arquivos e conta com os diretórios + 2 em um diretório. A resposta tem 2 extras (como conta a si mesmo e a seus pais). stat -c %h .dá a mesma informação quels -ld . | cut -d" " -f 2
ctrl-alt-Delor

Respostas:

12

Supondo que o bash 4+ (que qualquer versão suportada do Ubuntu possui):

num_files() (
    shopt -s nullglob
    cd -P -- "${1-.}" || return
    set -- *
    echo "$#"
)

Chame como num_files [dir]. diré opcional, caso contrário, ele usa o diretório atual. Sua versão original não conta arquivos ocultos, nem isso. Se você quer isso, shopt -s dotglobantes set -- *.

Seu exemplo original conta não apenas arquivos regulares, mas também diretórios e outros dispositivos - se você realmente deseja apenas arquivos regulares (incluindo links simbólicos para arquivos regulares), será necessário verificá-los:

num_files() (
    local count=0

    shopt -s nullglob
    cd -P -- "${1-.}" || return
    for file in *; do
        [[ -f $file ]] && let count++
    done
    echo "$count"
)

Se você encontrar o GNU, algo como isso também é uma opção (observe que isso inclui arquivos ocultos, o que seu comando original não fez):

num_files() {
    find "${1-.}" -maxdepth 1 -type f -printf x | wc -c
}

(mude -typepara -xtypese você também deseja contar links simbólicos para arquivos regulares).

Chris Down
fonte
Não setfalhará se houver muitos arquivos? Eu acho que você pode ter que usar xargse algum código de soma para fazer isso funcionar no caso geral.
l0b0
1
Além disso shopt -s dotglob, se você quiser arquivos começando com .a ser contado
Trauma Digital
1
@ l0b0 Acho que não setirá falhar nessas circunstâncias, já que não estamos fazendo um exec. A saber, no meu sistema, getconf ARG_MAXproduz 262.144, mas se eu fizer test_arg_max() { set -- {1..262145}; echo $#; }; test_arg_max, ele alegremente responde 262145.
kojiro
@DavidRicherby -maxdepthnão é POSIX.
Chris Down
4
@MichaelMartinez Escrever código óbvio não é um substituto para escrever código correto.
Chris Down
3

f=(target_dir/*);echo ${#f[*]}

funciona corretamente para arquivos com espaços, novas linhas etc. no nome.

Aaron Davies
fonte
você pode fornecer algum contexto? Isso deve ocorrer em um script bash?
Codecowboy
poderia. você também pode colocá-lo diretamente no shell. essa versão assumiu que você queria o diretório atual; Eu editei para ficar mais próximo da sua pergunta. basicamente, ele cria uma variável de matriz de shell que contém todos os arquivos no diretório e depois imprime a contagem dessa matriz. deve funcionar em qualquer shell com matrizes - bash, ksh, zsh, etc. - mas provavelmente não é simples sh / ash / dash.
Aaron Davies
2

lsé multi-colunas somente se tiver saída direta para um terminal, você pode remover a opção "-1", você pode remover a wcopção "-l", ler apenas o primeiro valor (solução lenta, não deve ser usada para evidências legadas, investigações criminais, missões críticas, operações táticas ..).

ls target | wc 
Emmanuel
fonte
5
Esta falha para nomes de arquivos contendo novas linhas.
l0b0
@Emmanuel Você precisará analisar o resultado wcpara obter o número de arquivos, mesmo no caso trivial, então como isso é uma solução?
l0b0
@Emmanuel Isso pode falhar se targetfor um globo que, quando expandido, inclui algumas coisas que começam com hífens. Por exemplo, fazer um novo diretório, entrar nele e não touch -- {1,2,3,-a}.txt && ls *|wc: (NB uso rm -- *.txtpara excluir esses arquivos.)
David Richerby
Você quis dizer wc -l? Caso contrário, você obterá novas contagens de linhas, palavras e bytes da lssaída. Foi o que David Richerby disse: você precisa analisá-lo novamente.
Erik
@erik Estou mentindo, wcsem argumento, que você não precisa analisar se seu cérebro sabe que o primeiro argumento é nova linha.
Emmanuel
2

Se você está buscando a sucessão (e não a exatidão exata ao lidar com arquivos com novas linhas em seus nomes, etc.), recomendo que você faça um alias wc -lpara lc("contagem de linhas"):

$ alias lc='wc -l'
$ ls target_dir|lc

Como outros observaram, você não precisa da -1opção ls, pois é automático quando lsestá gravando em um pipe. (A menos que você tenha um lsalias para sempre usar o modo de coluna. Já vi isso antes, mas não com muita frequência.)

Um lcalias é bastante útil em geral, e para esta pergunta, se você olhar para o caso "conte o diretório atual", ls|lcé o mais sucinto possível.

Aaron Davies
fonte
2

Até agora, a Aaron é a única abordagem mais sucinta que a sua. Uma versão mais correta da sua abordagem pode parecer com:

ls -aR1q | grep -Ecv '^\./|/$|^$'

Que lista recursivamente todos os arquivos - e não os diretórios - um por linha, incluindo arquivos .dot sob o diretório atual, usando shell globs conforme necessário para substituir caracteres não imprimíveis. O grep filtra todas as listagens de diretório pai ou .. ou * / ou linhas em branco - portanto, deve haver apenas uma linha por arquivo - cuja contagem total o grep retorna para você. Se você deseja incluir diretórios filhos, faça:

ls -aR1q | grep -Ecv '^\.{1,2}/|^$'

Remova os -Rdois casos, se você não deseja resultados recursivos.

mikeserv
fonte
1
Eu tendem a preferir fazer esse tipo de coisa find. Se você deseja apenas uma contagem, isso deve funcionar: find -mindepth 1 -maxdepth 1 -printf '\n'|wc -l(remova os controles de profundidade para obter resultados recursivos).
Aaron Davies
@AaronDavies - isso realmente não funciona. Coloque uma nova linha em qualquer um desses nomes de arquivo e veja por si mesmo. Além disso, para fazer o mesmo de maneira portável, você faz: find . \! -name . -prune | wc -l- o que ainda não funciona, é claro.
precisa saber é o seguinte
1
Eu não sigo - a printfinstrução imprime uma string constante (uma nova linha) que não inclui o nome do arquivo, portanto os resultados são independentes de qualquer nome de arquivo estranho. Esse truque não pode ser feito com um findque não suporta printf, é claro.
Aaron Davies
@AaronDavies - oh, é verdade. Eu assumi que o nome do arquivo estava incluído. Porém, pode ser feito de maneira find .//. \!. -name . -prune | grep -c '^\.//\.'
portável
brilhante! /sendo o único outro caractere que não pode aparecer nos nomes dos arquivos, a .//.sequência é garantida para aparecer exatamente uma vez para cada arquivo, certo? algumas perguntas: por que .//.e por quê -prune? quando isso seria diferente find . \! -name . | grep -c '^\.'? (i assumir a .sua \!.é um erro de digitação.)
Aaron Davies