Ruby File:: directory? вопросы

Я новичок в рубине, но до сих пор наслаждаюсь им. Есть некоторые вещи, которые дали мне некоторые проблемы, и следующее не является исключением.

То, что я пытаюсь сделать здесь, это создать своего рода "супер-каталог" путем подклассификации "Dir". Я добавил метод поддиалогов, который предназначен для отображения файлов объектов каталога и вставки их в массив, если файл является самой папкой. Проблема в том, что результаты моего теста (File.directory?) Странны - вот мой код метода:

  def subdirs
    subdirs = Array.new
    self.each do |x|
      puts "Evaluating file: #{x}"
      if File.directory?(x)
        puts "This file (#{x}) was considered a directory by File.directory?"
        subdirs.push(x)
        #yield(x) if block_given?
      end
    end
    return subdirs
  end

И странно, хотя в каталоге, который я выбрал ( "/tmp" ), есть много каталогов - результат этого вызова содержит только списки ".". и ".."

puts "Testing new Directory custom class: FileOps/DirClass"

nd   = Directory.new("/tmp")
subs = nd.subdirs

И результаты:

Evaluating file: mapping-root
Evaluating file: orbit-jvxml
Evaluating file: custom-directory
Evaluating file: keyring-9x4JhZ
Evaluating file: orbit-root
Evaluating file: .
This file (.) was considered a directory by File.directory?
Evaluating file: .gdmFDB11U
Evaluating file: .X0-lock
Evaluating file: hsperfdata_mishii
Evaluating file: .X11-unix
Evaluating file: .gdm_socket
Evaluating file: ..
This file (..) was considered a directory by File.directory?
Evaluating file: .font-unix
Evaluating file: .ICE-unix
Evaluating file: ssh-eqOnXK2441
Evaluating file: vesystems-package
Evaluating file: mapping-jvxml
Evaluating file: hsperfdata_tomcat

Ответы

Ответ 1

Выполняется ли script из /tmp? Моя догадка (я не пробовал это) заключается в том, что File.directory?(x) тестирует, есть ли каталог с именем x в текущем каталоге - поэтому, если вы используете это из другого каталога, вы всегда найдете . и .., но не другие каталоги.

Попробуйте изменить File.directory?(x) на File.directory?("#{path}/#{x}").

Ответ 2

Марк Уэстлинг уже ответил на ваш ближайший вопрос, но, поскольку вы упоминаете, что вы новичок в Ruby, вот несколько других предложений стиля:
def subdirs
  subdirs = Array.new

Обычно предпочтительнее использовать литерал-синтаксис, где это возможно, поэтому общий способ выразить это будет subdirs = [].

  self.each do |x|

self - это неявный приемник в Ruby, поэтому вы можете просто оставить его. (В конце концов, это не Python.) Однако основная цель кода - общение, поэтому, если вы считаете, это лучше передает ваше намерение, оставьте его.

Говоря об общении: x не является ужасно коммуникативным именем, если вы не говорите о точках в декартовой системе координат. Как насчет file, или если вам неудобно понятие Unix, что каталоги также являются файлами, тем более нейтральным entry? (На самом деле, лучший, вероятно, был бы path, но мы скоро увидим, как это может запутаться.)

Мое третье предложение на самом деле является личным предпочтением, которое противоречит обычным рубиновым стилям: обычный стиль Ruby определяет, что однострочные блоки ограничены {/}, а многострочные блоки ограничены do/end, как и вы. Мне это не нравится, потому что это произвольное различие, которое не передает никакого смысла. (Помните, что "сообщение"?) Итак, я на самом деле делаю что-то по-другому: если блок необходим по своей природе и, например, изменяет какое-то глобальное состояние, я использую ключевые слова (потому что блок на самом деле do), и если блок функциональный по своей природе и просто возвращает новый объект, я предпочитаю использовать фигурные скобки (потому что они выглядят хорошо математически). Итак, в этом случае я бы использовал фигурные скобки.

    if File.directory?(x)

Как уже объяснил Марк, вам нужно сделать что-то вроде File.directory?(File.join(path, entry)) здесь, где Dir#path является общедоступным атрибутом класса Dir, возвращающим путь, который был инициализирован Dir.new.

Здесь вы также видите, почему мы не использовали path как имя параметра блока. В противном случае нам нужно было бы написать File.directory?(File.join(self.path, path)).

      subdirs.push(x)

Канонический способ добавления элемента к массиву или, действительно, почти что-либо добавить к чему-либо в Ruby, - это оператор <<. Итак, это должно читать subdirs << entry.

Array#push является псевдонимом Array#<<, который в основном предназначен для использования Array в качестве стека (в сочетании с Array#pop).

    end
  end
  return subdirs

