HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

各言語でコマンドライン引数を扱う

そういえばやってなかったなということで、各言語でコマンドライン引数を扱うプログラムを書いてみたメモ。

要件は以下の通り。

  • コマンドライン引数として、3個以上の文字列を与える
  • 標準出力に、以下を順に出力
    • 引数として与えられた文字列を改行区切りで出力
    • 引数の個数を出力
    • 引数の3番目の文字列を出力

環境

手元にあるものということで、環境は以下のものに限定する。

実行例

[各言語でのコマンド実行] a b c d e

出力例

a
b
c
d
e
5
c

Java

public class Main {
    public static void main(String[] args) {
        for (String s : args) {
            System.out.println(s);
        }
        System.out.println(args.length);
        System.out.println(args[2]);
    }
}

C

#include <stdio.h>

int main(int argc, char** argv) {
    for (int i = 1; i < argc; ++i) {
        printf("%s\n", argv[i]);
    }
    printf("%d\n", argc - 1);
    printf("%s\n", argv[3]);

    return 0;
}

C++

#include <iostream>

using namespace std;

int main(int argc, char** argv) {
    for (int i = 1; i < argc; ++i) {
        cout << argv[i] << endl;
    }
    cout << (argc - 1) << endl;
    cout << argv[3] << endl;

    return EXIT_SUCCESS;
}

PHP

<?php

for ($i = 1; $i < count($argv); ++$i) {
    echo $argv[$i] . PHP_EOL;
}
echo (count($argv) - 1) . PHP_EOL;
echo $argv[3] . PHP_EOL;

Python 2

import sys

for i in range(1, len(sys.argv)):
    print sys.argv[i]
print len(sys.argv) - 1
print sys.argv[3]

Python 3

import sys

for i in range(1, len(sys.argv)):
    print(sys.argv[i])
print(len(sys.argv) - 1)
print(sys.argv[3])

Ruby

for a in ARGV
    print a,"\n"
end
print ARGV.length,"\n"
print ARGV[2],"\n"

Perl

for (my $i = 0; $i < @ARGV; ++$i) {
    print $ARGV[$i],"\n";
}
my $argc = @ARGV;
print $argc,"\n";
print $ARGV[2],"\n";

Go

package main

import (
    "fmt"
    "os"
)

func main() {
    for i := 1; i < len(os.Args); i += 1 {
        fmt.Println(os.Args[i])
    }
    fmt.Println(len(os.Args) - 1)
    fmt.Println(os.Args[3]);
}

bash

#! /bin/bash

for c in "$@" ; do
    echo ${c}
done
echo $#
echo $3

まとめ

それぞれの言語ごとの仕様を表にまとめてみる。

言語 変数名 最初の引数の添え字 個数
Java (*1) args 0 args.length
C (*1) argv 1 argc - 1
C++ (*1) argv 1 argc - 1
PHP $argv 1 count($argv) - 1
Python 2/3 sys.argv 1 len(sys.argv) - 1
Ruby ARGV 0 ARGV.length
Perl $ARGV 0 @ARGV (*3)
Go os.Args 1 len(os.Args) - 1
bash $n または ${n} (*2) 1 $#
  • (*1)変数名はmainメソッド/関数の引数の宣言による。
  • (*2)「n」は正の整数。
  • (*3)文脈によっては、「my $argc = @ARGV」などとしてやらないと、個数でなく引数を結合した文字列が得られてしまう。

文字列のマッチング方法によるパフォーマンスの違い

Javaで文字列のパターンマッチをしようと思ったら、以下の3通りに書ける。

String str = "Hello World";
if (str.matches("H.*W")) {
    ...
}
String str = "Hello World";
if (java.util.regex.Pattern.matches("H.*W", str)) {
    ...
}
String str = "Hello World";
java.util.regex.Pattern p = java.util.regex.Pattern.compile("H.*W");
java.util.regex.Matcher m = m.matcher(str);
if (m.matches()) {
    ...
}

まぁ、1番目は内部で2番目の処理を呼び出しているだけだが。

で、3番目のように正規表現コンパイルを事前に行っておく形とそうでない形とでパフォーマンスにどれだけの差が出るのだろうかとふと思ったので計ってみた。

計測に使用したプログラム

import java.util.Scanner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String str = sc.nextLine();
        String regex = sc.nextLine();

        int delta = 100000;
        int max = 2000000;
        for (int i = delta; i <= max; i += delta) {
            System.out.printf("%8d,%8d,%8d,%8d%n",
                i,
                test1(i, str, regex),
                test2(i, str, regex),
                test3(i, str, regex));
        }
    }
    private static long test1(int count, String str, String regex) {
        long S = System.currentTimeMillis();
        for (int i = 0; i < count; ++i) {
            str.matches(regex);
        }
        long G = System.currentTimeMillis();
        return G - S;
    }
    private static long test2(int count, String str, String regex) {
        long S = System.currentTimeMillis();
        for (int i = 0; i < count; ++i) {
            Pattern.matches(regex, str);
        }
        long G = System.currentTimeMillis();
        return G - S;
    }
    private static long test3(int count, String str, String regex) {
        long S = System.currentTimeMillis();
        Pattern p = Pattern.compile(regex);
        for (int i = 0; i < count; ++i) {
            Matcher m = p.matcher(str);
            m.matches();
        }
        long G = System.currentTimeMillis();
        return G - S;
    }
}

入力

正規表現コンパイルにかかる時間に着目しているので、正規表現の複雑さの違いということで2種類の正規表現を試してみた。

  • a: [a-z]+
  • b: [A-Z][a-z]* [A-Z][a-z]*

計測結果

N test1-a test2-a test3-a
100000 191 64 20
200000 172 68 22
300000 79 64 26
400000 83 78 21
500000 100 99 25
600000 120 121 31
700000 138 140 36
800000 154 153 39
900000 176 177 45
1000000 205 197 50
1100000 217 217 54
1200000 235 239 61
1300000 258 259 65
1400000 279 274 71
1500000 306 292 76
1600000 331 326 86
1700000 340 348 89
1800000 364 362 91
1900000 390 383 97
2000000 410 392 100

f:id:hhelibex:20171126155414p:plain

正規表現 b

