Closed5

色々なプログラミング言語でinput-loop

四ツ山伊吹四ツ山伊吹

はじめに

input-loopとは?

ここでは以下に挙げるようなものをいう:

  • 標準入力ストリームを標準出力ストリームへコピーする。
  • 行単位でストリームから読み込み、行単位でストリームへ書き出す。
  • end-of-file (EOF) に到達したら終了する。

方針

  • 実行可能ファイルとして定義する。
    • インタプリタ型言語はファイルの先頭にshebangを付与する。
    • コンパイル型言語はコンパイルする。
    • chmod u+x
  • その言語“らしさ”を求める。
  • 実行速度、実行効率などは特段突き詰めない。
  • 簡素に記述する。
  • 対話環境が用意できるものでワンライナーで記述できる言語は併せて記述する。

見本

  • sedコマンドをUnix shellで実行した時の振る舞いを見本とする。
  • 以下の./executablesedに置き換えても成り立つ。
sample
$ echo foo | ./executable⏎
foo
$ 
sample
$ ./executable⏎
foo⏎
foo
bar⏎
bar
^D
$ 
sample
$ ./executable < input.txt⏎
金魚は青空を食べてふくらみ
鉢の中で動かなくなる
鳩だか 鉢のガラスにうすい影を走らせる
來たのは花辨か 白い雲の斷片
$ 
input.txt
金魚は青空を食べてふくらみ
鉢の中で動かなくなる
鳩だか 鉢のガラスにうすい影を走らせる
來たのは花辨か 白い雲の斷片

参考

https://www.rosettacode.org/wiki/Input_loop

https://www.rosettacode.org/wiki/Copy_stdin_to_stdout

http://fornext1119.web.fc2.com/0101/0005.html

四ツ山伊吹四ツ山伊吹

Python

input-loop.py
#!/usr/local/bin/python3

import sys


def main():
    while (line := sys.stdin.readline()):
        sys.stdout.write(line)


if __name__ == '__main__':
    sys.exit(main())
REPL
>>> import sys⏎
>>> while (line := sys.stdin.readline()): sys.stdout.write(line)...

別解

REPL
>>> import sys⏎
>>> [print(line, end="") for line in sys.stdin]

説明

  • 中核の部分は割と見たまんまな書き方・あじわい。
    • なんとなく対称性を感じられて好き。
  • line := sys.stdin.readline()の部分はPython 3.8から導入されたAssignment Expressions[1]
  • sys.stdin.readlineメソッドは、行末の改行を含めて返す。
  • sys.stdout.writeメソッドは、引数に取った文字列の行末に改行を加えずに書き込む。
  • そういうわけで、sys.stdin.readlineしてsys.stdout.writeすると問題なくinput-loopを達成できる。
  • これらのメソッドと組み込み関数のinput()print()とではそれぞれ微妙に振る舞いが異なる。
    • したがって、単純にこれらを入れ替えて代用することはできない。
  • ワンライナーではPythonの文法の制約を受けて[2] while statementとimport statementを同じ行に書くことはできない。
  • ワンライナーでは入力行をechoした後にその書き出された文字数まで端末へ出力されるのが厄介。
    • 気に入らなければ、sys.stdout.write(line)print(line, end = "") で置換可能。
  • 別解はリストの内包表記を利用した書き方。
脚注
  1. https://peps.python.org/pep-0572/ ↩︎

  2. https://docs.python.org/3/reference/compound_stmts.html#grammar-token-python-grammar-stmt_list ↩︎

四ツ山伊吹四ツ山伊吹

C

input-loop.c
#include <stdio.h>

int main(int argc, char const *argv[])
{
        char *line = NULL;
        size_t linecap = 0;
        ssize_t linelen;

        while ((linelen = getline(&line, &linecap, stdin)) > 0)
                fwrite(line, linelen, 1, stdout);

        return 0;
}

説明

  • (書き方はこれで合ってんのかな…)と自信を持てないくらい書き慣れない者にとってはCって難しい。
    • 一応コンパイルして検証済み。
  • getline(3)のBSD Library Functions Manual[1]のEXAMPLESセクションに記載されていた例が役立った。
  • ストリームから1行だけ読み取るというとgets(3)などが挙げられる
    • gets(3)はISO C99に適合しているが、C11以降は廃止となっている。
脚注
  1. https://opensource.apple.com/source/Libc/Libc-1439.40.11/stdio/FreeBSD/getline.3.auto.html ↩︎

四ツ山伊吹四ツ山伊吹

Unix shell (sh)

input-loop.sh
#!/bin/sh

while read -r -- line
do
  printf -- '%s\n' "${line}"
done
ワンライナー
$ while read -r line; do printf '%s\n' "${line}"; done⏎

説明

  • たぶんIEEE Std 1003.1準拠。
  • readコマンドの-r
  • --
  • 外部コマンドの呼び出しに変えるとオーバーヘッドが増大する。
四ツ山伊吹四ツ山伊吹

C#

input-loop.cs
using System;

namespace InputLoop
{
    public class Program
    {
        public static void Main(string[] args)
        {
            Console.OpenStandardInput().CopyTo(Console.OpenStandardOutput());
        }
    }
}
input-loop.cs (.NET 6 / C# 10)
namespace InputLoop;
Console.OpenStandardInput().CopyTo(Console.OpenStandardOutput());

説明

  • 中核部分は読んで字の如く、「(システムの)操作卓の標準入力を開いて、それを同じ標準出力へ複製する」という素直な陳述。
    • ループもないし、「読め」「書け」という命令もない。
    • これがオブジェクト指向プログラミングですか。

  • 前者は伝統的な記法に則っている。外側の部分は質実剛健な感じ。悪くいえば冗長っぽい。
  • 一方で後者のように、最近では簡潔な書き方もできるようになっている。
    • C# 9.0で導入された「最上位レベルのステートメント」[1]、C# 10の「ファイルスコープの名前空間の宣言」[2]、.NET 6 SDKの「ImplicitUsings」[3]であたかもスクリプト言語のような書き味に。
    • F# Interactiveとは異なり、ふつうは.csprojをつくって…という従来の手順は必要。
脚注
  1. https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-9#top-level-statements ↩︎

  2. https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-10#file-scoped-namespace-declaration ↩︎

  3. https://docs.microsoft.com/ja-jp/dotnet/core/project-sdk/msbuild-props#implicitusings ↩︎

このスクラップは2023/08/11にクローズされました