Как я могу упростить этот сценарий bash, который печатает количество файлов в рабочем каталоге?

Para activar o desactivar Zoom, puede utilizar Alt+Windows+8

1
05.08.2017, 13:06
4 ответа
find. -maxdepth 1 -type f | awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}'

Пояснение:

  1. find. -maxdepth 1 -type f-искать все файлы в текущем каталоге.
  2. awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}'
    • NR= количество строк, полученных от программы find, равное количеству файлов.
    • (NR > 1) ? "s" : "")-если количество строк (файлов )больше 1, добавить окончание sк слову file.
1
28.04.2021, 23:58

Сначала вы можете использовать более быструю команду, чтобы найти правильный номер. Мои тесты показывают, что find. -maxdepth 1 -type f -not -name '.*' | wc -lбыстрее, чемls -l | grep '^-' | wc -l(примерно на 4 :3 ). Если вам также нужны скрытые файлы, вы должны использовать ls -aили пропустить часть -not -name '.*'с помощью find.

Далее вам не нужно считать файлы дважды, а вместо этого сохранить результат в переменной и использовать его повторно:

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

Как видите, вы должны использовать одну подоболочку, иначе переменная не будет доступна для второй команды. Я также использовал специфичный для bash [[... ]]вместо [... ]здесь. В целом это лучшее решение.

Наконец, вы также можете использовать переменную Bash $PROMPT_COMMANDдля выполнения некоторого кода непосредственно перед выводом приглашения $PS1. Таким образом, вам не нужно хранить весь код в переменной $PS1. Однако переменная $countбудет глобальной . Это может выглядеть так:

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'

Не знаю, какой из этих шагов вы бы посчитали упрощением:)

РЕДАКТИРОВАТЬ

Я только что нашел эту ветку SO . Его можно использовать для подсчета таких файлов

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

Скорость составляет около 1 :13 по сравнению с версией find. Это мужественно, потому что он запускает меньше процессов (по той же причине, что версия find быстрее, чем версия ls ). У протектора есть и другие решения с extglob. Все зависит от вопроса, нужны ли вам файлы или также ссылки на файлы или еще что-то. Опять же, код стал быстрее, но теперь выглядит более сложным, так к какому «простому» вы стремитесь?

РЕДАКТИРОВАТЬ 2

Обратите внимание, хотя в комментариях я сказал, что накладные расходы на запуск процесса могут быть лишь небольшими по сравнению с накладными расходами на сортировку, когда вам нужно сортировать множество файлов, для меня это начинает показывать, если я запускаю код в /usr/bin/, содержащий около 2500 файлов.

3
28.04.2021, 23:58

Я только что сделал это. Вместо lsя использовал расширение пути, потому что если ваши имена файлов содержат странные символы (новой строки, например ), это разбивает вывод.

#!/usr/bin/env bash

((n=0))

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

((n==1)) && { echo "${n} file"; exit; }
echo "${n} files"
1
28.04.2021, 23:58

Первый способ упростить скрипт — это сохранить количество файлов в переменной, чтобы вам не приходилось его пересчитывать.

Другие способы узнать количество файлов в текущем каталоге:

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

Здесь упрощение заключается в использовании опции -cкоманды grep для подсчета совпадений. Существует риск ошибочного подсчета совпадений при анализе вывода ls , если есть файл с именем $'some\n-file', который якобы ставит дефис в начале строки.

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

.*в grep учитывает совпадения как «обычный файл», так и «обычный пустой файл». Команда stat выводит тип каждого файла, а глобус оболочки *позволяет избежать проблем с ls.

Если вы знакомы с bash, но слышали о zsh и его мощном синтаксисе подстановки имен файлов , вы можете:

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

Где мы создаем массив с именем a, который заполняется списком файлов *, отфильтрованных только «обычными файлами» с ..

Чтобы правильно напечатать множественное число, рассмотрим оператор case:

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

Оператор case обеспечивает большую гибкость, если вы хотите напечатать разные сообщения для разного количества файлов, например :"Нет файлов!".

Проще говоря, рассмотрим условное:

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

Наконец-то сошлись на предложении стилдрайвера:

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

Это присваивает значениеn(в конечном итоге )путем открытия подстановки команд; внутри этой временной подстановки команд я создаю два массива:filesдля всего и dirsтолько для каталогов. Последним действием подстановки команд является сообщение о разнице между ними. Затем printfвыводит количество файлов вместе с соответствующим суффиксом множественного числа.

Здесь используются все настройки, которые у вас есть для dotglob; если вы хотите принудительно подсчитать (или пропустить )точечные -файлы,затем вы должны установить или отключить dotglobвнутри подстановки команды:

использовать текущее значение dotglob:

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

включить подсчет точечных -файлов независимо от текущего точечного глобуса:

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

принудительно запретить подсчет точечных -файлов независимо от текущего точечного глобуса:

n=$(shopt -u dotglob;
    files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))
3
28.04.2021, 23:58

Теги

Похожие вопросы