N test1-b test2-b test3-b
100000 313 92 19
200000 217 130 46
300000 164 161 42
400000 213 217 52
500000 273 271 66
600000 325 326 79
700000 379 389 89
800000 434 431 104
900000 482 490 117
1000000 540 539 131
1100000 584 588 143
1200000 646 641 156
1300000 694 703 173
1400000 745 752 187
1500000 808 816 195
1600000 883 873 211
1700000 932 927 226
1800000 992 984 241
1900000 1032 1020 254
2000000 1083 1088 271

f:id:hhelibex:20171126155536p:plain

まぁ予想通りだが、毎回コンパイルすることになるtest1、test2のケースは、test3に比べて断然遅いことが分かる。 また、正規表現の複雑さが増すと、コンパイルやマッチングに時間が掛かることも読み取れる。

各言語でメッセージダイジェスト

手元にある各言語で、メッセージダイジェストを出力してみたメモ。

要件は以下の通り。

  • 標準入力から、0バイト以上の任意のバイト列が入力される
  • 標準出力に、入力に対する以下のハッシュ文字列を改行区切りで出力する
    • MD5
    • SHA-1
    • SHA-224
    • SHA-256
    • SHA-384
    • SHA-512

環境

手元にあるものということで、環境は以下のものに限定する。

入力

Hello World!

出力

8ddd8be4b179a529afa5f2ffae4b9858
a0b65939670bc2c010f4d5d6a0b3e4e4590fb92b
de9d76f0f6a015ab6629138a42835e7b44571995e4abb291c0817261
03ba204e50d126e4674c005e04d82e84c21366780af1f43bd54a37816b6ab340
07f60df0b95043b3a3717638e7776ab76ebaa4fc705ba659063229cf162980c04a9f7496dcda50de6510d40fde3eba8a
830445e86a0cfafac4e1531002356f384847a11a7456fb8ccb81ab36e37bff28f34fa2c5bfdd347e964c5c5df0fc305de6394368219307b2ceeb0ec84b7c2b31

