grep -l で取得したファイル名リストを for in で回す

Bash の話。

grep -l コマンドで、検索文字列を含むファイル名のみを出力できる。こんな感じだ。

# -r オプションは指定ディレクトリ配下を再帰的に検索する
$ grep -lr '検索文字列' './テスト ディレクトリ/Texts'
./テスト ディレクトリ/Texts/Test1.md
./テスト ディレクトリ/Texts/Test2.md
./テスト ディレクトリ/Texts/Test3.md

で、この結果を配列として解釈させ、for in で回そうとしていた。

file_names=( $( eval grep -lr '検索文字列' './テスト ディレクトリ/Texts' ) )

for file_name in "${file_names[@]}"; do
  echo "${file_name}"
done

普通のパスであれば特に問題にならないのだが、「テスト ディレクトリ」のように、半角スペースを含むパスを含んでいると、この部分で区切られてしまうのだ。ファイルが3つヒットしていた場合だと、要素数が 3 ではなくて 6 と解釈されてしまうワケだ。

じゃあ、ファイルごとのパスをダブルクォートかなんかで囲んでやろうと思い、

file_names=( $( eval grep -lr '検索文字列' './テスト ディレクトリ/Texts' | awk '{ print "\"" $0 "\"" }' ) )

こんな風にして、

"./テスト ディレクトリ/Texts/Test1.md"
"./テスト ディレクトリ/Texts/Test2.md"
"./テスト ディレクトリ/Texts/Test3.md"

といった結果が得られるようにしたのだが、なぜかコレでもスペース部分で区切られてしまった。

for x in `grep -l foobar * | awk '{print "\"" $0 "\""}'`
do
  eval echo $x
done

色々試行錯誤した結果、$IFS を一時的に変更することで上手くいった。

# 元の $IFS を退避する
ORIGINAL_IFS=$IFS
# 改行コードを指定する
IFS=$'\n'

# 検索処理を配列に控える
file_names=( $( eval grep -lr '検索文字列' './テスト ディレクトリ/Texts' ) )

# $IFS を元に戻す
IFS=$ORIGINAL_IFS

# コレで、パスにスペースを含んでいても上手く読み取ってくれる
for file_name in "${file_names[@]}"; do
  echo "${file_name}"
done

コレで OK。grep -l コマンドの結果は \n で改行されているように見えて、実際は \n とは違うようだ。$IFS 周りは全然分からない。