回答:
bashの堅牢な方法の1つは、配列に展開し、最初の要素のみを出力することです。
pattern="*.txt"
files=( $pattern )
echo "${files[0]}" # printf is safer!
(ただecho $files
、できますが、欠落しているインデックスは[0]として扱われます。)
これは、ファイル名を展開するときに、スペース/タブ/改行およびその他のメタキャラクターを安全に処理します。有効なロケール設定により、「最初の」内容が変更される可能性があることに注意してください。
bash 補完関数を使用して、これをインタラクティブに行うこともできます。
_echo() {
local cur=${COMP_WORDS[COMP_CWORD]} # string to expand
if compgen -G "$cur*" > /dev/null; then
local files=( ${cur:+$cur*} ) # don't expand empty input as *
[ ${#files} -ge 1 ] && COMPREPLY=( "${files[0]}" )
fi
}
complete -o bashdefault -F _echo echo
これにより、_echo
関数がecho
コマンドへの引数を完了するようにバインドされます(通常の完了をオーバーライドします)。余分な「*」が上記のコードに追加されます。ファイル名の一部でタブを押すだけで、うまくいけば正しいことが起こります。
コードはやや複雑で、設定または仮定nullglob
(shopt -s nullglob
)ではなくcompgen -G
、グロブをいくつかの一致に展開し、配列に安全に展開し、最後にCOMPREPLYを設定して引用が堅牢になるようにします。
bash'sを使用してこれを部分的に(プログラムで展開)することもできますがcompgen -G
、引用符なしで標準出力に出力されるため、堅牢ではありません。
いつものように、完了はかなり複雑です。これは、環境変数を含む他のものの完了を中断します(デフォルトの動作をエミュレートする詳細については、こちらの_bash_def_completion()
関数を参照してください)。
compgen
補完関数の外側で使用することもできます:
files=( $(compgen -W "$pattern") )
注意すべき点の1つは、「〜」はグロブではなく、別の展開段階でbashによって処理されることです。$ variablesやその他の展開も同様です。compgen -G
単にファイル名のグロビングcompgen -W
を行いますが、bashのデフォルトの拡張をすべて提供しますが、拡張が多すぎる可能性があります(``
および を含む$()
)。とは異なり-G
、-W
は安全に引用されています(格差は説明できません)。目的は-W
トークンを展開することであるため、このようなファイルが存在しなくても「a」から「a」に展開することを意味するため、おそらく理想的ではありません。
これは理解しやすいですが、望ましくない副作用があるかもしれません:
_echo() {
local cur=${COMP_WORDS[COMP_CWORD]}
local files=( $(compgen -W "$cur") )
printf -v COMPREPLY %q "${files[0]}"
}
次に:
touch $'curious \n filename'
echo curious*
tab
printf %q
値を安全に引用するための使用に注意してください。
最後の1つのオプションは、GNUユーティリティで0で区切られた出力を使用することです(bash FAQを参照)。
pattern="*.txt"
while IFS= read -r -d $'\0' filename; do
printf '%q' "$filename";
break;
done < <(find . -maxdepth 1 -name "$pattern" -printf "%f\0" | sort -z )
このオプションを使用すると、並べ替え順序を少し細かく制御できます(globを展開するときの順序は、ロケールに応じて異なります/ LC_COLLATE
大文字と小文字を区別する場合としない場合があります)。
zshでは、[1]
glob修飾子を使用します。この特殊なケースは最大で1つの一致を返しますが、それでもリストであり、グロブは割り当て(配列割り当ては別として)などの単一の単語を予期するコンテキストでは展開されないことに注意してください。
echo *.txt([1])
kshまたはbashでは、一致するリスト全体を配列に詰め込み、最初の要素を使用できます。
tmp=(*.txt)
echo "${tmp[0]}"
どのシェルでも、位置パラメータを設定して最初のパラメータを使用できます。
set -- *.txt
echo "$1"
これにより、位置パラメータが破壊されます。そうしたくない場合は、サブシェルを使用できます。
echo "$(set -- *.txt; echo "$1")"
独自の位置パラメータのセットを持つ関数を使用することもできます。
set_to_first () {
eval "$1=\"\$2\""
}
set_to_first f *.txt
echo "$f"
*.txt([1,n])
簡単な解決策:
sh -c 'echo "$1"' sh *.txt
または、必要にprintf
応じて使用します。
同じことを考えながら、私はこの古い質問に出くわしました。私はこれで終わった:
echo $(ls *.txt | head -n1)
あなたは、もちろん、置き換えることができるhead
とtail
し、-n1
他の番号で。
名前に改行を含むファイルを使用している場合、上記は機能しません。改行を使用するには、次のいずれかを使用できます。
ls -b *.txt | head -n1 | sed -E 's/\\n/\n/g'
(BSDでは動作しません)ls -b *.txt | head -n1 | sed -e 's/\\n/\'$'\n/g'
ls -b *.txt | head -n1 | perl -pe 's/\\n/\n/g'
echo -e "$(ls -b *.txt | head -n1)"
(特殊文字で動作します)私がよく遭遇するユースケースは、glob展開後にtop / bottomディレクトリを定義することです(たとえば、バージョン管理されたSDKまたはビルドツールでいっぱいのディレクトリ)。この状況では、通常、そのディレクトリ名を変数に保存して、シェルスクリプト内のいくつかの場所で使用します。
このコマンドは通常私のためにそれを行います:
export SDK_DIR=$(dirname /path/to/versioned/sdks/*/. | tail -n1)
免責事項:Glob拡張では、フォルダーをsemverで並べ替えることはできません。あなたは警告されました。これはDockerfile
、ディレクトリのバージョンが1つしかない場合に便利ですが、ディレクトリのバージョンは画像ごとに異なる場合があります🤷
mkdir "$(echo one; echo two)"
意味を確認してください。
tail
ですか?
dirname
は単一のパス名のみを使用するため、特定の実装でサポートされていることがわからない限り、複数のパス名で動作することに依存することはできません。