Shellの基礎知識(基礎編)

【Shellの基礎知識】リダイレクトの基本|標準入出力とエラー出力

リダイレクトとは?

通常、サーバー上での作業時には端末を開いて作業を行います。実行されたコマンドの出力は概ねコンソール画面上に表示されますが、運用上の要件により、作業内容のログや過去の出力内容などを取っておきたいことがあります。そんな時に役立つのがリダイレクトです。

リダイレクトとは?シェルスクリプトでの基本概念

Unix系のOSにおいては、コマンドの実行結果やエラー結果の出力先、あるいはデータ入力元をデフォルトから変更することを指します。

リダイレクトで出力先を変えてファイルなどに結果を残しておくと、何か問題が起こった時の調査に役立つ為、このリダイレクトはよく用いられます。そして、この「入力元」や「出力先」を変えることをリダイレクションといいます。

通常、標準出力(stdout)はターミナル画面に表示され、標準入力(stdin)はキーボード入力、標準エラー出力(stderr)はエラー情報を画面に出力します。しかし、リダイレクトを活用することで、これらのデータを以下のように柔軟に操作できます

リダイレクト活用ポイント

  • 標準出力をファイルに保存してログとして記録
  • 標準エラー出力を分離してエラーログに出力
  • ファイルや他のコマンドから入力を取得して効率的に処理

リダイレクトの基本的な使い方

  • 出力をファイルに保存する
    コマンドの結果をターミナル画面ではなくファイルに保存したい場合、リダイレクトを使用します。

echo "Hello, World!" > output.txt

  • エラー情報を別ファイルに記録する
    エラー出力をログファイルに保存してトラブルシューティングに活用します。

ls nonexistentfile 2> error.log

  • 複数の出力を1つの場所にまとめる
    標準出力と標準エラー出力を同じファイルにまとめることも可能です。

command > all_output.log 2>&1

リダイレクトの基本を理解すれば、データの流れを自在に操作できるようになり、より効率的で柔軟なスクリプト作成が可能になります。

リダイレクトとは、方向を変える、宛先を変える、という意味を持つ言葉なのですが、うまく日本語訳として定着したものはなくカタカナで呼ぶのが一般的です。

ファイルディスクリプタの基本:リダイレクトの仕組みを支える要素

UNIX系OSには、「ファイルデイスクリプタ」というものがあり、「プロセス」と「使用するファイル」とを結び付けています。何かのプロセスがファイルを作成して書き込みを行う場合など、必ずプロセスはファイルディスクリプタを使って対象となるファイルヘアクセスしています。

ファイルディスクリプタは、ファイルディスクリプタ【n】番というように、数値で表現されます。下記に代表的な3つのファイルディスクリプタを記載します。シェルスクリプトでリダイレクトを利用する際、ファイルディスクリプタ(File Descriptor)はその背後で重要な役割を果たしています。特にシェルスクリプトでは、以下の3つの標準的なファイルディスクリプタが頻繁に使われます。

ディスクリプタ意味動作
0標準入力キーボードからの入力
1標準出力端末画面への出力
2標準エラー端末画面への出力

このファイルディスクリプタのうち、0番、1番、2番の3つは予約されているもので、何かプロセスが動作する時は、必ずこの3つのファイルデイスクリプタが利用可能な状態になります。

ファイルディスクリプタの基本的な使い方

標準出力をファイルにリダイレクト
標準出力(ファイルディスクリプタ番号 1)を特定のファイルにリダイレクトする例です。

echo "Hello, World!" > output.txt

結果: output.txt に "Hello, World!" が保存されます。

標準エラー出力をリダイレクト
標準エラー出力(ファイルディスクリプタ番号 2)を別のファイルにリダイレクトします。

ls nonexistentfile 2> error.log

結果: error.log にエラーメッセージが記録されます。

標準出力と標準エラー出力を同時にリダイレクト
両方の出力を同じファイルにまとめることも可能です。

command > output.log 2>&1

結果: 標準出力と標準エラー出力がoutput.logに記録されます。

カスタムファイルディスクリプタの実践的な活用方法

シェルスクリプトでは、デフォルトの0, 1, 2以外にカスタムファイルディスクリプタを使用することもできます。これにより、複数のファイルやストリームを効率的に操作できます。

カスタムファイルディスクリプタの作成
ファイルディスクリプタ番号 3 を作成し、特定のファイルに接続する例です。

exec 3> custom_output.txt
echo "This is written to file descriptor 3" >&3
exec 3>&-

  • 説明:
    • exec 3> custom_output.txt: ファイルディスクリプタ番号 3 を作成し、custom_output.txt に接続。
    • echo … >&3: ファイルディスクリプタ 3 にデータを出力。
    • exec 3>&-: ファイルディスクリプタ 3 を閉じる。

