Что означает -1 в "пути ls -1"?
Я смотрю на некоторый шелл-код, который предназначен для подсчета количества файлов в каталоге. Это читает:
COUNT=$(ls -1 ${DIRNAME} | wc -l)
Что это -1
часть значит? Я не могу найти ничего об этом в других вопросах, просто передавая ссылки на итерации по файлам в каталоге, а это не то, что я смотрю. Кроме того, удаление этого из команды, кажется, не имеет никакого эффекта.
2 ответа
COUNT=$(ls -1 ${DIRNAME} | wc -l)
... это ошибочный способ подсчета файлов в каталоге: ls -1
говорит ls
не помещать несколько файлов в одну строку; убедившись, что wc -l
Затем, подсчитав количество строк, будет считать файлы.
Теперь поговорим о "багги":
- Имена файлов могут содержать буквальные переводы строк. Как версия
ls
обрабатывает это в зависимости от реализации; некоторые версии могут удваивать количество таких файлов (системы GNU этого не делают, но я бы не хотел делать ставки, скажем, о случайных выпускахbusybox
плавает на встроенных роутерах). - Расширение без кавычек
${DIRNAME}
позволяет разделить имя каталога и развернуть глобус перед передачейls
, так что если имя содержит пробел, оно может стать несколькими аргументами. Это должно быть"$DIRNAME"
или же"${DIRNAME}"
вместо.
... также, это неэффективно, так как вызывает несколько внешних инструментов (ls
а также wc
) что-то сделать, что оболочка может управлять внутри.
Если вы хотите что-то более надежное, эта версия будет работать со всеми оболочками POSIX:
count_entries() { set -- "${1:-.}"/*; if [ -e "$1" ]; then echo "$#"; else echo 0; fi; }
count=$(count_entries "$DIRNAME") ## ideally, DIRNAME should be lower-case.
... или, если вы хотите, чтобы он выполнялся быстрее (не требуя вложенной оболочки), см. ниже (нацеливание только на bash):
# like above, but write to a named variable, not stdout
count_entries_to_var() {
local destvar=$1
set -- "${2:-.}"/*
if [[ -e "$1" || -L "$1" ]]; then
printf -v "$destvar" %d "$#"
else
printf -v "$destvar" %d 0
fi
}
count_entries_to_var count "$DIRNAME"
... или, если вы нацелены на bash и не хотите беспокоиться о функции, вы можете использовать массив:
files=( "$DIRNAME"/* )
if [[ -e "${files[0]}" || -L "${files[0]}" ]]; then
echo "At least one file exists in $DIRNAME"
echo "...in fact, there are exactly ${#files[@]} files in $DIRNAME"
else
echo "No files exist in $DIRNAME"
fi
Наконец - если вы хотите иметь дело со списком имен файлов, слишком больших, чтобы поместиться в памяти, и у вас есть GNU find
рассмотрите возможность использования этого:
find "$DIRNAME" -mindepth 1 -maxdepth 1 -printf '\n' | wc -l
... который вообще избегает помещения имен в поток (и, таким образом, генерирует поток, для которого можно было бы просто измерить длину в байтах, а не количество строк, если так было выбрано).
В дополнение к прекрасному ответу Чарльза Даффи:
Есть один крайний случай, на который его ответ не распространяется: если первая запись каталога окажется неработающей символической ссылкой, тестирование на расширение глобуса выполняется с помощью -e
недостаточно, учитывая, что Bash всегда применяет тест на существование к цели символической ссылки, которая по определению не существует в случае разрыва символической ссылки. Другими словами: для сломанной символической ссылки, -e
будет указывать false, даже если сама ссылка существует. Поэтому полностью надежное решение должно использовать что-то вроде [[ -e "$1" || -L "$1" ]]
(-L
проверяет, является ли его аргумент символической ссылкой, сломанной или нет.)
Вот немного короче bash
альтернатива (использует подоболочку):
count=$(shopt -s nullglob; entries=(*); echo "${#entries[@]}")
shopt -s nullglob
гарантирует, что шаблон расширяется до пустой строки, если ничего не совпадает.entries=(*)
собирает все совпадения (в текущем каталоге) в массивеecho "${#entries[@]}"
вывести количество элементов массива.- Поскольку никакие внешние утилиты не задействованы, эта команда не подлежит
getconf ARG_MAX
предел, поэтому должен работать с большими каталогами.
Обратите внимание, что подсчитанное выше скрыто (.*
) предметы также зависит от состояния dotglob
вариант. Однако легко встроить в команду фиксированную логику скрытых элементов, включенных или не включенных:
Явно включить скрытые элементы:
count=$(shopt -s nullglob dotglob; entries=(*); echo "${#entries[@]}")
Явно исключить скрытые элементы:
count=$(shopt -s nullglob; shopt -u dotglob; entries=(*); echo "${#entries[@]}")
Все это можно обернуть в гибкую функцию:
countEntries [<dir>] ... counts based on current state of the `dotglob` option
countEntries <dir> 0 ... counts non-hidden entries only
countEntries <dir> 1 ... counts all entries, including hidden ones
#!/usr/bin/env bash
# SYNOPSIS
# countEntries [<dir> [<includeHidden>]]
# DESCRIPTION
# <dir> defaults to .
# <includeHidden> default to the current state of `shopt dotglob`;
# a value of 0 explicitly EXcludes, 1 explicity INcludes hidden items.
countEntries() ( # Run entire function in subhell.
local dir=${1:-.} includeHidden=$2 entries
shopt -s nullglob
case $includeHidden in
0) # EXclude hidden entries
shopt -u dotglob
;;
1) # INclude hidden entries
shopt -s dotglob
;;
# Otherwise: use *current state* of `dotglob`
esac
entries=("$1"/*) # Collect in array
echo "${#entries[@]}" # Output count.
)