Bash リダイレクトまとめ

語り尽くされた話題ではあるが、GNU のマニュアルを読んで自分用のメモを書く。

読んだマニュアルはこれ。
https://www.gnu.org/software/bash/manual/html_node/Redirections.html

## リダイレクトとは

コマンドが実行される前に、その入力元と出力先を変更 (_redirect_) すること、またそのための記法のこと。

リダイレクトは、コマンドのファイルハンドルを複製したり、開いたり、閉じたり、別のファイルを参照させたりすることができ、これによってコマンドがどこから入力を受け取りどこに書き込むかを変更することができる。

## 基本的な記法

リダイレクトは、コマンドの前または後に置くことができる。
通常は出入力とも後置することが多い。

よく使うのは、出力を捨てたり、`while` 文に入力を与えるケースだろう。

```bash
$ echo Hello World > /dev/null # 標準入力を捨てる
```

```bash
# while 文に入力を与える
while read -r LINE ; do
    echo $LINE
done < <(echo -e 'Hello\nWorld')
```

## 出力

出力のリダイレクト。
リダイレクト演算子が `>` で始まるものの場合、そのリダイレクトは出力を変更するものとして扱われる。
リダイレクト演算子の前には数字を置くことができ (E.g. `1>word`, `2>word`, etc.)、これが変更対象のファイルディスクリプタを表す。この数字を省略した場合、変更対象のファイルディスクリプタのデフォルト値は `1` (標準出力) となる。

