一般的なShellで空白を含むPathをループしたい願望
まず、Shellって何かがわからないところから入るわけだけど、
いや、自分が打っているのが所謂sh
なのかbash
なのかzsh
なのかがわからないってことなんだけど、
まあ複数ファイルを順次処理したい時ってあるわけですね。
forを使ったループ
シンプルな例だと以下のように書くわけです。
for f in path/to/target/*.txt do echo "file is: $f" done
このディレクトリーにa.txt
, b.txt
, c.txt
があれば、以下のように表示されるでしょう。
file is: path/to/target/a.txt file is: path/to/target/b.txt file is: path/to/target/c.txt
スペース(など)が含まれたパス
ところが、スペースが含まれているようなディレクトリー名だと、これがうまく動かないのです。
ディレクトリー名をpath/to target with space/
だとしてみましょう。
ls "path/to target with space/"
a a.csv b c d.csv
こんな場合ですね。
同じようなループを実行すると、当然うまく行きません。
for f in path/to target with space/*.csv do echo "file is: $f" done
結果はこんな感じになったりしてしまいます。
file is: path/to file is: target file is: with file is: space/*.csv
解決方法1: IFS
組み込み変数を使用
どうにかスペース入のパスをループできないのかなーと検索した結果見つけたのが、
組み込み変数IFS
とfind
コマンドを組み合わせる方法です。
IFS
を変更すると、shellが扱う区切り文字を変更できるとこのことです。
まずfind
を実行すると、以下のような結果が取得できます。
> find "path/to target with space" -type f -name "*.csv" path/to target with space/a a.csv path/to target with space/b c d.csv
結果が改行されて出てくるので、IFS
に改行記号\n
を設定後ループするとうまくいくようです。
ただし、IFS
を元に戻せるように、退避してから実行した方が良いようです。
IFS_BACK="$IFS" IFS=$'\n' for f in `find "path/to target with space" -type f -name "*.csv"` do echo "file is: $f" done IFS="$IFS_BACK"
file is: path/to target with space/a a.csv file is: path/to target with space/b c d.csv
解決方法2: ""
でくくる
とりあえず上をコピペして使っていたのですが、 今日もっと簡単にできる方法を発見してしまったのでそちらに書き換えることにしました。
皆さんご存知のように、shellではダブルクォートに囲まれた部分は1つとみなしてくれるのです。
ただし、中にある*
は展開してくれません。
まず、ダメな例。
for f in "path/to target with space/*.csv" do echo "file is: $f" done
これの結果は以下のようになります。
file is: path/to target with space/*.csv
*
がそのままですね。
すごく単純すぎて気づかなかったのですが、
*
をダブルクォートの外に出してあげれば展開されるんですよね、これ。
for f in "path/to target with space/"*".csv" do echo "file is: $f" done
file is: path/to target with space/a a.csv file is: path/to target with space/b c d.csv
どこでダブルクォートを終わらせてもいいみたいなので、変数の部分だけくくるとかが現実的なのかなという感じです。
BASE_PATH="path/to target with space" for f in "$BASE_PATH"/*.csv do echo "file is: $f" done
結論
shell難しい。
自分のshell力の低さを痛感させられた。