この記事でやること
標準入出力とパイプだけで双方向プロセス間通信します。
使うのはprint関数とreadlineの類とpopen関数の類のみです。
「〇〇言語と〇〇言語を繋ぐ為の特別なライブラリ」は一切使いません。
これにより、例えば
「python/C++での機械学習の結果をRで検定するElectron製のアプリ」
みたいな事が出来る。
何を今更…
IPCくらい皆してますよね。でも当記事は広く浅く数を揃えました。以上です。
これがUNIXの哲学である。
一つのことを行い、またそれをうまくやるプログラムを書け。
協調して動くプログラムを書け。
標準入出力(テキスト・ストリーム)を扱うプログラムを書け。標準入出力は普遍的インターフェースなのだ。
— ダグラス・マキルロイ、UNIXの四半世紀
レギュレーション
親は「子プロセスを起動、積極的に送信、返事を受信」をして、
子プロセスは単に「受信、送信」をするものとします。
標準で出来る場合はJSONをパースして貰います。
Object的な何かを送受信する場合に普及と利便性を両立していると
僕が勝手に思っているからです。
ちなみに、親プロセスになれるかどうかは僕のスキル次第です。
qiitaの賢者たちが教えてくれたら増えるかも?
試した言語
今の所、親と子の両方をするのは下記の言語たち
言語 | 感想 |
---|---|
python3 | お堅いと勘違いされがちだが意外と黒魔術施行可能。 |
javascript | GUI強い。関数系の怖い人がたまに居る。 |
C/C++ | 奥義的な何か?僕も本気になった時には使うよ。 |
shellscript | この用途には一番強い。汎用ではないと思う。 |
R | 統計特化型。統計目的なら強力。 |
子だけするのは下記の言語達
言語 | 感想 |
---|---|
rust | 皆に愛されているアレ。ちゃんと使ったことはない。 |
nim | 刺さる人には刺さる。伸びて欲しい。 |
vimscript | vimはいいぞ1 |
実装
python
コードが短い。書くのはとても楽。
自らの遅さをCとの連携でどうにかする系のグルー言語。
親
1回限りの場合はこう
from subprocess import PIPE, Popen
commands = [
('python', './child.py'),
('node', './child.js'),
('Rscript', './child.R'),
('sh', './child.sh'),
('./child_cpp'),
('./child_nim'),
('./child_rust'),
('vim', '-', '--not-a-term ', '-u', 'NONE',
'-esS', './child.vim'),
]
for command in commands:
task = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE)
result = task.communicate(b'{"hoge": "fuga"}\n')
print(' ', result[0])
何度もやり取りしたい場合はこう。
この場合、最後に改行文字を入れないとデッドロックしてしまう。
terminateを入れないと終わってくれなかったりもする。
from subprocess import PIPE, Popen
commands = [
('python', './child.py'),
('node', './child.js'),
('Rscript', './child.R'),
('sh', './child.sh'),
('./child_cpp'),
('./child_nim'),
('./child_rust'),
]
for command in commands:
task = Popen(command, stdin=PIPE, stdout=PIPE, stderr=PIPE)
task.stdin.write(b'{"hoge": "fuga"}\n')
task.stdin.flush()
result = task.stdout.readline()
print(' ', result)
task.terminate()
task.wait()
子
JSONをパースして返す所までして、4行で済むのはやはり優れていると思う。
import json
data = input()
result = json.loads(data)
print(f'Python got data and parsed {result["hoge"]}\n')
javascript
兎に角非同期一辺倒という印象。
今回はnode.jsを使いました。
親
const { spawn } = require('child_process');
commands = [
['python3', ['./child.py']],
['node', ['child.js']],
['Rscript', ['child.R']],
['sh', ['child.sh']],
['./child_cpp', []],
['./child_nim', []],
['./child_rust', []],
['vim', ['-', '--not-a-term',
'-u', 'NONE', '-esS', './child.vim']]
]
let data = '{"hoge": "fuga"}\n'
for (let command of commands){
let child = spawn(command[0], command[1], {stdio: 'pipe'})
child.stdin.setEncoding('utf-8')
child.stdin.write(data)
child.stdout.on('data', (data) => {console.log(' '+ String(data).trim())});
child.stdin.end()
}
子
process.stdin.resume();
process.stdin.setEncoding('utf8');
let input_string = ''
process.stdin.on('data', data =>{
console.log('JavaScript got data and parsed', JSON.parse(data).hoge)
})
process.stdin.on('end', () =>process.exit())
C++
初見殺しであり、多くの初心者の心を負ってきた(異論は認めない)
最強なので、速度が欲しい時に僕も使うことがある。
using namespace stdって俳句を詠む事も出来る。
親
Windowsの場合はpopenじゃなくて_popenというのがあるらしい。
詳しくないのでアレなんですけどね…。
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <vector>
#define bufsize 32
int main(void) {
std::vector<std::string> commands = {"python ./child.py",
"node ./child.js",
"Rscript ./child.R",
"sh ./child.sh",
"./child_cpp",
"./child_nim",
"./child_rust",
"vim - --not-a-term -u NONE -esS ./child.vim"};
char data[bufsize] ={};
for (auto command=commands.begin(); command!=commands.end(); command++){
FILE *fp = popen(command->c_str(), "w");
fputs("{\"hoge\": \"fuga\"}", fp);
fgets(data, bufsize , fp);
std::cout << data << std::endl;
pclose(fp);
}
return(0);
}
子
#include <iostream>
int main(){
std::string data;
std::getline(std::cin, data);
std::cout << "C++ got data " << data << std::endl;
}
nim
楽しいので好きです。でも、仕事に使うのはまだかなぁ…。
子
import std/json
let data = readLine(stdin)
let jsonNode = parseJson(data)
echo "Nim got data and parsed " & jsonNode["hoge"].getStr()
vimscript
vimの為に存在する言語。neovimも互換性は高い。
一応親をすることも出来るけど、それってシェルの力なので今回はパス。
子
norm iVimscript got data
%p
q!
shellscript
本来はこれが本命。今回はdashさんに来てもらいました。
JSON parserは組み込まれませんが、今回はjqを使いました。
親
commands="python ./child.py;
node ./child.js;
Rscript ./child.R;
sh ./child.sh;
./child_cpp;
./child_nim;
./child_rust;
vim - --not-a-term -u NONE -esS ./child.vim;"
data='{\"hoge\": \"fuga\"}'
for n in $(seq 8); do
result=$(echo $commands | cut -d \; -f $n)
echo ' '$(eval "echo $data | $result")
done
子
read data
echo "Shell got data $(echo $data| jq .hoge -r)"
Rscript
無料の統計ソフト
親
commands= c("python ./child.py",
"node ./child.js",
"Rscript ./child.R",
"sh ./child.sh",
"./child_cpp",
"./child_nim",
"./child_rust",
"vim - --not-a-term -u NONE -esS ./child.vim")
for (command in commands){
data<-system(command, intern=TRUE, input='{"hoge": "fuga"}', ignore.stderr=TRUE)
cat(paste(' ', data, '\n'))
}
子
ここでreadLinesに1を指定しているのは1行だけ読むよという意味。
data <- readLines('stdin', 1)
cat(paste('Rscript got data', data, sep=' '))
cat('\n')
Rust
知識がなくてpopen出来なかった…(´・ω・`)
子
use std::io;
use std::fmt::format;
fn main() {
let mut data = String::new();
io::stdin().read_line(&mut data)
.expect("Failed to read line");
println!("{}", format!("{}{}", "Rust got data ", data));
}
感想
要するに色んな言語のHello world!なんですけどね。
それに、他のやり方も色々あるのでこの方法がベストというわけでもないのでしょう。
でも、汎用性が高いですよね。
-
vimはね…標準入出力との親和性が高いし、僕は組み込みUNIX機を使うので老害だなんてバッサリ切らないで欲しい ↩