|
||||
|
Глава 14. Сценарии и системное администрирование
Программистам часто приходится писать небольшие сценарии для запуска внешних программ и работы с операционной системой на достаточно высоком уровне. Особенно это относится к ОС UNIX, где для повседневной работы составляются многочисленные сценарии на языке интерпретатора команд (shell). Ruby не всегда удобно использовать в качестве такого «склеивающего» языка, поскольку он задуман как более универсальный инструмент. Но в принципе все, что можно сделать на языке bash (и ему подобных), можно реализовать и на Ruby. Нередко для этой цели можно воспользоваться каким-то более традиционным языком. Преимущества Ruby в его универсальности, богатстве функций и объектной ориентированности. Предполагая, что найдутся люди, желающие использовать Ruby для взаимодействия с операционной системой на таком уровне, мы продемонстрируем несколько полезных приемов. Выстроить эту главу было трудно, поскольку есть много способов логически сгруппировать рассматриваемый материал. Если вы не смогли найти нужную тему там, где ожидали, попробуйте просмотреть другие разделы. Кроме того, многие вопросы, которые можно было бы включить сюда, вошли в другие главы. Обратите внимание, в частности, на главу 10, где рассматриваются ввод/вывод и атрибуты файлов; эта информация часто бывает полезна при написании сценариев. 14.1. Запуск внешних программНикакой язык не может использоваться в качестве «клея», если он не позволяет запускать внешние программы. В Ruby для этого есть несколько способов. Не могу не обмолвиться о том, что перед запуском внешней программы неплохо бы понять, что она делает. Я имею в виду вирусы и другие потенциально разрушительные программы. Не запускайте произвольную командную строку, особенно поступившую из внешнего источника. Это касается не только приложений, ориентированных на Web. 14.1.1. Методы system и execМетод system(из модуля Kernel) эквивалентен одноименной функции из библиотеки языка С. Он выполняет указанную команду в отдельной оболочке. system("/usr/games/fortune") # Вывод направляется, как обычно, на stdout... Второй параметр, если он задан, должен содержать список аргументов; как правило, аргументы можно задавать и в командной строке — эффект будет тот же. Разница лишь в том, что алгоритм расширения имени файла применяется только к первой из переданных строк. system("rm", "/tmp/file1") system("rm /tmp/file2") # Оба варианта годятся. # А тут есть различие... system("echo *") # Печатается список всех файлов. system("echo","*") # Печатается звездочка (расширение # имени файла не производится). # Более сложные командные строки тоже работают. system("ls -l | head -n |") Посмотрим, как это будет работать в семействе операционных систем Windows. В случае с простой исполняемой программой поведение должно быть таким же, как в UNIX. В зависимости от варианта Ruby для вызова встроенных в оболочку команд может потребоваться запуск cmd.ехе— интерпретатора команд в Windows (в некоторых версиях ОС он называется command.com). Ниже приведены примеры запуска внешней и встроенной команды: system("notepad.ехе","myfile.txt") # Никаких проблем... system("cmd /с dir","somefile") # 'dir' - встроенная команда! Другое решение — воспользоваться библиотекой Win32APIи определить собственный вариант метода system. require "Win32API" def system(cmd) sys = Win32API.new("crtdll", "system", ['P'], 'L') sys.Call(cmd) end system("dir") # cmd /с необязательно! Таким образом, можно добиться более-менее системно-независимого поведения system. Но если вы хотите запомнить выведенную программой информацию (например, в переменной), то system— не лучший способ (см. следующий раздел). Упомяну еще метод exec. Он ведет себя аналогично systemс тем отличием, что новый процесс замещает текущий. Поэтому код, следующий за exec, исполняться не будет. puts "Содержимое каталога:" exec("ls", "-l") puts "Эта строка никогда не исполняется!" 14.1.2. Перехват вывода программыПростейший способ перехватить информацию, выведенную программой, — заключить команду в обратные кавычки, например: listing = `ls -l` # Одна строка будет содержать несколько строчек (lines). now = `date` # "Mon Mar 12 16:50:11 CST 2001" Обобщенный ограничитель %xвызывает оператор обратных кавычек (который в действительности является методом модуля Kernel). Работает он точно так же: listing = %x(ls -l) now = %x(date) Применение %xбывает полезно, когда подлежащая исполнению строка содержит такие символы, как одиночные и двойные кавычки. Поскольку обратные кавычки — это на самом деле метод (в некотором смысле), то его можно переопределить. Изменим его так, чтобы он возвращал не одну строку, а массив строк. Конечно, при этом мы создадим синоним старого метода, чтобы его можно было вызвать. alias old_execute ` def `(cmd) out = old_execute(cmd) # Вызвать исходный метод обратной кавычки. out.split("\n") # Вернуть массив строк! end entries = `ls -l /tmp` num = entries.size # 95 first3lines = %x(ls -l | head -n 3) how_many = first3lines.size # 3 Как видите, при таком определении изменяется также поведение ограничителя %x. В следующем примере мы добавили в конец команды конструкцию интерпретатора команд, которая перенаправляет стандартный вывод для ошибок в стандартный вывод: alias old_execute ` def `(cmd) old_execute(cmd + " 2>&1") end entries = `ls -l /tmp/foobar` # "/tmp/foobar: No such file or directory\n" Есть, конечно, и много других способов изменить стандартное поведение обратных кавычек. 14.1.3. Манипулирование процессамиВ этом разделе мы обсудим манипулирование процессами, хотя создание нового процесса необязательно связано с запуском внешней программы. Основной способ создания нового процесса — это метод fork, название которого в соответствии с традицией UNIX подразумевает разветвление пути исполнения, напоминая развилку на дороге. (Отметим, что в базовом дистрибутиве Ruby метод forkна платформе Windows не поддерживается.) Метод fork, находящийся в модуле Kernel(а также в модуле Process), не следует путать с одноименным методом экземпляра в классе Thread. Существуют два способа вызвать метод fork. Первый похож на то, как это обычно делается в UNIX, — вызвать и проверить возвращенное значение. Если оно равно nil, мы находимся в дочернем процессе, в противном случае — в родительском. Родительскому процессу возвращается идентификатор дочернего процесса (pid). pid = fork if (pid == nil) puts "Ага, я, должно быть, потомок." puts "Так и буду себя вести." else puts "Я родитель." puts "Пора отказаться от детских штучек." end В этом не слишком реалистичном примере выводимые строки могут чередоваться, а может случиться и так, что строки, выведенные родителем, появятся раньше. Но сейчас это несущественно. Следует также отметить, что процесс-потомок может пережить своего родителя. Для потоков в Ruby это не так, но системные процессы — совсем другое дело. Во втором варианте вызова метод forkпринимает блок. Заключенный в блок код выполняется в контексте дочернего процесса. Так, предыдущий вариант можно было бы переписать следующим образом: fork do puts "Ага, я, должно быть, потомок." puts "Так и буду себя вести." end puts "Я родитель." puts "Пора отказаться от детских штучек." Конечно, pid по-прежнему возвращается, мы просто не показали его. Чтобы дождаться завершения процесса, мы можем вызвать метод waitиз модуля Process. Он ждет завершения любого потомка и возвращает его идентификатор. Метод wait2ведет себя аналогично, только возвращает массив, содержащий РМ, и сдвинутый влево код завершения. Pid1 = fork { sleep 5; exit 3 } Pid2 = fork { sleep 2; exit 3 } Process.wait # Возвращает pid2 Process.wait2 # Возвращает [pid1,768] Чтобы дождаться завершения конкретного потомка, применяются методы waitpidи waitpid2. pid3 = fork { sleep 5; exit 3 } pid4 = fork { sleep 2; exit 3 } Process.waitpid(pid4,Process::WNOHANG) # Возвращает pid4 Process.waitpid2(pid3, Process::WNOHANG) # Возвращает [pid3,768] Если второй параметр не задан, то вызов может блокировать программу (если такого потомка не существует). Второй параметр можно с помощью ИЛИ объединить с флагом Process::WUNTRACED, чтобы перехватывать остановленные процессы. Этот параметр системно зависим, поэкспериментируйте. Метод exit!немедленно завершает процесс (не вызывая зарегистрированных обработчиков). Если задан целочисленный аргумент, то он возвращается в качестве кода завершения; по умолчанию подразумевается значение 1 (не 0). pid1 = fork { exit! } # Вернуть код завершения -1. pid2 = fork { exit! 0 } # Вернуть код завершения 0. Методы pidи ppidвозвращают соответственно идентификатор текущего и родительского процессов. proc1 = Process.pid fork do if Process.ppid == proc1 puts "proc1 - мой родитель" # Печатается это сообщение. else puts "Что происходит?" end end Метод killслужит для отправки процессу сигнала, как это понимается в UNIX. Первый параметр может быть целым числом, именем POSIX-сигнала с префиксом SIG или именем сигнала без префикса. Второй параметр — идентификатор процесса-получателя; если он равен нулю, подразумевается текущий процесс. Process.kill(1,pid1) # Послать сигнал 1 процессу pid1. Process.kill ("HUP",pid2) # Послать SIGHUP процессу pid2.. Process.kill("SIGHUP",pid2) # Послать SIGHUP процессу pid3. Process.kill("SIGHUP",0) # Послать SIGHUP самому себе. Для обработки сигналов применяется метод Kernel.trap. Обычно он принимает номер или имя сигнала и подлежащий выполнению блок. trap(1) { puts "Перехвачен сигнал 1" } sleep 2 Process.kill(1,0) # Послать самому себе. О применениях метода trapв более сложных ситуациях читайте в документации по Ruby и UNIX. В модуле Processесть также методы для опроса и установки таких атрибутов процесса, как идентификатор пользователя, действующий идентификатор пользователя, приоритет и т.д. Дополнительную информацию вы отыщете в справочном руководстве по Ruby. 14.1.4. Стандартный ввод и выводВ главе 10 мы видели, как работают методы IO.popenи IO.pipe, но существует еще небольшая библиотека, которая иногда бывает удобна. В библиотеке Open3.rbесть метод popen3, который возвращает массив из трех объектов IO. Они соответствуют стандартному вводу, стандартному выводу и стандартному выводу для ошибок того процесса, который был запущен методом popen3. Вот пример: require "open3" filenames = %w[ file1 file2 this that another one_more ] inp, out, err = Open3.popen3("xargs", "ls", "-l") filenames.each { |f| inp.puts f } # Писать в stdin процесса. inp.close # Закрывать обязательно! output = out.readlines # Читать из stdout. errout = err.readlines # Читать также из stderr. puts "Послано #{filenames.size} строк входных данных." puts "Получено #{output.size} строк из stdout" puts "и #{errout.size} строк из stderr." В этом искусственном примере мы выполняем команду ls -lдля каждого из заданных имен файлов и по отдельности перехватываем стандартный вывод и стандартный вывод для ошибок. Отметим, что вызов closeнеобходим, чтобы порожденный процесс увидел конец файла. Также отметим, что в библиотеке Open3 используется метод fork, не реализованный на платформе Windows; для этой платформы придется пользоваться библиотекой win32-open3(ее написали и поддерживают Дэниэль Бергер (Daniel Berger) и Парк Хисоб (Park Heesob)). См. также раздел 14.3. 14.2. Флаги и аргументы в командной строкеСлухи о кончине командной строки сильно преувеличены. Хоть мы и живем в век графических интерфейсов, ежедневно тысячи и тысячи программистов по тем или иным причинам обращаются к командным утилитам. Мы уже говорили, что корнями своими Ruby уходит в UNIX. Но даже в Windows существует понятие командной строки, и, честно говоря, мы не думаем, что в обозримом будущем она исчезнет. На этом уровне для управления работой программы применяются аргументы и флаги. О них мы и поговорим ниже. 14.2.1. Разбор флагов в командной строкеДля разбора командной строки чаще всего применяется библиотека getoptlong(библиотека getopts.rb, обладающая менее развитой функциональностью, считается устаревшей). Она понимает однобуквенные и длинные флаги и распознает двойной дефис ( --) как признак конца флагов. В целом библиотека ведет себя так же, как соответствующие функции GNU. Необходимо создать объект класса GetoptLong, который и будет выполнять функции анализатора. Затем ему передаются допустимые значения флагов, а он извлекает их по одному. У объекта-анализатора есть метод set_options, который принимает список массивов. Каждый массив содержит один или несколько флагов (в виде строк) и один «признак наличия аргумента», который говорит, должны ли эти флаги сопровождаться аргументами. Все флаги в одном массиве считаются синонимами; первый из них является «каноническим именем», которое и возвращает операция get. Предположим, что имеется программа, понимающая следующие флаги: -hили --help(печать справки), -fили --file(указание имени файла), -lили --lines(вывод не более указанного числа строк, по умолчанию 100). Такая программа могла бы начинаться следующим образом: require "getoptlong" parser = GetoptLong.new parser.set_options( ["-h", "--help", GetoptLong::NO_ARGUMENT], ["-f", "--file", GetoptLong::REQUIRED_ARGUMENT], ["-l", "--lines", GetoptLong::OPTIONAL_ARGUMENT]) Теперь можно в цикле вызвать метод get(см. листинг 14.1). Наличие операторных скобок begin-endимитирует цикл с проверкой условия в конце. У метода getесть синоним get_option, существуют также итераторы eachи each_option, которые в точности идентичны. Листинг 14.1. Получение флагов из командной строки filename = nil lines = 0 # По умолчанию вывод не усекается. loop do begin opt, arg = parser.get break if not opt # Только для отладки... puts (opt +" => " + arg) case opt when "-h" puts "Usage: ..." break # Прекратить обработку, если задан флаг -h. when "-f" filename = arg # Запомнить аргумент - имя файла. when "-l" if arg != "" lines = arg # Запомнить аргумент - число строк (если задан). else lines = 100 # Оставляемое по умолчанию число строк. end end rescue => err puts err break end end puts "имя файла = #{filename}" puts "число строк = #{lines}" Метод getвозвращает nil, если флаг отсутствует, но пустую строку, если для флага не задан аргумент. Возможно, это ошибка. В этом примере мы перехватываем исключения. Всего их может быть четыре: • AmbiguousOption— указано сокращенное длинное имя флага, но сокращение не уникально; • InvalidOption— неизвестный флаг; • MissingArgument— для флага не задан аргумент; • NeedlessArgument— указан аргумент для флага, который не должен сопровождаться аргументом. Сообщения об ошибках обычно выводятся на stderr, но вывод можно подавить, присвоив акцессору quiet=значение true. Библиотека getoptlongрасполагает и другими возможностями, которых мы здесь не обсуждали. Подробности вы найдете в документации. Существуют другие библиотеки, например OptionParser, предлагающие несколько иную функциональность. Дополнительная информация приведена в архиве приложений Ruby. 14.2.2. Константа ARGFГлобальная константа ARGFпредставляет псевдофайл, получающийся в результате конкатенации всех имен файлов, заданных в командной строке. Во многих отношениях она ведет себя так же, как объект IO. Когда в программе встречается «голый» метод ввода (без указания вызывающего объекта), обычно имеется в виду метод, подмешанный из модуля Kernel(например, getsи readlines). Если в командной строке не задано ни одного файла, то по умолчанию источником ввода является объект stdin. Но если файлы заданы, то данные читаются из них. Понятно, что конец файла достигается в конце последнего из указанных файлов. Если хотите, можете обращаться к ARGFявно: # Скопировать все файлы на stdout. puts ARGF.readlines Быть может, вопреки ожиданиям, признак конца файла устанавливается после каждого файла. Так, предыдущий код выведет все файлы, а следующий — только первый файл: until ARGF.eof? puts ARGF.gets end Является ли это ошибкой, предоставим судить вам. Впрочем, сюрпризы могут быть и приятными. Входные данные — не просто поток байтов; мы можем применять к ARGFоперации seekи rewind, как если бы это был «настоящий файл». С константой ARGFассоциирован метод file— он возвращает объект IO, соответствующий файлу, обрабатываемому в данный момент. Естественно, возвращаемое значение изменяется по мере перехода от одного файла к другому. А если мы не хотим интерпретировать имена аргументов в командной строке как имена файлов? Тогда не надо обращаться к методам ввода без указания вызывающего объекта. Если вы хотите читать из стандартного ввода, укажите в качестве такого объекта STDIN, и все будет работать правильно. 14.2.3. Константа ARGVГлобальная константа ARGVпредставляет список аргументов, переданных в командной строке. По сути дела, это массив. n = ARGV.size argstr = '"' + ARGV*"," + '"' puts "Мне было передано аргументов: #{n}..." puts "Вот они: #{argstr}" puts "Заметьте, что ARGV[0] = #{ARGV[0]}" Если запустить эту программу с аргументами red green blue, то она напечатает: Мне было передано аргументов: 3... Вот они: "red,green,blue" Заметьте, что ARGV[0] = red Ясно, что отдельно передавать число аргументов, как в былые времена, не нужно; эта информация — часть массива. Привычных к старым соглашениям программистов может смутить также тот факт, что нулевой элемент массива — настоящий аргумент (а не, скажем, имя сценария). Нумерация аргументов начинается с нуля, а не с единицы, как в языке С и в различных интерпретаторах команд. 14.3. Библиотека ShellНе всегда Ruby удобен в качестве языка сценариев. Например, в языке bash для запуска внешней программы достаточно просто указать ее имя безо всякого дополнительного синтаксиса. Оборотной стороной мощи и гибкости Ruby является более сложный синтаксис. Кроме того, функциональность разнесена по различным классам, модулям и библиотекам. Это послужило основанием для создания библиотеки Shell, которая упрощает, к примеру, организацию конвейеров команд и перенаправление вывода в файл. Кроме того, она сводит воедино функциональность из разных источников, скрывая ее за интерфейсом объекта Shell. (На платформе Windows эта библиотека работает не во всех случаях.) 14.3.1. Использование библиотеки Shell для перенаправления ввода/выводаВ классе Shellдля создания объектов есть два метода: newи cd. Первый создает объект, ассоциированный с текущим каталогом, второй — объект, для которого рабочим будет указанный каталог. require "shell" sh1 = Shell.new # Работать в текущем каталоге. sh2 = Shell.cd("/tmp/hal") # Работать в каталоге /tmp/hal. Библиотека Shellопределяет несколько встроенных команд (например, echo, catи tee) в виде методов. Они всегда возвращают объекты класса Filter(как и определяемые пользователем команды, с которыми мы вскоре познакомимся). Класс Filterпонимает, что такое перенаправление ввода/вывода. В нем определены методы (или операторы) <, >и |, которые ведут себя примерно так, как мы ожидаем по многолетнему опыту написания shell-сценариев. Если методу перенаправления передать в качестве параметра строку, то она будет считаться именем файла. Если же параметром является объект IO, он используется для операций ввода/вывода. Примеры: sh = Shell.new # Вывести файл motd на stdout. sh.cat("/etc/motd") > STDOUT # Напечатать его еще раз. (sh.cat < "/etc/motd") > STDOUT (sh.echo "Это тест") > "myfile.txt" # Добавить строку в конец файла /etc/motd. sh.echo("Hello, world!") >> "/etc/motd" # Вывести два файла на stdout и продублировать (tee) вывод в третий файл. (sh.cat "file1" "file2") | (tee "file3") > STDOUT Отметим, что у оператора >высокий приоритет. Скобки, которые вы видите в данном примере, в большинстве случаев обязательны. Вот два примера правильного использования и один — неправильного: # Интерпретатор Ruby понимает такую конструкцию... sh.cat("myfile.txt") > STDOUT # ...и такую тоже. (sh.cat "myfile.txt") > STDOUT # TypeError! (ошибка связана с приоритетами). sh.cat "myfile.txt" > STDOUT Отметим еще, что можно «инсталлировать» системные команды по своему выбору. Для этого служит метод def_system_command. Ниже определяются два метода: lsи ll, которые выводят список файлов в текущем каталоге (в коротком и длинном формате). # Имя метода совпадает с именем команды... # Необходим только один параметр: Shell.def_system_command "ls" # А здесь должно быть два параметра: Shell.def_system_command "ll", "ls -l" sh = Shell.new sh.ls > STDOUT # Короткий формат. sh.ll > STDOUT # Длинный формат. Вы, наверное, обратили внимание на то, что в большинстве случаев мы явно отправляем вывод объекту STDOUT. Связано это с тем, что объект Shellавтоматически вывод команд никуда не направляет. Он просто ассоциирует его с объектом Filter, который уже может быть связан с файлом или с объектом IO. 14.3.2. Дополнительные замечания по поводу библиотеки shell.rbМетод transactисполняет блок в контексте вызывающего объекта. Таким образом, допустима следующая сокращенная запись: sh = Shell.new sh.transact do echo("Строка данных") > "somefile.txt" cat("somefile.txt","otherfile.txt") > "thirdfile" cat("thirdfile") | tee("file4") > STDOUT end Итератор foreachпринимает в качестве параметра файл или каталог. Если это файл, он перебирает все его строки, а если каталог — все имена файлов в нем. sh = Shell.new # Напечатать все строки файла /tmp/foo. sh.foreach("/tmp/foo") {|l| puts l } # Вывести список файлов в каталоге /tmp. sh.foreach("/tmp") {|f| puts f } Метод pushdirзапоминает текущий каталог, а метод popdirделает последний запомненный каталог текущим. У них есть синонимы pushdи popd. Метод pwdвозвращает текущий рабочий каталог, его синонимы — getwd, cwdи dir. sh = Shell.cd "/home" puts sh.pwd # /home sh.pushd "/tmp" puts sh.pwd # /tmp sh.popd puts sh.pwd # /home Для удобства в класс Shellимпортируются методы из различных источников, в том числе из класса File, модуля FileTestи библиотеки ftools.rb. Это избавляет от необходимости выполнять require, include, создавать объекты, квалифицировать вызовы методов и т. д. sh = Shell.new flag1 = sh.exist? "myfile" # Проверить существование файла. sh.delete "somefile" # Удалить файл. sh.move "/tmp/foo", "/tmp/bar" # Переместить файл. У библиотеки Shellесть и другие возможности, которые мы здесь не рассматриваем. Дополнительную информацию ищите в документации. 14.4. Переменные окруженияИногда необходимо обращаться к переменным окружения, которые являются связующим звеном между программой и внешним миром. Переменные окружения — это просто метки, связанные с некоторым текстом (обычно небольшим); в них хранятся, например, пути к файлам, имена пользователей и т.п. Переменные окружения широко применяются в ОС UNIX. Система Windows (а еще раньше MS-DOS) позаимствовала эту идею у UNIX, поэтому приведенные ниже коды будут работать на обеих платформах. 14.4.1. Чтение и установка переменных окруженияГлобальная константа ENV— это хэш, с помощью которого можно читать и изменять переменные окружения. В примере ниже мы читаем значение переменной PATH, (в Windows вместо двоеточия нужно употреблять точку с запятой): bypath = ENV["PATH"] # А теперь получим массив... dirs = mypath.split(":") А вот пример установки переменной. Новый процесс мы создали, чтобы проиллюстрировать две вещи. Во-первых, дочерний процесс наследует переменные окружения от своего родителя. Во-вторых, значение переменной окружения, установленное в дочернем процессе, родителю не видно. ENV["alpha"] = "123" ENV["beta"] = "456" puts "Родитель: alpha = #{env['alpha']}" puts "Родитель: beta = #(env['beta']}" fork do # Код потомка... x = ENV["alpha"] ENV["beta"] = "789" y = ENV["beta"] puts " Потомок: alpha = #{x}" puts " Потомок: beta = #{y}" end Process.wait a = ENV["alpha"] b = ENV["beta"] puts "Родитель: alpha = #{a}" puts "Родитель: beta = #{b}" Программа выводит следующие строки: Родитель: alpha = 123 Родитель: beta = 456 Потомок: alpha = 123 Потомок: beta = 789 Родитель: alpha = 123 Родитель: beta = 456 Это следствие того факта, что родитель ничего не знает о переменных окружения своих потомков. Поскольку программа на Ruby обычно исполняется в подоболочке, то после ее завершения все сделанные изменения переменных окружения не будут видны в текущей оболочке. 14.4.2. Хранение переменных окружения в виде массива или хэшаВажно понимать, что объект ENV— не настоящий хэш, а лишь выглядит как таковой. Например, мы не можем вызвать для него метод invert; будет возбуждено исключение NameError, поскольку такого метода не существует. Причина такой реализации в том, что существует тесная связь между объектом ENVи операционной системой; любое изменение хранящихся в нем значений отражается на состоянии ОС, а такое поведение с помощью простого хэша не смоделируешь. Однако имеется метод to_hash, который вернет настоящий хэш, отражающим текущее состояние: envhash = ENV.to_hash val2var = envhash.invert Получив такой хэш, мы можем преобразовать его к любому другому виду (например, в массив): envarr = ENV.to_hash.to_a Обратное присваивание объекту ENVнедопустимо, но при необходимости можно пойти обходным путем: envhash = env.to_hash # Выполняем произвольные операции... и записываем обратно в ENV. envhash.each {|k,v| ENV[k] = v } 14.4.3. Импорт переменных окружения как глобальных переменныхСуществует библиотечка importenv.rb, которая импортирует все переменные окружения, сопоставляя им глобальные переменные программы: require "importenv" # Теперь переменные окружения стали глобальными переменными... # Например, $PWD и $LOGNAME where = $PWD who = $LOGNAME puts "В каталоге #{where}, вошел как #{who}" Поскольку библиотека importenvпользуется библиотекой trace_var, отражение на самом деле двустороннее: если присвоить глобальной переменной новое значение, реальная переменная окружения получит то же значение. require "importenv" puts "Мой путь #$PATH" # Печатается: /usr/local/bin:/usr/bin:/usr/ucb:/etc:. $PATH = "/ruby-1.8.0:" + $PATH puts "Моя переменная $PATH теперь равна #{ENV['PATH']}" # Печатается: /ruby-1.8.0:/usr/local/bin:/usr/bin:/usr/ucb:/etc:. Еще раз подчеркнем, что любые изменения переменных окружения, выполненные внутри программы на Ruby, не отражаются на их значениях, видимых вне этой программы. 14.5. Сценарии на платформе Microsoft Windows
Уже отмечалось, что Ruby больше любит ОС UNIX. В каком-то смысле это правда: язык разрабатывался в среде UNIX, в ней лучше всего и работает. Сейчас он, впрочем, перенесен на другие платформы, в том числе на Macintosh; ведется даже работа по переносу на Palm OS. Но если UNIX — основная платформа, то следующая по значимости — Windows. Пользователи Windows не брошены на произвол судьбы. Существует немало инструментов и библиотек для этой платформы, а разрабатывается еще больше, многие аспекты Ruby, даже механизм потоков, изначально не зависят от платформы. Наибольшие трудности возникают при управлении процессами, выполнении ввода/вывода и других операций низкого уровня. В прошлом существовало несколько вариантов Ruby для Windows. Интерпретатор мог быть собран компилятором gcc или Visual С, его работа могла зависеть от наличия библиотеки Cygwin DLL и т.д. Но в последние годы появился «моментальный» инсталлятор для Windows (см. раздел 14.6). Среда изменяется слишком быстро, чтобы можно было ее сейчас документировать, однако в этом разделе мы все же рассмотрим некоторые вопросы написания сценариев и автоматизации на платформе Windows. Описанные приемы и утилиты должны работать в любой ОС. Если возникнут проблемы, сообщество придет на помощь. 14.5.1. Расширение Win32APIРасширение Win32API— исключительно мощный инструмент, если вы собираетесь программировать на относительно низком уровне. Оно позволяет вызывать из Ruby функции Windows API, находящиеся в любой DLL. Указанная функция становится объектом, а методу new передаются параметры, точно описывающие функцию. Первый параметр — строка, идентифицирующая DLL, в которой находится функция (например, crtdll). Второй параметр — имя самой функции, третий — массив строк, описывающих типы параметров функции (массив импорта), а четвертый — строка, описывающая тип возвращаемого значения (строка экспорта). Массив импорта может содержать следующие значения (регистр не играет роли): I целое L число N число P указатель на строку Строка экспорта также может содержать любое из этих значений, а также значение «V», означающее «void». После того как объект создан, можно обратиться к его методу callдля вызова функции Windows. Синоним — Call. В примере ниже мы вызываем функцию GetCursorPos, которая возвращает указатель на структуру POINT. Эта структура состоит из двух полей типа long. Чтобы получить их значения, мы можем воспользоваться методом unpack: require 'Win32API' result = "0"*8 # Восемь байтов (достаточно для двух long). getCursorXY = Win32API.new("user32","GetCursorPos",["P"],"V") getCursorXY.call(result) x, y = result.unpack("LL") # Два long. В данном случае функция вернула составные двоичные данные, а иногда такие данные нужно подать на вход функции. Понятно, что для этого нужно воспользоваться методом pack, который упакует данные в строку. У описанной техники может быть много применений. Еще два примера приведены в разделах 10.1.20 и 14.1.1. 14.5.2. Расширение Win32OLEРасширение Win32OLE(правильно писать его имя строчными буквами: win32ole) реализует интерфейс к OLE-автоматизации в Windows. Программа на Ruby может выступать в роли клиента любого сервера автоматизации, к числу которых относятся, например, Microsoft Word, Outlook, Internet Explorer, а также многие продукты третьих фирм. Для того чтобы начать взаимодействие с внешним приложением, мы создаем объект класса WIN32OLE. С его помощью мы получим доступ ко всем свойствам и методам, которые раскрывает данное приложение. В примере ниже объект ассоциируется с редактором Microsoft Word. Атрибуту visibleмы присвоим значение true, а в конце вызовем метод quit, чтобы завершить внешнюю программу. require "win32ole" word = WIN32OLE.new "Word.Application" word.visible = true # ... word.quit Свойства сервера автоматизации выглядят как атрибуты объекта. Их можно читать и устанавливать. Имеется и альтернативная нотация, в которой для доступа к свойствам используется конструкция, напоминающая хэш. player["FileName"] = "file.wav" name = player["FileName"] # Эквивалентно следующим предложениям: # player.FileName = "file.wav" # name = player.FileName У этой нотации есть то преимущество, что она позволяет проще осуществлять динамический доступ к свойствам, как показано в искусственном примере ниже: puts "Введите имя свойства" prop = gets puts "Введите новое значение" val = gets old = obj[prop] obj[prop] = val puts "#{prop} было #{old}... стало #{obj[prop]}" Но обратимся к более жизненным примерам. Следующий код получает от пользователя имя файла, передает его Microsoft Word и распечатывает файл: require "win32ole" print "Введите имя файла для распечатки: " docfile = gets word = WIN32OLE.new "Word.Application" word.visible = true word.documents.open docfile word.options.printBackground = false # Можно было бы также установить свойство printBackground в true, # но тогда пришлось бы дожидаться, пока весь файл будет # скопирован в буфер принтера, и только потом вызывать quit... word.activeDocument.printout word.quit В следующем примере проигрывается WAV-файл. Недостаток заключается в том, что в конце программы мы поставили sleepна случайно выбранное время, а не просто дожидаемся, когда воспроизведение закончится. Предлагаем читателю устранить этот недочет в качестве упражнения. require "win32ole" sound = WIN32OLE.new("MCI.MMcontrol") wav = "с:\\windows\\media\\tada.wav" sound.fileName = wav sound.autoEnable = true sound.command = "Open" sound.command = "Play" sleep 7 В листинге 14.2 мы просим Internet Explorer открыть диалог для ввода текста. Листинг 14.2. Открытие диалога для ввода текста в браузереrequire "win32ole" def ieInputBox( msg, default ) ie = WIN32OLE.new("InternetExplorer.Application"); ie.visible = false ie.navigate "about:blank" sleep 0.01 while (ie.busy) script = ie.Document.Script; result = script.prompt(msg,default); ie.quit result end # Главная программа... result = ieInputBox( "Введите свое имя", "Дэйв Боумэн") if result puts result else puts "Пользователь нажал Cancel" end В листинге 14.3 мы открываем IE в небольшом окне и выводим в него HTML-документ. Листинг 14.3. Для вывода в окно браузера требуется win32olehtml = <<EOF <html> <body> <h3>A теперь что-нибудь</h3> <h2>совсем</h2> <h1>другое...</h1> </body> </html> EOF ie = WIN32OLE.new("InternetExplorer.Application"); ie.left = 150 ie.top = 150 ie.height = 200 ie.width = 300 ie.menubar = 0 ie.toolbar = 0 ie.navigate "about:blank" ie.visible=TRUE; ie.document.open ie.document.write html ie.document.close sleep 5 ie.quit В следующем примере открывается диалоговое окно, где пользователь может выбрать файл из списка: require "win32ole" cd = WIN32OLE.new("MSComDlg.CommonDialog") # Задать фильтр файлов cd.filter = "All Files(*.*)| *.*" + "| Ruby Files(*.rb)|*.rb" cd.filterIndex = 2 cd.maxFileSize = 128 # Установить MaxFileSize. cd.showOpen() file = cd.fileName # Получить путь к файлу. if not file or file=="" puts "Файл не выбран." else puts "Пользователь выбрал: #{file}\n" end И, наконец, определим IP-адрес своего компьютера: require "win32ole" ws = WIN32OLE.new "MSWinsock.Winsock" # Получить свойство LocalIP ipAddress = ws.localIP puts "Локальный IP-адрес равен : #{ipAddress}" Как видите, возможности не ограничены. Развлекайтесь и не забывайте делиться своими программами с другими! 14.5.3. Использование ActiveScriptRubyНаверняка вам приходилось открывать в браузере Internet Explorer страницы, содержащие код на языке JavaScript или VBScript. (Мы не будем здесь касаться различий между JScript и JavaScript.) Но сценарий можно написать и на языке ActiveScriptRuby, представляющем собой мост между COM и Ruby. Вот как можно включить код на Ruby в HTML-страницу (листинг 14.4). Листинг 14.4. Код на Ruby, встроенный в HTML-страницу<html> <script language="RubyScript"> # Это код на Ruby... def helloMethod @window.alert "Работает Ruby!" end </script> <body> Это кнопка... <input id=Hello type=button onclick="helloMethod" language="RubyScript"> </body> </html> С помощью той же техники можно вызывать написанный на Ruby код из любого Windows-приложения, поддерживающего интерфейс IActiveScript, например из Explorer или WScript (исполняемый файл называется WSH). Дополнительную информацию вы можете найти на странице arton (http://vvww.geocities.co.jp/SiliconValley-PaolAlto/9251/rubymain.html). 14.6. Моментальный инсталлятор для WindowsС точки зрения пользователей Microsoft Windows одним из самых значительных шагов в развитии Ruby за последние годы стал так называемый «моментальный инсталлятор» (one-click installer). Главным разработчиком этого проекта (официально он называется Ruby Installer) является Курт Гиббс (Curt Hibbs). Процедура инсталляции выполнена в «родном» для Windows стиле. Инсталлятор особенно ценен тем, что работает в полном соответствии с ожиданиями пользователей Windows. Он имеет графический интерфейс и выполняет шаги установки в строго определенном порядке. Разумеется, инсталлируется двоичная версия, так что компилятор не нужен. Но это не единственные его привлекательные черты. Устанавливаемый дистрибутив весьма полон («батарейки в комплекте»). Он включает не только интерпретатор Ruby со всеми системными классами и стандартными библиотеками, но и целый ряд дополнительных библиотек и приложений. Многие из них предназначены исключительно для платформы Win32. Устанавливаются следующие компоненты (некоторые из них необязательны): • сам интерпретатор Ruby (пакет ruby-mswin32и пакет RubySrcдля тех, кто хочет познакомиться с исходными текстами на языке С); • два часто используемых приложения: RubyGemsи rake; • бесплатная копия книги Дейва Томаса (Dave Thomas) и Энди Ханта (Andy Hunt) «Programming Ruby» — первое издание в формате Windows Help; • библиотека fxruby(обычно несколько версий), реализующая привязки к комплекту инструментов для создания графических интерфейсов FOX; • инструменты для разработки приложений трехмерной графики OpenGLи GLUT; • утилиты fxirbи fxri— графические версии программ irbи ri, написанные с применением библиотеки FXRuby; • FreeRIDE — интегрированная среда разработки для Ruby с встроенным редактором, обозревателем исходных текстов и отладчиком (работа над совершенствованием этой программы ведется постоянно); • SciTE — текстовый редактор на базе Scintilla; • SWin и VRuby — инструменты для обработки сообщений Windows и разработки графических интерфейсов (обе являются частью проекта VisualuRuby, во главе которого стоит Ясухира Насикава); • два анализатора XML (XMLParser и Expat), а также HTMLParser; • библиотеки для работы с базами данных RubyDBI и DBD/ODBC; • прочие библиотеки и инструменты, в том числе log4r, zlib, OpenSSL, Iconv, readlineи другие. Планируются, но еще не готовы варианты этого инсталлятора и для других платформ. 14.7. Библиотеки, о которых полезно знатьЕсли вы программируете на Ruby в Windows, вам абсолютно необходим пакет, созданный Дэниэлем Бергером (Daniel Berger), одним из самых известных специалистов по Ruby на этой платформе. Библиотека win32-utils— в действительности целый набор мелких библиотек. Мы не можем рассмотреть их все подробно, но хотя бы перечислим. • win32-changenotify— для мониторинга событий файловой системы; • win32-clipboard— для взаимодействия с буфером обмена Windows; • win32-etc— предоставляет аналоги таких UNIX-функций, как getpwnamи getpwuid; • win32-event— интерфейс с событиями Windows (объектами Event); • win32-eventlog— интерфейс с журналом событий; • win32-ipc— базовый класс для всех объектов синхронизации в Windows (используется в библиотеке win32-eventи др.); • win32-mmap— интерфейс к файлам, проецируемым на память, в Windows; • win32-open3— библиотека open3для Windows (запустить команды и получить три описателя файлов); • win32-pipe— именованные каналы в Windows; • win32-process— реализация для Windows методов fork, waitи kill, имеющихся в UNIX; • win32-sapi— интерфейс к Microsoft Speech API; • win32-service— интерфейс к службам Windows; • win32-shortcut— интерфейс для создания и модификации ярлыков в Windows; • win32-sound— интерфейс для воспроизведения звуковых файлов в Windows; Вот еще несколько библиотек, которые полезно иметь под рукой: • Win32::Console— это перенос пакетов Win32::Console и Win32::Console::ANSI, первоначально написанных на языке Perl. Эта библиотека значительно упрощает работу с консолью в Windows (изменение цветов, позиционирование курсора, запрос информации и эмуляцию управляющих символов ANSI); • ActiveDirectoryпозволяет легко взаимодействовать с экземплярами Active Directory, работающими на серверах под управлением Microsoft Windows; • ruby-inifileпозволяет работать с ini-файлами (читать, разбирать и обновлять их). В сети есть еще много библиотек, которые могут вам пригодиться. Ищите их на сайтах http://raa-ruby-lang.org и http://rubyforge.org. 14.8. Работа с файлами, каталогами и деревьямиПри выполнении рутинных задач приходится много работать с файлами и каталогами, в том числе с целыми иерархиями каталогов. Немало материала на эту тему вошло в главу 4, но кое-какие важные моменты мы хотим осветить здесь. Поскольку ввод/вывод — вещь системно-зависимая, то для различных систем приходится применять разные приемы. Если сомневаетесь, экспериментируйте!.. 14.8.1. Несколько слов о текстовых фильтрахМногие инструменты, которыми мы постоянно пользуемся (как поставляемые производителем, так и разрабатываемые собственными силами), — просто текстовые фильтры. Иными словами, они принимают на входе текст, каким-то образом преобразуют его и выводят. Классическими примерами текстовых фильтров в UNIX служат, в частности, программы sedи tr. Иногда файл настолько мал, что целиком помещается в памяти. В этом случае возможны такие виды обработки, которые по-другому было бы сложно реализовать. file = File.open(filename) lines = file.readlines # Какие-то операции... lines.each { |x| puts x } Бывает, что нужно обрабатывать файл построчно. IO.foreach(filename) do |line| # Какие-то операции... puts line end Наконец, не забывайте, что все имена файлов, указанные в командной строке, автоматически собираются в объект ARGF, представляющий конкатенацию всех выходных данных (см. раздел 14.2.2). Мы можем вызывать, к примеру, метод ARGF.readlines, как если бы ARGFбыл объектом класса IO. Вся выходная информация будет, как обычно, направлена на стандартный вывод. 14.8.2. Копирование дерева каталогов (с символическими ссылками)Пусть нужно скопировать целое дерево каталогов в новое место. Сделать это можно по-разному, но если в дереве есть символические ссылки, задача усложняется. В листинге 14.5 приведено рекурсивное решение. Оно достаточно дружелюбно — контролирует входные данные и выводит информацию о порядке запуска. Листинг 14.5. Копирование дерева каталоговrequire "fileutils" def recurse(src, dst) Dir.mkdir(dst) Dir.foreach(src) do |e| # Пропустить . и .. next if [".",".."].include? e fullname = src + "/" + e newname = fullname.sub(Regexp.new(Regexp.escape(src)),dst) if FileTest:rdirectory?(fullname) recurse(fullname,newname) elsif FileTest::symlink?(fullname) linkname = 'ls -l #{fullname}'.sub(/.* -> /,"").chomp newlink = linkname.dup n = newlink.index($oldname) next if n == nil n2 = n + $oldname.length - 1 newlink[n..n2] = $newname newlink.sub!(/\/\//,"/") # newlink = linkname.sub(Regexp.new(Regexp.escape(src)),dst) File.symlink(newlink, newname) elsif FileTest::file?(fullname) FileUtils.copy(fullname, newname) else puts "??? : #{fullname}" end end end # "Главная программа" if ARGV.size != 2 puts "Usage: copytree oldname newname" exit end oldname = ARGV[0] newname = ARGV[1] if ! FileTest::directory?(oldname) puts "Ошибка: первый параметр должен быть именем существующего каталога." exit end if FileTest::exist? (newname) puts "Ошибка: #{newname} уже существует." exit end oldname = File.expand_path(oldname) newname = File.expand_path(newname) $оldname=oldname $newname=newname recurse(oldname, newname) Возможно, и существуют варианты UNIX, в которых команда cp -Rсохраняет символические ссылки, но нам о них ничего не известно. Программа, показанная в листинге 14.5, была написана для решения этой практической задачи. 14.8.3. Удаление файлов по времени модификации и другим критериямПредположим, вы хотите удалить самые старые файлы из какого-то каталога. В нем могут, к примеру, храниться временные файлы, протоколы, кэш браузера и т.п. Ниже представлена небольшая программа, удаляющая файлы, которые в последний раз модифицировались раньше указанного момента (заданного в виде объекта Time): def delete_older(dir, time) Dir.chdir(dir) do Dir.foreach(".") do |entry| # Каталоги не обрабатываются. next if File.stat(entry).directory? # Используем время модификации. if File.mtime(entry) < time File.unlink(entry) end end end end delete_older("/tmp",Time.local(2001,3,29,18,38,0)) Неплохо, но можно обобщить. Создадим метод delete_if, который принимает блок, возвращающий значение trueили false. И будем удалять те и только те файлы, которые удовлетворяют заданному критерию. def delete_if(dir) Dir.chdir(dir) do Dir.foreach(".") do |entry| # Каталоги не обрабатываются. next if File.stat(entry).directory? if yield entry File.unlink(entry) end end end end # Удалить файлы длиннее 3000 байтов. delete_if("/tmp") { |f| File.size(f) > 3000 } # Удалить файлы с расширениями LOG и BAK. delete_if("/tmp") { |f| f =~ /(log|bak)$/i } 14.8.4. Вычисление свободного места на дискеПусть нужно узнать, сколько байтов свободно на некотором устройстве. В следующем примере это делается по-простому, путем запуска системной утилиты: def freespace(device=".") lines = %x(df -k #{device}).split("\n") n = lines.last.split[1].to_i * 1024 end puts freespace("/tmp") # 16772204544 Эту задачу лучше решать, обернув метод statfsв расширение Ruby. Такие попытки в прошлом предпринимались, но, похоже, проект умер. Для Windows имеется несколько более элегантное решение (предложено Дэниэлем Бергером): require 'Win32API' GetDiskFreeSpaceEx = Win32API.new('kernel32', 'GetDiskFreeSpaceEx', 'PPPP', 'I') def freespace(dir=".") total_bytes = [0].pack('Q') total_free = [0].pack('Q') GetDiskFreeSpaceEx.call(dir, 0, total_bytes, total_free) total_bytes = total_bytes.unpack('Q').first total_free = total_free.unpack('Q').first end puts freespace("С:") # 5340389376 Этот код должен работать во всех вариантах Windows. 14.9. Различные сценарииПриведем еще несколько примеров. Не претендуя на оригинальность, мы отнесли их к категории «разное». 14.9.1. Ruby в виде одного файлаИногда нужно быстро или временно установить Ruby. Или даже включить Ruby в состав собственной программы, поставляемой в виде одного исполняемого файла. Мы уже познакомились с «моментальным инсталлятором» Ruby для Windows. Существуют планы (пока еще не оформившиеся) создать подобный инсталлятор для Linux и Mac OS X. Эрик Веенстра (Erik Veenstra) недавно добился значительных успехов в создании пакетов, включающих как Ruby, так и написанные на нем приложения. Он автор пакетов AllInOneRuby, Tar2RubyScript и RubyScript2Exe (все они есть на его сайте http://www.erikveen.dds.nl). AllInOneRuby — это дистрибутив Ruby в одном файле. В пакет входят интерпретатор Ruby, системные классы и стандартные библиотеки, упакованные в единый архив, который легко перемещать или копировать. Например, его можно записать на USB-диск, носить в кармане и «установить» на любую машину за считанные секунды. Работает AllInOneRuby на платформах Windows и Linux; имеется также экспериментальная поддержка для Mac OS X. Что такое Tar2RubyScript, следует из самого названия. Программа получает на входе дерево каталогов и создает самораспаковывающийся архив, включающий написанную на Ruby программу и архив в формате tar. Идея та же, что у JAR-файлов в языке Java. Запускаемый сценарий должен называться init.rb; если сохраняется библиотека, а не автономное приложение, этот файл можно опустить. Название RubyScript2Exe, наверное, не вполне удачно. Программа действительно преобразует написанное на Ruby приложение в один двоичный файл, однако работает она не только в Windows, но и в Linux и Mac OS X. Можете называть ее компилятором, хотя в действительности она им, конечно, не является. Она собирает файлы, являющиеся частью установленного дистрибутива Ruby на вашей машине, поэтому не нуждается в кросс-компиляции (даже если бы такая возможность имелась). Имейте в виду, что исполняемый файл «усечен» в том смысле, что неиспользуемые библиотеки Ruby в него не включаются. Архив, созданный программой Tar2RubyScript, можно запустить на любой машине, где установлен Ruby (и программы, которые необходимы самому приложению). RubyScript2Exe не имеет такого ограничения, поскольку включает (наряду с вашим приложением) интерпретатор Ruby, всю среду исполнения и все необходимые внешние программы. Можете использовать эти инструменты вместе или порознь. 14.9.2. Подача входных данных Ruby по конвейеруПоскольку интерпретатор Ruby — это однопроходный транслятор, можно подать ему на вход некий код и выполнить его. Это может оказаться полезным, когда обстоятельства вынуждают вас работать на традиционном языке сценариев, но для каких-то сложных задач вы хотите применить Ruby. В листинге 14.6 представлен bash-сценарий, который вызывает Ruby (посредством вложенного документа) для вычисления интервала в секундах между двумя моментами времени. Ruby-программа печатает на стандартный вывод одно значение, которое перехватывается вызывающим сценарием. Листинг 14.6. bash-сценарий, вызывающий Ruby#!/usr/bin/bash # Для вычисления разницы в секундах между двумя моментами временами # bash вызывает Ruby... export time1="2007-04-02 15:56:12" export time2="2007-12-08 12:03:19" cat <<EOF | ruby | read elapsed require "parsedate" time1 = ENV["time1"] time2 = ENV["time2"] args1 = ParseDate.parsedate(time1) args2 = ParseDate.parsedate(time2) args1 = args1[0..5] args2 = args2[0..5] t1 = Time.local(*args1) t2 = Time.local(*args2) diff = t2 — t1 puts diff EOF echo "Прошло секунд = " $elapsed В данном случае оба исходных значения передаются в виде переменных окружения (которые необходимо экспортировать). Строки, читающие эти значения, можно было бы записать так: time1="$time1" # Включить переменные оболочки непосредственно time2="$time2" # в строку... Но возникающие при этом проблемы очевидны. Очень трудно понять, имеется ли в виду переменная bash или глобальная переменная Ruby. Возможна также путаница при экранировании и расстановке кавычек. Флаг -eпозволяет создавать однострочные Ruby-сценарии. Вот пример обращения строки: #!/usr/bin/bash string="Francis Bacon" ruby -e "puts '$string'.reverse" | read reversed # $reversed теперь равно "nocaB sicnarF" Знатоки UNIX заметят, что awkиспользовался подобным образом с незапамятных времен. 14.9.3. Получение и установка кодов завершенияМетод exitвозбуждает исключение SystemExitи в конечном счете возвращает указанный код завершения операционной системе (или тому, кто его вызвал). Этот метод определен в модуле Kernel. Метод exit!отличается от него в двух отношениях: он не выполняет зарегистрированные обработчики завершения и по умолчанию возвращает -1. # ... if (all_OK) exit # Нормально (0). else exit! # В спешке (-1). end Когда операционная система печатает возвращенный Ruby код (например, выполнив команду echo $?), мы видим то же самое число, что было указано в программе. Если завершается дочерний процесс, то код его завершения, полученный с помощью метода wait2(или waitpid2), будет сдвинут влево на восемь битов. Это причуда стандарта POSIX, которую Ruby унаследовал. child = fork { sleep 1; exit 3 } pid, code = Process.wait2 # [12554,768] status = code << 8 #3 14.9.4. Работает ли Ruby в интерактивном режиме?Чтобы узнать, работает ли программа в интерактивном режиме, нужно проверить стандартный ввод. Метод isatty?возвращает true, если устройство интерактивное, а не диск или сокет. (Для Windows этот метод не реализован.) if STDIN.isatty? puts "Привет! Я вижу, вы печатаете" puts "на клавиатуре." else puts "Входные данные поступают не с клавиатуры." end 14.9.5. Определение текущей платформы или операционной системыЕсли программа хочет знать, в какой операционной системе исполняется, то может опросить глобальную константу RUBY_PLATFORM. В ответ будет возвращена загадочная строка (что-то вроде i386-cygwinили sparc-solaris2.7), содержащая информацию о платформе, для которой был собран интерпретатор Ruby. Поскольку мы в основном работаем с вариантами UNIX (Solaris, AIX, Linux) и Windows (98, NT, 2000, XP), то считаем полезным следующий очень грубый код. Он отличает UNIX от Windows (бесцеремонно отправляя всех остальных в категорию «прочие»). def os_family case RUBY_PLATFORM when /ix/i, /ux/i, /gnu/i, /sysv/i, /solaris/i, /sunos/i, /bsd/i "unix" when /win/i, /ming/i "windows" else "other" end end Этот небольшой набор регулярных выражений корректно распознает абсолютное большинство платформ. Конечно, это весьма неуклюжий способ обработки системных зависимостей. Даже если вы правильно определите семейство ОС, отсюда еще не следует, что нужная вам функциональность имеется (или отсутствует). 14.9.6. Модуль EtcМодуль Etcполучает различную информацию из файлов /etc/passwdи /etc/group. Понятно, что полезен он только на платформе UNIX. Метод getloginвозвращает имя пользователя, от имени которого запущена программа. Если он завершается неудачно, может помочь метод getpwuid(принимающий в качестве необязательного параметра идентификатор пользователя uid). myself = getlogin # hal9000 myname = getpwuid(2001).name # hal9000 # Если параметр не задан, getpwuid вызывает getuid... me2 = getpwuid.name # hal9000 Метод getpwnamвозвращает структуру passwd, которая содержит поля name, dir(начальный каталог), shell(начальный интерпретатор команд) и др. rootshell = getpwnam("root").shell # /sbin/sh Методы getgrgidи getgrnamведут себя аналогично, но по отношению к группам. Они возвращают структуру group, содержащую имя группы и т.д. Итератор passwdобходит все записи в файле /etc/passwd. Запись передается в блок в виде структуры passwd. all_users = [] passwd { |entry| all_users << entry.name } Имеется также итератор group для обхода записей в файле /etc/group. 14.10. ЗаключениеНа этом мы завершаем обсуждение применения Ruby для решения рутинных задач автоматизации. Мы видели, как передавать в программу и получать от нее информацию в виде переменных окружения и с помощью стандартного ввода/вывода. Мы познакомились с типичными операциями «склеивания», позволяющими разным программам взаимодействовать. Рассмотрели мы и различные уровни взаимодействия с операционной системой. Поскольку значительная часть изложенного материала системно зависима, я призываю вас экспериментировать. Между платформами Windows и UNIX имеются серьезные отличия. Есть они и между разными операционными системами, относящимися к одному семейству. Следующая тема, которую мы рассмотрим, тоже весьма широка. Речь пойдет о работе с данными в разных форматах, от графических до XML. |
|
||
Главная | В избранное | Наш E-MAIL | Добавить материал | Нашёл ошибку | Наверх |
||||
|