善用 exec redirection

寫 shell script 時常碰到 list 不好處理的問題,裡面如果包含空白之類的 IFS 區隔,一不小心就會發生意外。例如目錄中包含 My Music 這個目錄, 用以下的 shell script 處理 ls -1 時,就會分別印 MyMusic

1
2
3
for f in `ls -1`; do
echo $f
done

解法可以用 read 讀入變數:

1
2
3
ls -1 | while read f; do
echo $f
done

如果需要複雜一些的篩選,甚至處理有 \n 在裡頭的檔案,那可以用 find -print0

1
2
3
4
find -mindepth 1 -maxdepth 1 -print0 | \
while read -d $'\0' f; do
echo $f
done

但這又有個討厭的地方,就是在迴圈中如果需要用到 stdin 就出錯了,因為 stdin 是接到 findstdout。解法會動用到 process substitution 跟 exec redirection 用來改變環境的 file descriptor 設定。

1
2
3
4
exec 3< <(find -mindepth 1 -maxdepth 1 -type d -print0)
while read -d $'\0' f <&3-; do
echo $f
done

主要差異是第一行,<(COMMAND) 的用意是把 find 的輸出接到一個 named pipe 或者是 /dev/fd 之下當成一個檔案(詳情請參閱 info bash),而 exec 3< 的意思則是用 fd 3 打開這個檔案。下一行 read 後面接的 <&3- 是把 fd 3 move 到 read 的 stdin,這樣就不會影響到 loop 中的 stdin 了。