SSD Nodes Learn 🎉 VPS $5.50/aydan başlayan
Rehberler Matt ConnorYazan Matt Connor · Güncellendi 2026-08-13

Bash geçmiş genişletmesi: !! ve !$ kullanımı

!! komutu son işlemi tekrarlarken !$ son argümanı getirir. Bash geçmiş genişletme operatörlerini ve komutu çalıştırmadan önce ekrana basan :p parametresini detaylıca inceleyin.

Bash geçmiş genişletmesi ne işe yarar

Bash geçmiş genişletmesi, siz çalıştırmadan önce kabuk geçmişinizdeki önceki bir komut satırını yeniden oluşturur. !! bir önceki komutu tekrarlar. !$ bir önceki komutun son argümanını ekler. Bu değiştirme işlemi metinseldir ve bash satırı kelimelere ayırmadan önce gerçekleşir; dolayısıyla geri dönen sonuç, ilk seferde yazdığınız karakterlerin aynısıdır.

Altı farklı biçim, bir sunucuda yapacağınız işlemlerin neredeyse tamamını kapsar:

  • !! önceki satırın tamamını tekrarlar, sudo !! ise aynı komutu root yetkisiyle tekrar çalıştırır.
  • !$ önceki satırın son argümanıdır.
  • !* önceki satırın tüm argümanlarını ifade eder.
  • !n n numaralı geçmiş kaydını çalıştırır, !-n ise bulunduğunuz noktadan geriye doğru sayar.
  • !string string ile başlayan en son komutu çalıştırır.
  • ^old^new önceki satırı, ilk old ifadesini new ile değiştirerek yeniden çalıştırır.

Aşağıdaki her şey, kendi sunucunuzdaki etkileşimli bir komut satırına yazılır. Geçmiş genişletmesi betiklerde kapalıdır; son bölüm bunun nedenini açıklamaktadır.

Tek seferde yazılan uzun bir yol

Bu durum, kendi maliyetini çıkaran bir örnektir. Bir release dizini hazırlıyorsunuz ve yol, ikinci kez yazıldığında yazım hatasına davetiye çıkaracak kadar uzundur.

sudo mkdir -p /srv/www/app/releases/2026-08-07
sudo chown -R deploy:deploy !$
ls -ld !$
sudo -u deploy nano !$/config.env

Her satırı çalıştırmadan önce bash, genişletmenin ürettiği satırı ekrana basar:

sudo chown -R deploy:deploy /srv/www/app/releases/2026-08-07

Bu echo sizin kontrolünüzdür. Komutun çıktısını okumadan önce bunu okuyun, çünkü bash'in !$ ifadesinden ne anladığını görmeniz için tek şansınız budur.

Zincir bozulmaz çünkü bash, yazdığınız !$ ifadesini değil, genişletilmiş satırı geçmişe kaydeder. Dolayısıyla 3. satır, son argümanını genişletilmiş 2. satırdan alır ve 4. satır da bunu 3. satırdan alır. 4. satır ayrıca bir belirleyiciden sonra metin gelebileceğini gösterir: !$/config.env, sonuna /config.env eklenmiş yol haline gelir, çünkü kelime belirleyici / karakterinde durur.

Aynı oturumdan bir örnek daha. Dosyayı düzenledikten sonra onu barındıran dizini listelemek istiyorsunuz:

ls -l !$:h

:h, baş (head) değiştiricisidir. Bir yoldan son bileşeni çıkarır; bu, dirname komutunun yaptığı işin aynısıdır. :t yalnızca son bileşeni tutar, :r uzantıyı kaldırır ve :e ise yalnızca uzantıyı tutar.

Son komutu !! ve sudo !! ile tekrarlama

sudo komutunu unutursunuz ve servis yöneticisi bunu size bildirir:

systemctl restart nginx
Failed to restart nginx.service: Interactive authentication required.
sudo !!

Bash, !! ifadesini bir önceki satırın metniyle değiştirir, böylece kabuk sudo systemctl restart nginx komutunu çalıştırır.

