標準入力から安全に文字列を受け取る方法いろいろ

# <font color="lightblue">予備知識</font>

最初に， [`fgets`]&#32;[`scanf`]&#32;[`printf`] などに共通する **テキストモード** を確認しておきましょう。これは，OSごとに違う改行コード

|OS|俗称|エスケープ表記|
|:---|:---|:---|
|Windows|CRLF|`\r\n`|
|Linux<br />macOS<br />FreeBSD<br />　　　　　　　など|LF|`\n`|
|Macintosh|CR|`\r`|

**これらすべてをC言語側からは `\n` として扱えるようにしてくれる特性を持ちます。**便利ですね。

# <font color="lightpink">指定サイズ内で1行を受け取る</font>

格納領域が次のように256文字ぶん用意されていると仮定します。

```c
char buffer[256];
```

[`scanf`]と[`fgets`]の主要な違いを簡単にまとめると以下のようになります。NULL文字`\0`が終端に自動付与されることを考慮すると，ユーザが実質入力できるのは255文字までであることに注意します。

|観点|[`scanf`]|[`fgets`]|
|:----|:----|:----|
|基本的な使用法|`scanf("%255s", buffer)`|`fgets(buffer, 256, stdin)`|
|改行の扱い|改行の直前まで読み込む|改行も一緒に読み込む|
|成功時の返り値|読み込んだパラメータ数|`buffer`|
|失敗時の返り値|0 または `EOF` (-1) | `NULL`|
|入力文字数の取得|できる|できない|

[`scanf`]は`"%s"`のようにサイズ指定せずに使うこともできますが，[バッファオーバーラン]の危険性があるので出来るだけちゃんと書きましょう。


## [`scanf`]の利用

[`scanf`]は多機能なので動作のカスタマイズが可能です。そのままの状態では[`fgets`]の劣化版のように見えてしまいますが，フォーマットをちゃんと書くことで実用性が向上します。

- `%s`はすべての空白文字を無視します。これには半角スペースも含まれています。そこで，`%s`の代わりに`%[^\n]`と書くことで，無視する対象を改行だけに限定することができます。`%255[^\n]`は，改行以外を1〜255文字読み込むことを意味します。
- `%`の代わりに`%*`を使うと，その部分を読み飛ばすことができます。1行に256文字以上入力されたとき，最初の255文字までを受け取って残りを捨てたい場合は，**`%255[^\n]%*[^\n]`**と書きます。
- さらに次に[`scanf`]を使う場合に備えて，残った改行は**`%*c`**で読み飛ばします。これで保持されていた入力データはすべて処理されたことになります。<ins>但し，次に読み取るものが`%d`など**数値**である場合には，これをしなくても特に影響はありません。</ins>

また，[`scanf`]は正常に読み取って変数に格納できたパラメータの数を返すので，これを成功したかどうかの判定に使うことができます。 **`%255[^\n]%*[^\n]`** で実行した場合の返り値は以下のようになります。

- 1文字以上読み取れたときは **1**
- 改行だけが入力されたときは **0**
- 最初から <kbd>Ctrl+D</kbd> (Windowsの場合は <kbd>Ctrl+Z</kbd>) でEOFが入力されたときは `EOF`<br />(この定数の値は **-1**)

失敗したときにはそのまま **`return 1;`** してプログラムを異常終了扱いにさせておくとよいでしょう。

```c:必ず1文字以上入力させる場合
#include <stdio.h>

int main(void)
{
    char buffer[256];

    printf("Input: ");
    if (scanf("%255[^\n]%*[^\n]", buffer) != 1) {
        return 1;
    }
    scanf("%*c");

    printf("Output: %s\n", buffer);
    return 0;
}
```

```c:改行だけの入力を認める場合<br><br><strong>(但しあらかじめ空文字列を格納しておく必要がある)</strong>
#include <stdio.h>

int main(void)
{
    char buffer[256] = "";

    printf("Input: ");
    if (scanf("%255[^\n]%*[^\n]", buffer) == EOF) {
        return 1;
    }
    scanf("%*c");

    printf("Output: %s\n", buffer);
    return 0;
}
```