_word_ にはファイルディスクリプタやファイルパスを指定する。
_word_ はシェル展開 ([_Shell expansions_](#shell-expansions)) の対象となるが、_word_ が _Word splitting_ の結果複数の単語に展開された場合にはエラーとなる。

```bash
$ ls > files.txt
$ cat files.txt
files.txt
```

リダイレクトに `>>` 演算子を使った場合、出力は追記モードで行われる。

```bash
$ ls >> files.txt
$ cat files.txt
files.txt
files.txt
```

リダイレクトに `>|` 演算子を使うと、[noclobber オプション](#the-noclobber-option) が有効化されていたとしても出力先ファイルを置き換えることができるようになる。

```bash
$ set -o noclobber
$ ls > files.txt
-bash: files.txt: cannot overwrite existing file
$ ls >| files.txt
$ cat files.txt
files.txt
```

## 入力

入力のリダイレクト。
リダイレクト演算子が `<` で始まるものの場合、そのリダイレクトは入力を変更するものとして扱われる。
リダイレクト演算子には数字を置くことができ (E.g. `0<word`, etc.)、これが変更対象のファイルディスクリプタを表す。この数字を省略した場合、変更対象のファイルディスクリプタのデフォルト値は `0` (標準入力) となる。

_word_ については出力に同じだ。

```bash
$ cat < files.txt
files.txt
```

### ヒアドキュメント

`<<` 演算子でヒアドキュメントを書くことができる。

```bash
$ cat << EOS
    Hello World
EOS
Hello World
```

ヒアドキュメントでは、_word_ の Parameter expansion, Command substitution, Arithmetic expansion, Filename expansion が行われない。[^1]

[^1]: Brace expansion と Tilde expansion については、マニュアルに記載がなかったが、私の環境ではこれら2つ共に展開されなかった。 > No parameter and variable expansion, command substitution, arithmetic expansion, or filename expansion is performed on word.

また、ヒアドキュメントの終わりを示す `EOS` は _delimiter_ であり、これは _word_ に _Quote expansion_ を適用した結果に一致する必要がある。

_word_ をクォートで囲むと、ヒアドキュメント内ではシェル展開が適用されなくなる。

```bash
$ cat << 'EOS'
    $HOME
EOS
$HOME
```

`<<-` 演算子を使うと、行頭のタブ文字を取り払うことができる。(タブ文字を`>   `で表現している)

```bash
$ cat <<- EOS
function(a) {
>   return function(b) {
>   >   return a + b;
>   }
}
EOS
function(a) {
return function(b) {
return a + b;
}
}
```

### ヒア文字列

`<<<` 演算子を使って、ヒア文字列を書くことができる。

```bash
$ cat <<< 'Hello World'
Hello World
```

標準入力に適当な文字列を渡したいときに便利だ。でも別に `echo` とパイプを使ってもいい。

```bash
$ echo Hello World | cat
Hello World
```

## ファイルディスクリプタの複製

`>&` と `<&` 演算子は、それぞれ出力/入力ファイルディスクリプタを複製する。

例えば次のように書くと、標準出力のファイルディスクリプタを標準エラー出力のファイルディスクリプタの複製で置き換えることになる。

```bash
$ echo Hello, stderr 1>&2
Hello, stderr # 標準エラー出力にプリントされている
```

次の例は、よく使う例だ。
まず、標準エラー出力を `/dev/null` にする。次に、標準出力を標準エラー出力の複製(=`/dev/null`)で置き換える。
その結果、プログラムのすべての標準出力/標準エラー出力が `/dev/null` に捨てられることになる。

```bash
$ echo Hello, stderr 2>/dev/null 1>&2
```

ここで、リダイレクトは左から右に評価されることを強調しておきたい。たまに見る「リダイレクトは右から左に評価される」という説明は誤りだ。

### ファイルディスクリプタの移動

ファイルディスクリプタを複製する際に、_word_ に続けて `-` と書くと、複製元のファイルディスクリプタを複製後に閉じることができる。

次は、標準出力と標準エラー出力を入れ替える例だ。
分かりやすさのために、標準出力と標準エラー出力の両方にメッセージを表示する関数を使おう。

標準出力と標準エラー出力を入れ替えるために、一時領域としてファイルディスクリプタ3を使っている。
そして入れ替えの最後で、一時領域として使ったファイルディスクリプタ3を閉じている。

```bash
$ f() { echo Hello, stdout; echo Hello, stderr 1>&2; }
$ f 1>/dev/null 3>&1 1>&2 2>&3-
Hello, stdout
$ f 2>/dev/null 3>&1 1>&2 2>&3-
Hello, stderr
```

### 空きファイルディスクリプタを得る

ファイルディスクリプタに _{varname}_ を指定すると 10 より大きなファイルディスクリプタを確保して、`varname` 変数に割り当ててくれる。

以下の例では、一時的に適当なファイルディスクリプタにコマンドの結果を流しておいて、あとでその結果を表示している。
私の環境で試したところ、変数に割り当てられたファイルディスクリプタを確認すると、`10` が使われていた[^2]。

[^2]: マニュアルには「10 より大きな」と書いてあるが、私の環境では 10 が割り当てられることもあった。> (..) the shell will allocate a file descriptor greater than 10 and assign it to {varname}.

また、変数に割り当てられたファイルディスクリプタは、明示的に閉じるまで開かれ放しであるため、`<&-` 演算子を使ってこれを閉じている。この挙動は bash の `varredir_close` オプションで制御することができる。

```bash
$ echo Hello, World {tmp}>&1 1>$tmp
$ cat <$tmp
Hello, World
$ echo $tmp
10
$ {tmp}<&-
```

もう一つの例として、標準出力と標準エラー出力の入れ替えを _{varname}_ 記法を使って行ってみよう。
ファイルディスクリプタが使用中である可能性を考えなくてよいため、こちらがより安全な書き方であると言えるだろう。

```bash
$ f() { echo Hello, stdout; echo Hello, stderr 1>&2; }
$ f 1>/dev/null {t}>&1 1>&2 2>&$t {t}>&-
Hello, stdout
$ f 2>/dev/null {t}>&1 1>&2 2>&$t {t}>&-
Hello, stderr
```

## 出入力

`<>` 演算子は、指定したファイルを読み書きの両方が可能なモードで開く。
`<`から始まる演算子であるので、ファイルディスクリプタを省略した場合のデフォルトは `0` (標準入力) となる。

```bash
$ echo Hello World 1<>output.txt
$ cat <>output.txt
Hello World
```

正直どのような使い道があるのか分かっていないので、詳しいことを知っていたらコメントして頂けると嬉しいです。

## Shell expansions

おまけ: シェル展開 (_Shell expansion_) についてのまとめ。

https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html

* Brace expansion
    * E.g. `{1,2,3}.txt` => `1.txt 2.txt 3.txt`
    * https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html
* Tilde expansion
    * E.g. `~` => `/home/user`
    * https://www.gnu.org/software/bash/manual/html_node/Tilde-Expansion.html
* Parameter expansion
    * E.g. `${1}` => `The first arg`
    * https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html
* Command substitution
    * E.g. `$(echo Hello)` => `Hello`
    * https://www.gnu.org/software/bash/manual/html_node/Command-Substitution.html
* Arithmetic expansion
    * E.g. `$(( 1 + 3 ))` => `4`
    * https://www.gnu.org/software/bash/manual/html_node/Arithmetic-Expansion.html
* Quote removal
    * E.g. `'Hello'` => `Hello`
    * https://www.gnu.org/software/bash/manual/html_node/Quote-Removal.html
* Filename expansion
    * E.g. `./*` => `./a.txt ./b.txt`
    * https://www.gnu.org/software/bash/manual/html_node/Filename-Expansion.html
* Word splitting
    * E.g. `wc -w <(echo 'Hello World')` => `2 /dev/fd/63`
    * https://www.gnu.org/software/bash/manual/html_node/Word-Splitting.html

## The noclobber option

おまけ: noclobber オプションについてのまとめ。

noclobber オプションをつけると、`>` 演算子を使ってリダイレクトした時に、ファイルの上書きを防ぐことができるようになる。

```bash
$ set -o noclobber
$ ls > files.txt
-bash: files.txt: cannot overwrite existing file
```

noclobber オプションがあったとしても、`>>` 演算子で追記をすることは可能だ。

```bash
$ ls >> files.txt
```