Kısa ve Okunabilirlik: Orta Bir Zemin
Gördüğünüz gibi, bu sorun orta derecede uzun ve biraz tekrarlayıcı ancak son derece okunabilir çözümlere ( terdon ve AB'nin bash cevapları) yanı sıra çok kısa ama sezgisel olmayan ve çok daha az kendi kendini belgeleyen çözümlere (Tim'in pitonu) ve bash cevapları ve glenn jackman'ın perl cevabı ). Tüm bu yaklaşımlar değerlidir.
Bu sorunu ayrıca, kompaktlık ve okunabilirlik arasındaki sürekliliğin ortasında kodla da çözebilirsiniz. Bu yaklaşım, küçük, ezoterik çözümlere daha yakın bir uzunlukla neredeyse daha uzun çözeltiler kadar okunabilir.
#!/usr/bin/env bash
read -erp 'Enter numeric grade (q to quit): '
case $REPLY in [qQ]) exit;; esac
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; exit; }
done
echo "Grade out of range."
Bu bash çözümüne, okunabilirliği artırmak için bazı boş satırlar ekledim, ancak daha da kısa olmasını isterseniz bunları kaldırabilirsiniz.
Boş satırlar bundan daha aslında sadece biraz daha kısa olan, dahil bir compactified, hala oldukça okunabilir varyant arasında AB'nin bash çözümü . Bu yönteme göre ana avantajları:
- Daha sezgisel.
- Notlar arasındaki sınırları değiştirmek (veya ek notlar eklemek) daha kolaydır.
- Ön ve arka boşlukları olan girdileri otomatik olarak kabul eder (nasıl
((
))
çalıştığının açıklaması için aşağıya bakın ).
Bu avantajların üçü de ortaya çıkar çünkü bu yöntem kullanıcının girdisini, kurucu basamaklarını manuel olarak incelemek yerine sayısal veri olarak kullanır.
Nasıl çalışır
- Kullanıcının girdisini okuyun. Girdikleri metin içinde hareket etmek için ok tuşlarını kullanmalarına izin verin (
-e
) ve \
bir kaçış karakteri ( -r
) olarak yorumlamayın .
Bu komut dosyası, zengin özelliklere sahip bir çözüm değildir - ayrıntılandırma için aşağıya bakın - ancak bu kullanışlı özellikler yalnızca iki karakter daha uzun olmasını sağlar. Hep kullanmanızı tavsiye -r
ile read
size kullanım tedarik izin vermelisiniz bilmedikçe, \
kaçar.
- Kullanıcı yazdıysa
q
veya Q
çıkın.
- Bir oluşturma , birleştirici bir dizi (
declare -A
). Her harf notuyla ilişkili en yüksek sayısal notla doldurun.
- Döngü aracılığıyla kullanıcı tarafından sağlanan sayı her harf o derecenin sayısal aralığına düşmek yeterince düşük olması durumunda kontrol en düşükten en yükseğe kadar Harf notları,.
İle ((
))
aritmetik değerlendirme, değişken adları ile genişletilmiş olması gerekmez $
. (Diğer çoğu durumda, değişkenin değerini yerine kullanmak istiyorsanız, bunu yapmanız gerekir .)
- Aralıkta kalırsa, sınıfı yazdırın ve çıkın .
Kısacası, - yerine kısa devre ve operatör ( &&
) kullanıyorum .if
then
- Döngü biterse ve hiçbir aralık eşleşmediyse, girilen sayının çok yüksek olduğunu (100'ün üzerinde) varsayalım ve kullanıcıya aralık dışında olduğunu söyleyin.
Tuhaf Girdi ile Bu Nasıl Davranıyor
Yayınlanan diğer kısa çözümler gibi, bu komut dosyası bir sayı olduğunu varsaymadan girişi kontrol etmez. Aritmetik değerlendirme ( ((
))
) otomatik olarak, yani ön ve arka boşluk şeritler de bu sorun, ancak:
- Hiç sayı gibi görünmeyen girdi 0 olarak yorumlanır.
- Sayıya benzeyen (yani bir rakamla başlıyorsa) ancak geçersiz karakterler içeriyorsa, komut dosyası hata verir.
- İle başlayan Çok basamaklı giriş
0
edilir olarak yorumlanır içinde sekizlik . Örneğin, komut dosyası 77'nin bir C olduğunu, 077 ise bir D olduğunu söyleyecektir, ancak bazı kullanıcılar bunu isteyebilir, ancak büyük olasılıkla istemez ve karışıklığa neden olabilir.
- Artı tarafta, aritmetik bir ifade verildiğinde, bu komut dosyası bunu otomatik olarak basitleştirir ve ilişkili harf derecesini belirler. Örneğin, size 320/4'ün B olduğunu söyleyecektir.
Genişletilmiş, Tam Özellikli Sürüm
Bu nedenlerle, girdinin iyi olup olmadığını kontrol eden ve diğer bazı geliştirmeleri içeren bu genişletilmiş komut dosyası gibi bir şey kullanmak isteyebilirsiniz.
#!/usr/bin/env bash
shopt -s extglob
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
while read -erp 'Enter numeric grade (q to quit): '; do
case $REPLY in # allow leading/trailing spaces, but not octal (e.g. "03")
*( )@([1-9]*([0-9])|+(0))*( )) ;;
*( )[qQ]?([uU][iI][tT])*( )) exit;;
*) echo "I don't understand that number."; continue;;
esac
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
done
echo "Grade out of range."
done
Bu hala oldukça kompakt bir çözüm.
Bu hangi özellikleri ekliyor?
Bu genişletilmiş komut dosyasının temel noktaları şunlardır:
- Giriş doğrulama. Terdon senaryosu kontrol girişi ile , bir kullanıcının ön ve sonunda boşluk ve kudreti veya sekizli olarak tasarlandığı olmayabilir bir ifade kabul etmeme girmek için izin daha sağlamdır, kurban bir kısalık, ancak başka bir yol gösterir, böylece (sıfır olmadığı sürece) .
if [[ ! $response =~ ^[0-9]*$ ]] ...
- Kullandığım
case
ile globbing genişletilmiş yerine [[
birlikte =~
düzenli ifade eşleştirme (olduğu gibi operatör Terdon cevabı ). Bunu (ve nasıl) bu şekilde yapılabileceğini göstermek için yaptım. Globs ve regexps, metinle eşleşen desenleri belirtmenin iki yoludur ve her iki yöntem de bu uygulama için uygundur.
- Gibi AB'nin bash komut , ben (ilk yaratılış dışında bir dış döngü içinde her şeyi ekte
cutoffs
dizisi). Rakamlar ister ve terminal girişi mevcut olduğu ve kullanıcı bunu bırakmasını söylemediği sürece ilgili harf notlarını verir. Bakılırsa do
... done
Bunu istiyor gibi, söz konusu kod etrafında görünüyor.
- Bırakmayı kolaylaştırmak için,
q
veya büyük / küçük harfe duyarlı olmayan herhangi bir varyasyonu kabul ediyorum quit
.
Bu komut dosyası acemilere aşina olmayan birkaç yapı kullanır; aşağıda detaylandırılmıştır.
Açıklama: Kullanımı continue
Dış while
halkanın gövdesinin geri kalanını atlamak istediğimde , continue
komutu kullanıyorum. Bu, daha fazla girdi okumak ve başka bir yineleme çalıştırmak için onu döngünün tepesine geri getirir.
Bunu ilk yaptığımda, içinde olduğum tek döngü dış while
döngüdür, bu yüzden continue
argüman olmadan çağırabilirim . (Ben bir case
yapıdayım, ama bu break
veya işlevini etkilemez continue
.)
*) echo "I don't understand that number."; continue;;
Ancak ikinci kez, for
kendisi dış while
döngü içine yuvalanmış bir iç döngü içinde . Herhangi bir continue
argüman kullanmazsam, bu continue 1
dış for
döngü yerine iç döngüye eşdeğer olur ve devam eder while
.
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
Bu durumda continue 2
bash'ı bulup ikinci döngüyü devam ettirmek için kullanıyorum.
Açıklama: case
Globs ile Etiketler
Bir case
sayının hangi harf notu bölmesine girdiğini ( AB'nin bash cevabında olduğu gibi ) bulmak için kullanmıyorum . Ancak case
, kullanıcının girdisinin dikkate alınması gerekip gerekmediğine karar vermek için kullanıyorum :
- geçerli bir sayı,
*( )@([1-9]*([0-9])|+(0))*( )
- quit komutu,
*( )[qQ]?([uU][iI][tT])*( )
- başka herhangi bir şey (ve dolayısıyla geçersiz girdi),
*
Bunlar kabuklu küreler .
- Her
)
birini (
, case
bir desen eşleştirildiğinde çalıştırılan komutlardan ayırmak için sözdizimi olan herhangi bir açıklıkla eşleşmeyen bir harf gelir.
;;
, case
bir patiküler büyük / küçük harf eşleşmesi için çalıştırılacak komutların sonunu gösteren sözdizimidir (ve daha sonraki hiçbir durum çalıştırıldıktan sonra test edilmemelidir).
Sıradan kabuk globbing, *
sıfır veya daha fazla karakteri, ?
tam olarak bir karakteri ve karakter sınıflarını / aralıklarını [
]
parantez içinde eşleştirmeyi sağlar . Ama bunun ötesine geçen genişletilmiş bir globbing kullanıyorum . Genişletilmiş globbing, bash
etkileşimli kullanılırken varsayılan olarak etkindir , ancak komut dosyası çalıştırıldığında varsayılan olarak devre dışıdır. shopt -s extglob
Script üstündeki komut onu açıyor.
Açıklama: Genişletilmiş Globbing
*( )@([1-9]*([0-9])|+(0))*( )
, sayısal girişi kontrol eden bir dizi ile eşleşir:
- Sıfır veya daha fazla boşluk (
*( )
). *(
)
Yapı maçları sıfır ya da sadece bir alandır burada parantez içinde desen, daha.
Aslında iki tür yatay boşluk, boşluk ve sekme vardır ve genellikle sekmeleri de eşleştirmek istenir. Ama bu konuda endişelenmiyorum, çünkü bu komut dosyası manuel, etkileşimli giriş ve GNU okuma çizgisini etkinleştirmek için -e
bayrak için yazılmıştır read
. Bu, kullanıcının sol ve sağ ok tuşlarıyla metninde ileri ve geri hareket edebilmesidir, ancak genellikle sekmelerin tam olarak girilmesini önlemenin yan etkisi vardır.
- Herhangi bir (
@(
)
) öğesinin bir örneği ( |
):
- Sıfır olmayan bir basamak (
[1-9]
) ve ardından *(
)
herhangi bir basamaktan ( [0-9]
) sıfır veya daha fazla ( ).
- Bir veya daha fazla (
+(
)
) / 0
.
- Sıfır veya daha fazla boşluk (
*( )
).
*( )[qQ]?([uU][iI][tT])*( )
, quit komutunu kontrol eden bir dizi ile eşleşir:
- Sıfır veya daha fazla boşluk (
*( )
).
q
veya Q
( [qQ]
).
- İsteğe bağlı olarak - yani, sıfır veya bir tekrarlama (
?(
)
) -:
u
veya U
( [uU]
) ardından i
veya I
( [iI]
) ardından t
veya T
( [tT]
).
- Sıfır veya daha fazla boşluk (
*( )
).
Değişken: Girdiyi Genişletilmiş Düzenli İfadeyle Doğrulama
Kullanıcının girdisini kabuk glob yerine normal ifadeye karşı test etmeyi tercih ediyorsanız, aynı çalışan ancak genişletilmiş globbing yerine [[
ve =~
( terdon cevabında olduğu gibi) kullanan bu sürümü kullanmayı tercih edebilirsiniz case
.
#!/usr/bin/env bash
shopt -s nocasematch
declare -A cutoffs
cutoffs[F]=59 cutoffs[D]=69 cutoffs[C]=79 cutoffs[B]=89 cutoffs[A]=100
while read -erp 'Enter numeric grade (q to quit): '; do
# allow leading/trailing spaces, but not octal (e.g., "03")
if [[ ! $REPLY =~ ^\ *([1-9][0-9]*|0+)\ *$ ]]; then
[[ $REPLY =~ ^\ *q(uit)?\ *$ ]] && exit
echo "I don't understand that number."; continue
fi
for letter in F D C B A; do
((REPLY <= cutoffs[$letter])) && { echo $letter; continue 2; }
done
echo "Grade out of range."
done
Bu yaklaşımın olası avantajları:
Bu özel durumda, sözdizimi biraz daha basittir, en azından ikinci desende, quit komutunu kontrol ediyorum. Ben ayarlamak başardı olmasıdır nocasematch
kabuk seçeneği ve tüm dava ve varyantları q
ve quit
otomatik kaplıydı.
Yani ne shopt -s nocasematch
komut yok. shopt -s extglob
Komut bu sürümde kullanılmaz globbing olarak kullanılmaz.
Düzenli ifade becerileri bash'ın uzatmalarında yeterlilikten daha yaygındır.
Açıklama: Normal İfadeler
=~
Operatörün sağında belirtilen kalıplara gelince , bu düzenli ifadeler nasıl çalışır.
^\ *([1-9][0-9]*|0+)\ *$
, sayısal girişi kontrol eden bir dizi ile eşleşir:
- Çizginin (
^
) sol kenarı - başlangıcı .
- Sıfır veya daha fazla (
*
, uygulanan postfix) boşluk. Bir boşluğun \
normal ifadede kaçması gerekmez , ancak [[
sözdizimi hatasını önlemek için buna ihtiyaç vardır .
- Aşağıdakilerden
(
)
biri veya diğeri ( |
) olan bir alt dize ( ) :
[1-9][0-9]*
: sıfır olmayan bir rakam ( [1-9]
) ve ardından *
herhangi bir rakamın ( [0-9]
) sıfır veya daha fazla ( , postfix uygulandı ).
0+
: bir veya daha fazla ( +
, uygulanan postfix) 0
.
- Daha
\ *
önce olduğu gibi sıfır veya daha fazla boşluk ( ).
- Çizginin sonu (
$
) , sağ kenarı - ( ).
case
Test edilen tüm ifadeyle eşleşen etiketlerin aksine =~
, sol ifadesinin herhangi bir kısmı sağ ifadesi olarak verilen desenle eşleşirse true değerini döndürür. Bu nedenle, satırın başlangıcını ve sonunu belirten ^
ve $
çapalarına ihtiyaç vardır ve yöntemde case
ve extglobs ile görünen herhangi bir şeye sözdizimsel olarak karşılık gelmez.
Parantez yapmak için gerekli olan ^
ve $
bir ayrılmalara bağlama [1-9][0-9]*
ve 0+
. Aksi takdirde bir ayrılma olur ^[1-9][0-9]*
ve 0+$
ve sıfır olmayan bir rakam ile başlayan herhangi bir giriş eşleşen ya da bir ile biten 0
(ya da halen arasında olmayan basamak içerebilir bunların her ikisi de,).
^\ *q(uit)?\ *$
, quit komutunu kontrol eden bir dizi ile eşleşir:
- Satırın başlangıcı (
^
).
- Sıfır veya daha fazla boşluk (
\ *
yukarıdaki açıklamaya bakınız).
- Mektup
q
. Veya Q
, shopt nocasematch
etkinleştirildiğinden beri .
- İsteğe bağlı olarak -
?
alt dizenin ( (
)
) sıfır veya bir örneği (düzeltme ) - :
u
, ardından i
, ardından t
. Veya shopt nocasematch
etkin u
olduğundan U
; bağımsız olarak, i
olabilir I
; ve bağımsız olarak t
olabilir T
. (Yani, olasılıklar ve ile sınırlı değildir .)uit
UIT
- Sıfır veya daha fazla boşluk (
\ *
).
- Satırın sonu (
$
).