!! tam olarak bir önceki satırdır, o satır ne olursa olsun. Klasik kaza burada gerçekleşir. Son komutun başarısız olan komut olduğunu düşünürsünüz, ancak o sırada bir cd veya history çalıştırmış olabilirsiniz ya da başarısız olan komut iki satır geride kalmış olabilir. sudo !! bu durumda yanlış komutu root yetkileriyle çalıştırır. Emin olmadığınız durumlarda önce yazdırın:

sudo !!:p

:p genişletilmiş satırı yazdırır ve çalıştırmaz. Yazdırılan satır geçmişinize eklenir; bu nedenle satır doğru görünüyorsa, bir sonraki satırda sade bir !! komutu ile çalıştırılabilir.

Son argümanı !$ ile yeniden kullanma

!$, bir önceki satırın son kelimesidir. Bu, en sık kullanacağınız biçimdir; çünkü son kelime genellikle üzerinde çalıştığınız şeydir: bir yol veya bir servis adı.

sudo systemctl status nginx
sudo systemctl reload !$

Burada insanları şaşırtan iki durum vardır.

Birincisi, !$ sizin aklınızdaki son argüman değil, son kelimedir. Eğer önceki satır bir yönlendirme ile bitiyorsa, son kelime yönlendirme hedefidir:

sudo nginx -T > /tmp/nginx-dump.conf
less !$

Bu örnekte sonuç beklendiği gibidir. Ancak journalctl -u nginx > /tmp/log.txt komutundan sonra, !$ ifadesi /tmp/log.txt olur, nginx değil. Ekrana yazdırılan satırı okuyun.

İkincisi, genişletme işleminin metinsel olmasıdır; bu nedenle bir değişken genişletilmemiş halde geri döner. ls $HOME/backups komutundan sonra, !$ size $HOME/backups karakterlerini verir ve bash bunu daha sonra normal bir parametre olarak tekrar genişletir. Bu sıralamayı unutmayın: geçmiş genişletmesi (history expansion), parametre genişletmesinden ve $( ) ile komut ikamesinden önce çalışır; bu nedenle değerleri değil, yalnızca metni görür.

Yakın formları bilmekte fayda vardır. !^ ilk argümandır, !:2 ikincidir ve !:2-4 bir aralıktır. !!:$, !$ yazmanın uzun yoludur.

Metni onaylamadan önce görmek isterseniz, Alt-. (veya Esc ve ardından .) tuşlarına basın. Readline, önceki komutun son argümanını doğrudan komut satırınıza ekler ve burada düzenleme yapmanıza olanak tanır. Bir önceki komutun son argümanına geri dönmek için tekrar basın. Siz Enter tuşuna basana kadar hiçbir şey çalıştırılmaz.

Tüm argümanları !* ile aktarın

!*, ilk kelime hariç önceki satırdaki her kelimedir.

stat /srv/www/app/shared/config.env /srv/www/app/shared/secrets.env
sudo chmod 600 !*

"İlk kelime hariç" ifadesi burada kritik bir işlev görür. !*, 0. kelimeyi atar ve başka hiçbir şeye dokunmaz; bu sayede seçenekler yollarla birlikte taşınır. ls -l file1 file2 sonrasında !*, -l file1 file2 değerini alır; bu nedenle sudo chmod 600 !* başarısız olur çünkü chmod, -l değerini bir argüman olarak alır. Kelimeleri sıfırdan başlayarak numaralandırın ve bir dilim alın: !!:2*, 2. kelimeden sona kadar olan kısmı ifade eder.

Aynı tuzak sudo için de geçerlidir. sudo chown deploy:deploy /srv/www/app sonrasında 0. kelime sudo, 1. kelime ise chown olur; bu nedenle !*, bir sonraki komuta chown deploy:deploy /srv/www/app değerini iletir. Bu, genellikle amaçlanan sonuç değildir.

!n ve !-n ile konumuna göre komut seçimi

history, her girdinin önünde bir numara olacak şekilde listeyi yazdırır.

history 5
  512  sudo nginx -t
  513  sudo systemctl reload nginx
  514  ss -tulpn
  515  sudo tail -f /var/log/nginx/error.log
  516  history 5

