Программирование на языке Ruby - Хэл Фултон
Шрифт:
Интервал:
Закладка:
file = File.new("diskfile")
info = file.stat
device = info.dev
devtype = info.rdev
inode = info.ino
10.1.13. Каналы
Ruby поддерживает разные способы читать из канала и писать в него. Метод класса IO.popen открывает канал и связывает с возвращенным объектом стандартные ввод и вывод процесса. Часто с разными концами канала работают разные потоки, но в примере ниже запись и чтение осуществляет один и тот же поток:
check = IO.popen("spell","r+")
check.puts("'T was brillig, and the slithy toves")
check.puts("Did gyre and gimble in the wabe.")
check.close_write
list = check.readlines
list.collect! { |x| x.chomp }
# list равно %w[brillig gimble gyre slithy toves wabe]
Отметим, что вызов close_write обязателен, иначе мы никогда не достигнем конца файла при чтении из канала. Существует также блочная форма:
File.popen("/usr/games/fortune") do |pipe|
quote = pipe.gets
puts quote
# На чистом диске можно искать бесконечно. - Том Стил.
end
Если задана строка "-", то запускается новый экземпляр Ruby. Если при этом задан еще и блок, то он работает в двух разных процессах, как в результате разветвления (fork); блоку в процессе-потомке передается nil, а в процессе-родителе — объект IO, с которым связан стандартный ввод или стандартный вывод.
IO.popen("-")
do |mypipe|
if mypipe
puts "Я родитель: pid = #{Process.pid}"
listen = mypipe.gets
puts listen
else
puts "Я потомок: pid = #{Process.pid}"
end
end
# Печатается:
# Я родитель: pid = 10580
# Я потомок: pid = 10582
Метод pipe возвращает также два конца канала, связанных между собой. В следующем примере мы создаем два потока, один из которых передает сообщение другому (то самое сообщение, которое Сэмюэль Морзе впервые послал по телеграфу). Если вы не знаете, что такое потоки, обратитесь к главе 3.
pipe = IO.pipe
reader = pipe[0]
writer = pipe[1]
str = nil
thread1 = Thread.new(reader,writer) do |reader,writer|
# writer.close_write
str = reader.gets
reader.close
end
thread2 = Thread.new(reader,writer) do |reader,writer|
# reader.close_read
writer.puts("What hath God wrought?")
writer.close
end
thread1.join
thread2.join
puts str # What hath God wrought?
10.1.14. Специальные операции ввода/вывода
В Ruby можно выполнять низкоуровневые операции ввода/вывода. Мы только упомянем о существовании таких методов; если вы собираетесь ими пользоваться, имейте в виду, что некоторые машиннозависимы (различаются даже в разных версиях UNIX).
Метод ioctl принимает два аргумента: целое число, определяющее операцию, и целое число либо строку, представляющую параметр этой операции.
Метод fcntl также предназначен для низкоуровневого управления файловыми потоками системно зависимым образом. Он принимает такие же параметры, как ioctl.
Метод select (в модуле Kernel) принимает до четырех параметров. Первый из них — массив дескрипторов для чтения, а остальные три необязательны (массив дескрипторов для записи, дескрипторов для ошибок и величина тайм-аута). Если на каком-то из устройств, дескрипторы которых заданы в первом массиве, оказываются новые данные для чтения или какое-то из устройств, дескрипторы которых перечислены во втором массиве, готово к выполнению записи, метод возвращает массив из трех элементов, каждый из которых в свою очередь является массивом, где указаны дескрипторы устройств, готовых к выполнению ввода/вывода.
Метод syscall из модуля Kernel принимает по меньшей мере один целочисленный параметр (а всего до девяти целочисленных или строковых параметров). Первый параметр определяет выполняемую операцию ввода/вывода.
Метод fileno возвращает обычный файловый дескриптор, ассоциированный с потоком ввода/вывода. Это наименее системно зависимый из всех перечислениях выше методов.
desc = $stderr.fileno # 2
10.1.15. Неблокирующий ввод/вывод
«За кулисами» Ruby предпринимает согласованные меры, чтобы операции ввода/вывода не блокировали выполнение программы. В большинстве случаев для управления вводом/выводом можно пользоваться потоками — один поток может выполнить блокирующую операцию, а второй будет продолжать работу.
Это немного противоречит интуиции. Потоки Ruby работают в том же процессе, они не являются платформенными потоками. Быть может, вам кажется, что блокирующая операция ввода/вывода должна приостанавливать весь процесс, а значит, и все его потоки. Это не так — Ruby аккуратно управляет вводом/выводом прозрачно для программиста.
Но если вы все же хотите включить неблокирующий режим ввода/вывода, такая возможность есть. Небольшая библиотека io/nonblock предоставляет методы чтения и установки для объекта IO, представляющего блочное устройство:
require 'io/nonblock'
# ...
test = mysock.nonblock? # false
mysock.nonblock = true # Отключить блокирующий режим.
# ...
mysock.nonblock = false # Снова включить его.
mysock.nonblock { some_operation(mysock) }
# Выполнить some_operation в неблокирующем режиме.
mysock.nonblock(false) { other_operation(mysock) }
# Выполнить other_operation в блокирующем режиме.
10.1.16. Применение метода readpartial
Метод readpartial появился сравнительно недавно с целью упростить ввод/вывод при определенных условиях. Он может использоваться с любыми потоками, например с сокетами.
Параметр «максимальная длина» (max length) обязателен. Если задан параметр buffer, то он должен ссылаться на строку, в которой будут храниться данные.
data = sock.readpartial(128) # Читать не более 128 байтов.
Метод readpartial игнорирует установленный режим блокировки ввода/вывода. Он может блокировать программу, но лишь при выполнении следующих условий: буфер объекта IO пуст, в потоке ничего нет и поток еще не достиг конца файла.
Таким образом, если в потоке есть данные, то readpartial не будет блокировать программу. Он читает не более указанного числа байтов, а если байтов оказалось меньше, то прочитает их и продолжит выполнение.
Если в потоке нет данных, но при этом достигнут конец файла, то readpartial немедленно возбуждает исключение EOFError.
Если вызов блокирующий, то он ожидает, пока не произойдет одно из двух событий: придут новые данные или обнаружится конец файла. Если поступают данные, метод возвращает их вызывающей программе, а в случае обнаружения конца файла возбуждает исключение EOFError.
При вызове метода sysread в блокирующем режиме он ведет себя похоже на readpartial. Если буфер пуст, их поведение вообще идентично.
10.1.17. Манипулирование путевыми именами
Основными методами для работы с путевыми именами являются методы класса File.dirname и File.basename; они работают, как одноименные команды UNIX, то есть возвращают имя каталога и имя файла соответственно. Если вторым параметром методу basename передана строка с расширением имени файла, то это расширение исключается.
str = "/home/dave/podbay.rb"
dir = File.dirname(str) # "/home/dave"
file1 = File.basename(str) # "podbay.rb"
file2 = File.basename(str,".rb") # "podbay"
Хотя это методы класса File, на самом деле они просто манипулируют строками.
Упомянем также метод File.split, который возвращает обе компоненты (имя каталога и имя файла) в массиве из двух элементов:
info = File.split(str) # ["/home/dave","podbay.rb"]
Метод класса expand_path преобразует путевое имя в абсолютный путь. Если операционная система понимает сокращения ~ и ~user, то они тоже учитываются.
Dir.chdir("/home/poole/personal/docs")
abs = File.expand_path("../../misc") # "/home/poole/misc"
Если передать методу path открытый файл, то он вернет путевое имя, по которому файл был открыт.
file = File.new("../../foobar")
name = file.path # "../../foobar"
Константа File::Separator равна символу, применяемому для разделения компонентов путевого имени (в Windows это обратная косая черта, а в UNIX — прямая косая черта). Имеется также синоним File::SEPARATOR.
Метод класса join использует этот разделитель для составления полного путевого имени из переданного списка компонентов:
path = File.join("usr","local","bin","someprog")
# path равно "usr/local/bin/someprog".
# Обратите внимание, что в начало имени разделитель не добавляется!
Не думайте, что методы File.join и File.split взаимно обратны, — это не так.