Joker genişlemesinden ilk eşleşmeyi nasıl alabilirim?


37

Bash ve Zsh gibi mermiler, joker karakteri argümanlara genişletir, örüntüyle eşleşen argümanlar:

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

Peki ya sadece ilk karşılaşmanın geri kazanılmasını istersem, tüm maçları değil mi?

$ echo *.txt
1.txt

Kabuğa özgü çözümlere aldırış etmiyorum, ancak dosya adlarında boşlukla çalışan bir çözüm istiyorum.


ls * .txt | kafa -1?
Archemar

1
@Archemar: Dosya adlarındaki yeni satırlarla çalışmaz.
Flimm

Yanıtlar:


24

Bash içindeki sağlam bir yol, bir diziye genişlemek ve sadece ilk elemanı çıkarmaktır:

pattern="*.txt"
files=( $pattern )
echo "${files[0]}"  # printf is safer!

(Sadece echo $fileseksik bir dizini [0] olarak kabul edebilirsiniz.)

Bu, dosya adlarını genişletirken, space / tab / newline ve diğer meta karakterleri güvenli bir şekilde işler. Geçerli yerel ayarların "ilk" in ne olduğunu değiştirebileceğini unutmayın.

Bunu bir bash tamamlama işleviyle etkileşimli olarak da yapabilirsiniz :

_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

Bu, _echoargümanları echokomuta tamamlama işlevini bağlar (normal tamamlamayı geçersiz kılma). Yukarıdaki koda ek bir "*" eklenir, kısmi bir dosyada sekmeye basabilirsiniz ve umarım doğru olanı ortaya çıkar.

Kod, küme veya varsayım yerine hafifçe kıvrımlıdır nullglob( shopt -s nullglob) compgen -G, glob'u bazı eşleşmelere genişletebileceğini kontrol ederiz , daha sonra güvenli bir şekilde bir diziye genişleriz ve son olarak COMPREPLY'yi belirleyerek, alıntılamanın sağlam olmasını sağlarız.

Şunları yapabilirsiniz kısmen bu bash en ile (programlı bir topak genişletmek) yapmak compgen -G, ancak Stdout'a tırnaksız çıkışı gibi sağlam değil.

Her zamanki gibi, tamamlama, ortam değişkenleri (bkz dahil diğer şeyler, bu tatili tamamlanması oldukça doludur _bash_def_completion()işlevi burada varsayılan davranışı taklit ayrıntıları için).

Ayrıca compgenbir tamamlama işlevinin dışında da kullanabilirsiniz :

files=( $(compgen -W "$pattern") )

Unutulmaması gereken noktalardan biri, "~" nin bir küre olmadığı, $ değişkenleri ve diğer açılımlar gibi ayrı bir genişleme aşamasında bash tarafından ele alınmasıdır. compgen -Gsadece dosya ismini globbing yapıyor, ancak compgen -Wmuhtemelen çok fazla genişleme ( ``ve de dahil olmak üzere $()) olmak üzere bash'ın varsayılan genişlemesini veriyor . Aksine -G, -W bir güvenle alıntılanan (ı eşitsizlik izah edemez). Amacı -Wbelirteçleri genişletmek olduğundan, bu böyle bir dosya olmasa bile "a" dan "a" ya çıkaracağı anlamına gelir, bu yüzden belki de ideal değildir.

Bunu anlamak daha kolaydır, ancak istenmeyen yan etkileri olabilir:

_echo() {
    local cur=${COMP_WORDS[COMP_CWORD]}
    local files=( $(compgen -W "$cur") ) 
    printf -v COMPREPLY %q "${files[0]}"  
}

Sonra:

touch $'curious \n filename'

echo curious*tab

printf %qDeğerleri güvenle alıntı yapmak için kullanıldığına dikkat edin.

Son seçeneklerden biri GNU yardımcı programları ile 0 ayrılmış çıktı kullanmaktır ( bash SSS'ye bakın ):

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 )

Bu seçenek, sıralama düzeni üzerinde size biraz daha fazla kontrol sağlar (bir küreyi genişletme sırası yerelinize göre olacaktır / LC_COLLATEve büyük / küçük harf olabilir veya olmayabilir), ancak aksi takdirde böyle küçük bir sorun için oldukça büyük bir çekiç ;-)


