[bash] IFS(Internal Field Separator)について

シェルスクリプトでreadを使う時にフィールド分割したくない場合やフィールドの区切り文字を変えたい場合なんかにIFSの値を変えたりする。
でも、実際にIFSの値を変えるとどこまで影響があるんだろうか?

read

これが最も一般的な使い方だと思う。
例えば、IFSが明示的に設定されていない場合は <空白><タブ><改行> が区切り文字になるので、入力に空白があると行が区切られて変数に格納される。

$ read a b c
IFS is Internal Field Separator.
$ echo $a
IFS
$ echo $b
is
$ echo $c
Internal Field Separator.

変数がフィールドより少ない場合は読み捨てられるのではなく、最後の変数に残りがすべて格納されるみたい。
例えば、IFSを','に変えると、カンマ区切りになって、空白はそのまま変数に格納される。
また、IFSは環境変数ではなく、あくまで特殊なシェル変数なので、exportはしない。
※IFSを設定されていないデフォルトの状態に戻す時は unset IFS を実行しよう。

$ IFS=','
$ read a b c
IFS is Internal Field Separator.
$ echo $a
IFS is Internal Field Separator.
$ echo $b

$ echo $c

$ read a b c
IFS is Internal,Filed,Separator.
$ echo $a
IFS is Internal
$ echo $b
Filed
$ echo $c
Separator.

位置パラメータ出力

readは入力だったけど、今度は出力。
位置パラメータを"$*"で展開するときの区切り文字もIFSに設定されている文字になる。
ただし、このルールは $* をダブルクォートで囲んだ場合のみ適用される。

ちなみにIFSに複数の文字が設定されている場合は先頭の文字で区切られる。

$ cat args.sh
#!/bin/sh
IFS=','
echo $*
echo "$*"
echo $@
echo "$@"
$ ./args.sh a b c
a b c
a,b,c
a b c
a b c

区切り文字を改行にしたい場合は IFS='\n' だとうまくいかないので、 IFS=$'\n' で設定する。
IFSに限らず、この記法だと、エスケープシーケンスに対応してくれる。

$ cat argsn.sh
#!/bin/sh
IFS=$'\n'
echo "$*"
$ ./argsn.sh a b c
a
b
c

展開後の単語分割

もう一つ、IFSの影響を受けるのが変数展開後の単語分割になる。
例えば、以下のように','で区切った文字列を変数に格納すると、変数展開後にフィールドに区切られてしまう。

$ IFS=','
$ var='IFS is Internal,Filed,Separator.'
$ echo $var
IFS is Internal Filed Separator.

echoで出力しているだけだと分かりにくいけど、実際に引数として渡すと、どのように分割されているのかが分かる。

$ cat argc.sh
#!/bin/sh
echo $#
echo $1
echo $2
echo $3
$ ./argc.sh $var
3
IFS is Internal
Filed
Separator.

$var の値がカンマで区切られて、3つの引数として渡されているのが分かる。その際にスペースはそのまま保存されている。

IFSの値が特別な場合でなくても、変数をクォートで囲まないと単語分割されるのは以下のような場面でよく出会うと思う。

$ unset IFS
$ var='a b c'
$ ./argc.sh $var
3
a
b
c
$ ./argc.sh "$var"
1
a b c


$

この話で面白いのはIFSの値を確認しようとして、echo $IFS をすると、それ自体が区切り文字なので表示されないところ。
必ず、ダブルクォートで囲んで評価しよう。

$ IFS=','
$ echo $IFS

$ echo "$IFS"
,
$

Man page of BASH
単語の分割
パラメータ展開、コマンド置換、算術式展開が行われたのが、ダブル クォートの内側ではない場合、シェルは展開の結果をスキャンして、 単語分割 を行います。

とあるように、変数展開だけでなく、コマンド置換、算術式展開も同様に単語分割の対象となる。

0 件のコメント:

コメントを投稿