```c:入力された文字数も欲しい場合<br><br>(<code>%255[^\n]</code>&#32;の後ろに，unsigned/intの場合は<strong><code>%n</code></strong>，size_t/ssize_tの場合は<strong><code>%zn</code></strong>を書きます)
#include <stdio.h>

int main(void)
{
    char buffer[256];
    size_t length;

    printf("Input: ");
    if (scanf("%255[^\n]%zn%*[^\n]", buffer, &length) != 1) {
        return 1;
    }
    scanf("%*c");

    printf("Output: %s\n", buffer);
    printf("Length: %zu\n", length);
    return 0;
}
```

（`%n`系は符号付き整数で受け取るように定義されていますが，負の値が出現する可能性がゼロなので，符号無し整数でも特に問題はありません）

## [`fgets`]の利用

[`fgets`]は改行も1行に含めて一緒に取り込みます。削除したい場合は自分でコードを書かなければなりません。[`strlen`]関数を使うなどして文字列長を求めることも必要です。

また， 返り値は以下のようになります。

- 1文字以上読み取れたときは `buffer` **(改行だけでも1文字以上という扱いになる)**
- 最初から <kbd>Ctrl+D</kbd> (Windowsの場合は <kbd>Ctrl+Z</kbd>) でEOFが入力されたときは `NULL`

失敗したときにはそのまま **`return 1;`** してプログラムを異常終了扱いにさせておくとよいでしょう。

```c:必ず1文字以上入力させ，改行を削除する場合
#include <stdio.h>
#include <string.h>

int main(void)
{
    char buffer[256];
    size_t length;

    printf("Input: ");
    if (fgets(buffer, 256, stdin) == NULL || buffer[0] == '\n') {
        return 1;
    }
    length = strlen(buffer);
    if (buffer[length - 1] == '\n') {
        buffer[--length] = '\0';
    }

    printf("Output: %s\n", buffer);
    printf("Length: %zu\n", length);
    return 0;
}
```

```c:改行だけの入力を認め，改行を削除する場合
#include <stdio.h>
#include <string.h>

int main(void)
{
    char buffer[256];
    size_t length;

    printf("Input: ");
    if (fgets(buffer, 256, stdin) == NULL) {
        return 1;
    }
    length = strlen(buffer);
    if (length > 0 && buffer[length - 1] == '\n') {
        buffer[--length] = '\0';
    }

    printf("Output: %s\n", buffer);
    printf("Length: %zu\n", length);
    return 0;
}
```

```c:改行だけの入力を認め，改行を削除しない場合
#include <stdio.h>

int main(void)
{
    char buffer[256];

    printf("Input: ");
    if (fgets(buffer, 256, stdin) == NULL) {
        return 1;
    }

    printf("Output: %s\n", buffer);
    return 0;
}
```

# <font color="lightpink">整数を受け取る</font>

## [`scanf`]の利用

一般的には[`scanf`]関数の `%d` を使うことが多いと思います。ここでは，複数個の数値が入力された際，余分なものは無視するように書いてみます。

```c:scanf関数で直接整数として受け取る場合
#include <stdio.h>

int main(void)
{
    int num;

    printf("Input: ");
    if (scanf("%d%*[^\n]", &num) != 1) {
        return 1;
    }
    scanf("%*c");

    printf("Output: %d\n", buffer);
    return 0;
}
```

しかし，これには

- 最初にいきなり空白文字が入力された場合に読み取りが終了しない
- 範囲外の値が入力された場合に対応できない

などの欠点があります。

## [`scanf`]と[`strtol`]の併用

[`scanf`]単独でやろうとすると上述のような欠点があるので，まず文字列として受け取って，あとから変換するアプローチを採ってみます。[`strtol`]関数を利用するので，整数の型は `long` (正確には`long int`) になります。なお，[`atoi`]や[`atol`]にはエラー検出機能は無いので注意してください。