Java

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Main {
    public static void main(String[] args) throws NoSuchAlgorithmException {
        MessageDigest[] mdAry = {
            MessageDigest.getInstance("MD5"),
            MessageDigest.getInstance("SHA-1"),
            MessageDigest.getInstance("SHA-224"),
            MessageDigest.getInstance("SHA-256"),
            MessageDigest.getInstance("SHA-384"),
            MessageDigest.getInstance("SHA-512"),
        };
        try (InputStream in = new BufferedInputStream(System.in)) {
            byte[] b = new byte[1024];
            int len;
            while ((len = in.read(b)) > 0) {
                for (MessageDigest md : mdAry) {
                    md.update(b, 0, len);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        for (MessageDigest md : mdAry) {
            byte[] digest = md.digest();
            for (byte b : digest) {
                System.out.printf("%02x", b);
            }
            System.out.println();
        }
    }
}

C

#include <stdio.h>
#include <string.h>
#include <openssl/md5.h>
#include <openssl/sha.h>

int main(int argc, char** argv) {
    MD5_CTX md5_ctx;
    unsigned char md5[MD5_DIGEST_LENGTH];
    SHA_CTX sha1_ctx;
    unsigned char sha1[SHA_DIGEST_LENGTH];
    SHA256_CTX sha224_ctx;
    unsigned char sha224[SHA224_DIGEST_LENGTH];
    SHA256_CTX sha256_ctx;
    unsigned char sha256[SHA256_DIGEST_LENGTH];
    SHA512_CTX sha384_ctx;
    unsigned char sha384[SHA384_DIGEST_LENGTH];
    SHA512_CTX sha512_ctx;
    unsigned char sha512[SHA512_DIGEST_LENGTH];

    if (!MD5_Init(&md5_ctx)) {
        return 1;
    }
    if (!SHA1_Init(&sha1_ctx)) {
        return 1;
    }
    if (!SHA224_Init(&sha224_ctx)) {
        return 1;
    }
    if (!SHA256_Init(&sha256_ctx)) {
        return 1;
    }
    if (!SHA384_Init(&sha384_ctx)) {
        return 1;
    }
    if (!SHA512_Init(&sha512_ctx)) {
        return 1;
    }

    char buf[1024];
    int len;
    while ((len = fread(buf, sizeof(char), sizeof(buf), stdin)) > 0) {
        if (!MD5_Update(&md5_ctx, buf, len)) {
            return 1;
        }
        if (!SHA1_Update(&sha1_ctx, buf, len)) {
            return 1;
        }
        if (!SHA224_Update(&sha224_ctx, buf, len)) {
            return 1;
        }
        if (!SHA256_Update(&sha256_ctx, buf, len)) {
            return 1;
        }
        if (!SHA384_Update(&sha384_ctx, buf, len)) {
            return 1;
        }
        if (!SHA512_Update(&sha512_ctx, buf, len)) {
            return 1;
        }
    }

    if (!MD5_Final(md5, &md5_ctx)) {
        return 1;
    }
    if (!SHA1_Final(sha1, &sha1_ctx)) {
        return 1;
    }
    if (!SHA224_Final(sha224, &sha224_ctx)) {
        return 1;
    }
    if (!SHA256_Final(sha256, &sha256_ctx)) {
        return 1;
    }
    if (!SHA384_Final(sha384, &sha384_ctx)) {
        return 1;
    }
    if (!SHA512_Final(sha512, &sha512_ctx)) {
        return 1;
    }

    for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", md5[i]);
    }
    fprintf(stdout, "\n");
    for (int i = 0; i < SHA_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", sha1[i]);
    }
    fprintf(stdout, "\n");
    for (int i = 0; i < SHA224_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", sha224[i]);
    }
    fprintf(stdout, "\n");
    for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", sha256[i]);
    }
    fprintf(stdout, "\n");
    for (int i = 0; i < SHA384_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", sha384[i]);
    }
    fprintf(stdout, "\n");
    for (int i = 0; i < SHA512_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", sha512[i]);
    }
    fprintf(stdout, "\n");

    return 0;
}

「-lcrypto」を付けてコンパイルする必要がある。

C++

C言語と大して変わらないけど、一応載せておく。

#include <cstdio>
#include <string>
#include <openssl/md5.h>
#include <openssl/sha.h>

int main(int argc, char** argv) {
    MD5_CTX md5_ctx;
    unsigned char md5[MD5_DIGEST_LENGTH];
    SHA_CTX sha1_ctx;
    unsigned char sha1[SHA_DIGEST_LENGTH];
    SHA256_CTX sha224_ctx;
    unsigned char sha224[SHA224_DIGEST_LENGTH];
    SHA256_CTX sha256_ctx;
    unsigned char sha256[SHA256_DIGEST_LENGTH];
    SHA512_CTX sha384_ctx;
    unsigned char sha384[SHA384_DIGEST_LENGTH];
    SHA512_CTX sha512_ctx;
    unsigned char sha512[SHA512_DIGEST_LENGTH];

    if (!MD5_Init(&md5_ctx)) {
        return EXIT_FAILURE;
    }
    if (!SHA1_Init(&sha1_ctx)) {
        return EXIT_FAILURE;
    }
    if (!SHA224_Init(&sha224_ctx)) {
        return EXIT_FAILURE;
    }
    if (!SHA256_Init(&sha256_ctx)) {
        return EXIT_FAILURE;
    }
    if (!SHA384_Init(&sha384_ctx)) {
        return EXIT_FAILURE;
    }
    if (!SHA512_Init(&sha512_ctx)) {
        return EXIT_FAILURE;
    }

    char buf[1024];
    int len;
    while ((len = fread(buf, sizeof(char), sizeof(buf), stdin)) > 0) {
        if (!MD5_Update(&md5_ctx, buf, len)) {
            return EXIT_FAILURE;
        }
        if (!SHA1_Update(&sha1_ctx, buf, len)) {
            return EXIT_FAILURE;
        }
        if (!SHA224_Update(&sha224_ctx, buf, len)) {
            return EXIT_FAILURE;
        }
        if (!SHA256_Update(&sha256_ctx, buf, len)) {
            return EXIT_FAILURE;
        }
        if (!SHA384_Update(&sha384_ctx, buf, len)) {
            return EXIT_FAILURE;
        }
        if (!SHA512_Update(&sha512_ctx, buf, len)) {
            return EXIT_FAILURE;
        }
    }

    if (!MD5_Final(md5, &md5_ctx)) {
        return EXIT_FAILURE;
    }
    if (!SHA1_Final(sha1, &sha1_ctx)) {
        return EXIT_FAILURE;
    }
    if (!SHA224_Final(sha224, &sha224_ctx)) {
        return EXIT_FAILURE;
    }
    if (!SHA256_Final(sha256, &sha256_ctx)) {
        return EXIT_FAILURE;
    }
    if (!SHA384_Final(sha384, &sha384_ctx)) {
        return EXIT_FAILURE;
    }
    if (!SHA512_Final(sha512, &sha512_ctx)) {
        return EXIT_FAILURE;
    }

    for (int i = 0; i < MD5_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", md5[i]);
    }
    fprintf(stdout, "\n");
    for (int i = 0; i < SHA_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", sha1[i]);
    }
    fprintf(stdout, "\n");
    for (int i = 0; i < SHA224_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", sha224[i]);
    }
    fprintf(stdout, "\n");
    for (int i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", sha256[i]);
    }
    fprintf(stdout, "\n");
    for (int i = 0; i < SHA384_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", sha384[i]);
    }
    fprintf(stdout, "\n");
    for (int i = 0; i < SHA512_DIGEST_LENGTH; ++i) {
        fprintf(stdout, "%02x", sha512[i]);
    }
    fprintf(stdout, "\n");

    return EXIT_SUCCESS;
}

「-lcrypto」を付けてコンパイルする必要がある。

PHP

<?php

$input = file_get_contents('php://stdin');

printf("%s\n", hash('md5', $input));
printf("%s\n", hash('sha1', $input));
printf("%s\n", hash('sha224', $input));
printf("%s\n", hash('sha256', $input));
printf("%s\n", hash('sha384', $input));
printf("%s\n", hash('sha512', $input));

メモリに載りきらないほどのデータが来たら対応できないのが難点。

Python 2

import sys
import hashlib

md5 = hashlib.md5()
sha1 = hashlib.sha1()
sha224 = hashlib.sha224()
sha256 = hashlib.sha256()
sha384 = hashlib.sha384()
sha512 = hashlib.sha512()

while True:
    line = sys.stdin.readline()
    if line == '':
        break
    md5.update(line)
    sha1.update(line)
    sha224.update(line)
    sha256.update(line)
    sha384.update(line)
    sha512.update(line)

print md5.hexdigest()
print sha1.hexdigest()
print sha224.hexdigest()
print sha256.hexdigest()
print sha384.hexdigest()
print sha512.hexdigest()

Python 3

import sys
import hashlib

md5 = hashlib.md5()
sha1 = hashlib.sha1()
sha224 = hashlib.sha224()
sha256 = hashlib.sha256()
sha384 = hashlib.sha384()
sha512 = hashlib.sha512()

while True:
    line = sys.stdin.buffer.readline()
    if line == b'':
        break
    md5.update(line)
    sha1.update(line)
    sha224.update(line)
    sha256.update(line)
    sha384.update(line)
    sha512.update(line)

print(md5.hexdigest())
print(sha1.hexdigest())
print(sha224.hexdigest())
print(sha256.hexdigest())
print(sha384.hexdigest())
print(sha512.hexdigest())

Ruby

require 'openssl'

mdAry = [
    OpenSSL::Digest.new("md5"),
    OpenSSL::Digest.new("sha1"),
    OpenSSL::Digest.new("sha224"),
    OpenSSL::Digest.new("sha256"),
    OpenSSL::Digest.new("sha384"),
    OpenSSL::Digest.new("sha512"),
]

while line = STDIN.gets
    for md in mdAry
        md.update(line)
    end
end

for md in mdAry
    puts md.hexdigest
end

Perl

use Digest::MD5;
use Digest::SHA;

my $md5 = Digest::MD5->new();
my $sha1 = Digest::SHA->new("sha1");
my $sha224 = Digest::SHA->new("sha224");
my $sha256 = Digest::SHA->new("sha256");
my $sha384 = Digest::SHA->new("sha384");
my $sha512 = Digest::SHA->new("sha512");

while (my $line = readline(STDIN)) {
    $md5->add($line);
    $sha1->add($line);
    $sha224->add($line);
    $sha256->add($line);
    $sha384->add($line);
    $sha512->add($line);
}

print $md5->hexdigest(),"\n";
print $sha1->hexdigest(),"\n";
print $sha224->hexdigest(),"\n";
print $sha256->hexdigest(),"\n";
print $sha384->hexdigest(),"\n";
print $sha512->hexdigest(),"\n";

yum install perl-Digest-MD5 perl-Digest-SHA」が必要。

Go

package main

import (
    "fmt"
    "io"
    "os"
    "crypto/md5"
    "crypto/sha1"
    "crypto/sha256"
    "crypto/sha512"
)

func main() {
    aMd5 := md5.New()
    aSha1 := sha1.New()
    aSha224 := sha256.New224()
    aSha256 := sha256.New()
    aSha384 := sha512.New384()
    aSha512 := sha512.New()

    for {
        buf := make([]byte, 1024)
        n, err := os.Stdin.Read(buf)
        if err == io.EOF {
            break
        }
        buf2 := make([]byte, 0);
        buf2 = append(buf2, buf[0:n]...)

        aMd5.Write(buf2)
        aSha1.Write(buf2)
        aSha224.Write(buf2)
        aSha256.Write(buf2)
        aSha384.Write(buf2)
        aSha512.Write(buf2)
    }

    fmt.Printf("%x\n", aMd5.Sum(nil))
    fmt.Printf("%x\n", aSha1.Sum(nil))
    fmt.Printf("%x\n", aSha224.Sum(nil))
    fmt.Printf("%x\n", aSha256.Sum(nil))
    fmt.Printf("%x\n", aSha384.Sum(nil))
    fmt.Printf("%x\n", aSha512.Sum(nil))
}

bash

#! /bin/bash

cat | tee \
    >(sha512sum | cut -d " " -f 1) \
    >(sha384sum | cut -d " " -f 1) \
    >(sha256sum | cut -d " " -f 1) \
    >(sha224sum | cut -d " " -f 1) \
    >(sha1sum | cut -d " " -f 1) \
    >(md5sum | cut -d " " -f 1) \
    >> /dev/null

バイナリデータのテストケースにも対応するためにteeコマンド+コマンド置換を使ったが、期待する順番通りに出力されないことがあるのが難点。

  • man md5sum
  • man sha1sum
  • man sha224sum
  • man sha256sum
  • man sha384sum
  • man sha512sum

各言語でbase64エンコード/デコード

手元にある各言語で、base64エンコード/デコードをしてみたメモ。

要件は以下の通り。

  • 標準入力は以下の通り構成される
    • 1行目はASCII文字(空白文字を含む)からなる文字列が与えられる
    • 2行目はbase64エンコードされた文字列が与えられる
    • 3行目以降はMIME形式に従ってbase64エンコードされた文字列が与えられる
  • 入力として与えられる「base64エンコードされた文字列」をbase64デコードしても、得られるのはASCII文字(空白文字を含む)のみからなる文字列とする
  • 入力される各行の長さは不明とする(つまり上限設定なし)
  • 入力の不正チェックは不要(上記仕様に従った入力が必ず与えられるとしてよい)
  • 標準出力には以下を出力する
    • 入力の1行目をMIME形式に従ってbase64エンコードした結果の文字列(複数行にわたることがある)
    • 入力の1行目を改行無しでbase64エンコードした結果の文字列
    • 入力の2行目をbase64デコードした結果の文字列
    • 入力の3行目以降をbase64デコードした結果の文字列
    • 各出力の末尾には改行コードを入れること

環境

手元にあるものということで、環境は以下のものに限定する。

なお、以下の言語は自前実装しなきゃならなくて辛いので割愛。

入力

od -cした結果で示す。

0000000   a   b   c   d   e   f   g   h   i   j   k   l   m   n   o   p
0000020   q   r   s   t   u   v   w   x   y   z   A   B   C   D   E   F
0000040   G   H   I   J   K   L   M   N   O   P   Q   R   S   T   U   V
0000060   W   X   Y   Z   0   1   2   3   4   5   6   7   8   9  \n   Y
0000100   W   J   j   Z   G   V   m   Z   2   h   p   a   m   t   s   b
0000120   W   5   v   c   H   F   y   c   3   R   1   d   n   d   4   e
0000140   X   p   B   Q   k   N   E   R   U   Z   H   S   E   l   K   S
0000160   0   x   N   T   k   9   Q   U   V   J   T   V   F   V   W   V
0000200   1   h   Z   W   j   A   x   M   j   M   0   N   T   Y   3   O
0000220   D   k   =  \n   Y   W   J   j   Z   G   V   m   Z   2   h   p
0000240   a   m   t   s   b   W   5   v   c   H   F   y   c   3   R   1
0000260   d   n   d   4   e   X   p   B   Q   k   N   E   R   U   Z   H
0000300   S   E   l   K   S   0   x   N   T   k   9   Q   U   V   J   T
0000320   V   F   V   W   V   1   h   Z   W   j   A   x   M   j   M   0
0000340  \r  \n   N   T   Y   3   O   D   k   =  \r  \n
0000354

人間に分かりやすく示すと大体以下のような感じ。

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3ODk=
YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0
NTY3ODk=

ただし3行目以降の各行は「CR LF」で終わっていることに注意。

出力

これもod -cした結果で示すと以下のような感じ。

0000000   Y   W   J   j   Z   G   V   m   Z   2   h   p   a   m   t   s
0000020   b   W   5   v   c   H   F   y   c   3   R   1   d   n   d   4
0000040   e   X   p   B   Q   k   N   E   R   U   Z   H   S   E   l   K
0000060   S   0   x   N   T   k   9   Q   U   V   J   T   V   F   V   W
0000100   V   1   h   Z   W   j   A   x   M   j   M   0  \r  \n   N   T
0000120   Y   3   O   D   k   =  \r  \n   Y   W   J   j   Z   G   V   m
0000140   Z   2   h   p   a   m   t   s   b   W   5   v   c   H   F   y
0000160   c   3   R   1   d   n   d   4   e   X   p   B   Q   k   N   E
0000200   R   U   Z   H   S   E   l   K   S   0   x   N   T   k   9   Q
0000220   U   V   J   T   V   F   V   W   V   1   h   Z   W   j   A   x
0000240   M   j   M   0   N   T   Y   3   O   D   k   =  \n   a   b   c
0000260   d   e   f   g   h   i   j   k   l   m   n   o   p   q   r   s
0000300   t   u   v   w   x   y   z   A   B   C   D   E   F   G   H   I
0000320   J   K   L   M   N   O   P   Q   R   S   T   U   V   W   X   Y
0000340   Z   0   1   2   3   4   5   6   7   8   9  \n   a   b   c   d
0000360   e   f   g   h   i   j   k   l   m   n   o   p   q   r   s   t
0000400   u   v   w   x   y   z   A   B   C   D   E   F   G   H   I   J
0000420   K   L   M   N   O   P   Q   R   S   T   U   V   W   X   Y   Z
0000440   0   1   2   3   4   5   6   7   8   9  \n
0000453

人間に分かりやすく示すと大体以下のような感じ。

YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0
NTY3ODk=
YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3ODk=
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

ただし、上の例でいうところの1~2行目の末尾は「CR LF」で終わっていることに注意。

Java

import java.io.IOException;
import java.io.PrintStream;
import java.util.Base64;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        // 今回はASCII文字しかない前提なので手抜きでScannerを使っている
        try (Scanner sc = new Scanner(System.in);
            PrintStream out = new PrintStream(System.out)
        ) {
            String line1 = sc.nextLine();

            // MIME format(encode)
            Base64.Encoder mimeEncoder = Base64.getMimeEncoder(/*76, "\n".getBytes()*/);
            out.write(mimeEncoder.encode(line1.getBytes()));
            out.print("\r\n"); // MimeEncoderでは最後の行にCR LFを付加しないので、その分を補う

            // no wrap format(encode)
            Base64.Encoder encoder = Base64.getEncoder();
            out.write(encoder.encode(line1.getBytes()));
            out.println();

            // no wrap format(decode)
            String line21 = sc.nextLine();
            Base64.Decoder decoder = Base64.getDecoder();
            out.write(decoder.decode(line21));
            out.println();

            // MIME format(decode)
            Base64.Decoder mimeDecoder = Base64.getMimeDecoder();
            StringBuilder sb = new StringBuilder();
            while (sc.hasNextLine()) {
                sb.append(sc.nextLine());
                sb.append("\r\n"); // わざとMIME形式を復元してみる
            }
            String line22 = sb.toString();
            out.write(mimeDecoder.decode(line22));
            out.println();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

PHP

<?php

$lines = file('php://stdin');

// MIME format(encode)
$line1 = array_shift($lines);
$line1 = preg_replace("/[\r\n]/", "", $line1);
print chunk_split(base64_encode($line1)/*, 76, "\n"*/);

// no wrap format(encode)
print base64_encode($line1) . PHP_EOL;

// no wrap format(decode)
$line21 = array_shift($lines);
$line21 = preg_replace("/[\r\n]/", "", $line21);
print base64_decode($line21) . PHP_EOL;

// MIME format(decode)
$line22 = implode('', $lines);
$line22 = preg_replace("/[\r\n]/", "", $line22);
print base64_decode($line22) . PHP_EOL;

Python 2

import sys
import base64

line1 = sys.stdin.readline()
line1 = line1[0:len(line1) - 1] # remove LF
res1 = base64.b64encode(line1)

# MIME format(encode)
for i in range(0, len(res1), 76):
    print res1[i:i + 76] + "\r"

# no wrap format(encode)
print res1

# no wrap format(decode)
line21 = sys.stdin.readline()
print base64.b64decode(line21)

# MIME format(decode)
line22 = ''
while True:
    tmp = sys.stdin.readline()
    if tmp == '':
        break
    line22 = line22 + tmp
print base64.b64decode(line22)

Python 3

import sys
import base64

line1 = sys.stdin.buffer.readline()
line1 = line1[0:len(line1) - 1] # remove LF
res1 = base64.b64encode(line1)

# MIME format(encode)
for i in range(0, len(res1), 76):
    print(res1[i:i + 76].decode() + "\r")

# no wrap format(encode)
print(res1.decode())

# no wrap format(decode)
line21 = sys.stdin.buffer.readline()
print(base64.b64decode(line21).decode())

# MIME format(decode)
line22 = b''
while True:
    tmp = sys.stdin.buffer.readline()
    if tmp == b'':
        break
    line22 = line22 + tmp
print(base64.b64decode(line22).decode())

Ruby

require 'base64'

line1 = STDIN.gets
line1 = line1[0, line1.length() - 1]
res1 = Base64.strict_encode64(line1)

# MIME format(encode)
i = 0
while i < res1.length()
    print res1[i, 76],"\r\n"
    i += 76
end

# no wrap format(encode)
print res1,"\n"

# no wrap format(decode)
line21 = STDIN.gets
line21 = line21[0, line21.length() - 1]
print Base64.strict_decode64(line21),"\n"

# MIME format(decode)
line22 = ""
while tmp = STDIN.gets
    line22 = line22 + tmp
end
print Base64.decode64(line22),"\n"

Perl

use MIME::Base64;

my $line1 = readline(STDIN);
chomp($line1);

# MIME format(encode)
print encode_base64($line1, "\r\n");

# no wrap format(encode)
print encode_base64($line1, ""),"\n";

# no wrap format(decode)
my $line21 = readline(STDIN);
chomp($line21);
print decode_base64($line21),"\n";

# MIME format(decode)
my $line22 = "";
while (my $tmp = readline(STDIN)) {
    $line22 = $line22 . $tmp;
}
print decode_base64($line22),"\n";

Go

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "encoding/base64"
)

func ReadLine(reader *bufio.Reader) (bytes []byte, err error) {
    prefix := false
    buf := make([]byte, 0, 1024)
    line := make([]byte, 0, 1)
    for {
        line, prefix, err = reader.ReadLine()
        if err == io.EOF {
            return
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        bytes = buf
        return
    }
}

func main() {
    stdin := bufio.NewReader(os.Stdin)

    line1, err := ReadLine(stdin)
    if err == io.EOF {
        panic(err)
    }
    len1 := base64.StdEncoding.EncodedLen(len(line1))
    res1 := make([]byte, len1);
    base64.StdEncoding.Encode(res1, line1)

    // MIME format(encode)
    for i := 0; i < len(res1); i += 76 {
        end := i + 76
        if end > len(res1) {
            end = len(res1)
        }
        fmt.Println(string(res1[i:end]) + "\r")
    }

    // no wrap format(encode)
    fmt.Println(string(res1))

    // no wrap format(decode)
    line21, err := ReadLine(stdin)
    if err == io.EOF {
        panic(err)
    }
    // base64.StdEncoding.DecodedLenが返す値はあくまで最大値なので、
    // パディングがあったりすると、それよりも短くなることがあるので、
    // Decodeしたときの返り値で実際の長さを取得して切り詰めてやる必要がある。
    len21 := base64.StdEncoding.DecodedLen(len(line21))
    res21 := make([]byte, len21)
    resLen21, _ := base64.StdEncoding.Decode(res21, line21)
    fmt.Println(string(res21[0:resLen21]))

    // MIME format(decode)
    line22 := make([]byte, 0)
    for {
        tmp, err := ReadLine(stdin)
        if err == io.EOF {
            break
        }
        line22 = append(line22, tmp...)
    }
    len22 := base64.StdEncoding.DecodedLen(len(line22))
    res22 := make([]byte, len22)
    resLen22, _ := base64.StdEncoding.Decode(res22, line22)
    fmt.Println(string(res22[0:resLen22]))
}

bash

#! /bin/bash

IFS= read -r line1

# MIME format(encode)
#     base64コマンドは"\n"を付加するので、sedで"\r\n"に変えてやる
echo -n "${line1}" | base64 | sed -e 's/$/\r/'

# no wrap format(encode)
echo -n "${line1}" | base64 -w 0
echo

# no wrap format(decode)
IFS= read -r line21
echo -n "${line21}" | base64 -d
echo

# MIME format(decode)
while IFS=$'\r' read -r tmp ; do
    line22="${line22}${tmp}"
done
echo -n "${line22}" | base64 -d
echo
  • man base64

各言語での1行読み込み時に得られる文字列の違い

手元にある各言語で、「1行」を読み込んだ時の文字列の違い、とりわけ改行コードの扱いについて調べてみたメモ。

環境

手元にあるものということで、環境は以下のものに限定する。

繰り返すが、環境は「CentOS 7」。ということで、改行コードは「LF」(一般に"\n"で表されることが多い)が標準となっている環境。

入力ファイル

od -cした結果で示す。

0000000   h   o   g   e  \r  \n   f   o   o  \r   b   a   r  \n   e   n
0000020   d
0000021

改行コードとして一般的に知られている3つのパターンを含み、かつファイルの末尾には改行コードが無いというファイルである。

  • "hoge"の後には、Windows系のOSで標準とされる改行コード「CR LF」
  • "foo"の後には、昔の(?)MacOS系のOSで標準とされる改行コード「CR」
  • "bar"の後には、UNIX/Linux系のOSで標準とされる改行コード「LF」
  • "end"の後には、いきなりEOF

プログラムでの処理

各言語で書くプログラムは、入力の末端に到達したと判定されるまで「1行」を読み込み、先頭にスラッシュ('/')を付加して「1行」の区切りが分かるようにしたうえでそのまま出力。その際に追加の改行コードが出力されないような方法で出力する。

出力パターン

答えを先に言ってしまう形になるが、試したケースで計6パターンの出力が得られた。 以下のような感じである。

  • パターン1:いわゆる改行コードはすべて認識し、得られる文字列からは取り除かれる
0000000   /   h   o   g   e   /   f   o   o   /   b   a   r   /   e   n
0000020   d
0000021
  • パターン2:「LF」が改行コードと認識され、かつ得られる文字列に改行コードが含まれる
0000000   /   h   o   g   e  \r  \n   /   f   o   o  \r   b   a   r  \n
0000020   /   e   n   d
0000024
  • パターン3:「LF」が改行コードと認識され、得られる文字列からは取り除かれる
0000000   /   h   o   g   e  \r   /   f   o   o  \r   b   a   r   /   e
0000020   n   d
0000022
  • パターン4:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれる
0000000   /   h   o   g   e   /   f   o   o  \r   b   a   r   /   e   n
0000020   d
0000021
  • パターン5:「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
0000000   /   h   o   g   e  \r   /   f   o   o  \r   b   a   r
0000016
  • パターン6:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
0000000   /   h   o   g   e   /   f   o   o  \r   b   a   r
0000015

Java

念のため、BufferedReader版とScanner版の両方を試した。

BufferedReader版

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String buf;
            while ((buf = in.readLine()) != null) {
                out.print('/');
                out.print(buf);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Scanner版

import java.io.PrintWriter;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            while (in.hasNextLine()) {
                String buf = in.nextLine();
                out.print('/');
                out.print(buf);
            }
        }
    }
}

