Como posso simplificar este script bash que imprime o número de arquivos no diretório de trabalho?

1

Estou trabalhando no meu prompt do bash PS1 e gostaria de imprimir o número de arquivos no diretório atual.

Eu escrevo um código de trabalho, mas,
Existe uma maneira de simplificar esse script (redundante)?

$(ls -l | grep ^- | wc -l) $(if [ $(ls -l | grep ^- | wc -l) -eq 1 ]; then echo "file"; else echo "files"; fi)

Meu objetivo é imprimir o número de arquivos em uma pasta e a instrução IF é necessária para manipular o plural, para gerenciar os dois casos:

  • 1 arquivo
  • 2 arquivos

por exemplo. my ~ pasta contém três arquivos, de modo que o script deve imprimir a palavra "arquivos"; meu ~ / Desktop tem apenas um arquivo, então o script deve imprimir "arquivo"

Eu escrevo a linha de código acima, que faz o trabalho, mas eu acho que existe uma maneira condensada e inteligente de obtê-lo ...

    
por mattia.b89 05.08.2017 / 10:27

4 respostas

1
find . -maxdepth 1 -type f | awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}'

Explicação:

  1. find . -maxdepth 1 -type f - procura todos os arquivos no diretório atual.
  2. %código%
    • awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}' = número de linhas recebidas do programa NR , que é igual ao número de arquivos.
    • find - se o número de linhas (arquivos) for maior que 1, adicione o (NR > 1) ? "s" : "") final à palavra s .
por 05.08.2017 / 18:05
3

Primeiro, você pode usar um comando mais rápido para encontrar o número correto. Meus testes mostram que find . -maxdepth 1 -type f -not -name '.*' | wc -l é mais rápido que ls -l | grep '^-' | wc -l (aproximadamente 4: 3). Se você também quiser arquivos ocultos, use ls -a ou omite a parte -not -name '.*' com find.

Depois, você não precisa contar os arquivos duas vezes, mas armazenar o resultado em uma variável e reutilizá-lo:

$(count=$(find . -maxdepth 1 -type f -not -name '.*' | wc -l)
  if [[ "$count" -eq 1 ]]; then
    echo "1 file"
  else
    echo "$count files"
  fi)

Como você pode ver, você tem que usar um subshell, caso contrário a variável não estará disponível para o segundo comando. Eu também usei o% basico [[ ... ]] em vez de [ ... ] aqui. Em geral, é a melhor solução.

Por fim, você também pode usar a variável de bash $PROMPT_COMMAND para executar algum código antes do prompt $PS1 ser impresso. Dessa forma, você não precisa armazenar todo o código na variável $PS1 . A variável $count será global . Poderia ser assim:

function count_files () {
  __count=$(find . -maxdepth 1 -type f -not -name '.*' | wc -l)
  if [[ "$__count" -eq 1 ]]; then
    __plural=
  else
    __plural=s
  fi
}
PROMPT_COMMAND=count_files
PS1='other stuff $__count file$__plural more stuff'

Não sei qual dessas etapas você consideraria como uma simplificação:

EDITAR

Acabei de encontrar este segmento SO . Pode ser usado para contar os arquivos como este

function count_files () {
  local name
  __count=0
  for name in *; do
    [[ -f "$name" ]] && ((__count++))
  done
  # like above ...
}

A velocidade é de cerca de 1:13 em comparação com a versão de localização. Isso é viril porque inicia menos processos (a mesma razão pela qual a versão find é mais rápida que a versão ls). O piso também tem algumas outras soluções com extglob. Tudo depende da pergunta se você deseja arquivos ou links para arquivos ou outros itens. Novamente, o código é mais rápido, mas parece mais complexo agora, então, que tipo de "simples" você está mirando?

EDIT 2

Note que nos comentários eu disse que a sobrecarga para iniciar um processo pode ser pequena comparada com a sobrecarga de ordenação quando você tem que ordenar muitos arquivos, para mim ele começa a mostrar se eu execute o código em /usr/bin/ , que tem cerca de 2.500 arquivos.

    
por 05.08.2017 / 11:01
3

A primeira maneira de simplificar o script seria salvar o número de arquivos em uma variável, para que você não precise recalculá-la.

Outras maneiras de gerar o número de arquivos no diretório atual:

n=$(ls -l | grep -c ^-)

Aqui, a simplificação está usando a opção -c do grep para contar as correspondências. Existe o risco de se confundir as correspondências quando analisar a saída de ls se houver um arquivo chamado $'some\n-file' , que finge colocar um hífen no início da linha.

n=$(stat -c %F -- * | grep -c 'regular .*file')

O .* no grep é responsável pelas correspondências "arquivo regular" e "arquivo vazio regular". O comando stat produz o tipo de cada arquivo, e o * shell glob evita as preocupações com ls .

Se você estiver familiarizado com o bash, mas já ouviu falar de zsh e sua poderosa sintaxe de globalização de nomes de arquivo , você poderia:

n=$(zsh -c 'a=( *(.) ); echo ${#a}')

Onde criamos uma matriz chamada a , que é preenchida com a lista de arquivos * , filtrada apenas por "arquivos simples" com . .

Para imprimir o plural corretamente, considere uma declaração de caso:

case $n in
(1) printf "1 file";;
(*) printf "$n files";;
esac

Uma instrução case permitiria mais flexibilidade se você quisesse imprimir mensagens diferentes para diferentes números de arquivos, por exemplo: "No files!".

Mais simplesmente, considere uma condicional:

[[ $n == 1 ]] && printf "1 file" || printf "$n files"

Finalmente, contornar a sugestão da steeldriver:

n=$( files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))
printf "%d file%s" "$n" "$(test "$n" -ne 1 && echo s)"

Isso atribui um valor a n (eventualmente) abrindo uma substituição de comando; dentro dessa substituição de comando temporária, eu crio duas matrizes: files para tudo e dirs para apenas diretórios. O último ato da substituição do comando é relatar a diferença entre os dois. O printf , em seguida, imprime o número de arquivos junto com o sufixo plural apropriado.

Isso usa qualquer configuração que você tenha para dotglob ; se você quiser forçar a contagem (ou omissão) de arquivos de pontos, você deve definir ou desmarcar dotglob dentro da substituição do comando:

use o valor atual de dotglob:

n=$( files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))

aplica a contagem de arquivos de pontos, independentemente do ponto atual:

n=$(shopt -s dotglob;
    files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))

aplica não a contagem de arquivos de pontos, independentemente do ponto atual:

n=$(shopt -u dotglob;
    files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))
    
por 05.08.2017 / 14:08
1

Eu acabei de fazer isso. Em vez de ls , usei a expansão do nome do caminho, porque se os nomes dos seus arquivos tiverem caracteres estranhos (novas linhas, por exemplo), a saída será quebrada.

#!/usr/bin/env bash

((n=0))

for i in *; do
  [[ -f "${i}" ]] && ((n++))
done

((n==1)) && { echo "${n} file"; exit; }
echo "${n} files"
    
por 05.08.2017 / 12:41