```c:strtol関数で文字列から変換する場合<br><br>(簡易版)
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

int main(void)
{
    char buffer[32], *endptr;
    long num;

    printf("Input: ");
    if (scanf("%31[^\n]%*[^\n]", buffer) != 1) {
        return 1;
    }
    scanf("%*c");
    
    num = strtol(buffer, &endptr, 10);
    if (*endptr != '\0' || errno == ERANGE) {
        return 1;
    }

    printf("Output: %ld\n", num);
    return 0;
}
```

```c:strtol関数で文字列から変換する場合<br><br><strong>(エラーメッセージも表示する完全版)</strong>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>

int main(void)
{
    char buffer[32], *endptr;
    long num;

    printf("Input: ");
    if (scanf("%31[^\n]%*[^\n]", buffer) != 1) {
        fprintf(stderr, "Error: No input specified\n");
        return 1;
    }
    scanf("%*c");
    
    num = strtol(buffer, &endptr, 10);
    if (*endptr != '\0') {
        fprintf(stderr, "Error: Invalid charcter found: %c\n", *endptr);
        return 1;
    }
    if (errno == ERANGE) {
        fprintf(stderr, "Error: Out of range (%s)\n",
                num == LONG_MAX ? "Overflow" : "Underflow");
        return 1;
    }

    printf("Output: %ld\n", num);
    return 0;
}
```

# <font color="MediumAquamarine">指定サイズ内で複数行を受け取る</font>

[`fread`]を使えば行という概念にとらわれず，格納領域がすべて埋まるか入力がEOFに到達するまで読み取ることができます。しかし，**バイナリモード**で読み取るため，最初に説明した改行コードの自動変換の機能を持ちません。テキストを処理する際には少し不便です。

そこで[`scanf`]の出番です。**`%4095[\x01-\xff]`**のように書くと，NULL文字以外の文字をすべて読み取ることができます。つまり，改行も読み取れるのです。

```c:確認用にstrtok関数を利用して行ごとに番号を付けて表示
#include <stdio.h>
#include <string.h>

int main(void)
{
    char buffer[4096], *p;
    size_t i = 0;

    printf("Input: \n");
    if (scanf("%4095[\x01-\xff]", buffer) != 1) {
        return 1;
    }

    printf("Output: \n");
    for (i = 0, p = strtok(buffer, "\n"); p; ++i, p = strtok(NULL, "\n")) {
        printf("[%zu] %s\n", i, p);
    }

    return 0;
}
```

# <font color="MediumAquamarine">任意サイズの1行を受け取る</font>

基本的にC言語で可変長は「どうしても」という場合以外は避けるべきですが，メモリが許す限りどんな長さの1行でも受け取ってくれる[`getline`]という関数があります。使用後に自分でメモリを[`free`]する必要があるので注意してください。

```c:改行だけの入力を認めず，改行を削除する場合
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    char *buffer = NULL;
    ssize_t length;

    printf("Input: ");
    if ((length = getline(&buffer, &(size_t){4096}, stdin)) == EOF) {
        return 1;
    }
    if (buffer[length - 1] == '\n') {
        buffer[--length] = '\0';
    }

    printf("Output: %s\n", buffer);
    printf("Length: %zd\n", length);
    free(buffer);
    return 0;
}
```

ここでは最初に確保される領域の大きさを 4096 文字にしました。オーバーした場合は自動的に拡張されますが，大量に入力されることが事前にわかっていれば大きめに設定しておくほうが望ましいです。

# <font color="MediumAquamarine">日本語文字列を文字単位で処理する</font>

## 基本事項

### 文字コードについて

さて，ここまでは`char`を文字と称してきましたが，厳密にはこれは誤りです。なぜならば，日本語などは`char`1個ぶんのデータでは表せないからです。例えば最近最も使われるUTF-8という文字コードだと，日本語で使われる1文字には**`char`3つ分**の領域が必要です。そのため`char`1つを**バイト**という単位で数え，日本語で使われる文字はそれらが複数なので**マルチバイト文字**と呼ばれます。

