1. ホーム
  2. bash

[解決済み] bashのループリストで空白をエスケープするには?

2022-07-17 04:40:56

質問

あるディレクトリのすべての子ディレクトリ(ファイルではない)をループするbashシェルスクリプトがあります。 問題は、いくつかのディレクトリ名がスペースを含んでいることです。

ここに私のテストディレクトリのコンテンツがあります。

$ls -F test
Baltimore/  Cherry Hill/  Edison/  New York City/  Philadelphia/  cities.txt

そして、ディレクトリをループするコードです。

for f in `find test/* -type d`; do
  echo $f
done

以下はその出力です。

テスト/ボルチモア
テスト/チェリー
ヒル
テスト/エジソン 
テスト/ニューヨーク
ニューヨーク
シティ
テスト/フィラデルフィア

Cherry HillとNew York Cityは、2つまたは3つの別々の項目として扱われます。

というように、ファイル名を引用してみました。

for f in `find test/* -type d | sed -e 's/^/\"/' | sed -e 's/$/\"/'`; do
  echo $f
done

が、無駄である。

何か簡単な方法があるはずだ。


以下の回答は素晴らしいです。 しかし、これをより複雑にするために、私は常にテスト ディレクトリにリストされたディレクトリを使用したいわけではありません。 時々、私は代わりにコマンドライン パラメータとしてディレクトリ名を渡したいのです。

私は、IFS を設定するという Charles の提案を受け、次のようになりました。

dirlist="${@}"
(
  [[ -z "$dirlist" ]] && dirlist=`find test -mindepth 1 -type d` && IFS=$'\n'
  for d in $dirlist; do
    echo $d
  done
)

というように、コマンドラインの引数にスペースがない限り(たとえ引数が引用されていても)、これはうまく動作します。 例えば、このようなスクリプトを呼び出すと test.sh "Cherry Hill" "New York City" は次のような出力を生成します。

チェリー
ヒル
新しい
ニューヨーク
都市

解決方法

まず、その方法はやめましょう。最も良い方法は find -exec を適切に使用することです。

# this is safe
find test -type d -exec echo '{}' +

もう一つの安全な方法は、NUL終端のリストを使うことです。 -print0 :

# this is safe
while IFS= read -r -d '' n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d -print0)

findから配列に代入し、後でその配列を渡すこともできます。

# this is safe
declare -a myarray
while IFS= read -r -d '' n; do
  myarray+=( "$n" )
done < <(find test -mindepth 1 -type d -print0)
printf '%q\n' "${myarray[@]}" # printf is an example; use it however you want

もしあなたのサイトが -print0 をサポートしていない場合、その結果は安全ではありません。ファイル名に改行を含むファイルが存在する場合、以下のように動作しません(もちろん、これは合法です)。

# this is unsafe
while IFS= read -r n; do
  printf '%q\n' "$n"
done < <(find test -mindepth 1 -type d)

上記のいずれかを使用しない場合、3 番目の方法 (単語分割を行う前にサブプロセスの出力全体を読み込むため、 時間とメモリの使用量の両面で効率的ではありません) は IFS 変数を使うことです。グロビングをオフにする ( set -f のようなグロブ文字を含む文字列を防ぐために [] , * または ? が展開されないようにします。

# this is unsafe (but less unsafe than it would be without the following precautions)
(
 IFS=$'\n' # split only on newlines
 set -f    # disable globbing
 for n in $(find test -mindepth 1 -type d); do
   printf '%q\n' "$n"
 done
)

最後に、コマンドラインパラメータの場合、シェルがサポートしていれば配列を使うべきです(つまり、ksh、bash、zshです)。

# this is safe
for d in "$@"; do
  printf '%s\n' "$d"
done

は分離を維持します。なお、クォート(と $@ ではなく $* のように) を使うことが重要です。配列はグロブ式など他の方法でも入力することができます。

# this is safe
entries=( test/* )
for d in "${entries[@]}"; do
  printf '%s\n' "$d"
done