紛らわしいけど重大な違いを引き起こすリダイレクト

何事もそうなのだけど、一文字違ったとか、順序が逆だったとかで予想とはだいぶ違う結果になってしまうことって、よくある。コマンドも然り。

  • 環境:MacBook OSX 10.6.7 bash
  • 疑問:コマンドの出力結果(標準出力と標準エラー出力)をすべて破棄したい。以下の例で正しい書き方はどれだろうか?
$ echo ABC >& /dev/null #......1
$ echo ABC &> /dev/null #......2
$ echo ABC >/dev/null 2>&1 #...3
$ echo ABC >/dev/null 2&>1 #...4
$ echo ABC 2>&1 >/dev/null #...5
$ echo ABC 2&>1 >/dev/null #...6

検証

1
$ echo ABC >& /dev/null
# 正解!(csh, tcsh 系の書き方)
2
$ echo ABC &> /dev/null
# 間違いと思われたが、なんとbashは「&>」も「>&」と解釈して処理しているようだ。
# つまり、正解!(しかし、好ましくはないと思う)

$ echo ABC &> file
$ cat file
ABC

$ _echo ABC &> file
$ cat filebash: _echo: command not found
3
$ echo ABC >/dev/null 2>&1
# 正解!(sh, bash 系の書き方)
4
$ echo ABC >/dev/null 2&>1
$ cat 1
ABC 2

$ _echo ABC >/dev/null 2&>1
$ cat 1bash: _echo: command not found

# 標準出力・標準エラー出力とも、「1」という名前のファイルに保存される。
# 処理結果から以下のコマンドと同等な解釈のようだ。
$ echo ’ABC 2’ >/dev/null &> 1
5
$ echo ABC 2>&1 >/dev/null
# 標準出力は破棄され、標準エラー出力は画面に表示される。

$ _echo ABC 2>&1 >/dev/nullbash: _echo: command not found
6
$ echo ABC 2&>1 >/dev/null
# 標準出力は破棄され、標準エラー出力は「1」という名前のファイルに保存される。

$ echo ABC 2&>1 >file
$ cat 1
$ cat file
ABC 2

$ _echo ABC 2&>1 >/dev/null
$ cat 1bash: _echo: command not found

# 処理結果から以下のコマンドと同等な解釈のようだ。
$ echo 'ABC 2' &> 1 >/dev/null

リダイレクトの復習

# 何も指定しなければ、コマンドを実行した結果は、画面に出力される。
$ echo ABC
ABC

# エラーも画面に出力される。
$ _echo ABC
-bash: _echo: command not found
# ファイルに出力したい時は「>ファイルパス」で指定する。
$ echo ABC >file
$ cat file
ABC

#「>>ファイルパス」とすれば、上書きではなく、追記になる。
$ echo ABC >>file
$ cat file
ABC
ABC

# 「>ファイルパス」なら上書きになる。
$ echo ABC >file
$ cat file
ABC
# ところが、エラーはファイルに出力されず、未だ画面に出力される。
$ _echo ABC >file
-bash: _echo: command not found

# エラーが発生すると、正常な実行結果は存在しないので、ファイルの中身も消えた。
$ cat file
# エラー結果をファイルに出力するには「2>ファイルパス」で指定する。
$ _echo ABC 2>file
$ cat file
-bash: _echo: command not found

# しかし今度は、正常な実行結果が画面に出力されてしまう。
$ echo ABC 2>file
ABC

# エラーが発生してないので、ファイルの中身も消えた。
$ cat file
  • つまり、コマンドの正常な実行結果と、エラーとなった結果は区別されているのだ。
    • 正常な実行結果は「標準出力」と呼ばれている。
    • エラーとなった結果は「標準エラー出力」と呼ばれている。
  • 「2>file」とは、標準エラー出力に file を指定したことになる。
  • 「1>file」とは、標準出力に file を指定したことになる。
  • つまり、標準出力・標準エラー出力ともにfileに出力したい場合は、両方のファイルディスクプリタを指定する必要があるのだ。
$ echo ABC 1>file 2>file
$ cat file
ABC
$ _echo ABC 1>file 2>file
$ cat file
-bash: _echo: command not found
  • ところで、fileの部分は長いパス名になることはよくあること。
  • その長いパス名を2度入力するのは、苦痛である。
  • そこで、「2>&1」という書き方がある。
    • 「&1」はこの時点での標準出力の出力先を意味する。
    • ちなみに「&2」ならこの時点での標準エラー出力の出力先を意味する。
  • つまり、コマンドは左から順に解釈されるので...
    • 「1>file 2>&1」=「標準出力はfile、標準エラー出力は標準出力(この時点ではfileになっている)」
    • ゆえに、標準出力・標準エラー出力ともにfileが設定される。
  • ちなみに、順序が逆になると...
    • 「2>&1 1>file」=「標準エラー出力は標準出力(この時点ではまだ画面)、標準出力はfile」
    • ゆえに、標準出力はfile、標準エラー出力には画面が設定されるのだ。
$ echo ABC 1>file 2>&1
$ cat file
ABC
$ _echo ABC 1>file 2>&1
$ cat file
-bash: _echo: command not found
  • fileの部分に、/dev/nullを指定すると、結果はすべて破棄される。
    • /dev/nullは、書き込んだデータをすべて捨てる特殊なデバイス
    • ちなみに/dev/ttyは、画面を意味するデバイス
  • ところで、標準出力「1>」の1は省略することが許されているので...
$ echo ABC >/dev/null 2>&1
$ _echo ABC >/dev/null 2>&1


これで、すべての結果が破棄されるようになった!

  • ちなみに、もし「2>&1」の書き方に自信がなくなったら、以下の書き方でもまったく問題ないのだ。
$ echo ABC 1>/dev/null 2>/dev/null