1. ホーム
  2. コース一覧
  3. Linux入門コース
  4. 標準入力・標準出力・標準エラー出力

Linux入門コース8/8

標準入力・標準出力・標準エラー出力

こちらでは、Linuxにおける入出力について解説します。

入出力については、Linuxを学習し始めた頃にはつまずきやすいポイントの1つです。この解説を通して、しっかり理解していきましょう!

「入力」や「出力」とは

例として、わたしたちがlsコマンドを実行するときを考えてみましょう。

まず、キーボードでlsとタイプすることでターミナルに入力します。

すると、それに従ってプログラムを実行してカレントディレクトリのファイルやディレクトリをターミナルに表示します。

このように、コンピュータはわたしたち人間が「入力」した内容を受けて処理を実行し、その実行結果をどこかに「出力」します。

(※ lsコマンドにつきましては、Envader > Linux基礎コース >『基本的なコマンドの練習(cd/ls/cat編)』の解説記事をご覧ください。)

標準入力 (stdin)

プログラムに入ってくるデータや入力元です。

ほとんどの場合、キーボードが標準入力元のデバイスになります。

/devディレクトリでは、stdinという名前のシンボリックリンクです。

(※ シンボリックリンクに関しましては、Envader > Linux基礎コース >『シンボリックリンク』の解説記事をご覧ください。)

/devディレクトリに関しましては後ほどご紹介します。

標準出力 (stdout)

プログラムの実行結果の出力先です。

一般的にはディスプレイが標準出力先となっています。他にも特定のファイルを出力先として指定することも可能です。

/devディレクトリではstdoutというという名前のシンボリックリンクです。

標準エラー出力 (stderr)

エラーメッセージの出力先です。こちらも標準出力と同様、ディスプレイが出力先となります。devディレクトリではstderrという名前のシンボリックリンクです。

ファイルディスクリプタ

ファイルディスクリプタ(File Descriptor, FD)は、プログラムが扱う操作の対象とするファイルをOSが識別するための番号で、ファイル識別子とも呼ばれます。

その中でも標準的に付けられているのが、0, 1, 2 の3つです。他のファイルディスクリプタとは違って、これら3つはプログラムが生成されると必ず作成されます。0が標準入力、1が標準出力、2が標準エラー出力に割り当てられています。

入出力ファイルディスクリプタ
標準入力0
標準出力1
標準エラー出力2

3以上の数字は、その他の出力先に割り当てられます。

※また、Linuxカーネルでは開いていないファイルにinodeという識別子をつけて管理しています。

一方で、ファイルディスクリプタは、プロセスの中で開かれているファイルに割り当てられる識別子であるという点で区別されます。

【TIP-1】ファイルディスクリプタの数には上限がある?

ファイルディスクリプタの番号は、ファイルを開く度に更新されて割り当てられます。

ただし、Linuxにおいて1つのプロセスで同時に開くことのできるファイルの数には上限があります。

(※プロセスとは、Linuxで実行されて動いているプログラムのことです。)

上限数は、ulimit -nを実行することで確認でき、標準では以下のように1024となっています。

ulimit -n
1024

ulimitコマンドとは

ulimitは、プロセスに割り当てるさまざまなシステムリソースの制限を行うためのコマンドです。

リソースのさまざまな設定を確認したり、設定したりできます。

ただし、ulimitコマンドによる設定は一時的なものなので、シェルを終了すると元の設定値に戻ってしまいます。

オプション

オプション用途
-H強制的な制限(Hard-limit)を設定する。</br>※ルートユーザーでしか上限を増やすことができません。
-S非強制的な制限(Soft-limit)を設定する。</br>※一般ユーザーでも設定可能で、Hard-limitの上限まで増やすことができます。
-a現在のリソースの設定すべてを一覧表示する。
-n同時に開けるファイル数を設定する。
-m利用できる最大のメモリサイズを設定する(KB)。
-tプロセスごとに利用できる最大のCPU時間を設定する(秒数)。
-p一人のユーザーが起動できる最大のプロセス数を設定する。

実際のファイルディスクリプタの設定は以下のように行います。

# Soft-limitに5000を設定して、確認してみます。
ulimit -Sn 5000
ulimit -Sn
5000
----------------------------------------------------------------------------
# Hard-limitに1040000を設定して、確認してみます。(まずはルートユーザーになる必要があります。)
sudo su
rootuser:/home/user# sudo -Hn 1040000
rootuser:/home/user# sudo -Hn
1040000

/dev/null とは

/dev/nullとは、MacやWindowsでいうところのゴミ箱のようなものです。何かしらの出力を /dev/nullにリダイレクトすることを「捨てる」といいます。

(※ 正確には、/dev/nullはゴミ箱とは違って一度捨ててしまったものを復元することができません。つまり、出力したものは/dev/null内では「無」と化すことから、”ブラックホール”というイメージの方が適切かもしれません。)

(※リダイレクトにつきましては、Envader > Linux基礎コース > 『リダイレクトについて』の解説記事をご参照ください。)

スペシャルファイル