C

#include <stdio.h>

int main(int argc, char** argv) {
    char buf[1024];

    while (fgets(buf, sizeof(buf), stdin)) {
        printf("/");
        printf("%s", buf);
    }

    return 0;
}

C++

念のため、cin.getlineとstringヘッダのgetlineの両方を試した。

cin.getline

#include <iostream>

using namespace std;

int main(int argc, char** argv) {
    char buf[1024];

    while (cin.getline(buf, sizeof(buf))) {
        cout << '/';
        cout << buf;
    }

    return EXIT_SUCCESS;
}

stringヘッダのgetline

#include <iostream>
#include <string>

using namespace std;

int main(int argc, char** argv) {
    string buf;

    while (getline(cin, buf)) {
        cout << '/';
        cout << buf;
    }

    return EXIT_SUCCESS;
}

PHP

<?php

$lines = file('php://stdin');

foreach ($lines as $line) {
    echo '/' . $line;
}

Python 2

import sys

while True:
    line = sys.stdin.readline()
    if line == '':
        break
    sys.stdout.write('/')
    sys.stdout.write(line)

print文を使うと、末尾に勝手に改行コードが挿入されるので、sys.stdout.writeを使う。

Python 3

import sys

while True:
    line = sys.stdin.readline()
    if line == '':
        break
    sys.stdout.write('/')
    sys.stdout.write(line)