標準出力を別のコマンドに渡す
以下の例では、標準出力をカスタムファイルディスクリプタに保存しつつ、別のコマンドにデータを渡します。

exec 3> output.log
ls /path/to/files | tee /dev/fd/3
exec 3>&-

  • 結果:
    • 標準出力がoutput.logに保存される。
    • lsの出力がターミナルにも表示される。

ファイルディスクリプタを使うメリットとは?

ファイルディスクリプタを使用する最大の理由は、シェルスクリプトやプログラムにおいてデータの入出力を柔軟かつ効率的に管理できることです。通常、標準入力(stdin)、標準出力(stdout)、標準エラー出力(stderr)の3つの基本的なストリームが使用されますが、カスタムファイルディスクリプタを活用することで、複数のデータフローを同時に扱うことが可能になります。

例えば、大量のログデータを処理する際には、通常の出力を確認しながらエラーログを別途記録する必要があります。この場合、標準出力をそのままターミナルに表示しつつ、標準エラー出力をエラーファイルに分離して保存することで、エラー管理が飛躍的に効率化されます。また、カスタムファイルディスクリプタを使用すれば、特定のデータを別のファイルに個別に記録しながら、次の処理にそのデータを引き渡すといった高度な操作が可能になります。

さらに、ファイルディスクリプタを使用することで、スクリプトの再利用性と柔軟性が向上します。標準ストリームだけでは対処できない複雑なシナリオにも対応できるため、リソースを効率的に管理しつつスクリプト全体のパフォーマンスを最大化できます。このように、ファイルディスクリプタはデータフロー制御を強化し、エラー処理やログ管理などさまざまな場面でその利便性を発揮します。

ファイルディスクリプタを使用する理由

  • 柔軟な入出力操作:
    デフォルトの標準ストリームだけでなく、カスタムストリームを活用して複雑な操作を簡単に実現。
  • 効率的なリソース管理:
    複数のストリームを同時に操作することで、スクリプトのパフォーマンス向上。
  • エラー処理の強化:
    標準出力と標準エラー出力を分離して記録することで、トラブルシューティングが容易に。

ファイルディスクリプタの管理: 使用後は適切に閉じる(exec … >&-)ことで、リソースリークを防ぐ。
番号の競合: 他のプロセスで既に使用されている番号を避ける。

リダイレクトを使ったデータ操作の実践方法

シェルスクリプトやプログラムを実行する際、データの流れは「標準入力(stdin)」「標準出力(stdout)」「標準エラー出力(stderr)」という3つのストリーム(データの通り道)を通じて処理されます。

これらの仕組みを理解することで、データの受け渡しやエラーハンドリングがより効率的に行えるようになります。リダイレクトをする場合、コマンドを記述した行の末尾に、以下のような記号を用いることで変更が可能です。

  • < (標準入力の変更)
  • << (標準入力の変更)
  • (標準出力の変更)
  • >> (標準出力の変更、アペンドモード)
  • 2> (標準エラー出力の変更)

また、これらの他に「&(アンパサンド)」を用いた記述方法もあります。

それぞれ見ていきましょう。

標準入力のリダイレクト 「<」

通常、コマンド実行時の標準入力は、キーボードが一般的です。そこで「コマンド < ファイル名」のように「<(小なり)」を使って記述することにより、標準入力をファイルへリダイレクトすることが可能です。

つまりキーボードから入力する代わりに、ファイルの内容を標準入力として扱うと言うことです。下記の「stdin.txt」を使って説明します。

stdin.txt

aaa
bbb

「標準入力」をファイルへリダイレクトして、ファイル中身の行数を「wc -l」コマンドを使用してコンソールへ出力させます。「wc -l」コマンドは、行数をカウントするコマンドです。

# stdin.txtの行数を数える
$ wc -l < stdin.txt

「stdin.txt」には、2行の文字列があることが分かります。

標準入力(Standard Input, stdin)は、プログラムがデータを受け取るためのストリームです。通常、キーボード入力がこのストリームに接続されています。

重要なのは矢印の方向(向き)ではなく「<(小なり)」とは、「標準入力」と言う事です。「wc -l < stdin.txt」は、「wc -l」コマンドへファイルの中身を標準入力として(キーボードに代わって)入力(渡す)しています。矢印の向きで理解してしまうと、後述の標準エラーでパニクリます!

標準入力のリダイレクト・ヒアドキュメント 「<<」

