Что означает -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.
)
Другие вопросы по тегам