print文を使うと、末尾に勝手に改行コードが挿入されるので、sys.stdout.writeを使う。

Ruby

while line = STDIN.gets
    print '/'
    print line
end

Perl

while (my $line = readline(STDIN)) {
    print '/';
    print $line;
}

Go

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func ReadLine(reader *bufio.Reader) (str string, err error) {
    prefix := false
    buf := make([]byte, 0, 1024)
    line := make([]byte, 0, 1)
    for {
        line, prefix, err = reader.ReadLine()
        if err == io.EOF {
            return
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        str = string(buf)
        return
    }
}

func main() {
    stdin := bufio.NewReader(os.Stdin)

    for {
        line, err := ReadLine(stdin)
        if err == io.EOF {
            break
        }

        fmt.Print("/")
        fmt.Print(line)
    }
}

bash

環境変数IFSの値によって挙動が変わるだろうということで、3パターン試した。

IFS=$'\n'

#! /bin/bash

while IFS=$'\n' read line ; do
    echo -n /
    echo -n ${line}
done

IFS=$'\r'

#! /bin/bash

while IFS=$'\r' read line ; do
    echo -n /
    echo -n ${line}
done

IFS=

#! /bin/bash

while IFS= read line ; do
    echo -n /
    echo -n ${line}
done

Awk

{
    printf("/");
    printf("%s", $0);
}

sed

言語なのかという突っ込みがありそうだが、行処理なら任せろというツールの一つであるので外せないということで。

s|^|/|

行頭にスラッシュを挿入するという単純な正規表現

まとめると‥

  • パターン1:いわゆる改行コードはすべて認識し、得られる文字列からは取り除かれる
  • パターン2:「LF」が改行コードと認識され、かつ得られる文字列に改行コードが含まれる
  • パターン3:「LF」が改行コードと認識され、得られる文字列からは取り除かれる
  • パターン4:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれる
  • パターン5:「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
  • パターン6:「CR LF」と「LF」が改行コードと認識され、得られる文字列からは取り除かれ、さらに改行コードの前にEOFが来ると末端と認識されてしまう
言語 1 2 3 4 5 6
Java(BufferedReader) O
Java(Scanner) O
C O
C++(cin.getline) O
C++(getline) O
PHP O
Python 2 O
Python 3 O
Ruby O
Perl O
Go O
bash(IFS=$'\n') O
bash(IFS=$'\r') O
bash(IFS=) O
Awk O
sed O

bashで「end」が出力されないというのは予想外だった。

bashを除くと、Go言語がちょっと特殊。

各言語でMap

手元にある各言語で、Map構造を使うプログラムを書いてみようと思ったメモ。

Map構造と言っても、「連想配列」がその言語にあればそれを使う。

要件は以下の通り。

  • 標準入力は、以下のデータから構成される
    • 1行目に、空白区切りのキーワードがいくつか入力される
    • 2行目以降は、1行に空白区切りの文字列が2つ書かれた行が入力される
  • 2行目以降の2つの文字列をkey-valueとしてmapに放り込む
  • 不正入力のチェックは不要とする
  • 標準出力には、1行目で指定されたキーワードに対応する値を改行区切りで出力する

環境

手元にあるものということで、環境は以下のものに限定する。

なお、そもそもが「使ってみようシリーズ」だったことを思い出し、今回はC言語bashにはお休みしてもらうことにした。

入力ファイルの例

  • 001.txt
One Six Two Five
Zero 000
One 111
Two 222
Three 333
Four 444
Five 555
Six 666
Seven 777
Eight 888
Nine 999
Eight 8
Five 5
Four 4
Nine 9
One 1
Seven 7
Six 6
Three 3
Two 2
Zero 0

期待される出力の例

  • 001.txt
1
6
2
5

Java

Javaでは、入力の処理を2パターン書いてみた。

BufferedReader版

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        try (BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String buf;

            buf = in.readLine();
            List<String> keywords = Arrays.asList(buf.split(" "));

            Map<String, String> map = new HashMap<>();

            while ((buf = in.readLine()) != null) {
                String[] tokens = buf.split(" ");
                map.put(tokens[0], tokens[1]);
            }

            for (String keyword : keywords) {
                if (map.containsKey(keyword)) {
                    out.println(map.get(keyword));
                } else {
                    out.println("");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Scanner版

import java.io.PrintWriter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String buf = in.nextLine();
            List<String> keywords = Arrays.asList(buf.split(" "));

            Map<String, String> map = new HashMap<>();

            while (in.hasNext()) {
                String key = in.next();
                String value = in.next();
                map.put(key, value);
            }

            for (String keyword : keywords) {
                if (map.containsKey(keyword)) {
                    out.println(map.get(keyword));
                } else {
                    out.println("");
                }
            }
        }
    }
}

C++

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <sstream>

using namespace std;

int main(int argc, char** argv) {
    // 1行目を読み込んで、空白区切りでリストに放り込む
    string line;
    getline(cin, line);

    stringstream ss(line);

    vector<string> keywords;
    string keyword;
    while (getline(ss, keyword, ' ')) {
        keywords.push_back(keyword);
    }

    // 2行目以降を読み込んで、マップに放り込む
    map<string, string> map;
    string key;
    string value;
    while (cin >> key >> value) {
        map[key] = value;
    }

    // 指定されたキーワードに対応する値を出力する
    for (vector<string>::iterator it = keywords.begin(); it != keywords.end(); ++it) {
        cout << map[*it] << endl;
    }

    return EXIT_SUCCESS;
}

PHP

<?php

$lines = file('php://stdin');

$keywords = split(' ', trim($lines[0]));
array_shift($lines);

$map = array();
foreach ($lines as $line) {
    $line = trim($line);
    $tmp = split(' ', $line);
    $map[$tmp[0]] = $tmp[1];
}

foreach ($keywords as $keyword) {
    if (array_key_exists($keyword, $map)) {
        printf("%s\n", $map[$keyword]);
    } else {
        printf("\n");
    }
}

Python 2

import sys

line = sys.stdin.readline()
keywords = line.split()

map = {}

while True:
    line = sys.stdin.readline()
    if line == '':
        break
    tmp = line.split()
    map[tmp[0]] = tmp[1]

for keyword in keywords:
    if keyword in map:
        print map[keyword]
    else:
        print ""

Python 3

import sys

line = sys.stdin.readline()
keywords = line.split()

map = {}

while True:
    line = sys.stdin.readline()
    if line == '':
        break
    tmp = line.split()
    map[tmp[0]] = tmp[1]

for keyword in keywords:
    if keyword in map:
        print(map[keyword])
    else:
        print("")

Ruby

line = STDIN.gets
keywords = line.split()

map = {}

while line = STDIN.gets
    tmp = line.split()
    map[tmp[0]] = tmp[1]
end

for keyword in keywords
    print map[keyword],"\n"
end

Perl

my $line = readline(STDIN);
chomp($line);
my @keywords = split(/ /, $line);

my %map = ();
while ($line = readline(STDIN)) {
    chomp($line);
    my @tmp = split(/ /, $line);
    $map{@tmp[0]} = @tmp[1];
}

foreach my $keyword(@keywords) {
    print "$map{$keyword}\n";
}

Go

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strings"
)