マルチバイト文字を扱う際，連続したバイト列としてとして扱うぶんには何も工夫しなくても困らないことが多いのですが，**1バイトではなく1文字ずつ取り出したい**あるいは**バイト長ではなく文字列長を求めたい**というときには特殊な処理を書く必要があります。

### ロケール設定

マルチバイト文字を正しく扱う場合，以下の設定を行います。

```c:ヘッダのインクルード
#include <locale.h>
```

```c:setlocale関数の実行
setlocale(LC_ALL, "");
```

### 使用する型など

- マルチバイト文字を1文字ずつ区別できるように格納する場合，`char`の代わりに**`wchar_t`**という型を使います。**`wchar.h`**のインクルードが必要です。
- `char`の代わりに`wchar_t`のリテラルを書きたい場合， `''` `""` はそれぞれ **`L''` `L""`** と書きます。
- `char`の代わりに`wchar_t`用のフォーマット指定子を使いたい場合， `%c` `%s` はそれぞれ **`%lc` `%ls`** と書きます。

## 例

```c:入力された文字列を1文字ずつ「」で括って表示する<br><br><strong>(<code>%n</code>で求められるのはバイト長であって文字列長ではありません，そのためループ条件変数としては使えません)</strong>
#include <stdio.h>
#include <wchar.h>
#include <locale.h>

int main(void)
{
    wchar_t buffer[256];
    setlocale(LC_ALL, "");
    
    printf("入力: ");
    if (scanf("%255l[^\n]%*[^\n]", buffer) != 1) {
        return 1;
    }
    scanf("%*c");

    printf("出力: ");
    for (wchar_t *c = buffer; *c; ++c) {
        printf("「%lc」", *c);
    }
    putchar('\n');

    return 0;
}
```

### macOS における [`scanf`] 関数および [`wscanf`] 関数のバグ

以下に述べることはmacOS固有です。Linuxでは確認できませんでした。WindowsやFreeBSDでは未確認です，誰か教えてください。

- [`scanf`]関数で<ins>改行のみ</ins>を入力した場合，`%255l[^\n]`のように指定しても**改行が1文字として含まれてしまう**というバグがあります。
  - これは次に述べる，`L""`を第1引数に渡せる版の[`wscanf`]関数では起きません。

- しかし，[`wscanf`]関数にもまた別のバグがあります。`%ls`で<ins>文字列だけ</ins>を読み取ろうとしているときに最初から <kbd>Ctrl+D</kbd> でEOFが入力された場合，[`wscanf`]関数は**`EOF`を返さずに 0 を返します**。
  - 「改行だけが入力された場合」と「最初からEOFが入力された場合」を区別することはできません。

[`wscanf`]: http://www.c-tipsref.com/reference/wchar/wscanf.html
[`fgets`]: http://www.c-tipsref.com/reference/stdio/fgets.html
[`strstr`]: http://www9.plala.or.jp/sgwr-t/lib/strstr.html
[`fread`]: http://www9.plala.or.jp/sgwr-t/lib/fread.html
[`scanf`]: http://www.c-tipsref.com/reference/stdio/scanf.html
[`printf`]: http://www.c-tipsref.com/reference/stdio/printf.html
[`atoi`]: http://www.c-tipsref.com/reference/stdlib/atoi.html
[`atol`]: http://www.c-tipsref.com/reference/stdlib/atol.html
[`strtol`]: http://www9.plala.or.jp/sgwr-t/lib/strtol.html
[`free`]: http://www9.plala.or.jp/sgwr-t/lib/free.html
[`strlen`]: http://www9.plala.or.jp/sgwr-t/lib/strlen.html
[`getline`]: https://linuxjm.osdn.jp/html/LDP_man-pages/man3/getline.3.html
[接頭符号]: https://ja.wikipedia.org/wiki/%E6%8E%A5%E9%A0%AD%E7%AC%A6%E5%8F%B7
[バッファオーバーラン]: https://ja.wikipedia.org/wiki/%E3%83%90%E3%83%83%E3%83%95%E3%82%A1%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%83%B3