はじめに
この記事は
シェルプログラミング入門1
シェルプログラミング入門2
シェルプログラミング入門3
の続編である。
ファイルディスクリプタ
ファイルディスクリプたはプロセスと使用するファイルを結びつけるものである。プログラムを書き込むとき、プロセスはファイルディスクリプタによってファイルへアクセスしている。
ファイルディスクリプタは数値で表されており、0~2番は予約されている。
ファイルディスクリプタ 0番
は標準入力(普通はキーボードから入力される)
ファイルディスクリプタ 1番
は標準出力(普通は端末画面に出力される)
ファイルディスクリプタ 2番
は標準エラー(普通は端末画面に出力される)
リダイレクション
リダイレクションは普通の入力元や出力先を換えること。
リダイレクト記号>
,>>
現在の作業ディレクトリにファイルがないものとする。
% echo abcdefg #標準出力に書き込まれる
abcdefg #結果は画面に
% echo abcdefg > abc #出力をファイルabcに向ける
% cat abc #ファイルabcに書き込まれている。
abcdefg
ちなみにリダイレクトは変数も使える。
STDOUT=/dev/null
command > $STDOUt
>
と>>
の違いは上書きするか追加されるかである。
% echo 123456 > abc #abcに123456を書き込む
% cat abc #内容が書き換えられた
123456
%
% echo xyz >> abc #abcにxyzを書き込む
% cat abc #内容が追加された
123456
xyz
ここで
% 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番なので
# echo abcdef > abcと同じ
% echo abcdef 1> abc
と書き換えらる。1は省略可能。
また、先ほどの例で
% cat abc xxx 2> nnn
123456
xyz
% cat nnn
cat: xxx: No such file or directory
とすると、標準エラー(ファイルディスクリプタ2番)の向きが変わり、出力先がnnnとなる。標準出力の向きは変わらないので普通に画面に表示される。
標準出力, 標準エラーの両方の向きを変えるときは以下のようにする。
% cat abc xxx > nnn 2>&1
% cat nnn
123456
xyz
cat: xxx: No such file or directory
リダイレクト記号<
標準入力をリダイレクトするときは<
を使う。
以下のようなxyz
を作成する。
ls -l abc nnn
% 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
リダイレクト記号の記述法のまとめ
以下にリダイレクト記号の記述法をまとめてみた。
>file #標準出力をfileにかく(1>file)
>>file #標準出力をfileに追加書きするく(1>>file)
>&m #標準出力をmばんのファイルディスクリプタにかく(1>&m)
>&- #標準出力をクローズする(1>&-)
<file #標準入力をmというファイルディスクリプタから読み込む
<&m #標準入力をmというファイルディスクリプタから読み込む
<&- #標準入力をクローズする(0<&-)
<<word #標準入力をヒアドキュメントから読み込む(0<<word)
パイプ |
|
は標準入出力を取り扱う。
command1 | command2
これは以下と同じである。
command1 > file
command2 < file
リダイレクトを使った書き込み
echo
コマンドは標準出力にメッセージを表示するので、エラーが発生したときにecho
を使っても、標準エラーには書き込まれない。
そういうときには以下のようにすればいい。
echo "Error" 1>&2
ファイルディスクリプタの3番以上の番号を使うときはexec
コマンドで明示的に指定しなければならない。
% echo "Error" 1>&8 #8番を指定してみる
sh: 8: Bad file descriptor #8番は使われていないと出る
% exec 8> hhh #8番を使ってhhhに書き込む
% echo abcdef 1>&8 #ファイルhhhに書き込まれた
基本的に3以上を使用することはほとんどない。
リダイレクトを使った読み込み
<<
を使うとファイルを使わずにそこに書いてあるテキストをそのまま入力できる。これをヒアドキュメントという。
command << word
・・・・
・・・・
word
標準出力と同じように、ファイルディスクリプタの番号を変えることができる。
command n<&m
execコマンド
execコマンドで現行のシェルに対して、リダイレクト処理を行うことができる。
% echo abc
abc #普通に表示される
% exec > /dev/null #標準出力先を変える
% echo abc #出力がリダイレクト先にいった
% cat nonexist #標準エラーは出力される
cat: nonexist: No such file or directory
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コマンドは例えば以下のように(普通はキーボードからの)入力を読み込んで変数にセットする。普通はキーボードからの入力だがリダイレクト記号を使ってファイルから読み込ませることも可能である。
read ANSWER
if [ "$ANSWER" = "yes" ]; then
・・・・
else
・・・・
fi
ファイルから読み込ませるとき普通はファイルの1行のみreadコマンドに渡るが、
以下のようにwhile文のときに使用すると行が続く限り1行ずつreadに処理させられる。
while read LINE
do
・・・・
done < file
while内で変数を設定したり変更したりしても、whileを抜けると全て元に戻ってしまう。(ちなみに現在ではSolarisのsh以外はほとんどwhile内の変数の値はそのまま利用可能になっている。)
while内で変更した値をwhileが終了したときに使いたいときは以下のように書く。
exec < file
while read LINE
do
・・・・
done
しかし、これだとファイルのみの入力しか受け付けない。
そこでキーボードからの入力をするときに別のファイルディスクリプタを使う
exec 3<&0 <file #3番を標準入力にする,標準入力をファイルからの入力にする
while read LINE
do
・・・・
done
exec 0<&3 3<&- #標準入力を3番にして、3番を閉じる
また、ファイルからの読み込みを3番で行うこともできる。
exec 3< file
while read LINE 0<&3
do
・・・・
done
exec 3<&-
readコマンドは空白やタブが何個か続いても1つの空白とみなされる。
そういうときにはIFS(単語の切れ目)変数の値をクリアする。
OLDIFS=$IFS
IFS=
while read LINE
do
echo "$LINE"
done
IFS=$OLDIFS
リダイレクションのクローズ
#標準入出力のクローズ
exec >&-
exec <&-
#ファイルディスクリプたn番のクローズ
exec n>&-
exec n<&-
ただし、コマンドの出力を捨てたい時はcommand > /dev/null
とした方がよい。
ファイルのゼロリセット
ファイルの大きさを0(ファイルの中身をクリア)にしたいときや中身のないファイルを作成したい時は次の2通りの方法がある。
>file
: >file
また、/dev/null
ファイルを使用する方法もある。
cat /dev/null > file
cp /dev/null file
ちなみにエディタでファイルを開いて、中身を削除しても1byteは残る。完全に0にするには上記の方法しかない。
ヒア・ドキュメント(Here Documents)
あるコマンドに渡すキーボードからの入力文字列をそのコマンドの直後に指定することができる。
ヒアドキュメントは以下のような形式でwordにはどんな文字を使ってもよい。
<<word
wordには「ここまでを入力のデータとして扱う」を明示するために、よくEOFやENDを使う。
次の例は・・・・がcommandの入力として扱われる。
command << END
・・・・
・・・・
END
cat
は引数なしだと、標準入力からの入力を画面にそのまま表示する。
% cat
This is a
This is a
input data.
input data.
#^Dで終了
これを<<
を使うと、
% cat << END
> This is a
> here document.
> END
This is a
here document.
このようになる。
入力に変数を入れることもできる。
% DOC=document
% cat << END
> This is a
> here $DOC.
> END
This is a
here document.
<<の後の文字列の前に\
もしくは'
を使うと、$DOCをそのまま表示させられる。
% DOC=document
#\ENDの代わりに'END'としても同じ
% cat << \END
> This is a
> here $DOC.
> END
This is a
here $DOC.
変数の前に\$DOC
と\
をつければ変数だけクウォートできる。
<<
の後に-
をつけると行頭のtabは無視される。
% cat <<- END
<tab> This is a
here document.
END
This is a
here document.
例えば、シェルプログラムのエラーメッセージを出力する時、以下のようにUsage関数を作り、catコマンドの出力を標準エラーに向ける。
Usage() {
cat 1>&2 <<- EOF
Usage: $0 [-options][etc..]
・・・・
・・・・
EOF
}
入力をヒアドキュメントから、出力をファイルにリダイレクトこともできる。
command << EOF >stdout 2>stderr
・・・・
EOF
パイプに渡すのも可能
command1 << EOF | command2
・・・・
EOF
edコマンド
edコマンドは-
オプションを指定すると画面にメッセージを出力しない。
読み込んだファイルの行を逆順に並び替える。
ed - file <<- ! #fileを開く。次の!までがヒアドキュメント
g/^/m0
w #上書き保存
q #edコマンドの終了
! #ヒアドキュメントの終了
g/^/m0
のg
はファイルの中で一致するパターンを探す。
g
の後の/
で囲まれた文字列が探す対象である。ちなみに^
は行頭を表す。
すなわち、g/^/
は全ての行頭で処理を行うという意味。
m0
は処理の内容で0行目に移動する(moveのm)ということ。
1行目から順番に最初のファイルの最初に移動させていくと行が全て逆さまになる。