!513, 513 numaralı girdiyi tekrar çalıştırır. !-2, şu an yazdığınız satırdan geriye doğru sayarak iki satır önceki girdiyi çalıştırır; bu nedenle !-1 ve !! aynı anlama gelir.

Numaraları kullanmadan önce mutlaka kontrol edin. !-2, herhangi bir komut çalıştırdığınızda (düşünmeden tetiklediğiniz bir ls dahil) her seferinde farklı bir noktayı işaret eder. Mutlak numaralar tek bir oturum içerisinde sabittir ancak aynı sunucudaki ikinci bir oturumda aynı numaralar geçerli değildir; ayrıca bir sonraki girişinizde geçmiş dosyası yeniden yüklendiğinde de numaralar değişir. Dün ezberlediğiniz bir numara, bugün farklı bir komutu işaret eder.

Bir komutu önekiyle !string kullanarak yeniden çalıştırma

!string, string ile başlayan en son komutu çalıştırır.

!ss

Bu komut, yukarıdaki listeden ss -tulpn ifadesini, yani Linux sunucusunda hangi portların açık olduğunu görme bölümünde açıklanan dinleme soketi kontrolünü yeniden çalıştırır. !?string?, komutun başlangıcında değil, satırın herhangi bir yerinde eşleşme sağlar; bu, komut adını hatırlamayıp sadece bir argümanı hatırladığınız durumlarda yardımcı olur.

Öneki uzun tutun. !s; en son çalıştırılan ss, sudo, systemctl veya shutdown komutlarından hangisiyle eşleşirse onu çalıştırır ve siz çalışana kadar hangisinin tetikleneceğini bilemezsiniz. !string:p, eşleşen komutu çalıştırmadan ekrana yazdırır. Hiçbir eşleşme bulunamazsa, bash bash: !ss: event not found çıktısını verir ve hiçbir işlem yapmaz; bu güvenli bir sonuçtur.

Bir yazım hatasını ^eski^yeni ile düzeltme

sudo systemctl status ngnix
Unit ngnix.service could not be found.
^ngnix^nginx

Kabuk, önceki satırı ilk ngnix ifadesini nginx ile değiştirerek yeniden çalıştırır. Yalnızca ilk eşleşme değiştirilir. Her eşleşmeyi değiştirmek için !!:gs/ngnix/nginx/ uzun biçimi kullanılır; burada s değiştirme anlamına gelir ve g işlemin tüm satır boyunca uygulanmasını sağlar.

Çalıştırmadan önce genişletmeyi görüntüleyin

Bir genişletmenin amaçlamadığınız bir şeyi çalıştırmasına neden olan kazaları önlemek için iki alışkanlık mevcuttur.

İlki, daha önce gördüğünüz :p komutudur. Bir genişletmenin sonuna bunu eklerseniz, bash komutu çalıştırmak yerine sonucu yazdırır: !!:p veya !systemctl:p. Yazdırılan satır geçmişinize kaydedilir, bu nedenle sonrasında !! komutunu kullanarak az önce okuduğunuz şeyi çalıştırabilirsiniz.

İkincisi daha güçlüdür çünkü hiçbir şeyi hatırlamanıza gerek kalmadan her genişletme için geçerli olur:

shopt -s histverify

Bu satırı ~/.bashrc dosyasına ekleyin. histverify ayarlandığında, Enter tuşuna bastığınızda genişletme hemen çalışmaz. Bash, genişletilmiş satırı komut satırınıza geri yazar; böylece satırı okuyup düzenleyebilir ve çalıştırmak için ikinci kez Enter tuşuna basabilirsiniz. Bu yöntem tek bir tuş vuruşu gerektirir ve tüm kaza türlerini ortadan kaldırır. Readline kullandığı için yalnızca etkileşimli komut satırında çalışır, başka hiçbir yerde çalışmaz.

Ctrl-R, çalışmanın diğer yoludur ve hiçbir şeyi genişletmez. Ctrl-R tuşlarına basın ve bir komutun bir kısmını yazın. Komut satırı bir reverse-i-search istemine dönüşür ve siz yazdıkça en son eşleşen komutu gösterir. Daha eski eşleşmelere geri gitmek için tekrar Ctrl-R tuşuna basın. Enter tuşu, gösterilen satırı çalıştırır. Ctrl-G aramayı iptal eder ve orijinal komut satırınızı geri getirir. Sol ok tuşu aramayı sonlandırır ve eşleşen satırı düzenlemeniz için komut satırında bırakır.