func ReadLine(reader *bufio.Reader) (str string, err error) {
    prefix := false
    buf := make([]byte, 0, 1024)
    line := make([]byte, 0, 1)
    for {
        line, prefix, err = reader.ReadLine()
        if err == io.EOF {
            return
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        str = string(buf)
        return
    }
}

func main() {
    stdin := bufio.NewReader(os.Stdin)

    line, err := ReadLine(stdin)
    if err == io.EOF {
        panic(err)
    }
    keywords := strings.Split(line, " ")

    aMap := map[string]string{}
    for {
        line, err := ReadLine(stdin)
        if err == io.EOF {
            break
        }
        tmp := strings.Split(line, " ")
        aMap[tmp[0]] = tmp[1]
    }

    for i := 0; i < len(keywords); i += 1 {
        value, exists := aMap[keywords[i]]
        if exists {
            fmt.Println(value)
        } else {
            fmt.Println()
        }
    }
}

ついに、指定されたReaderから1行を読み込む関数を自作した‥

各言語で日時文字列の解析

手元にある各言語で、標準入力から日時文字列を読み込んで、標準出力にUNIX TIME値を吐き出すプログラムを書いてみようと思ったメモ。

要件は以下の通り。

  • 入力される日時文字列は1つのみ
    • 不正入力のチェックは不要とする
  • OS等のタイムゾーンJST
  • 対応するUNIX TIME値を出力
    • 例えば、「2017/12/31 12:34:56 +0900」が入力されたら「1514723696」を出力

環境

手元にあるものということで、環境は以下のものに限定する。

入力ファイルの例

  • 001.txt
2017/12/31 12:34:56 +0900

期待される出力の例

  • 001.txt
1514723696

Java

import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Scanner;
import java.util.Date;

public class Main {
    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
            PrintWriter out = new PrintWriter(System.out)
        ) {
            String str = in.nextLine();

            DateFormat df = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss ZZZZZ");
            Date date = df.parse(str);

            out.println(date.getTime() / 1000);
        } catch (ParseException e) {
            e.printStackTrace();
        }
    }
}

