この回答には、あらゆる種類のファイルに対する解決策があります。改行またはスペースを使用します。
古代のbash、さらには古いposixシェルだけでなく、最近のbashのソリューションもあります。
この回答[1]の下にリストされているツリーは、テストに使用されます。
選択する
select
配列を使用して作業するのは簡単です:
$ dir='deep/inside/a/dir'
$ arr=( "$dir"/* )
$ select var in "${arr[@]}"; do echo "$var"; break; done
または、定位置パラメーターを使用する場合:
$ set -- "$dir"/*
$ select var; do echo "$var"; break; done
したがって、唯一の実際の問題は、配列内または位置パラメータ内で「ファイルのリスト」(正しく区切られている)を取得することです。読み続けます。
バッシュ
bashで報告する問題は見当たりません。Bashは、指定されたディレクトリ内を検索できます。
$ dir='deep/inside/a/dir'
$ printf '<%s>\n' "$dir"/*
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
または、ループが好きな場合:
$ set -- "$dir"/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
上記の構文は、(少なくともcshではなく)すべての(合理的な)シェルで正しく機能することに注意してください。
上記の構文が持つ唯一の制限は、他のディレクトリに降りることです。
しかし、bashはそれを行うことができます。
$ shopt -s globstar
$ set -- "$dir"/**/*
$ for f; do printf '<%s>\n' "$f"; done
<deep/inside/a/dir/directory>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/zz last file>
一部のファイル(ファイルで終わるファイルなど)のみを選択するには、*を置き換えます。
$ set -- "$dir"/**/*file
$ printf '<%s>\n' "$@"
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/directory/zz last file>
<deep/inside/a/dir/file>
<deep/inside/a/dir/zz last file>
壮健
タイトルに「スペースセーフ」を配置するとき、私はあなたが意図したものが「堅牢」であると仮定します。
スペース(または改行)について堅牢にする最も簡単な方法は、スペース(または改行)を持つ入力の処理を拒否することです。シェルでこれを行う非常に簡単な方法は、ファイル名がスペースで拡張された場合にエラーで終了することです。これを行うにはいくつかの方法がありますが、最もコンパクトな(およびposix)(ただし、suddirectories名とドットファイルの回避を含む1つのディレクトリコンテンツに制限されます):
$ set -- "$dir"/file* # read the directory
$ a="$(printf '%s' "$@" x)" # make it a long string
$ [ "$a" = "${a%% *}" ] || echo "exit on space" # if $a has an space.
$ nl='
' # define a new line in the usual posix way.
$ [ "$a" = "${a%%"$nl"*}" ] || echo "exit on newline" # if $a has a newline.
使用したソリューションがこれらの項目のいずれかで堅牢な場合、テストを削除します。
bashでは、上記で説明した**を使用して、サブディレクトリを一度にテストできます。
ドットファイルを含める方法はいくつかありますが、Posixソリューションは次のとおりです。
set -- "$dir"/* "$dir"/.[!.]* "$dir"/..?*
見つける
何らかの理由でfindを使用する必要がある場合は、区切り文字をNUL(0x00)に置き換えます。
bash 4.4+
$ readarray -t -d '' arr < <(find "$dir" -type f -name file\* -print0)
$ printf '<%s>\n' "${arr[@]}"
<deep/inside/a/dir/file name>
<deep/inside/a/dir/file with a
newline>
<deep/inside/a/dir/directory/file name>
<deep/inside/a/dir/directory/file with a
newline>
<deep/inside/a/dir/directory/file>
<deep/inside/a/dir/file>
bash 2.05+
i=1 # lets start on 1 so it works also in zsh.
while IFS='' read -d '' val; do
arr[i++]="$val";
done < <(find "$dir" -type f -name \*file -print0)
printf '<%s>\n' "${arr[@]}"
POSIXLY
findにNUL区切り文字がなく、読み取り用の-d
(nor -a
)がない有効なPOSIXソリューションを作成するには、まったく異なるアプローチが必要です。
-exec
シェルの呼び出しでfindからの複合体を使用する必要があります。
find "$dir" -type f -exec sh -c '
for f do
echo "<$f>"
done
' sh {} +
または、選択が必要な場合(selectはshではなくbashの一部です):
$ find "$dir" -type f -exec bash -c '
select f; do echo "<$f>"; break; done ' bash {} +
1) deep/inside/a/dir/file name
2) deep/inside/a/dir/zz last file
3) deep/inside/a/dir/file with a
newline
4) deep/inside/a/dir/directory/file name
5) deep/inside/a/dir/directory/zz last file
6) deep/inside/a/dir/directory/file with a
newline
7) deep/inside/a/dir/directory/file
8) deep/inside/a/dir/file
#? 3
<deep/inside/a/dir/file with a
newline>
[1]このツリー(\ 012は改行です):
$ tree
.
└── deep
└── inside
└── a
└── dir
├── directory
│ ├── file
│ ├── file name
│ └── file with a \012newline
├── file
├── file name
├── otherfile
├── with a\012newline
└── zz last file
次の2つのコマンドで構築できます。
$ mkdir -p deep/inside/a/dir/directory/
$ touch deep/inside/a/dir/{,directory/}{file{,\ {name,with\ a$'\n'newline}},zz\ last\ file}