Komutu önce görmek istediğinizde Ctrl-R kullanın. Komut bir satır öncesindeyse ve hala ekranda okuyabiliyorsanız !! ve !$ kullanın.

Neden echo "done!" komutu event not found hatası veriyor?

echo "deploy done!"
bash: !": event not found

Geçmiş genişletme (history expansion), tırnak işaretleri çözümlenmeden önce çalışır ve çift tırnaklar ! karakterini korumaz. Yalnızca tek tırnaklar ve ters eğik çizgi bunu sağlar. Bu nedenle echo 'deploy done!' beklediğiniz çıktıyı verir. Ters eğik çizgi de genişletmeyi durdurur ancak çift tırnak içinde bash, ters eğik çizgiyi çıktıda bırakır; bu yüzden tek tırnaklar temiz çözümdür.

Bu durum en çok parolalarda sorun yaratır çünkü güçlü bir parola genellikle ! karakterini içerir. mysql -u app -p"S3cret!pass" gibi bir komut ya event-not-found hatasıyla başarısız olur ya da geçmişte eşleşen bir girdi varsa sessizce farklı bir dizge gönderir. Tek tırnak kullanın veya daha iyisi, gizli bilginin komut satırına hiç ulaşmaması için aracın size parola sormasını sağlayın. Bu alışkanlık, hesap hijyeninizin bir parçası olmalıdır: Anahtarların ve parolaların yeni bir sunucuda nasıl yönetilmesi gerektiğini öğrenmek için yeni bir VPS üzerindeki ilk on dakika rehberine bakın.

Eğer ! karakterleriyle dolu bir metin bloğunu yapıştırmak üzereyseniz, oturum için genişletmeyi set +H ile kapatın ve işiniz bittiğinde set -H ile tekrar açın.

HISTSIZE, HISTFILESIZE ve geçmişinizin tutulduğu yer

İki değişken vardır ve isimleri birbirine yakın olduğu için insanlar bunları karıştırır.

  • HISTSIZE, çalışan kabuğun (shell) bellekte kaç komut tutacağını belirler.
  • HISTFILESIZE, diskteki dosyada kaç satır tutulacağını belirler; ~/.bash_history, HISTFILE aksini belirtmediği sürece bu geçerlidir.

Dosya, kabuk kapandığında yazılır ve o anda HISTFILESIZE satıra kadar kırpılır. Ubuntu'nun varsayılan ~/.bashrc değeri 1000 ve 2000 olarak ayarlanmıştır. Sizinkinin halihazırda ne içerdiğini kontrol edin:

grep HIST ~/.bashrc
echo "$HISTSIZE $HISTFILESIZE $HISTFILE"

Eğer !string aramalarınızda çalıştırdığınızdan emin olduğunuz komutları bulamıyorsanız her iki değeri de yükseltin. 10000 ve 20000 gibi değerler yaygındır ve dosya düz metin olduğu için neredeyse hiçbir maliyeti yoktur. Negatif bir değer, hiçbir sınır olmadığı anlamına gelir.

Zaman damgaları, bir sunucuya bir hafta aradan sonra geri döndüğünüzde yardımcı olur:

export HISTTIMEFORMAT='%F %T '

history, her girdinin önüne bir tarih ve saat yazdırır; çünkü bash, dosyadaki her komuttan önce epoch saniyelerini tutan bir yorum satırı yazmaya başlar.

Yaygın bir şikayet, birden fazla terminal kullandığınızda geçmişin kaybolmasıdır. Her kabuk kendi listesini bellekte tutar ve çıkışta dosyaya yazar; bu nedenle histappend olmadan, kapanan son kabuk diğerlerinin kaydettiklerinin üzerine yazar. İki ayar bunu düzeltir:

shopt -s histappend
export PROMPT_COMMAND='history -a'