C

#define _XOPEN_SOURCE
#include <stdio.h>
#include <time.h>

int main(int argc, char** argv) {
    char str[32];
    fgets(str, sizeof(str) - 1, stdin);

    struct tm timeinfo;
    strptime(str, "%Y/%m/%d %H:%M:%S %z", &timeinfo);
    long second = mktime(&timeinfo);

    printf("%d\n", second);

    return 0;
}

strptimeを使ったソースを警告も出さないようにコンパイルするためには、マクロ定数を定義してやる必要がある。

上記を見ると、%zについては、「glibc での注意」として書いてあるが、「サポートさせようとしているが、多くの場合、tmフィールドは変更されない」らしい、つまり無視される、と。 実際、「+0000」を与えて試してみたら、見事に無視されて、tmフィールドのタイムゾーン情報には"JST"が入っていた・・・

C++

#include <ctime>
#include <iostream>

using namespace std;

int main(int argc, char** argv) {
    char str[32];
    cin.getline(str, sizeof(str));

    struct tm timeinfo;
    strptime(str, "%Y/%m/%d %H:%M:%S %z", &timeinfo);
    long second = mktime(&timeinfo);

    cout << second << endl;

    return EXIT_SUCCESS;
}

上記を見ると、%zについては、「glibc での注意」として書いてあるが、「サポートさせようとしているが、多くの場合、tmフィールドは変更されない」らしい、つまり無視される、と。 実際、「+0000」を与えて試してみたら、見事に無視されて、tmフィールドのタイムゾーン情報には"JST"が入っていた・・・