Linuxには、さまざまなファイルの種類がありますが、その中のひとつにスペシャルファイルという特殊なファイルがあります。スペシャルファイルは、OSの核であるカーネルの機能を呼び出す際のインターフェースとして機能します。

後述するデバイスファイルやパイプなどがこれに該当します。

(※パイプにつきましては、『Envader > Linux基礎コース > リダイレクトについて』の解説記事の中でご説明しておりますので、そちらをご参照ください。)

デバイスディレクトリ・デバイスファイル

まずは /dev から見ていきます。/dev ディレクトリ(device directory)はルートディレクトリ配下に置かれている、様々なデバイスファイルをまとめたディレクトリです。デバイスファイルは、OSが検出・認識したデバイスを操作するためのスペシャルファイル(実際はインターフェース)です。

たとえば、stdinであればキーボード、stdoutやstderrであればディスプレイといったデバイスを操作するためのデバイスファイルということになります。実際にdevディレクトリを確認すると、様々なファイルが確認できます。

# /devを確認してみます。
ls -la
...
brw-rw----  1 root disk      8,   0 Feb  5 13:27 sda
brw-rw----  1 root disk      8,  16 Feb  5 13:27 sdb
crw-rw----  1 root disk     21,   0 Feb  5 13:27 sg0
crw-rw----  1 root disk     21,   1 Feb  5 13:27 sg1
lrwxrwxrwx  1 root root          15 Feb  3 12:11 stderr -> /proc/self/fd/2
lrwxrwxrwx  1 root root          15 Feb  3 12:11 stdin -> /proc/self/fd/0
lrwxrwxrwx  1 root root          15 Feb  3 12:11 stdout -> /proc/self/fd/1
...

また、OSは、デバイスファイルに紐付けられているメジャー番号マイナー番号によってデバイスを識別します。たとえば、sdaであれば、メジャー番号が 8、マイナー番号が 0 になっています。stdin、stdout、stderrはすべて 15 が割り振られていますが、リンクを辿っていき、実体を確認するとそれぞれ異なる番号が割り振られていることが分かります。

試しに、/dev/stdin の実体を確認してみましょう。

ls -la /dev
...
lrwxrwxrwx  1 root root          15 Feb  3 12:11 stdin -> /proc/self/fd/0
...
# シンボリックリンクの/proc/self/fd/0を確認します。
ls -la /proc/self/fd/0
lrwx------ 1 user user 64 Feb 25 06:18 /proc/self/fd/0 -> /dev/pts/6
# こちらの/dev/pts/6が、/dev/stdinの実体です。
ls -la /dev/pts/6
crw--w---- 1 user tty 136, 6 Feb 25  2022 /dev/pts/6

Linuxでは多くの場合、/dev/stdin -> /proc/self/fd/0 -> /dev/pts/6というように、シンボリックリンクになっています。この /dev/pts/6 というのが標準入力の実体であり、キーボードを制御するためのインターフェースになります。

sdaやsdbといった sdから始まるファイルはSCSIディスク(SCSI disk)というディスク、sg0やsg1といった sgから始まるファイルはSCSIデバイスにSCSIコマンドを直接伝達したり、その応答を受け取ったりなどといった目的に使われるSCSI汎用ドライバ(SCSI generic)のデバイスファイルです。また、仮想的なデバイスドライバである、Linux擬似SCSIデバイスドライバを使うことで、SCSIデバイス以外のデバイスに対しても、SCSIコマンドを使用することができます。

※SCSI(Small Computer System Interface)とは、ハードウェアの規格のひとつです。SCSIデバイスにはハードディスクドライブ(HDD)やCD-ROMドライブなどがあります。ちなみに、本来HDDはHD(ハードディスク)という記憶媒体にデータを書き込んだり、HDの中のデータを読み込んだりするための記憶装置ですが、HDDの構造をご確認いただければ分かる通り、HDDの中にディスク(プラッタ)が内蔵されており、これらは一体化されています。

このあたりの詳細については、今は理解できなくても構いません。なるほど、いろんなデバイスがあって、/dev にはそれらを扱うためのいろんなファイルが格納されてるのか~という理解で十分です。

ブロックデバイス・キャラクタデバイス

これまでにls -laで見てきた中で、ファイルの種類が”b”や”c”となっているものがありました。

このように、デバイスファイルにはブロックデバイスキャラクタデバイスの2つがあります。

ブロックデバイスキャラクタデバイス
データ転送データをブロックというまとまった単位で転送する。文字を単位としており、一文字ずつ逐次的にデータを転送する。
デバイスの例メモリ、HDキーボード、マウス

※ ”逐次的”というのは「1つずつ順を追って」という意味です。

nullとは

/devディレクトリには、nullというデバイスファイルがあります。「nullデバイス」や「ビットバケツ」と呼ぶこともあります。

ls -la /dev
...
crw-rw-rw-  1 root root      1,   3 Feb  5 13:27 null
...

