ファイル名に空白を含むファイル群に対して操作をする時のメモ.
IFS(Internal Field Separator)
ほとんどのシェル(少なくても zsh や bash)において,IFS(デリミター)のデフォルトは <スペース,タブ,改行, null文字>.
echo -n $IFS | od -ac
# 0000000 sp ht nl nul
# \t \n \0
# 0000004
ls して得たファイル群を for で 回そうとすると,IFS の値によって出力が変化.
tree
#=>
# .
# ├── a\ 1.txt
# ├── a\ 2.txt
#
# 0 directories, 2 files
for file in `ls`; do
echo $file
done
# default =>
# a
# 1.txt
# a
# 2.txt
# IFS=$'\n' =>
# a 1.txt
# a 2.txt
この原因は以下の実行結果からも確認可能.
ls | od -ac
# =>
# 0000000 a sp 1 . t x t nl a sp 2 . t x t nl
# a 1 . t x t \n a 2 . t x t \n
# 0000020
IFS がコマンド実行に影響を与える例
以下は,IFS変更が他のコマンドに影響する例.
() { # 無名関数
tree
local IFS_ORG=$IFS
local IFS=$IFS
local array=()
IFS=$'\n' # file は '\n' 区切りのブロック (->ファイル名)
for file in `ls`; do
echo -e "\n${file}" :
IFS=$IFS_ORG # 配列生成時のデリミター : ' \t\n\0' (->各要素ファイル名のスペース区切り)
echo "IFS :"; echo -n ${IFS} | od -c
array=(`echo $file`)
for index in `seq ${#array[@]}`; do
echo "array[${index}]: ${array[${index}]}"
done; echo
# 以下比較用コード
IFS=$'\n' # 配列生成時のデリミター : '\n'
echo "IFS :"; echo -n ${IFS} | od -c
array=(`echo $file`)
for index in `seq ${#array[@]}`; do
echo "array[${index}]: ${array[${index}]}"
done; echo
done
}
# =>
# .
# └── sec\ 1\ sub\ 1\ text.txt
#
# 0 directories, 1 file
#
# sec 1 sub 1 text.txt :
# IFS :
# 0000000 \t \n \0
# 0000004
# array[1]: sec
# array[2]: 1
# array[3]: sub
# array[4]: 1
# array[5]: text.txt
#
# IFS :
# 0000000 \n
# 0000001
# array[1]: sec 1 sub 1 text.txt
空白を含んだファイルを扱うために IFS を変更すると他のコマンドの挙動にも影響し,思わぬ動作を引き起こすことも.
したがって,IFS を極力変更しない,または変更しても早い段階で元に戻したほうが安全.
余談
上記の例のように,zsh は配列のインデックスが 0 から始まるので注意.
以下のようにすればインデックスが 0 からスタート.
if [ "$ZSH_NAME" = "zsh" ]; then # zsh の時
setopt localoptions ksharrays # ksh, bash ライクなインデックス(0からスタート)
fi
また,スクリプト内で setopt localoptions shwordsplit
といったローカルなシェルオプションを設定すれば,"" で囲まずに変数展開した際にスペースで分割可能.
しかし,IFS を自分で変更したほうが柔軟かつ bash などとの互換性もあって良いかも.