Ruby File :: каталог? вопросы - PullRequest
15 голосов
/ 17 ноября 2009

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

То, что я пытаюсь сделать здесь, - это создать своего рода «супер-каталог» путем подкласса «Dir». Я добавил метод под названием «subdirs», предназначенный для вывода списка файлов объекта каталога и помещения их в массив, если файл сам является каталогом. Проблема в том, что результаты моего теста (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

Ответы [ 4 ]

17 голосов
/ 17 ноября 2009

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

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

9 голосов
/ 17 ноября 2009

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

def subdirs
  subdirs = Array.new

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

  self.each do |x|

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

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

Мое третье предложение на самом деле касается личных предпочтений, которые противоречат общепринятому стилю Ruby: общепринятый стиль 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 для методов, которые функциональны по своей природе (потому что они на самом деле «возвращают» значение), и не return для императивных методов (потому что их real Возвращаемое значение - не то, что следует за ключевым словом 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:" и Rubyist "Enumerable#inject «:

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

inject использует возвращаемое значение предыдущей итерации для заполнения следующей, поэтому мы должны вернуть subdirs, даже если мы пропускаем итерацию, используя next subdirs вместо простого 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 s в Python, списки на функциональных языках или ассоциативные массивы в PHP и Perl.

0 голосов
/ 17 ноября 2009

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

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

Dir.glob ("*"). Выберите {| f | File.directory?(f)}

0 голосов
/ 17 ноября 2009

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

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...