20

Zsh'de, [1] glob niteleyicisini kullanın . Bu özel durumun en fazla bir eşleşme ile sonuçlanmasına rağmen hala bir liste olduğunu ve ödevlerin (dizi atamaları) gibi tek bir kelime bekleyen bağlamlarda globların genişletilmediğini unutmayın.

echo *.txt([1])

Ksh veya bash'da, bir dizideki tüm eşleşme listesini düzenleyebilir ve ilk elemanı kullanabilirsiniz.

tmp=(*.txt)
echo "${tmp[0]}"

Herhangi bir kabukta konumsal parametreleri ayarlayabilir ve ilkini kullanabilirsiniz.

set -- *.txt
echo "$1"

Bu konumsal parametreleri gizler. Eğer istemiyorsanız, bir alt kabuk kullanabilirsiniz.

echo "$(set -- *.txt; echo "$1")"

Ayrıca kendi konumlandırma parametreleri olan bir işlevi de kullanabilirsiniz.

set_to_first () {
  eval "$1=\"\$2\""
}
set_to_first f *.txt
echo "$f"

1
İlk $ n $ karşılaşmaları alabilmeniz için kullanabilirsiniz*.txt([1,n])
Emre

6

Deneyin:

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

Dosya adı genişlemesinin geçerli yerel ayarda geçerli olan harmanlama sırasına göre sıralandığını not edin.



1

Aynı şeyi merak ederken sadece bu eski soruyu rastladım. Ben bununla bitti:

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

Elbette, herhangi bir numara headile tailve -n1ile değiştirebilirsiniz .


Adında yeni satırları olan dosyalar üzerinde çalışıyorsanız yukarıdaki çalışmaz. Yeni hatlarla çalışmak için bunlardan herhangi birini kullanabilirsiniz:

  • ls -b *.txt | head -n1 | sed -E 's/\\n/\n/g' (BSD üzerinde çalışmıyor)
  • 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)" (Herhangi bir özel karakterle çalışır)

3
Hayır, dosya adı yeni satırlara sahipse bu başarısız olur.
Isaac,

7
dosya isimlerinin newlines içerdiği yerlerde ne tür bir çılgın dünya yaşıyoruz?
billynoah

-1

Sıklıkla karşılaştığım bir kullanım durumu, glob genişlemesinden sonra üst / alt dizini (örneğin, sürümlendirilmiş SDK'larla dolu bir dizin veya oluşturma araçları) tanımlamaktır. Bu durumda genellikle bu dizin adını bir kabuk betiğinin içinde birkaç yerde kullanılmak üzere bir değişkene kaydetmek isteyeceğim.

Bu komut genellikle benim için yapar:

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

Feragatname: Glob genişlemesi klasörlerinizi semver'e göre sıralamayacaktır; uyarılmıştın. DockerfileBir dizinin yalnızca bir sürümüne sahipseniz bu harikadır , ancak bu dizin sürümü görüntüden görüntüye değişebilir 🤷


U&L'ye Hoşgeldiniz! Bu, dizin adlarının çoğunu işlemez, ancak dizin adlarını yeni bir satırla işlemez. Böyle bir dizin oluşturmayı deneyin mkdir "$(echo one; echo two)"ve ne demek istediğimi anlayın.
Flimm

Diğer alternatiflere, özellikle de kullanılan versiyona kıyasla avantajı nedir tail?
RalfFriedl

Standart dirnameyalnızca tek bir yol adı alır, bu nedenle özel uygulamanızın desteklediğini bilmediğiniz sürece birden fazla yol üzerinde çalışmaya güvenemezsiniz.
Kusalananda

@ Flimm İyi nokta; Sanırım çoğu geliştirici, klasör yapılarında yeni çizgiler varsa başa çıkmada daha büyük problemler yaşayacaklarını ... Asla bununla uğraşmak zorunda kalmamıştım ve kullandığım yarı değerli herhangi bir kap ve yazılımla başa çıkmayı beklemiyorum.
Andrew Odri

@RalfFriedl İyi bir soru; bu aslında geçerli bir dizin olmayan her şeyi filtrelendiriyor (ve listelenmiyor / geçmiyor ve ..)
Andrew Odri
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.