Blogged by Ujihisa. Standard methods of programming and thoughts including Clojure, Vim, LLVM, Haskell, Ruby and Mathematics written by a Japanese programmer. github/ujihisa

Sunday, March 28, 2010

How to Get Each Characters

STDIN.getc returns a buffered character from STDIN. How do I get a character from STDIN immediately without buffering?

This code looks well, but it doesn't work as you expect. The input characters will be still buffered.

STDIN.sync = true
p STDIN.getc

Curses.getch

require 'curses'
p Curses.getch

This works, but there is an undesired side-effect. Curses.getch clears the screen.

stty raw

orig_stty = `stty -g`
system 'stty raw echo'
p STDIN.getc
system 'stty', orig_stty

This works, but some platform don't support the command.

Conclusion

Getting a character immediately looks easy, but it actually isn't easy.

Sunday, March 21, 2010

Spawn On JRuby

As the previous posts of this blog, ruby 1.9's Kernel.spawn is very useful. I ported it to ruby 1.8 partly. How about on JRuby?

Currently there are two ways of using spawn on JRuby, but both of them have problem.

fork + exec + spawn-for-legacy library

I made a wrapper library spawn-for-legacy which provides ruby 1.9 style spawn, using fork and exec.

If JRuby has fork and exec, you can use spawn-for-legacy. Unfortunatelly when you use fork on JRuby, you have to set a command line option to JRuby interpreter.

If you kill pid0, will the internal fork be automatically killed?

Try the previous code. This doesn't show 'bah!' message. It seems to be true that internal fork will be automatically killed. But it's wrong. This code doesn't show 'bah!' message just because the internal fork didn't started yet when Process.kill was called.

Try to wait until both blocks are certainly for forked.

This code shows 'bah!' message after the main routine finished!

So, let's think about how to kill the internal process.

  • The value of pid1 is not available in the outer main routine
  • An external command ps is not available in Windows
  • Usually the process ID pid0 and pid1 are sequential, but depending on it is dangerous

Here is the solution of it:

I added a line of at_exit. This is cumbersome a little bit, but safe and easy.

With Spawn

The previous discussion was actually for getting ready. Windows doesn't have fork. We can use spawn instead of fork in most cases.

This succeeded!

One More Thing

When you have to fork a ruby process with spawn, for example in case to rewrite fork to spawn, how do you write?

fork { p Dir.pwd }

to

spawn 'ruby', '-e', 'p Dir.pwd'

is not correct. The ruby that booted the code itself is not always 'ruby'. It can be ruby19, jruby, rbx or other rubies. In this case, your the following approach.

require 'rbconfig'
spawn RbConfig::CONFIG['ruby_install_name'] + RbConfig::CONFIG['EXEEXT'], '-e', 'p Dir.pwd'

Compatible Array#flatten

Array#flatten with depth argument is useful.

[[1], [2], [[3], 4]].flatten(1)
#=> [1, 2, [3], 4]

That's been available since ruby 1.8.7, but older ruby such as ruby 1.8.6 doesn't support it.

Here is the pure-ruby implementation of Array#flatten. Use it in case.

I used it for my library spawn-for-legacy to support ruby 1.8.6.

Rails programmers don't need this code. Rails now only supports ruby newer than 1.8.7.

Sunday, March 7, 2010

How To Run An External Command Asynchronously

This issue looks easy, but is actually complex.

On shell, you may run a command asynchronously just by adding & at the end of command.

$ sleep 10 &
[1] 8048

You can obtain the process ID, so you can stop the command.

$ kill 8048

How can we run an external command on Ruby?

system()

The easiest way to run an external command is system().

system 'sleep 10 &'

The return value of system() is the exit code. In this case, the system() returns true immediately after that ran, but it doesn't return process ID. To obtain the process ID, you can use $?.

system 'sleep 10 &'
p $? #=> 8050

But unfortunatelly the $? is not the processs ID of sleep, but sh -c sleep 10. There is a cussion. You cannot obtain the process ID of sleep directly.

One more problem: the notation is available only in UNIX based platform.

system() without shell

