Как видите, при таком определении изменяется также поведение ограничителя %x
.
В следующем примере мы добавили в конец команды конструкцию интерпретатора команд, которая перенаправляет стандартный вывод для ошибок в стандартный вывод:
alias old_execute `
def `(cmd)
old_execute(cmd + ' 2>&1')
end
entries = `ls -l /tmp/foobar`
# '/tmp/foobar: No such file or directory
'
Есть, конечно, и много других способов изменить стандартное поведение обратных кавычек.
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' }