Evet, şöyle bir dizi şey görüyoruz:
while read line; do
echo $line | cut -c3
done
Ya da daha kötüsü:
for line in `cat file`; do
foo=`echo $line | awk '{print $2}'`
echo whatever $foo
done
(gülme, onlardan çok gördüm).
Genellikle kabuk komut dosyası başlayanlar. Bunlar, C veya python gibi zorunlu dillerde yapabileceğinizlerin tam anlamıyla çevirileridir, ancak mermilerdeki işleri böyle yapmazsınız ve bu örnekler çok verimsizdir, tamamen güvenilmezdir (potansiyel olarak güvenlik sorunlarına yol açar) ve yönetirseniz hataların çoğunu gidermek için kodunuz okunaksız hale gelir.
kavramsal olarak
C veya diğer birçok dilde, yapı taşları bilgisayar talimatlarının sadece bir üstündedir. İşlemcinize ne yapacağınızı ve sonra ne yapacağınızı söyleyin. İşlemcinizi elinize alıyorsunuz ve mikro-yönetiyorsunuz: o dosyayı açıyorsunuz, birçok baytı okuyorsunuz, bunu yapıyorsunuz, onunla yapıyorsunuz.
Kabuklar daha yüksek bir dildir. Bir dilin bile olmadığını söyleyebilir. Tüm komut satırı tercümanlarından önce. İş, çalıştırdığınız komutlar tarafından yapılır ve kabuk sadece onları düzenlemek içindir.
Unix'in sunduğu harika şeylerden biri boru ve varsayılan olarak tüm komutların işlediği varsayılan stdin / stdout / stderr akışlarıydı.
45 yılda, komutların gücünden faydalanmak ve bir göreve işbirliği yapmalarını sağlamak için bu API'den daha iyi bir şey bulamadık. Bu muhtemelen insanların bugün hala mermi kullanmasının ana nedeni.
Bir kesme aletiniz ve bir harf çevirisi aletiniz var ve şunları yapabilirsiniz:
cut -c4-5 < in | tr a b > out
Kabuk sadece sıhhi tesisat yapıyor (dosyaları açın, boruları ayarlayın, komutları çağırın) ve hepsi hazır olduğunda, kabuğun hiçbir şey yapmadan akması sağlanır. Araçlar işlerini eşzamanlı, verimli bir şekilde, diğerini bloke etmemek için yeterli tamponlama ile kendi hızlarında yaparlar, sadece güzel ve çok basit.
Bir aracın kullanılmasının bir maliyeti vardır (ve bunu performans noktasında geliştireceğiz). Bu araçlar C'deki binlerce komutla yazılabilir. Bir işlem yaratılmalı, alet yüklenmeli, kullanıma hazır hale getirilmeli, daha sonra temizlenmeli, işlem imha edilmeli ve beklenmelidir.
Davet etmek cut
, mutfak çekmecesini açmak, bıçağı almak, kullanmak, yıkamak, kurutmak, tekrar çekmeceye koymak gibidir. Ne zaman yaparsın:
while read line; do
echo $line | cut -c3
done < file
Dosyanın her satırında olduğu gibi, read
aleti mutfak çekmecesinden almak (çok sakarca çünkü bunun için tasarlanmamıştır ), çizgiyi okumak, okuma aletinizi yıkamak, çekmeceye koymak. Sonra echo
ve cut
araç için bir toplantı planlayın , çekmeceden çıkarın, onları çağırın, yıkayın, kurutun, çekmeceye geri koyun ve benzeri.
Bu araçlardan bazıları ( read
ve echo
) çoğu kabukta inşa edilmiştir, ancak bu, o zamandan beri fark yaratmaz echo
ve cut
ayrı işlemlerde çalıştırılması gerekir.
Bir soğanı kesmek gibi ama bıçağınızı yıkamak ve her dilim arasında tekrar mutfak çekmecesine koymak gibi.
Burada bariz yol, cut
aletinizi çekmeceden almak, bütün soğanı dilimlemek ve tüm iş bittikten sonra çekmeceye geri koymaktır.
IOW, kabuklarda, özellikle de metni işlemek için, mümkün olduğunca az yardımcı program başlattınız ve bir sonrakiini çalıştırmadan önce her birinin başlamasını, çalıştırılmasını ve temizlenmesini bekleyen sıralı binlerce aracı çalıştırmamalarını sağlayın.
Bruce'un iyi cevabında daha fazla okuma . Kabuklardaki düşük seviyeli metin işleme dahili araçları (belki de hariç zsh
) sınırlıdır, zahmetlidir ve genellikle genel metin işlemesi için uygun değildir.
Verim
Daha önce de belirtildiği gibi, bir komut çalıştırmanın bir maliyeti vardır. Bu komut yerleşik değilse büyük bir maliyet, ancak yerleşik olsalar bile, maliyet büyüktür.
Ve kabuklar bu şekilde çalışacak şekilde tasarlanmadı, performans programlama dilleri yapmayı önemsemediler. Onlar değil, sadece komut satırı tercümanları. Böylece bu cephede çok az optimizasyon yapıldı.
Ayrıca, kabukları komutları ayrı işlemlerde çalıştırır. Bu yapı taşları ortak bir hafızayı veya durumu paylaşmaz. Bir fgets()
veya fputs()
C'de yaptığınız zaman , bu stdio'daki bir fonksiyondur. stdio, pahalı sistem çağrılarını çok sık yapmaktan kaçınmak için, tüm stdio işlevleri için giriş ve çıkış için dahili tamponları tutar.
Karşılık gelen daha yerleşik kabuk araçları ( read
, echo
, printf
) bunu yapamaz. read
bir satır okumak içindir. Newline karakterini okursa, çalıştırdığınız bir sonraki komut onu kaçıracaktır. Bu nedenle read
, girişi bir defada bir bayt okumak zorundadır (bazı uygulamalar girdilerin topakları okudukları ve geri aradıkları normal bir dosya ise optimizasyonu vardır, ancak bu sadece normal dosyalar için çalışır ve bash
örneğin sadece 128 baytlık parçalar okur). hala metin yardımcı programlarının yapabileceğinden çok daha az.
Çıktı tarafında aynı, echo
sadece çıktısını tamponlayamaz, hemen çalıştırması gerekir çünkü çalıştırdığınız bir sonraki komut bu arabelleği paylaşmaz.
Açıkçası, komutları sırayla çalıştırmak, onları beklemeniz gerektiği anlamına gelir, bu kabuktan ve aletlerden ve sırttan kontrol sağlayan küçük bir zamanlayıcı dansıdır. Bu aynı zamanda (bir boru hattında uzun süre çalışan aletlerin kullanılmasının tersine), mümkün olduğunda birkaç işlemciyi aynı anda kullanamayacağınız anlamına gelir.
Bu while read
döngü ve (sözde) eşdeğeri cut -c3 < file
arasında hızlı testimde testlerimde 40000 civarında bir CPU zaman oranı var (bir saniye ile yarım gün arasında). Ancak sadece kabuk yapıları kullanıyor olsanız bile:
while read line; do
echo ${line:2:1}
done
(burada ile bash
), hala 1: 600 civarında (bir saniye vs 10 dakika).
Güvenilirlik / okunabilirlik
Bu kodu doğru yapmak çok zor. Verdiğim örnekler vahşi doğada çok sık görülüyor ancak birçok böcekleri var.
read
birçok farklı şey yapabilen kullanışlı bir araçtır. Kullanıcıdan girdi okuyabilir, farklı değişkenlerde saklamak için kelimelere bölebilir. read line
yok değil girişin bir çizgi okumak, ya da belki çok özel bir şekilde bir çizgi okur. Aslında okur kelimeleri girişten ile ayrılan bu kelimeleri $IFS
ve nerede ters eğik çizgi ayırıcılar veya satır karakteri kaçmak için kullanılabilir.
Varsayılan olarak $IFS
, gibi bir girişte:
foo\/bar \
baz
biz
read line
beklediğiniz gibi değil , "foo/bar baz"
içine saklayacağım .$line
" foo\/bar \"
Bir satır okumak için, aslında:
IFS= read -r line
Bu çok sezgisel değil, ama bu şekilde, kabukları böyle kullanılmadığını hatırla.
İçin aynı echo
. echo
dizileri genişletir. Rasgele bir dosyanın içeriği gibi rastgele içerikler için kullanamazsınız. Bunun printf
yerine buraya ihtiyacınız var.
Ve elbette, herkesin içine düştüğü değişkenden alıntı yapmanın tipik bir unutması var . Yani daha fazlası:
while IFS= read -r line; do
printf '%s\n' "$line" | cut -c3
done < file
Şimdi, birkaç uyarı daha:
- Bunun dışında
zsh
, giriş NUL karakterleri içeriyorsa, bu en azından GNU metin programları sorun yaşamazsa işe yaramaz.
- Son satırdan sonra veri varsa, atlanacaktır.
- döngü içinde, stdin yeniden yönlendirilir, bu nedenle içindeki komutların stdin'den okumamasına dikkat etmeniz gerekir.
- döngüler içindeki komutlar için başarılı olup olmadıklarına dikkat etmiyoruz. Genellikle, hata (disk dolu, okuma hataları ...) koşulları, genellikle doğru eşdeğerden daha zayıf olarak, kötü ele alınacaktır .
Yukarıdaki bu sorunlardan bazılarını ele almak istiyorsak, bu olur:
while IFS= read -r line <&3; do
{
printf '%s\n' "$line" | cut -c3 || exit
} 3<&-
done 3< file
if [ -n "$line" ]; then
printf '%s' "$line" | cut -c3 || exit
fi
Bu giderek daha az okunaklı hale geliyor.
Verileri argümanlarla komutlara geçirmenin veya değişkenlerde çıktılarını almanın bir kaç sorunu var:
- Argümanların büyüklüğü üzerindeki sınırlama (bazı metin yardımcı programları uygulamalarında da bir sınırlama vardır, ancak ulaşılanların etkisi genellikle daha az problemlidir)
- NUL karakteri (ayrıca metin yardımcı programlarıyla ilgili bir sorun).
- başladığında
-
(veya +
bazen) seçenek olarak alınan argümanlar
- tipik gibilerin döngüler kullanılan çeşitli komutların çeşitli tuhaflıklar
expr
, test
...
- çok baytlık karakterleri tutarsız şekillerde işleyen çeşitli kabukların (sınırlı) metin işleme operatörleri.
- ...
Güvenlik Hususları
Komutlara kabuk değişkenleri ve argümanlar ile çalışmaya başladığınızda , bir mayın tarlasına girersiniz.
Değişkenlerinizden alıntı yapmayı unutursanız , seçenek işaretleyicinin sonunu unutun, çok baytlık karakterlerle yerel olarak çalışın (bugünlerde norm), er ya da geç kırılganlık yaratacak hatalar getireceğiniz kesin.
Ne zaman döngüler kullanmak isteyebilirsiniz.
TBD
yes
yazılır?