Bash Completion Anotomisi
Terminal’le, Bash’le ilk tanıştığım andan itibaren tab-completion olayının
hastası olmuş, hemen:
Hmmm… Kesin öğrenmeliyim! Ben de kendi komutlarımı oluşturmalıyım!
demiştim. Neticede bir şekilde programlanabilir birşey olmalıydı. Mac OS’la
uğraşmaya başladığım ilk günlerde tanıştığım MacPorts içinde
pek çok bash-completion eklentileriyle geliyordu. Bu sayede tab ile tamamlama
yapabilmek için gerekli kaynağı inceleme şansı bulmuştum.
Mantık olarak, bir şekilde, bir değişkende bir yerde tamamlanacak kelimeler
durmalıydı. tab bu kelimeler içinde cycle yapmayı sağlamalıydı.
dscacheutil
Denemek için ilk yaptığım tamamlama dscacheutil içindi. DNS işleriyle ilgili
bu tool’u dns-cache’i silmek için kullanıyordum. Komutu:
dscacheutil -flushcache
şeklinde kullanıyordum. Yapmak istediğim şey dscacheutil yazdıktan sonra -
yazıp tab tuşuna basmak ve ilgili opsiyonları otomatik olarak yazdırmaktı.
dscacheutil -h
ile baktım başka ne gibi opsiyonlar var. Pekde anlamadım diğer özellikleri. İçlerin kafama yatanları seçtim:
-flushcache -statistics -configuration -cachedump -h -q
Bash’in complete adında minik bir komutu var. Mantık olarak, şu komut
yazılıp tab tuşuna basılınca bu fonksiyon çalışsın.
Gördüğüm örneklerde tamamlama işini yapacak fonksiyon adı, ilgili komut adına
_ eklenerek oluyor. Yani dscacheutil için yazacağınız fonksiyonun adı
_dscacheutil olmalı. Bu sadece bir notasyon yani zorunlu bir durum değil.
complete -F FONKSİYON KOMUT
complete -F _dscacheutil dscacheutil
Düşe kalka bir şekilde ilk completion’ımı yazmayı başarmıştım:
_dscacheutil()
{
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="-flushcache -statistics -configuration -cachedump -h -q"
if [[ ${cur} == -* ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
return 0
fi
}
complete -F _dscacheutil dscacheutil
Satır Satır “_dscacheutil”
Şimdi, 4.satırda COMPREPLY adında boş bir array var. cur Current /
Cursor yani tab basıldığında aktif olarak yakalanan / gelen. prev de
Previous yani tamamlanacak olan kelimelerde, aktif olandan bir önceki.
Yani tamamlamak istediğimiz kelimeler 7.satırdaki opts değişkeninde
sırasıyla;
-flushcache -statistics -configuration -cachedump -h -q
şeklinde duruyor. dscacheutil yazıp bir boşluk verip tabe ilk bastığınızda
index 0 ve ilk kelime: -flushcache yani cur daki değer olur.
Eğer dscacheutil yazıp, bir boşluk bırakıp, -s yazıp tabe bastığınızda
tamamlanacak kelimler listesindeki ikinci kelimeyi Current yani cur
yapmış olursunuz. Çünkü kelimelerde -s ile başlayan bir tek -statistics
var. Eğer -s ile başlayan iki kelime olsaydı, -statistics ve -super
gibi; İlk tabe basışta cur: -statistics, ikincide cur: -super ve
prev: -statistics olacaktı.
Bu fonksiyonun asıl gizli silahı compgen. -W parametresi WORD LIST yani
kelime listesi anlamında. Hemen daha iyi anlamak için:
compgen -W "ali veli selami"
# ali
# veli
# selami
compgen -W "ali veli selami" -- veli
# veli
şeklinde çıktı verir. Yani yukarıdaki örnekte opts: ali veli selami,
cur: veli olmuş ve işlem bize sonuç olarak veli dönmüştür. Yani
9.satırı düşünürsek:
COMPREPLY=( $(compgen -W "ali veli selami" -- veli) )
gibi. 8.satıra bakarsak;
if [[ ${cur} == -* ]] ; then
yani; - ile başlayanları yakala. opts içinde geçen ve - ile başlayan.
complete -F _dscacheutil dscacheutil
-F function yani, dscacheutil komutu için _dscacheutil fonksiyonunu
kullanarak tamamlama yap.
Özetle, dscacheutil komutundan sonra - + tab yapınca
-flushcache -statistics -configuration -cachedump -h -q kelimeleri içinde
dönüp duracağız.
bundle exec
İlk denemem üzerinden epeyce bir zaman geçmişti. Keza geçen süre içinde bash bilgim de arttı. Hep gördüğüm, merak ettiğim bir konu da zincirleme tamamlama. Yani;
KOMUT ALT-KOMUT ALT-KOMUT / OPSIYON
gibi tamamlamalar. Buna en güzel örnek Gitflow. git flow
feature start şeklinde arka arkaya 3-4 komutu tamamlamak mümkün. Bu
durumdan yola çıkarak bundle komutu için benzer bir yardımcı yazmaya
karar verdim.
Umarım yazıyı okuyanlar Ruby ile ilgilidir. Çünkü Bundler Ruby ve Rails dünyasının çok yakından bildiği ve kullandığı ruby modülü / kütüphanesi paket yöneticisidir. Özellikle bu okuduğunuz blog’da kullandığım Octopress’de bile Bundler kullanmaktayım.
Tamamlama işlemi için bundle exec rake ve rake’e ait alt özellikler vs
zincirleme işlem yapmam gerekiyor. Öncelikle bundlea ait alt komutları,
daha sonra da rakee ait komutları tamamlamam gerekiyor.
İlk önce 8.satırda yazan bundle komutlarını buldum. bundle --help
ile tek tek baktım neler var diye. Tabi bir başka durum daha vardı. Bu
tamamlama özelliği eğer bundle komutu varsa çalışmalıydı. Bundler’ı
genelde tüm sisteme kurmak yerine proje bazlı kullanmak daha temizdir.
gemleri kurarken;
bundle install --path vendor/bundle
şeklinde kullanıyorum. Bulunduğum proje altında vendor/bundle dizinine
kurduruyorum. Bu bakımdan tüm sistem yerine proje bazlı kurulum var. Yani
ilgili proje / ruby versiyonu aktif olduğu zaman, eğer varsa Bundler aktif
oluyor. Bunun içinde fonksiyonun en başında which bundle ile kontrol
ediyorum.
bundle komutuna ait alt komutlar ve opsiyonlar var. Opsiyonlar -- ile
başlıyor. Yani bundle --verbose install şeklinde bir tamamlama sekansı
olabilir. Keza, rake veya exec arkasında da ilgili tamamlamalar gelmeli.
rake komutu da başlı başına kendine has opsiyonlara sahip. rake -T
özelliği ile ilgili RAKE TASK’leri listeleyebiliyorsunuz. Örnek;
rake -T
rake clean # Clean out caches: .pygments-cache, .gist-cache, .sass-cache
rake copydot[source,dest] # copy dot files for deployment
rake deploy # Default deploy task
rake gen_deploy # Generate website and deploy
rake generate # Generate jekyll site
gibi… Tabi rake komutu da aynı bundle komutu gibi başka bir dosyanın
varlığına bağlı. Rakefile tüm task’lerin ve diğer ayarların bulunduğu
dosya. Eğer Rakefile olmazsa rake komutu da çalışmaz.
gemlerin yeri aktif olan ruby modülleri bölgesinde olmadığı için, bundle
komutuyla vendor/bundle altına kurduğumuz executable yani çalıştıralabilir
dosyaları da çağırabiliyoruz.
bundle exec ile, vendor/bundle altına kurulan gemlerin executablelarını da
çağırabiliyorsunuz. Örneğin, Octopress için kurulan gemlerin içinde;
compass jekyll posix-spawn-benchmark redcloth tilt
dw kramdown rackup sass
haml maruku rake sass-convert
html2haml marutex rdiscount scss
binary dosyalar var.. Çalıştırmak için: bundle exec html2haml gibi
kullanabilirsiniz. Bu bakımdan bund exec dedikten sonra komutu da
tamamlamamız / bulmamız gerekiyor. Bunun için 28 ve 29.satırlara
bakalım. Önce, vendor/bundle altındaki bin dizinlerini bulmamız gerekiyor.
find . -name 'bin' # içinde bin kelimesi geçen file/folder’ları bul
./vendor/bundle/ruby/1.9.1/bin
./vendor/bundle/ruby/1.9.1/gems/classifier-1.3.3/bin
./vendor/bundle/ruby/1.9.1/gems/compass-0.11.6/bin
./vendor/bundle/ruby/1.9.1/gems/directory_watcher-1.4.1/bin
./vendor/bundle/ruby/1.9.1/gems/haml-3.1.4/bin
./vendor/bundle/ruby/1.9.1/gems/haml-3.1.4/vendor/sass/bin
./vendor/bundle/ruby/1.9.1/gems/jekyll-0.11.0/bin
./vendor/bundle/ruby/1.9.1/gems/kramdown-0.13.4/bin
./vendor/bundle/ruby/1.9.1/gems/maruku-0.6.0/bin
./vendor/bundle/ruby/1.9.1/gems/posix-spawn-0.3.6/bin
./vendor/bundle/ruby/1.9.1/gems/rack-1.3.5/bin
./vendor/bundle/ruby/1.9.1/gems/rake-0.9.2.2/bin
./vendor/bundle/ruby/1.9.1/gems/rb-fsevent-0.9.1/bin
./vendor/bundle/ruby/1.9.1/gems/rdiscount-1.6.8/bin
./vendor/bundle/ruby/1.9.1/gems/RedCloth-4.2.9/bin
./vendor/bundle/ruby/1.9.1/gems/sass-3.1.12/bin
./vendor/bundle/ruby/1.9.1/gems/tilt-1.3.3/bin
# xargs ile bu sonuçları tek satır yapıyoruz
find . -name 'bin' | xargs
Elimizde, space karakteri ile ayrılmış upuzun bir string var artık.
Yani aynı ilk _dscacheutil örneğindeki $opts gibi oldu. Yapmanız gereken
elimizdeki liste içinden ilk elemanı almak. Bunun için küçük bir nurmara
yapıyoruz.
bin_folder=( $(find . -name 'bin' | xargs) # ./vendor/bundle/ruby/1.9.1/bin
Artık içine bakmamız gereken dizini biliyoruz. Şimdi yapmamız gereken aynı
şekilde liste alıp space ile ayrılmış string oluşturmak.
executables=`command ls ${bin_folder} | xargs`
command ls aslında bildiğiniz ls komutunu çağırıyor ama eğer alias ya da
başka bir override durumu varsa; Yani siz belkide:
alias ls="ls -al --color"
yaptınız ve her ls komutu çalıştığında aslında ls -al --color çalıştırıyor
olabilirsiniz. Bunu engellemek için bash’in içindeki default komutu çağırmak
gerekiyor. command bu işe yarıyor.
Son olarak 34. satırda;
COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) )
word list olarak compgen komutuna $commands değişkenini geçiyoruz. Yani
tüm yaptığımız, en son satırda kullanacağımız COMPREPLY için gerekli
string’i oluşturmak.
Bu tamamlama fonksiyonunu kullanabilmek için; .bashrc ya da .profile ya da
her ne kullanıyorsanız, o dosyaya eklemek. Örneğin bu _bundler_complete
fonksiyonunu $HOME/bundler_complete.sh şeklinde kaydettiyseniz; kullandığınız
.bashrc içinde
source $HOME/bundler_complete.sh
yapmanız gerekiyor. Bash Completion konusuyla ilgili daha fazla ve detaylı bilgi için link’e tıklayabilirsiniz.