PHP

(2017/12/07)DateTimeクラス版を書いてみたので追記。大して変わらないけど(ぉ

strtotime()版

<?php

$lines = file('php://stdin');
$str = $lines[0];

date_default_timezone_set("Asia/Tokyo");
$second = strtotime($str);

printf("%d\n", $second);

DateTimeクラス版

<?php

$lines = file('php://stdin');
$str = $lines[0];

date_default_timezone_set("Asia/Tokyo");
$dt = new DateTime($str);
$second = $dt->getTimestamp();

printf("%d\n", $second);

Python 2

import sys
import re
from datetime import datetime
import time

str = sys.stdin.readline()
str = re.sub(r' [+][0-9][0-9][0-9][0-9][\r]*\n', "", str)

dt = datetime.strptime(str, "%Y/%m/%d %H:%M:%S")
second = int(time.mktime(dt.timetuple()))

print second

最初、%zを入れたら、ValueError: 'z' is a bad directive in format '%Y/%m/%d %H:%M:%S %z'って怒られた‥

次に、%zを外したら、ValueError: unconverted data remains: +0900って怒られた‥

なので、時差を指定している部分を正規表現による文字列置換で削除‥当然、JST扱いされるので、「+0000」を与えたテストケースは通らない‥

Python 3

import sys
import re
from datetime import datetime
import time

str = sys.stdin.readline()
str = re.sub(r'[\r]*\n', "", str)

dt = datetime.strptime(str, "%Y/%m/%d %H:%M:%S %z")
second = int(dt.timestamp())

print(second)

Ruby

require 'time'

str = STDIN.gets

second = Time.parse(str).to_i

print second,"\n"

Perl

DateTime::Format::Strptime版

use DateTime::Format::Strptime;

my $str = readline(STDIN);

my $strp = DateTime::Format::Strptime->new(
    pattern => "%Y/%m/%d %H:%M:%S %z"
);
my $second = $strp->parse_datetime($str)->epoch;

print $second,"\n";

これをやるには、「yum install perl-DateTime-Format-Strptime」をしておく必要がある。

Time::Piece版

use Time::Piece;

my $str = readline(STDIN);
chomp($str);

my $dt = Time::Piece->strptime($str, "%Y/%m/%d %H:%M:%S %z");
my $second = $dt->epoch;

print $second,"\n";

これをやるには、「yum install perl-Time-Piece」をしておく必要がある。

Go

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "time"
)

func main() {
    stdin := bufio.NewReader(os.Stdin)
    buf := make([]byte, 0, 1024)
    str := ""
    for {
        line, prefix, err := stdin.ReadLine()
        if err == io.EOF {
            break
        }
        buf = append(buf, line...)
        if prefix {
            continue
        }
        str = string(buf)
        break
    }

    t, err := time.Parse("2006/01/02 15:04:05 -0700", str)
    if err != nil {
        panic(err)
    }

    fmt.Println(t.Unix())
}

Go言語の日時のフォーマット指定子の仕様は何度見ても謎だ‥

bash

#! /bin/bash

IFS=$'\n' read str

date -d "${str}" +"%s"
  • man date(ぉ