先ほど確認したsdデバイスファイルやsgデバイスファイルは物理的なデバイスを操作するためのファイルでした。/dev/null の解説の冒頭で説明したとおり、nullはいわば「ゴミ箱」や「ブラックホール」のようなものなのです。しかし、これは物理的に実在するゴミ箱ではなく、あたかもそ実在するかのような ”仮想的な”ゴミ箱であることから、擬似デバイスと呼びます。

つまり、/dev/null は仮想的なデバイスを扱うためのデバイスファイルです。

/dev/null は、Linuxにおけるファイルの中でもスペシャルファイルに分類されます。先ほどからゴミ箱やブラックホールと比喩しているとおり、/dev/null に出力されたデータは全て破棄され、/dev/null からデータを読み込もうとしても何も読み込まれません。

たとえば、出力に標準出力と標準エラー出力が混じっているけれど、標準出力だけを出力したいケースがあったとします。その場合は、標準エラー出力の部分のみを捨てて出力すればよいということになります。コマンドとしては以下のようになります。

# sample_fileの標準エラー出力を捨てます。
cat sample_file.txt 2> /dev/null

※ちなみに、>1>の省略形であるため、どちらも標準出力をリダイレクトするという意味になります。

また、ファイルの中身を空にしたいという場合にも利用できます。その場合には以下のように /dev/null を対象のファイルにリダイレクトします。

# メッセージの記述されたsample_file.txtというテキストファイルを作成します。
vi sample_file.txt
==================================
1 | # Sample messages here.
2 | Hello guys.
3 | I am sample user.
==================================
# リダイレクトと/dev/nullを使って、sample_file.txtを空にします。
cat /dev/null > sample_file.txt
# sample_file.txtの中身を確認してみますが、空なので何も出力されません。
cat sample_file.txt
# ファイルの詳細を確認すると、中身が0バイトであることが分かります。
ls -la sample_file.txt
-rw-r--r-- 1 user user 0 Feb 26 06:13 sample_file.txt
# ファイルタイプを確認すると「sample_file.txtの中身は空だよ~」と返されます。
file sample_file.txt
sample_file.txt: empty

標準出力・標準エラー出力のみ出力する

標準出力であれば1、標準エラー出力であれば2とったように、それぞれのファイルディスクリプタとリダイレクトを組み合わせて/dev/null に捨てることで、どちらかのみを出力することができます。以下の例とともに見ていきましょう。

# exampleという実行ファイルの出力結果には、標準出力と標準エラー出力が混ざっています。
# 実際にexampleを実行してみます。(実行ファイルを実行するには、"**./**"を使います。)
./example
htehlilsoiwsoarnledr!r
# このうち、標準出力のみを出力したい場合は、2> /dev/nullとして標準エラー出力のみを捨てます。
./example 2> /dev/null
helloworld!
--------------------------------------------------------------------------------------
# 今度は標準エラー出力のみを出力してみます。(1>を>としても同じ結果が得られます。)
./example 1> /dev/null
thisisanerr

【TIP-2】{コマンド} >/dev/null 2>&1 とは?

こちらは、標準出力と標準エラー出力を両方とも捨てたい場合に使われるコマンドです。

このコマンドは、{コマンド} >/dev/null2>&1 の2つに分けられます。それぞれ何をしているのか確認していきましょう。

  • {コマンド} >/dev/null:{コマンド}の標準出力先を/dev/null に設定しています。

  • 2>&1:標準エラー出力(2)の出力先を標準出力(1)と同じところに設定しています。

    {FD-a}>&{FD-b}とすることで、指定した{FD-a}の出力先を{FD-b}に設定されている出力先に設定できます。

よって{コマンド} >/dev/null 2>&1は、まずコマンドの標準出力先を /dev/null に設定したあと、標準エラー出力先も標準出力と同じ出力先(/dev/null)に設定しています。結果的に標準出力も標準エラー出力も同じ /dev/null を出力先とするため、「どちらの出力も捨てる」ということになります。

よくある間違い

  • {コマンド} >/dev/null 2&>1
    • &>が逆になってしまっています。
    • 2&>1は「標準出力と標準エラー出力の両方を 1 という名前のファイルに出力する」という意味になります。
    • >&&>は別物になるので注意しましょう。
  • {コマンド} >/dev/null 2 &>1
    • 2の両側にスペースが空いています。
    • この場合の2は、コマンドの引数として
    • そして&>1は上記と同じように「標準出力と標準エラー出力の両方を 1 という名前のファイルに出力する」という意味になります。
    • これらからわかるように、2&>1&>1はどちらも「標準出力と標準エラー出力の両方とも 1 というファイルを出力先とする」という意味になってしまいます。これらが2>&1とは大きく異なるということは言うまでもありません。

まとめ

今回はLinuxにおける入出力について解説しました。ファイルディスクリプタやデバイスファイルなどについて詳しく見てきました。最後の方ではメタ文字の扱い方について間違っている書き方を交えながらご説明しました。

シェルスクリプトを書くときなどによく利用しますが、覚えづらくて間違えやすいところになります。実際にご自身のターミナル(コマンドプロンプト)やEnvaderで問題を解きながら練習しつつ、何度も復習することをオススメします。

問題を解くためには、十分な画面サイズのPC環境をご利用下さい。