histappend, kabuğun listesini dosyanın üzerine yazmak yerine dosyanın sonuna eklemesini sağlar. history -a, her komut isteminden sonra yeni satırları ekler; böylece düzgün kapatılmak yerine sonlandırılan bir oturumdaki komutlar da korunmuş olur. Ubuntu'nun varsayılan ~/.bashrc ayarı zaten histappend değerini içerir. Bu durum, yönettiğiniz makine sayısı arttıkça daha önemli hale gelir; çünkü geçmiş kullanıcı bazlı ve makine bazlı tutulur. Bu nedenle, tek bir iş istasyonundan birden fazla sunucuyu yönetirken aradığınız !$ sadece başka bir sunucuda olabilir.

Gizli bilgileri bash geçmişinin dışında tutmak

HISTCONTROL, hangi satırların kaydedileceğine karar verir.

  • ignorespace, boşluk ile başlayan tüm satırları atlar.
  • ignoredups, bir öncekiyle aynı olan satırları atlar.
  • ignoreboth, yukarıdaki her iki işlemi de gerçekleştirir.
  • erasedups, kaydedilmekte olan satırın tüm eski kopyalarını siler.
export HISTCONTROL=ignoreboth

ignorespace aktifken, komutun başına bir boşluk eklemek komutu bellek içi listeden uzak tutar, böylece komut hiçbir zaman dosyaya yazılmaz. Bu özelliğe güvenmeden önce echo "$HISTCONTROL" ile değeri kontrol edin. Ubuntu'nun varsayılan kullanıcı .bashrc ayarı ignoreboth değerini atar, ancak bir root kabuğu veya minimal bir imaj bu değeri tanımsız bırakabilir; bu durumda başına boşluk eklemek hiçbir işe yaramaz ve satır diğerleri gibi kaydedilir.

Bu yöntemin neyi koruduğu konusunda gerçekçi olun. Satır ~/.bash_history dosyasında yer almaz. Ancak süreç çalışırken ps çıktısında görünür kalır, bu nedenle sunucudaki diğer kullanıcılar bunu okuyabilir. sudo, çalıştırdığı komutu sistem günlüğüne kaydeder. Geçmiş dosyası düz metin halindedir, bu yüzden dosya modunu 600 olarak tutun ve ev dizininizi okuyabilen herkesin son birkaç bin komutunuzu görebileceğini unutmayın.

Eğer listede zaten hassas bir veri varsa, girişi silin ve dosyayı yeniden yazın:

history
history -d 517
history -w

history -d, ilgili girişi bellekten siler ve history -w mevcut listeyi dosyanın üzerine yazar. history -c tüm listeyi temizler. HISTIGNORE, gizli bilgilerden ziyade gürültü (gereksiz kayıt) için kullanılan ilgili ayardır: HISTIGNORE='ls:pwd:history:clear', bu satırları listeden uzak tutarak aramalarınızın anlamlı sonuçlar vermesini sağlar.

Shell betiklerinde geçmiş genişletme (history expansion) neden çalışmaz

Geçmiş genişletme, etkileşimli kabuklara (interactive shells) özgü bir özelliktir. Bir betik, geçmiş listesinin etkin olmadığı ve genişletmenin kapalı olduğu etkileşimli olmayan bir kabukta çalışır; bu nedenle !! ve !$ satırda normal metin olarak kalır. Bir betik içindeki sudo !!, sudo'dan tam olarak !! adında bir komutu çalıştırmasını ister ve bu işlem başarısız olur.

İçinde bulunduğunuz kabuğu kontrol edin:

echo $-

Çıktı, mevcut seçenek bayraklarının kümesidir; himBHs gibi bir şey görünür. i, kabuğun etkileşimli olduğu, H ise geçmiş genişletmenin etkin olduğu anlamına gelir. Aynı satırı bir betik içinde çalıştırdığınızda bu harflerin ikisi de orada olmaz.