system 'sleep 10' uses shell but system 'sleep', '10' doesn't use shell. Separate the arguments. But system 'sleep', '10', '&' is not available.

Thread

To emulate '&' feature, how about using Ruby's asynchronous features? Thread is the easiest way of that.

t = Thread.start do
  system 'sleep', '10'
end
t.kill

Looks good, but it doesn't work. Thread certainly will stop, but the external command booted by the thread will still alive.

exec()

How about using another way of booting an external command? There is exec(), which roughly means the combination of system() and exit().

exec 'ls'

roughly means

system 'ls'
exit

So you think that the following code will work well.

t = Thread.start do
  exec 'sleep', '10'
end
t.kill

Unfortunatelly it doesn't work. You cannot use exec() in a child thread.

fork()

Look for another way of supporting asynchronous feature on Ruby. fork(). fork() copies the Ruby process itself.

pid = fork do
  exec 'sleep', '10'
end
Process.kill pid

It works! fork() is awesome!

But there is a bad news. Thread works on all platforms, but fork works on only some platforms. I'm sorry Windows users and NetBSD users.

spawn()

spawn = fork + exec. spawn() works on all platforms! It's perfect! But... ruby 1.8 doesn't have spawn()...

Summary

  • Ruby 1.9 + UNIX: Use fork and exec, or use spawn.
  • Ruby 1.8 + UNIX: Use fork and exec.
  • Ruby 1.9 + Windows: Use spawn.
  • Ruby 1.8 + Windows: Use other OS, other Ruby, or consider using this library

Saturday, March 6, 2010

How To Avoid The Worst Case

I broke an expensive unopened wine bottle. The white wine was spilt all over my kitchen.

Yesterday I bought a wine bottle and a box of beer at a liquor store which is located far from my home. I bought them because they were on sale. To save my money, I came home on my foot, saving $1.75. Today I ate lunch and drunk a sip of beer. After the lunch, I was trying to get a snack. There was the wine bottle in front of the snack. My elbow hit the bottle, and broke it. The delicious expensive wine has gone. $14 and the heavy work has gone.

While I was wiping the floor with smelling the wine, I was thinking what was the cause and how should I do after that. This incident was happened without any other people except for me. I'm the true culprit. So, how can I avoid such tragedy in my future?

The causes are the following two. The fact I was drunk a little bit and the location of wine was not safe a little bit. Both of them are not crucial. But the incident certainly happened.

This issue can be summarized as that we should assume anything for people who are in bad condition. People often become bad because of drowsiness, alcohol, anger and depression. Assume the condition. Never put glass products on the edge, or the place people can touch easily.

Friday, March 5, 2010

All About Spawn

Spawn = Fork + Exec, but works also on platforms which don't support fork.

The usage of spawn is often said to be cryptic. Here I'll describe common cases.

  • system('ls')

    pid = spawn(['ls', 'ls'])
    Process.wait(pid)
    
  • system('ls .')

    pid = spawn('ls', '.')
    Process.wait(pid)
    
  • system('ls . &')

    pid = spawn('ls', '.')
    
  • system('ls . > a.txt')

    pid = spawn('ls', '.', :out => 'a.txt')
    Process.wait(pid)
    
  • system('ls . >> a.txt')

    pid = spawn('ls', '.', :out => ['a.txt', 'a'])
    Process.wait(pid)
    
  • system('ls . >& a.txt')

    pid = spawn('ls', '.', [:out, :err] => ['a.txt', 'w'])
    Process.wait(pid)
    
  • IO.popen('cat a.txt') {|io| p io.read

    i, o = IO.pipe
    spawn('cat a.txt', :out => o)
    o.close
    p i.read
    
  • system('make all &')

    spawn('make', 'all)
    
  • Dir.chdir('a') { system 'make all &' }

    spawn('make', 'all', :chdir => 'a')
    
  • Passing ENV:

    • Shell: $ AAA=1 make all &
    • Ruby: ENV['AAA'] = '1'; system('make all &')
    • Ruby with Spawn: spawn({'AAA' => '1'}, 'make', 'all')

Further information can be available in process.c in ruby trunk. Here the documentation from the file:

Followers