基本的な用法は「<」と同じですが、次のように「<<」を使うことで、ファイルを使わずに「そこに書いてあるテキスト」をそのまま入力とすることができます。

$ cat << EOF
echo "実行しているマシンのホスト名は$(hostname -s)です。"
EOF

この用法を「ヒア・ドキュメント(Here Document)」と言います。「<<」のあとに任意のワードを書くと、次にその同じワードが出てくる行までに書かれたテキストを標準入力からのものだと解釈します。

「ヒア・ドキュメント(heredocument)」の詳細については、以前の記事でも取り上げているので、下記のリンクページを参照してください。

標準出力のリダイレクト 「>」

逆に、端末の画面上に出るスクリプトの実行結果をファイルに残す場合もあります。

そのような時にはどうすればよいでしょうか。

この場合は、「>(大なり)」記号を用いると、ファイルへの出力が可能になります。

# stdout.txtへ文字列を出力する
$ echo "hello" > stdout.txt

実行結果は、標準出力先が画面ではなく、「stdout.txt」というファイルになるため、画面には何も表示されませんが、「stdout.txt」と言うファイルが作成され、「stdout.txt」ファイルの中身へ"Hello"は記述されているはずです。見てみましょう。

きちんと「stdout.txt」の中に"hello"という文字列が記述されています。

標準出力のリダイレクト 「>(大なり)」を使って文字列を記述した場合、既に「stdout.txt」というファイルが存在していれば、上書きされ元の内容はなくなってしまいます。

逆にファイルが存在していない場合は、新規に作成します。
ファイルのアクセス権(パーミッションや所有者など)はそのまま引き継がれ変更しません。

何度実行されても、同じ結果(冪等性)が要求されるような各種設定ファイル等への記述に、この「>」が良く使用されます。ファイルを上書きするのではなく、今ある内容の後ろに結果を追加したい場合には、標準出力のリダイレクト・アペンド(追記)モード 「>>」を使います。

標準出力(Standard Output, stdout)は、プログラムの通常の出力結果を流すストリームです。デフォルトではターミナル画面に表示されます。

標準出力のリダイレクト・アペンド(追記)モード 「>>」

標準出力のリダイレクト方法にはもう一つあり「>」記号を二つ重ね「>>」と表現します。「>」一つのものと異なるのは、「>>」でリダイレクトした場合、すでに対象のファイルに何かが書かれていた場合、それらを上書きせずに最後の行に追記して出力する点です。

逆に「>」だと既にある内容は消去して一から書き直します。先ほどの「>」の検証で使用したファイル「stdout.txt」へ文字列 "world" を追加してみます。

# stdout.txtへ文字列 "world" を追記する
$ echo "world" >> stdout.txt

「stdout.txt」ファイルの文字列 "hello" の下部へ文字列"world"が追記されています。

時系列に出力されるログファイルの記述に、この「>>」が良く使用されます。

「>>」を使用した場合も「stdout.txt」ファイルが存在しなかった場合は、「>」のときと同じようにファイルが新規に作成します。

標準エラー(コマンドの出すエラーメッセージ)を同じようにファイルに書き込みたい場合は、標準エラー出力のリダイレクト 「2>」(その番号である2を付けてリダイレクト)を使用します。

標準エラー出力のリダイレクト 「2>」

普通、コマンドの文法エラー時には画面上にその旨が表示されます。一見標準出力のリダイレクトでこちらもファイルに出力されるように見えますが、違います。

内部的には結果を出力する標準出力とエラー内容を出力する標準エラー出力が分けられており、その2つが画面という同じ出力先を指定しているために両方とも画面に出力されています。

標準エラー出力のみを別のファイル等に出力したい場合は「2>」の記号を用いて記述します。

# 存在しないファイル「nothing.txt」を使用した例
# 標準出力の向け先は「stdout.txt」
$ ls -l nothing.txt > stdout.txt

「ls -l」コマンドで画面上へエラーが出力されてしまいました。この場合、標準出力は何もない為「stdout.txt」へは何も記述されていません。

では次は、標準エラーとして記述してみます。

# 存在しないファイル「nothing.txt」を使用した例
# 標準エラーの向け先は「stdout.txt」
$ ls -l nothing.txt 2> stdout.txt

今度は、標準エラーが「stdout.txt」へ記述されています。しかし、これですと標準出力と標準エラーは別々に処理する必要が出てきて非常に面倒くさいですよね?

そんな場合「2>&1」のように、ファイルディスクリプタの番号を明示的に指定すれば、そのファイルデイスクリプタが向いている出力先を別のファイルに変更することが可能です。