Bu, kabuk çalışmalarınızın iki yarısı arasındaki sınırdır. Komut satırında !$ ve Ctrl-R, görebildiğiniz komutlarda tuş vuruşlarından tasarruf etmenizi sağlar. Bir dosyada ise bunun yerine isimlendirme yaparsınız: yolu bir değişkene atayın veya çıktıyı komut ikamesi ile yakalayın. Kişisel geçmişinize bağlı bir betik, onu çalıştıran bir sonraki kişi için farklı bir sonuç doğurur ki bu, betiklerin varlık amacına aykırıdır.

Bu nedenle, bu sayfadaki her örnek canlı bir komut satırına yazılmak üzere tasarlanmıştır. Bunların hiçbiri, bir .sh dosyasına yapıştırıldığında aynı şekilde davranmaz.

FAQ

bash içinde !! ne işe yarar?

!!, bir önceki komut satırının tam metnini genişletir; bu nedenle sudo !!, son komutunuzu root yetkileriyle tekrar çalıştırır. Genişletme işlemi metinseldir ve bash satırı ayrıştırmadan önce gerçekleşir; bash, komutu çalıştırmadan hemen önce nihai satırı ekrana yazdırır. Bir önceki satırın ne olduğundan emin değilseniz, önce sudo !!:p yazın. :p, genişletilmiş ifadeyi çalıştırmadan ekrana yazdırır ve bu satırı geçmişe ekler; böylece ardından gelen bir !! komutu bu satırı çalıştırır.

Bir önceki komutun son argümanını nasıl tekrar kullanabilirim?

!$ kullanın. sudo mkdir -p /srv/www/app/releases komutundan sonra, ls -ld !$ satırı ls -ld /srv/www/app/releases haline gelir. Bu, satırdaki son kelimeyi alır; dolayısıyla satırın sonundaki bir yönlendirme hedefi de son kelime olarak kabul edilir. Ayrıca sonuna metin ekleyebilirsiniz: !$/config.env, yola ekleme yapar çünkü kelime belirteci / karakterinde durur. İnteraktif alternatif ise, aynı metni komut satırınıza yerleştiren ve Enter tuşuna basmadan önce kontrol etmenize olanak tanıyan Alt-. komutudur.

Metnim ünlem işareti içerdiğinde bash neden "event not found" hatası veriyor?

Çift tırnaklar ! ifadesini geçmiş genişletmesinden korumaz; bu nedenle echo "done!", bash'in bir geçmiş olayı arayıp bash: !": event not found çıktısını vermesine neden olur. Tek tırnaklar ise bunu korur, bu yüzden echo 'done!' şeklinde yazın. Ters eğik çizgi de genişletmeyi durdurur ancak çift tırnak içinde bash, ters eğik çizgiyi çıktıda bırakır. İçinde ! bulunan uzun bir bloğu yapıştırmak için, set +H ile oturum boyunca genişletmeyi kapatın.

!! ve !$ neden bash betiğimde çalışmıyor?

Geçmiş genişletmesi yalnızca interaktif kabuklarda etkindir. Bir betik interaktif olmayan şekilde çalışır, bu nedenle kabuk hiçbir zaman bir geçmiş listesi oluşturmaz ve !! satırda düz metin olarak kalır. Hangi modda olduğunuzu görmek için echo $- komutunu çalıştırın: interaktif bir kabuk, i ve H dahil olmak üzere bayrakları yazdırır; bir betik ise hiçbirini yazdırmaz. Betiklerde bunun yerine bir değişken veya komut ikamesi kullanın.

Bir parolayı bash geçmişimden nasıl uzak tutabilirim?

~/.bashrc dosyasında HISTCONTROL=ignorespace veya HISTCONTROL=ignoreboth ayarını yapın, ardından komutu tek bir boşlukla başlatın; böylece komut asla kaydedilmez. Değeri önce echo "$HISTCONTROL" ile doğrulayın, çünkü bu ayar tanımlanmamışsa baştaki boşluk hiçbir işe yaramaz. Bu yöntem, satırı yalnızca ~/.bash_history dosyasından uzak tutar. Komut çalışırken ps üzerinde görünür olmaya devam eder ve sudo, yürütülen işlemleri kaydeder. Eğer bir gizli bilgi zaten kaydedilmişse, history ile numarasını bulun, ardından history -d <number> ve ardından history -w komutlarını çalıştırarak dosyayı yeniden yazın.