LoginSignup
0
0

More than 1 year has passed since last update.

色んな言語を標準入出力で繋ぐ(5*8種類)

Last updated at Posted at 2021-12-31

この記事でやること

標準入出力とパイプだけで双方向プロセス間通信します。
使うのは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!なんですけどね。
それに、他のやり方も色々あるのでこの方法がベストというわけでもないのでしょう。
でも、汎用性が高いですよね。


  1. vimはね…標準入出力との親和性が高いし、僕は組み込みUNIX機を使うので老害だなんてバッサリ切らないで欲しい 

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0