標準エラー出力(Standard Error, stderr)は、プログラムがエラーや警告を出力するためのストリームです。デフォルトでは標準出力と同じくターミナル画面に表示されますが、ストリームは独立しています。

ファイル記述子「&」を用いた表記

標準エラーと標準出力の出力先を同じにしたい場合は、「&」(アンパサンド)を用いて以下のように記述します。

# 「&」(アンパサンド)を用いて標準エラーと標準出力の出力先を一緒にする
$ ls -l nothing.txt stdout.txt > stdout.txt 2>&1

「&」を用いて標準エラーと標準出力の出力先が「stdout.txt」へ統一されました。

パイプ(|)と組み合わせたデータ操作

シェルスクリプトやターミナルでデータを効率的に処理するには、リダイレクトとパイプ(|)を組み合わせる方法が非常に便利です。パイプを使うことで、1つのコマンドの出力を次のコマンドの入力として渡すことができ、複雑なデータ処理をシンプルに実現できます。

パイプ ( | )とは?

パイプ(|)は、コマンド同士をつなぐための記号で、あるコマンドの標準出力(stdout)を別のコマンドの標準入力(stdin)として渡します。これにより、中間ファイルを使わずにデータを処理できます。

コマンドの書式

command1 | command2

  • command1: 最初に実行されるコマンドで、出力が次のコマンドに渡される。
  • command2: 受け取ったデータを処理するコマンド。

データを処理してファイルに保存
以下は、特定のログを抽出してファイルに保存する例です。

# 特定のログを抽出してファイルに保存する
cat logfile.txt | grep "ERROR" > error.log

  • grep "ERROR": エラー行を抽出。
  • error.log: 結果をerror.logに保存。

標準エラー出力を別ファイルにリダイレクト
以下は、エラーだけをログに保存しつつ、通常の出力をさらに処理する例です。

# エラーだけをログに保存しつつ、通常の出力をさらに処理する
command 2> error.log | another_command

  • 2> error.log: 標準エラー出力をerror.logに保存。
  • another_command: 標準出力をさらに処理。

パイプの基本的な使い方

ファイル内容を検索
以下は、特定の文字列を含む行をファイルから抽出する例です。

# 特定の文字列を含む行をファイルから抽出する
cat logfile.txt | grep "ERROR"

  • cat logfile.txt: ファイル内容を標準出力に表示。
  • grep "ERROR": 標準入力からERRORを含む行を抽出。

データを並べ替え
以下は、ファイル内のデータをソートする例です。

# ファイル内のデータをソートする
cat data.txt | sort

  • sort: 入力データを昇順に並べ替え。

大量のデータ処理
以下の例は、データベースのログを解析して特定の条件に一致するデータを抽出し、件数をカウントするシェルスクリプトの一部です。

# データベースのログを解析して特定の条件に一致するデータを抽出し、件数をカウントする
cat db_log.txt | grep "INSERT" | awk '{print $3}' | sort | uniq -c

  • grep "INSERT": 挿入操作を抽出。
  • awk '{print $3}': 第3列を取得。
  • sort: ソート。
  • uniq -c: 一意のデータごとにカウント。

まとめ

本記事をもって【Shellの基礎知識】は終了です。これまでの内容で、簡単なシェルスクリプトなら記述できるようになっているかと思いますが、シェルは非常に奥の深いスクリプト言語です。

これまでに説明しきれなかった用法も沢山ありますので、これを機会にぜひシェルマスターを目指してください。シェルを極めてるにつれて、サーバー構築の生産性は想像以上に向上していくのを実感します。

現場で一目置かれる存在になることは間違いありません。

お疲れさまでした!


この記事を読んだら、次は 「【Shellの基礎知識】中級者向けShellスクリプト|実用的な書き方とコツ」を読むのがおすすめです!

よく読まれている記事

1

IT入門シリーズ 🟢 STEP 1: ITの基礎を知る(ITとは何か?) 📌 IT初心者が最初に学ぶべき基本知識。ITの概念、ネットワーク、OS、クラウドの仕組みを学ぶ ...

2

「私たちが日々利用しているスマートフォンやインターネット、そしてスーパーコンピュータやクラウドサービス――これらの多くがLinuxの力で動いていることをご存じですか?無料で使えるだけでなく、高い柔軟性 ...

3

この記事は、Linuxについて勉強している初心者の方向けに「Shellスクリプト」について解説します。最後まで読んで頂けましたら、Shellスクリプトはどのような役割を担っているのか?を理解出来るよう ...

-Shellの基礎知識(基礎編)
-, , , ,