シェルプログラミング入門4

# はじめに

この記事は
[シェルプログラミング入門 1](https://qiita.com/twrcd1227/items/b7d6e16aa4e9f895a831)
[シェルプログラミング入門 2](https://qiita.com/twrcd1227/items/03310278eea64319b0e4)
[シェルプログラミング入門 3](https://qiita.com/twrcd1227/items/f89467925634cc0988b8)
の続編である。

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

ファイルディスクリプたはプロセスと使用するファイルを結びつけるものである。プログラムを書き込むとき、プロセスはファイルディスクリプタによってファイルへアクセスしている。

ファイルディスクリプタは数値で表されており、0~2 番は予約されている。

`ファイルディスクリプタ 0番`は**標準入力**(普通はキーボードから入力される)
`ファイルディスクリプタ 1番`は**標準出力**(普通は端末画面に出力される)
`ファイルディスクリプタ 2番`は**標準エラー**(普通は端末画面に出力される)

# リダイレクション

リダイレクションは普通の入力元や出力先を換えること。

## リダイレクト記号`>`,`>>`

現在の作業ディレクトリにファイルがないものとする。

```sh
% echo abcdefg #標準出力に書き込まれる
abcdefg       #結果は画面に
% echo abcdefg > abc  #出力をファイルabcに向ける
% cat abc　　　　　　　#ファイルabcに書き込まれている。
abcdefg
```

ちなみにリダイレクトは変数も使える。

```sh
STDOUT=/dev/null
command > $STDOUt
```

`>`と`>>`の違いは上書きするか追加されるかである。

```sh
% echo 123456 > abc  #abcに123456を書き込む
% cat abc    #内容が書き換えられた
123456
%
% echo xyz >> abc  #abcにxyzを書き込む
% cat abc    #内容が追加された
123456
xyz
```

ここで

```sh
% cat abc xxx # abcの中身の表示、xxxはないのでエラー
123456
xyz
cat: xxx: No such file or directory

# abcとxxxをnnnに書き込む
# xxxのエラーはnnnに書き込まれず画面に表示される
% cat abc xxx > nnn
cat: xxx: No such file or directory
```

このときエラーメッセージは**標準エラー**に出力されたものである。標準出力の出力は nnn に書き込まれた。
リダイレクト記号`>`は**標準出力**の向きを換えることができる。

標準出力はファイルディスクリプタの 1 番なので

```sh
# echo abcdef > abcと同じ
% echo abcdef 1> abc
```

と書き換えらる。1 は省略可能。

また、先ほどの例で

```sh
% cat abc xxx 2> nnn
123456
xyz
% cat nnn
cat: xxx: No such file or directory
```

とすると、標準エラー(ファイルディスクリプタ 2 番)の向きが変わり、出力先が nnn となる。標準出力の向きは変わらないので普通に画面に表示される。

標準出力, 標準エラーの両方の向きを変えるときは以下のようにする。

```sh
% cat abc xxx > nnn 2>&1
% cat nnn
123456
xyz
cat: xxx: No such file or directory
```

## リダイレクト記号`<`

標準入力をリダイレクトするときは`<`を使う。

以下のような`xyz`を作成する。

```sh:xyz
ls -l abc nnn
```

```sh
% ls -l abc nnn #普通にls -lを実行する
-rw-r--r--  1 Tomoki  staff  11  5  8 17:18 abc
-rw-r--r--  1 Tomoki  staff  36  5  9 16:46 nnn

#xyzファイルをshの標準入力としてリダイレクトする
% sh < xyz
-rw-r--r--  1 Tomoki  staff  11  5  8 17:18 abc
-rw-r--r--  1 Tomoki  staff  36  5  9 16:46 nnn

#標準入力の0を省略せずに書くと、
% sh 0< xyz
-rw-r--r--  1 Tomoki  staff  11  5  8 17:18 abc
-rw-r--r--  1 Tomoki  staff  36  5  9 16:46 nnn
```

## リダイレクト記号の記述法のまとめ

以下にリダイレクト記号の記述法をまとめてみた。

```sh
>file  #標準出力をfileにかく(1>file)
>>file  #標準出力をfileに追加書きするく(1>>file)
>&m  #標準出力をmばんのファイルディスクリプタにかく(1>&m)
>&-  #標準出力をクローズする(1>&-)

<file  #標準入力をmというファイルディスクリプタから読み込む
<&m  #標準入力をmというファイルディスクリプタから読み込む
<&-  #標準入力をクローズする(0<&-)
<<word  #標準入力をヒアドキュメントから読み込む(0<<word)
```

## パイプ `|`

`|`は標準入出力を取り扱う。

```sh
command1 | command2
```

これは以下と同じである。

```sh
command1 > file
command2 < file
```

## リダイレクトを使った書き込み

`echo`コマンドは標準出力にメッセージを表示するので、エラーが発生したときに`echo`を使っても、標準エラーには書き込まれない。
そういうときには以下のようにすればいい。

```sh
echo "Error" 1>&2
```

ファイルディスクリプタの 3 番以上の番号を使うときは`exec`コマンドで明示的に指定しなければならない。

```sh
% echo "Error" 1>&8 #8番を指定してみる
sh: 8: Bad file descriptor #8番は使われていないと出る
% exec 8> hhh      #8番を使ってhhhに書き込む
% echo abcdef 1>&8 #ファイルhhhに書き込まれた
```

基本的に 3 以上を使用することはほとんどない。

## リダイレクトを使った読み込み

`<<`を使うとファイルを使わずにそこに書いてあるテキストをそのまま入力できる。これをヒアドキュメントという。

```sh
command << word
・・・・
・・・・
word
```

標準出力と同じように、ファイルディスクリプタの番号を変えることができる。

```sh
command n<&m
```

## exec コマンド

exec コマンドで現行のシェルに対して、リダイレクト処理を行うことができる。

```sh
% echo abc
abc #普通に表示される
% exec > /dev/null #標準出力先を変える
% echo abc   #出力がリダイレクト先にいった
% cat nonexist  #標準エラーは出力される
cat: nonexist: No such file or directory
```

```sh
exec < file #fileから入力する
exec 1>&2  #標準出力を標準エラーに向ける
exec 2> /dev/null #標準エラーを捨てて画面に出さないようにする
exec > /dev/null 2>&1  #標準エラーと標準出力を全て捨てる
exec >>file  #標準出力をfileに追加書きする

exec n> file #ファイルディスクリプタn番を使って書き込む
exec n>> file #ファイルディスクリプタn番を使って追加書き

exec n< file #ファイルディスクリプタn番から読み込む
```

## read コマンド

read コマンドは例えば以下のように(普通はキーボードからの)入力を読み込んで変数にセットする。普通はキーボードからの入力だがリダイレクト記号を使ってファイルから読み込ませることも可能である。

```sh
read ANSWER
if  [  "$ANSWER" = "yes"  ]; then
　　　・・・・
else
     ・・・・
fi
```

ファイルから読み込ませるとき普通はファイルの 1 行のみ read コマンドに渡るが、
以下のように while 文のときに使用すると行が続く限り 1 行ずつ read に処理させられる。

```sh
while  read  LINE
do
    ・・・・
done < file
```

while 内で変数を設定したり変更したりしても、while を抜けると全て元に戻ってしまう。(ちなみに現在では Solaris の sh 以外はほとんど while 内の変数の値はそのまま利用可能になっている。)

while 内で変更した値を while が終了したときに使いたいときは以下のように書く。

```sh
exec  <  file
while  read  LINE
do
    ・・・・
done
```

しかし、これだとファイルのみの入力しか受け付けない。
そこでキーボードからの入力をするときに別のファイルディスクリプタを使う

```sh
exec  3<&0  <file #3番を標準入力にする,標準入力をファイルからの入力にする
while  read  LINE
do
  ・・・・
done
exec  0<&3  3<&- #標準入力を3番にして、3番を閉じる
```

また、ファイルからの読み込みを 3 番で行うこともできる。

```sh
exec 3< file
while  read  LINE  0<&3
do
   ・・・・
done
exec  3<&-
```

read コマンドは空白やタブが何個か続いても 1 つの空白とみなされる。
そういうときには IFS(単語の切れ目)変数の値をクリアする。

```sh
OLDIFS=$IFS
IFS=
while  read  LINE
do
    echo "$LINE"
done
IFS=$OLDIFS
```

## リダイレクションのクローズ

```sh
#標準入出力のクローズ
exec >&-
exec <&-

#ファイルディスクリプたn番のクローズ
exec n>&-
exec n<&-
```

ただし、コマンドの出力を捨てたい時は`command  >  /dev/null`とした方がよい。

## ファイルのゼロリセット

ファイルの大きさを 0(ファイルの中身をクリア)にしたいときや中身のないファイルを作成したい時は次の 2 通りの方法がある。

```sh
>file
: >file
```

また、`/dev/null`ファイルを使用する方法もある。

```sh
cat /dev/null > file
cp /dev/null file
```

ちなみにエディタでファイルを開いて、中身を削除しても 1byte は残る。完全に 0 にするには上記の方法しかない。

## ヒア・ドキュメント(Here Documents)

あるコマンドに渡すキーボードからの入力文字列をそのコマンドの直後に指定することができる。

ヒアドキュメントは以下のような形式で word にはどんな文字を使ってもよい。

```txt
<<word
```

word には「ここまでを入力のデータとして扱う」を明示するために、よく EOF や END を使う。

次の例は・・・・が command の入力として扱われる。

```sh
command << END
   ・・・・
   ・・・・
END
```

`cat`は引数なしだと、標準入力からの入力を画面にそのまま表示する。

```sh
% cat
This is a
This is a
input data.
input data.
#^Dで終了
```

これを`<<`を使うと、

```sh
% cat << END
> This is a
> here document.
> END
This is a
here document.
```

このようになる。
入力に変数を入れることもできる。

```sh
% DOC=document
% cat << END
> This is a
> here $DOC.
> END
This is a
here document.
```

<<の後の文字列の前に`\`もしくは`'`を使うと、$DOC をそのまま表示させられる。

```sh
% DOC=document
#\ENDの代わりに'END'としても同じ
% cat << \END
> This is a
> here $DOC.
> END
This is a
here $DOC.
```

変数の前に`\$DOC`と`\`をつければ変数だけクウォートできる。

`<<`の後に`-`をつけると行頭の tab は無視される。

```sh
% cat <<- END
  <tab>  This is a
  here document.
END
This is a
  here document.
```

例えば、シェルプログラムのエラーメッセージを出力する時、以下のように Usage 関数を作り、cat コマンドの出力を標準エラーに向ける。

```sh
Usage() {
    cat 1>&2 <<- EOF
        Usage: $0 [-options][etc..]
         ・・・・
         ・・・・
    EOF
}
```

入力をヒアドキュメントから、出力をファイルにリダイレクトこともできる。

```sh
command << EOF >stdout 2>stderr
    ・・・・
EOF
```

パイプに渡すのも可能

```sh
command1 << EOF | command2
    ・・・・
EOF
```

### ed コマンド

ed コマンドは`-`オプションを指定すると画面にメッセージを出力しない。
読み込んだファイルの行を逆順に並び替える。

```sh
ed - file <<- ! #fileを開く。次の!までがヒアドキュメント
g/^/m0
w  #上書き保存
q  #edコマンドの終了
!  #ヒアドキュメントの終了
```

`g/^/m0`の`g`はファイルの中で一致するパターンを探す。
`g`の後の`/`で囲まれた文字列が探す対象である。ちなみに`^`は行頭を表す。
すなわち、`g/^/`は全ての行頭で処理を行うという意味。
`m0`は処理の内容で 0 行目に移動する(move の m)ということ。
1 行目から順番に最初のファイルの最初に移動させていくと行が全て逆さまになる。

# 終わりに

## 続き

[シェルプログラミング入門 4](https://qiita.com/twrcd1227/items/617026321448b63d5f6b)

## 前回までのリンク

[シェルプログラミング入門 1](https://qiita.com/twrcd1227/items/b7d6e16aa4e9f895a831)
[シェルプログラミング入門 2](https://qiita.com/twrcd1227/items/03310278eea64319b0e4)
[シェルプログラミング入門 3](https://qiita.com/twrcd1227/items/f89467925634cc0988b8)