ワイルドカード展開から最初の一致を取得するにはどうすればよいですか?


38

BashやZshなどのシェルは、ワイルドカードを引数に展開します。引数はパターンに一致する数だけです。

$ echo *.txt
1.txt 2.txt 3.txt

しかし、すべての一致ではなく、最初の一致のみを返したい場合はどうすればよいですか?

$ echo *.txt
1.txt

シェル固有のソリューションは気にしませんが、ファイル名の空白に対応するソリューションが欲しいです。


ls * .txt | ヘッド-1?
アルケマール14

1
@Archemar:ファイル名の改行では機能しません。
フリム14

回答:


25

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コマンドへの引数を完了するようにバインドされます(通常の完了をオーバーライドします)。余分な「*」が上記のコードに追加されます。ファイル名の一部でタブを押すだけで、うまくいけば正しいことが起こります。

コードはやや複雑で、設定または仮定nullglobshopt -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大文字と小文字を区別する場合としない場合があります)。


20

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"

1
そして、最初の$ n $マッチを取得するには、次を使用できます*.txt([1,n])
Emre

6

試してください:

for i in *.txt; do printf '%s\n' "$i"; break; done
1.txt

ファイル名の展開は、現在のロケールで有効な照合シーケンスに従ってソートされることに注意してください。



1

同じことを考えながら、私はこの古い質問に出くわしました。私はこれで終わった:

echo $(ls *.txt | head -n1)

あなたは、もちろん、置き換えることができるheadtailし、-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)" (特殊文字で動作します)

3
いいえ、ファイル名に改行がある場合は失敗します。
アイザック

7
ファイル名に改行が含まれる場合、どのようなクレイジーな世界に住んでいますか?
ビリーノア

-1

私がよく遭遇するユースケースは、glob展開後にtop / bottomディレクトリを定義することです(たとえば、バージョン管理されたSDKまたはビルドツールでいっぱいのディレクトリ)。この状況では、通常、そのディレクトリ名を変数に保存して、シェルスクリプト内のいくつかの場所で使用します。

このコマンドは通常私のためにそれを行います:

export SDK_DIR=$(dirname /path/to/versioned/sdks/*/. | tail -n1)

免責事項:Glob拡張では、フォルダーをsemverで並べ替えることはできません。あなたは警告されました。これはDockerfile、ディレクトリのバージョンが1つしかない場合に便利ですが、ディレクトリのバージョンは画像ごとに異なる場合があります🤷


U&Lへようこそ!これはほとんどのディレクトリ名を処理しますが、改行を含むディレクトリ名は処理しません。このようなディレクトリを作成して、mkdir "$(echo one; echo two)"意味を確認してください。
フリム

他の選択肢、特に使用するバージョンと比較した利点は何tailですか?
RalfFriedl

Standard dirnameは単一のパス名のみを使用するため、特定の実装でサポートされていることがわからない限り、複数のパス名で動作することに依存することはできません。
クサラナナンダ

@Flimm良い点; フォルダー構造に改行が含まれていると、ほとんどの開発者が対処すべき大きな問題を抱えていると思います。
アンドリューオドリ

@RalfFriedl良い質問です。これは基本的に、有効なディレクトリではないものを除外します(そして、。/ ..をリスト/トラバースしません)
Andrew Odri
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.