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 tab
e 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 tab
e 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 tab
e 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 bundle
a ait alt komutları,
daha sonra da rake
e ait komutları tamamlamam gerekiyor.
_bundler_complete() | |
{ | |
if [[ ! `which bundle` ]]; then | |
return | |
fi | |
local cur prev commands | |
commands="help install update package exec config check list show outdated console open viz init gem platform" | |
cur="${COMP_WORDS[COMP_CWORD]}" | |
prev="${COMP_WORDS[COMP_CWORD-1]}" | |
if [[ "${cur}" == -* ]]; then | |
local bundle_options="--no-color --verbose" | |
COMPREPLY=( $(compgen -W "${bundle_options}" -- ${cur}) ) | |
return 0 | |
fi | |
case "${prev}" in | |
"rake") | |
if [[ ! -e Rakefile ]]; then | |
return | |
fi | |
local rake_options=$(bundle exec rake -T | awk '{print $2}' | xargs) | |
COMPREPLY=( $(compgen -W "${rake_options}" -- ${cur}) ) | |
return 0 | |
;; | |
"exec") | |
bin_folder=( $(find . -name 'bin' | xargs) ) | |
executables=`command ls ${bin_folder} | xargs` | |
commands="${commands} rake ${executables}" | |
;; | |
esac | |
COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) ) | |
return 0 | |
} | |
complete -F _bundler_complete bundle |
İ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.
gem
leri 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.
gem
lerin 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 gem
lerin executable
larını da
çağırabiliyorsunuz. Örneğin, Octopress için kurulan gem
lerin 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.