2水準系直行表の作成
2水準系直行表を作成するスクリプト
require 'terminal-table' require 'csv' def dup_row(rows) new_rows = [] rows.each do |row| 2.times do new_rows << row.dup end end new_rows end def add_cols(rows) c = [0,1].cycle new_rows = rows.dup new_rows.each do |row| d = c.next add_row = [] row.each do |v| add_row << (v ^ d) end row << d row.concat(add_row) end new_rows end count = ARGV[0].to_i rows = [[0], [1]] Math.log2(count).to_i.times do rows = add_cols(dup_row(rows)) end format = ARGV[1] if format == "terminal" table = Terminal::Table.new do |t| t.headings = ["No."].concat rows.first.size.times.map { |i| i+1 } t.rows = rows.map.with_index do |row, i| ["##{i+1}"].concat(row) end end puts table else csv_rows = [] header = ["No."].concat rows.first.size.times.map { |i| i+1 } rows.each.with_index do |row, i| csv_rows << CSV::Row.new(header, ["##{i+1}"].concat(row)) end table = CSV::Table.new(csv_rows) puts table end
コマンド
第一引数は因子数
第二引数は表示形式。 terminal
を指定すると表形式で表示。それ以外は CSVで出力
> ruby main.rb 15 terminal
結果
+-----+---+---+---+---+---+---+---+---+---+----+----+----+----+----+----+ | No. | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | +-----+---+---+---+---+---+---+---+---+---+----+----+----+----+----+----+ | #1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | | #2 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | | #3 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | | #4 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | | #5 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | | #6 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | | #7 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | | #8 | 0 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 | 1 | | #9 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | | #10 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | | #11 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | | #12 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | | #13 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | | #14 | 1 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | | #15 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | | #16 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | +-----+---+---+---+---+---+---+---+---+---+----+----+----+----+----+----+
子プロセス間でパイプをつなぐ
親プロセスで作成したパイプを子プロセス間でつなぐ方法
以下のスクリプトは、標準入力で 何か文字を入力すると、それが出力されるサンプル。
out_r, out_w = IO.pipe script1 = <<~SCRIPT1 $stdout.sync = true loop do v = gets puts '1:' + v sleep 1 end SCRIPT1 spawn("ruby", "-e", script1, { :out => out_w }) script2 = <<~SCRIPT2 loop do puts '2:' + gets sleep 1 end SCRIPT2 spawn("ruby", "-e", script2, { :in => out_r }) loop do sleep(1) end
Windowsファイルパスを標準添付ライブラリで生成/パースする時の言語ごとの違い
Ruby
、 C#
、 Go
の標準添付ライブラリで file:///c:\temp\doc.txt
のようなパスを生成・パースした時の違いを示します。Ruby
だけは標準ライブラリの仕様上少し手を加えてあげないと動きませんでした。そのあたり少しフェアじゃないかもしれません。
参考サイト
C#
using System; namespace ConsoleApp22 { class Program { static void GenUrl(string winPath) { Uri u = new Uri(winPath); Console.WriteLine(u.AbsoluteUri); } static void GenPath(string urlPath) { Uri u = new Uri(urlPath); string winPath = u.LocalPath + Uri.UnescapeDataString(u.Fragment); Console.WriteLine(winPath); } static void Main(string[] args) { GenUrl(@"c:\temp\テスト doc#1.txt"); // file:///c:/temp/%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt GenPath(@"file:///c:\temp\テスト doc#1.txt"); // c:\temp\テスト doc#1.txt } } }
Go
package main import ( "fmt" "net/url" ) func GenUrl(winPath string) { u := &url.URL{} u.Scheme = "file" u.Path = winPath fmt.Println(u.String()) } func GenPath(urlPath string) { u, _ := url.Parse(urlPath) fmt.Println(u.Path + "#" + u.Fragment) } func main() { GenUrl("c:\\temp\\テスト doc#1.txt") // file://c:%5Ctemp%5C%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt GenPath("file:///c:\\temp\\テスト doc#1.txt") // /c:\temp\テスト doc#1.txt }
Ruby
require 'URI' def escape_path(path) path.gsub(/\\/, "\/").split("/").map { |elem| URI.encode_www_form_component(elem) }.join("/") end def gen_url(win_path) uri = URI::Generic.build({ scheme: "file", path: escape_path("/#{win_path}") }) puts uri.to_s end def gen_path(url_path) uri = URI.parse(escape_path(url_path.gsub(/^file:\/\/\//, ""))) puts uri.path end def decode_gen_path(url_path) uri = URI.parse(escape_path(url_path.gsub(/^file:\/\/\//, ""))) puts URI.decode_www_form_component(uri.path) end gen_url(%q|c:\temp\テスト doc#1.txt|) # file:///c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt gen_path(%q|file:///c:\\temp\\テスト doc#1.txt|) # c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt decode_gen_path(%q|file:///c:\\temp\\テスト doc#1.txt|) # c:/temp/テスト doc#1.txt
結果
- file:/// スキーマの生成
言語 | 生成されたパス |
---|---|
C# | file:///c:/temp/%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt |
Go | file://c:%5Ctemp%5C%E3%83%86%E3%82%B9%E3%83%88%20doc%231.txt |
Ruby | file:///c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt |
- file:/// スキーマのパース
言語 | パースされたパス |
---|---|
C# | c:\temp\テスト doc#1.txt |
Go | /c:\temp\テスト doc#1.txt |
Ruby(デコードなし) | c%3A/temp/%E3%83%86%E3%82%B9%E3%83%88+doc%231.txt |
Ruby(デコードあり) | c:/temp/テスト doc#1.txt |
やはり C# はすごいきれいに作れる印象があります。
Go は C# 同様に平易に生成・パースができました。ただ、パースした際に頭に /
がついてくるので実際にファイルパスとして使用するときは一工夫が必要そうです。
Ruby は Windowsはあまり得意ではないプラットフォームであることから自然には生成・パースはできませんでした。生成・パースには工夫が必要でした。ただ、Rubyは addressable
という gem によって簡単に生成・パースできるようになるため、そこまで問題ではないと思いました。
おまけ
addressable
を使用した Ruby の例を以下に示します。
require 'addressable/uri' def gen_url(win_path) uri = Addressable::URI.convert_path(win_path) puts uri.to_s end def gen_path(url_path) uri = Addressable::URI.parse(url_path) puts uri.path end gen_url(%q|c:\temp\テスト doc#1.txt|) # file:///c:/temp/%E3%83%86%E3%82%B9%E3%83%88%20doc#1.txt gen_path(%q|file:///c:\\temp\\テスト doc#1.txt|) # /c:\temp\テスト doc
プラットフォームがWindowsかそうでないかで処理を変える方法
RUBY_PLATFORM
で Ruby が動作しているプラットフォームがわかります。
Windowsは x64-mingw32
とかになります。windows
ではありません。
Visual Studio とかでビルドすると mswin
が入るようです。(環境がないのでフルネームはわかりません...)
なので、これらの文字列が RUBY_PLATFORM
に含まれているかどうかを確認することで Windwos
で動作しているかが判定できます。
def bar(msg) puts "#{__callee__} #{msg}" end 3.times do |i| if RUBY_PLATFORM =~ /mingw|mswin/ bar("win: #{i}") else bar("other: #{i}") end end
何回も呼ぶようなメソッドを包含するような場合だと、定義ごと分岐したほうがいいかもしれません。
if RUBY_PLATFORM =~ /mingw|mswin/ def foo(i) puts "#{__callee__} win: #{i}" end else def foo(i) puts "#{__callee__} other: #{i}" end end 3.times do |i| foo(i) end
複数のメソッドを定義しないといけない場合は上記の場合だと煩雑になるかもしれません。その場合は require を分岐させる方法がいいかもしれません。
if RUBY_PLATFORM =~ /mingw|mswin/ require File.join(__dir__, "method_win.rb" else require File.join(__dir__, "method.rb" end 3.times do |i| method(i) end
Windowsでプロセスグループを指定して spawn する方法
Linux で プロセスグループを指定して spawn を実行する場合は pgroup
を指定しますが、Windowsではこのオプションはサポートされていません。Windowsでプロセスグループを作成するには new_pgroup
を指定します。ただし、spawnの記述を間違うとshell経由でプロセスが起動してしまうため、このオプションが機能しなくなるため注意が必要です。
Ruby プログラム(間違い)
spawnの第一引数にコマンドライン引数も含めて渡してしまうとshell経由での起動となるため、Ctrl-Cがshellに伝わってしまってshellごと子プロセスが終了する。
a.rb
spawn "ruby b.rb", new_pgroup: true loop do print "test\n" sleep(1) rescue Interrupt exit(false) end
b.rb
loop do print "#{Time.now}\n" sleep(1) end
Ruby プログラム(shellを経由しない場合)
spawnにコマンドと引数を分けて渡してやると、shellを経由しなくなる。複数の引数を渡したい場合は、それぞれを分けて渡すこと。
a.rb
spawn "ruby", "b.rb", new_pgroup: true loop do print "test\n" sleep(1) rescue Interrupt exit(false) end
調査用 Go プログラム (参考)
// +build ignore package main import ( "fmt" "os/exec" "syscall" "time" ) const ( CREATE_NEW_PROCESS_GROUP = 0x00000200 ) func main() { var cmd *exec.Cmd cmd = exec.Command("ruby", "-e", "'sleep'") cmd.SysProcAttr = &syscall.SysProcAttr{ CreationFlags: CREATE_NEW_PROCESS_GROUP, } cmd.Start() time.Sleep(1 * time.Second) fmt.Println(cmd.Process.Pid) for { time.Sleep(1 * time.Second) } }
調査用 C言語 プログラム (参考)
#include <stdio.h> #include <Windows.h> #include <string.h> #include <wchar.h> void main() { BOOL fRet; STARTUPINFO aStartupInfo; PROCESS_INFORMATION aProcessInformation; SECURITY_ATTRIBUTES sa; DWORD dwCreationFlags; const WCHAR* prog = NULL; WCHAR cmd[] = L"ruby C:\\dev\\ruby\\test_spawn\\b.rb"; sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.lpSecurityDescriptor = NULL; sa.bInheritHandle = TRUE; memset(&aStartupInfo, 0, sizeof(aStartupInfo)); memset(&aProcessInformation, 0, sizeof(aProcessInformation)); aStartupInfo.cb = sizeof(aStartupInfo); aStartupInfo.dwFlags = STARTF_USESTDHANDLES; aStartupInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); aStartupInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); aStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); dwCreationFlags = NORMAL_PRIORITY_CLASS | CREATE_NEW_PROCESS_GROUP; printf("start\n"); fRet = CreateProcess(prog, cmd, &sa, &sa, sa.bInheritHandle, dwCreationFlags, NULL, NULL, &aStartupInfo, &aProcessInformation); printf("fRet: %d\n", fRet); CloseHandle(aProcessInformation.hThread); printf("PID: %d\n", aProcessInformation.dwProcessId); WaitForSingleObject(aProcessInformation.hProcess, INFINITE); printf("end \n"); }
参考資料
spawnとCtrl+C
参考サイト
モチベ
プロセスグループとCtrl+Cの関係がよくわからなかったので調べてみた。
結論
pgroup: false
だと子プロセスにも SIGINTを送信する
pgroup: true
だと子プロセスには SIGINTは送信しない
プロセスグループなしで spawn
以下のプログラムを動かすとします。
a.rb
spawn "ruby b.rb" begin sleep rescue Interrupt puts "interrupt a.rb" exit(false) end
b.rb
begin sleep rescue Interrupt puts "interrupt b.rb" exit(false) end
ruby a.rb
を実行した後、Ctrl+C
を押します。出力は以下のような感じなると思います。
^Cinterrupt b.rb interrupt a.rb
どちらも終了します。
プロセスグループありで spawn
先ほどの a.rb
の spawn
に pgroup: true
オプションを付けます。これを付けると b.rb
が a.rb
とは別のプロセスグループで動作するようになります。
a.rb
spawn "ruby b.rb", pgroup: true # ← pgroup: true オプションを追加 begin sleep rescue Interrupt puts "interrupt a.rb" exit(false) end
先ほどと同様に ruby a.rb
を実行した後、Ctrl+C
を押します。出力は以下のような感じになると思います。
^Cinterrupt a.rb
ps aux | grep ruby
をすると ruby b.rb
のプロセスが残っているのがわかります。
以上
Fiddle で MessageBox 表示
Ruby3.0から Win32API ライブラリが廃止になってしまったらしく 久々に Win32API使おうと思ったら使えなくて困った。 代替品として fiddle/import を使ってねということだったの使ってみた。
require "fiddle/import" require 'fiddle/types' def L(text) text.encode(Encoding::UTF_16LE) end module WIN32API extend Fiddle::Importer dlload 'C:\\Windows\\System32\\user32.dll' include Fiddle::Win32Types extern 'HWND GetForegroundWindow()' extern 'int MessageBoxW(HWND, const wchar_t*, const wchar_t*, UINT)' end hwnd = WIN32API.GetForegroundWindow WIN32API.MessageBoxW(hwnd, L("こんにちわ"), L("hello"), 0)
MessageBoxWの第二引数、第三引数は LPCTSTR なんですが、Fiddle::Win32Types には定義されていないので Rubyが認識できる型で指定してあげる必要があります。LPCTSTR は const wchar_t* なのでそれで置き換えています。
また Rubyは文字列のエンコーディングがデフォルト UTF-8なのですが、wchar_t は UTF-16LE なので MessageBoxWに渡す前にエンコーディングの変更をしてあげる必要があります。