Вот еще один разрыв между моим личным стилем и обычным стилем Ruby: в Ruby, если нет явного return, возвращаемое значение будет просто значением последнего выражения. Это означает, что вы можете оставить ключевое слово return, а обычный стиль Ruby говорит, что вы должны. Однако я предпочитаю использовать это аналогично блочным разделителям: используйте return для методов, которые являются функциональными по своей природе (потому что они фактически "возвращают" значение) и no return для императивных методов (поскольку их реальное возвращаемое значение а не то, что происходит после ключевого слова return, но какие побочные эффекты у них есть в среде). Итак, как и вы, я бы использовал ключевое слово return, несмотря на общий стиль.

Также принято разделять возвращаемое значение с остальной части тела метода пустой строкой. (То же самое касается кода установки, кстати.)

end

Итак, вот где мы стоим прямо сейчас:

def subdirs
  subdirs = []

  each { |entry|
    if File.directory?(File.join(path, entry))
      subdirs << entry
    end
  }

  return subdirs
end

Как вы можете видеть, выражение if действительно служит только для пропуска одной итерации цикла. Это гораздо лучше, если использовать ключевое слово next:

def subdirs
  subdirs = []

  each { |entry|
    next  unless File.directory?(File.join(path, entry))
    subdirs << entry
  }

  return subdirs
end

Как вы можете видеть, нам удалось удалить один уровень вложенности из структуры блока, который всегда является хорошим знаком.

Эта идиома называется "оговоркой о защите" и довольно популярна в функциональном программировании, где многие языки даже имеют встроенные защитные конструкции, но также широко используются в любом другом языке на планете, потому что это значительно упрощает поток управления: идея аналогична охраннику, размещенному вне замка: вместо того, чтобы позволить плохим парням в замке (метод, процедура, функция, блок,...), а затем больно пытаться отслеживать каждое их движение и постоянно боясь пропустить что-то или потерять их, вы просто отправляете охранника у входа в свой замок (начало вашего метода, блок,...), который не позволяет им начинать (который прыгает до конца процедуры, возвращается раньше из метода, пропускает одну итерацию цикла,...). В Ruby вы можете использовать raise, return, next и break для этого. На других языках даже GOTO является прекрасным (это один из тех редких случаев, когда GOTO может реально очистить поток управления).

Однако мы можем упростить это еще больше, узнав шаблон итератора: вы берете список (записи каталога), а затем вы "сквоируете" этот список до одного объекта (массив subdirs). Для теоретика категории это кричит "катаморфизм", хардкорному функциональному программисту "fold", программисту-программисту "reduce" программисту Smalltalk "inject:into:" и рубисту "Enumerable#inject":

def subdirs
  return inject([]) { |subdirs, entry|
    next subdirs  unless File.directory?(File.join(path, entry))
    subdirs << entry
  }
end

inject использует возвращаемое значение предыдущей итерации для семени следующего, поэтому нам нужно вернуть subdirs, даже если мы пропустим итерацию, используя next subdirs вместо plain next ( который возвратит nil, так что на следующей итерации subdirs будет nil и subdirs << entry будет поднимать NoMethodError.)

(Обратите внимание, что я использовал фигурные скобки для блока, хотя блок фактически модифицирует его аргумент. Я считаю, что это все еще "функциональный" блок. YMMV.)

Последнее, что мы можем сделать, это признать, что то, что мы делаем, это просто фильтровать (или, другими словами, "выбирать" ) элементы массива. И Ruby уже имеет встроенный метод, который делает именно это: Enumerable#select. Свидетель однолинейной удивительности, которая использует всю мощь Ruby:

def subdirs
  return select { |entry| File.directory?(File.join(path, entry)) }
end

Как общий совет: узнайте чудеса Enumerable. Это рабочая лошадь программирования Ruby, аналогичная IEnumerable<T> в .NET, dict в Python, списки в функциональных языках или ассоциативных массивах в PHP и Perl.

Ответ 3

Я сделал несколько незначительных изменений...

class Directory < Dir
  def subdirs
    subdirs = []
    each do |x|
      puts "Evaluating file: #{x}"
      if File.directory? File.join path, x
        puts "This file (#{x}) was considered a directory by File.directory?"
        subdirs << x
        #yield(x) if block_given?
      end
    end
    subdirs
  end
end
puts "Testing new Directory custom class: FileOps/DirClass"

nd   = Directory.new "/tmp"
puts subs = nd.subdirs

Ответ 4

Замените * любым способом, который вы хотите, и вам хорошо идти. Glob доставит вам все файлы в каком-то пути, используя bash globbing, чтобы вы могли использовать * и **, а также диапазоны и т.д. Довольно сладкий.

Выбор работает как противоположный отбраз, вишня выбирает только значения, которые истинны в блоке выбора.

Dir.glob( "*" ). select {| f | File.directory?(f)}