find. -maxdepth 1 -type f | awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}'
Пояснение:
find. -maxdepth 1 -type f
-искать все файлы в текущем каталоге. awk 'END {printf("%d %s%s\n", NR, "file", (NR > 1) ? "s" : "")}'
NR
= количество строк, полученных от программы find
, равное количеству файлов. (NR > 1) ? "s" : "")
-если количество строк (файлов )больше 1, добавить окончание s
к слову file
. Сначала вы можете использовать более быструю команду, чтобы найти правильный номер. Мои тесты показывают, что 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 файлов.
Я только что сделал это. Вместо ls
я использовал расширение пути, потому что если ваши имена файлов содержат странные символы (новой строки, например ), это разбивает вывод.
#!/usr/bin/env bash
((n=0))
for i in *; do
[[ -f "${i}" ]] && ((n++))
done
((n==1)) && { echo "${n} file"; exit; }
echo "${n} files"
Первый способ упростить скрипт — это сохранить количество файлов в переменной, чтобы вам не приходилось его пересчитывать.
Другие способы узнать количество файлов в текущем каталоге:
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
внутри подстановки команды:
n=$( files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))
n=$(shopt -s dotglob;
files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))
n=$(shopt -u dotglob;
files=(*); dirs=(*/); echo $(( ${#files[@]} - ${#dirs[@]} )))