SSD Nodes Learn 🎉 VPS dari $5.50/bln
Panduan Matt ConnorOleh Matt Connor · Dikemas kini 2026-08-13

Cara Guna Bash History Expansion !! dan !$

Ketahui cara menggunakan !! untuk mengulang perintah terakhir dan !$ untuk argumen terakhir. Pelajari enam teknik pengembangan sejarah Bash serta fungsi :p untuk semakan.

Fungsi pengembangan sejarah bash

Pengembangan sejarah bash membina semula baris perintah terdahulu daripada sejarah shell anda sebelum bash menjalankannya. !! mengulangi perintah sebelumnya. !$ memasukkan argumen terakhir daripada perintah sebelumnya. Penggantian ini bersifat tekstual dan berlaku pada peringkat awal, sebelum bash memecahkan baris kepada perkataan, jadi apa yang dikembalikan adalah aksara tepat yang anda taip pada kali pertama.

Enam bentuk merangkumi hampir semua perkara yang akan anda lakukan pada pelayan:

  • !! mengulangi keseluruhan baris sebelumnya, dan sudo !! menjalankannya semula sebagai root.
  • !$ ialah argumen terakhir daripada baris sebelumnya.
  • !* ialah setiap argumen daripada baris sebelumnya.
  • !n menjalankan entri sejarah nombor n, dan !-n mengira secara undur dari kedudukan anda sekarang.
  • !string menjalankan perintah paling terkini yang bermula dengan string.
  • ^old^new menjalankan semula baris sebelumnya dengan old pertama digantikan oleh new.

Segala-galanya di bawah ditaip pada prompt interaktif di pelayan anda sendiri. Pengembangan sejarah dimatikan dalam skrip, yang dijelaskan oleh bahagian terakhir.

Satu laluan panjang, ditaip sekali

Ini adalah kes yang sangat berbaloi. Anda sedang menyediakan direktori release, dan laluannya cukup panjang sehingga menaipnya kali kedua mengundang kesilapan taip.

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

Sebelum menjalankan setiap baris, bash mencetak baris yang dihasilkan oleh pengembangan tersebut:

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

Gema (echo) itu adalah semakan anda. Baca ia sebelum anda membaca output arahan tersebut, kerana itu adalah satu-satunya peluang untuk anda melihat apa yang bash tentukan sebagai !$.

Rantaian ini kekal kerana bash menyimpan baris yang telah dikembangkan dalam sejarah, bukan !$ yang anda taip. Jadi baris 3 mengambil argumen terakhirnya daripada baris 2 yang telah dikembangkan, dan baris 4 mengambilnya daripada baris 3. Baris 4 juga menunjukkan bahawa teks boleh mengikuti penentu (designator): !$/config.env menjadi laluan tersebut dengan /config.env di hujungnya, kerana penentu perkataan berhenti pada /.

Satu lagi contoh daripada sesi yang sama. Selepas menyunting fail tersebut, anda ingin menyenaraikan direktori yang menyimpannya:

ls -l !$:h

:h ialah pengubah suai head. Ia membuang komponen terakhir daripada sesuatu laluan, tugas yang sama dilakukan oleh dirname. :t hanya mengekalkan komponen terakhir, :r membuang sambungan (extension), dan :e hanya mengekalkan sambungan tersebut.

Ulangi arahan terakhir dengan !! dan sudo !!

Anda terlupa sudo, dan pengurus servis memberitahu perkara tersebut:

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

Bash menggantikan !! dengan teks baris sebelumnya, jadi shell menjalankan sudo systemctl restart nginx.

!! adalah tepat baris sebelumnya, walau apa pun baris itu. Di sinilah kemalangan klasik berlaku. Anda percaya arahan terakhir adalah arahan yang gagal, tetapi sejak itu anda telah menjalankan cd, atau history, atau arahan yang gagal itu adalah dua baris ke belakang. sudo !! kemudian menjalankan arahan yang salah dengan keistimewaan root. Cetak arahan itu dahulu apabila anda tidak pasti:

sudo !!:p

:p mencetak baris yang dikembangkan dan tidak menjalankannya. Baris yang dicetak itu ditambah ke dalam sejarah anda, jadi jika ia kelihatan betul, !! biasa pada baris seterusnya akan menjalankannya.

Gunakan semula argumen terakhir dengan !$

!$ ialah perkataan terakhir daripada baris sebelumnya. Ini ialah bentuk yang paling kerap anda gunakan, kerana perkataan terakhir biasanya merupakan perkara yang sedang anda kerjakan: laluan atau nama servis.

sudo systemctl status nginx
sudo systemctl reload !$

Dua perkara sering mengejutkan pengguna di sini.

Pertama, !$ ialah perkataan terakhir, bukan argumen terakhir yang anda fikirkan. Jika baris sebelumnya berakhir dengan pengalihan (redirection), perkataan terakhir ialah sasaran pengalihan tersebut:

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

Perkara itu berfungsi dengan baik. Namun selepas journalctl -u nginx > /tmp/log.txt, !$ ialah /tmp/log.txt dan bukannya nginx. Baca baris yang dipaparkan.

Kedua, pengembangan ini bersifat tekstual, jadi pemboleh ubah akan kembali dalam bentuk yang belum dikembangkan. Selepas ls $HOME/backups, !$ memberikan anda aksara $HOME/backups, yang kemudiannya dikembangkan semula oleh bash sebagai parameter biasa. Ingat urutan ini: pengembangan sejarah (history expansion) dijalankan sebelum pengembangan parameter dan sebelum penggantian arahan dengan $( ), jadi ia tidak melihat nilai, hanya teks.

Bentuk yang berkaitan juga perlu diketahui. !^ ialah argumen pertama, !:2 ialah argumen kedua, dan !:2-4 ialah julat. !!:$ ialah cara panjang untuk menulis !$.

Jika anda lebih suka melihat teks tersebut sebelum melaksanakannya, tekan Alt-. (atau Esc kemudian .). Readline akan memasukkan argumen terakhir daripada arahan sebelumnya terus ke dalam prompt anda, di mana anda boleh menyuntingnya. Tekan sekali lagi untuk kembali ke argumen terakhir daripada arahan sebelumnya. Tiada apa-apa yang akan dijalankan sehingga anda menekan Enter.

Lalukan setiap argumen dengan !*

!* ialah setiap perkataan daripada baris sebelumnya kecuali perkataan pertama.

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

Frasa "kecuali perkataan pertama" memainkan peranan penting. !* membuang perkataan 0 dan tiada yang lain, jadi pilihan (options) akan disertakan bersama laluan (paths). Selepas ls -l file1 file2, !* menjadi -l file1 file2, jadi sudo chmod 600 !* gagal kerana chmod menerima -l sebagai argumen. Nombor bagi perkataan bermula dari sifar dan ambil hirisan (slice) sebaliknya: !!:2* bermaksud perkataan 2 sehingga ke penghujung.

Perangkap yang sama berlaku dengan sudo. Selepas sudo chown deploy:deploy /srv/www/app, perkataan 0 ialah sudo dan perkataan 1 ialah chown, jadi !* menyerahkan chown deploy:deploy /srv/www/app kepada arahan seterusnya. Itu jarang sekali hasil yang anda inginkan.

Memilih arahan mengikut kedudukan dengan !n dan !-n

history mencetak senarai dengan nombor di hadapan setiap entri.

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 menjalankan entri 513 sekali lagi. !-2 menjalankan entri dua baris ke belakang, dikira daripada baris yang sedang anda taip sekarang, jadi !-1 dan !! membawa maksud yang sama.

Semak nombor tersebut serta-merta sebelum anda menggunakannya. !-2 menghala ke lokasi baharu setiap kali anda menjalankan sesuatu, termasuk ls yang anda lancarkan tanpa berfikir. Nombor mutlak adalah stabil dalam satu sesi, tetapi ia bukan nombor yang sama dalam sesi kedua pada mesin yang sama, dan ia tidak lagi sama selepas log masuk anda yang seterusnya membaca semula fail sejarah. Nombor yang anda hafal semalam menghala ke arahan yang berbeza pada hari ini.

Menjalankan semula arahan menggunakan awalan dengan !string

!string menjalankan arahan paling terkini yang bermula dengan string.

!ss

Perintah itu menjalankan semula ss -tulpn daripada senarai di atas, iaitu semakan soket yang sedang mendengar (listening socket) yang diterangkan dalam port mana yang terbuka pada pelayan Linux. !?string? memadankan rentetan di mana-mana bahagian baris arahan dan bukannya pada permulaan sahaja, yang membantu apabila anda mengingati argumen tetapi bukan nama arahannya.

Pastikan awalan tersebut panjang. !s akan memadankan ss, sudo, systemctl atau shutdown, mengikut mana yang dijalankan paling baru, dan anda tidak akan tahu yang mana satu sehingga ia dijalankan. !string:p mencetak padanan tersebut tanpa menjalankannya. Jika tiada apa-apa yang sepadan, bash akan mencetak bash: !ss: event not found dan tidak menjalankan apa-apa, yang merupakan hasil yang selamat.

Betulkan satu ralat taip dengan ^lama^baharu

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

Shell menjalankan semula baris sebelumnya dengan ngnix pertama digantikan oleh nginx. Hanya padanan pertama yang ditukar. Untuk menukar setiap padanan, gunakan bentuk panjang !!:gs/ngnix/nginx/, di mana s bermaksud gantikan dan g bermaksud lakukan pada keseluruhan baris.

Cetak pengembangan sebelum anda menjalankannya

Dua tabiat dapat menghalang kemalangan di mana pengembangan menjalankan sesuatu yang tidak anda niatkan.

Tabiat pertama ialah :p, yang telah anda lihat sebelum ini. Tambahkannya pada pengembangan dan bash akan mencetak hasilnya dan bukannya menjalankannya: !!:p, atau !systemctl:p. Baris yang dicetak akan masuk ke dalam sejarah anda, jadi !! selepas itu akan menjalankan perkara yang baru sahaja anda baca.

Tabiat kedua lebih berkesan, kerana ia terpakai pada setiap pengembangan tanpa perlu anda mengingati apa-apa:

shopt -s histverify

Letakkan baris tersebut dalam ~/.bashrc. Dengan histverify ditetapkan, sesuatu pengembangan tidak akan berjalan apabila anda menekan Enter. Bash akan menulis semula baris yang telah dikembangkan ke dalam prompt anda supaya anda boleh membaca dan menyuntingnya, kemudian anda menekan Enter buat kali kedua untuk menjalankannya. Ia memerlukan satu tekanan kekunci tambahan dan ia menghapuskan keseluruhan kelas kemalangan tersebut. Ia memerlukan readline, jadi ia hanya berfungsi pada prompt interaktif dan tidak di tempat lain.

Ctrl-R ialah cara lain untuk bekerja, dan ia tidak pernah mengembangkan apa-apa. Tekan Ctrl-R dan taip sebahagian daripada arahan. Prompt akan berubah menjadi prompt reverse-i-search dan menunjukkan padanan paling terkini semasa anda menaip. Tekan Ctrl-R sekali lagi untuk melangkah kembali ke padanan yang lebih lama. Enter akan menjalankan baris yang sedang dipaparkan. Ctrl-G membatalkan carian dan mengembalikan prompt asal anda. Kekunci anak panah kiri menamatkan carian dan meninggalkan baris yang dipadankan dalam prompt anda untuk disunting.

Gunakan Ctrl-R apabila anda mahu melihat arahan tersebut terlebih dahulu. Gunakan !! dan !$ apabila arahan tersebut baru berusia satu baris dan anda masih boleh membacanya pada skrin.

Mengapa echo "done!" mengeluarkan ralat event not found

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

Pengembangan sejarah (history expansion) dijalankan sebelum petikan diselesaikan, dan tanda petikan berkembar tidak melindungi aksara !. Hanya tanda petikan tunggal dan garis miring terbalik (backslash) yang mampu melindunginya. Oleh itu, echo 'deploy done!' mencetak hasil seperti yang dijangkakan. Garis miring terbalik juga menghentikan pengembangan tersebut, namun di dalam petikan berkembar, bash akan mengekalkan garis miring terbalik dalam output, jadi penggunaan petikan tunggal adalah penyelesaian yang lebih kemas.

Masalah ini paling ketara dengan kata laluan, kerana kata laluan yang kuat sering mengandungi !. Perintah seperti mysql -u app -p"S3cret!pass" akan gagal dengan ralat event-not-found, atau jika terdapat entri sejarah yang sepadan, ia akan menghantar rentetan yang berbeza secara senyap. Gunakan petikan tunggal, atau lebih baik lagi, biarkan alat tersebut meminta input daripada anda supaya maklumat sulit tidak langsung sampai ke baris perintah. Tabiat ini adalah sebahagian daripada kebersihan akaun anda: lihat sepuluh minit pertama pada VPS baharu untuk mengetahui cara mengendalikan kunci dan kata laluan pada pelayan yang baru disediakan.

Jika anda ingin menampal blok teks yang penuh dengan aksara !, matikan pengembangan untuk sesi tersebut dengan set +H, dan hidupkan semula dengan set -H.

HISTSIZE, HISTFILESIZE dan lokasi sejarah arahan anda

Terdapat dua pemboleh ubah yang sering tertukar kerana namanya hampir sama.

  • HISTSIZE ialah bilangan arahan yang disimpan oleh shell yang sedang berjalan di dalam memori.
  • HISTFILESIZE ialah bilangan baris yang disimpan dalam fail pada cakera, ~/.bash_history melainkan HISTFILE menetapkan sebaliknya.

Fail ini ditulis apabila shell ditutup, dan ia akan dipotong kepada HISTFILESIZE baris pada ketika itu. Nilai lalai ~/.bashrc pada Ubuntu menetapkan 1000 dan 2000. Semak nilai yang anda gunakan sekarang:

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

Tingkatkan kedua-dua nilai tersebut jika carian !string anda sering terlepas arahan yang anda tahu pernah dijalankan. Nilai 10000 dan 20000 adalah biasa dan hampir tidak memakan ruang, kerana fail tersebut hanyalah teks biasa. Nilai negatif bermaksud tiada had langsung.

Setem masa (timestamps) membantu apabila anda kembali ke pelayan selepas seminggu:

export HISTTIMEFORMAT='%F %T '

history kemudian akan mencetak tarikh dan masa di hadapan setiap entri, kerana bash mula menulis baris komen yang mengandungi saat epoch sebelum setiap arahan dalam fail tersebut.

Aduan biasa ialah sejarah arahan hilang apabila anda menggunakan lebih daripada satu terminal. Setiap shell menyimpan senarai sendiri dalam memori dan menulisnya keluar semasa ditutup, jadi tanpa histappend, shell terakhir yang ditutup akan menimpa apa yang disimpan oleh shell lain. Dua tetapan ini akan membetulkannya:

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

histappend menyebabkan shell menambah senarainya ke hujung fail dan bukannya menggantikan fail tersebut. history -a menambah baris baharu selepas setiap prompt, jadi sesi yang dimatikan secara paksa dan bukannya ditutup dengan betul masih akan meninggalkan arahan-arahannya. Nilai lalai ~/.bashrc pada Ubuntu sudah menetapkan histappend. Perkara ini menjadi lebih penting apabila anda menguruskan lebih banyak mesin, kerana sejarah disimpan mengikut pengguna bagi setiap mesin. Jadi, apabila anda menguruskan beberapa pelayan daripada satu stesen kerja, !$ yang anda cari mungkin berada pada hos yang lain.

Menjaga rahsia daripada sejarah bash anda

HISTCONTROL menentukan baris mana yang akan disimpan.

  • ignorespace membuang sebarang baris yang bermula dengan ruang kosong (space).
  • ignoredups membuang baris yang sama dengan baris sebelumnya.
  • ignoreboth melakukan kedua-dua perkara di atas.
  • erasedups membuang setiap salinan lama bagi baris yang sedang disimpan.
export HISTCONTROL=ignoreboth

Dengan ignorespace aktif, satu ruang kosong di hadapan akan menghalang sesuatu arahan daripada dimasukkan ke dalam senarai dalam memori, jadi ia tidak akan sampai ke fail tersebut. Semak nilainya sebelum anda bergantung kepadanya dengan echo "$HISTCONTROL". Pengguna lalai Ubuntu .bashrc menetapkan ignoreboth, tetapi shell root atau imej minimal mungkin membiarkannya tidak ditetapkan, dan dalam keadaan itu ruang kosong di hadapan tidak memberi kesan dan baris tersebut disimpan seperti biasa.

Bersikap jujur tentang perkara yang dilindungi oleh kaedah ini. Baris tersebut tidak akan masuk ke dalam ~/.bash_history. Ia masih boleh dilihat dalam output ps semasa proses berjalan, jadi mana-mana pengguna lain pada pelayan tersebut boleh membacanya di sana. sudo merekodkan arahan yang dijalankan dalam log sistem. Fail sejarah adalah teks biasa, jadi pastikan ia berada pada mod 600 dan ingat bahawa sesiapa yang boleh membaca direktori home anda boleh membaca beberapa ribu arahan terakhir anda.

Jika sesuatu yang sensitif sudah berada dalam senarai, buang entri tersebut dan tulis semula fail:

history
history -d 517
history -w

history -d membuang entri tersebut daripada memori, dan history -w menulis senarai semasa ke atas fail tersebut. history -c mengosongkan keseluruhan senarai. HISTIGNORE ialah tetapan berkaitan untuk gangguan dan bukannya rahsia: HISTIGNORE='ls:pwd:history:clear' menghalang baris-baris tersebut daripada masuk ke dalam senarai supaya carian anda memberikan hasil yang berguna.

Mengapa pengembangan sejarah tidak berfungsi dalam skrip shell

Pengembangan sejarah (history expansion) adalah ciri shell interaktif. Skrip berjalan dalam shell bukan interaktif, di mana senarai sejarah tidak diaktifkan dan pengembangan dimatikan, jadi !! dan !$ kekal dalam baris tersebut sebagai teks biasa. sudo !! di dalam skrip mengarahkan sudo untuk menjalankan perintah yang dinamakan secara literal sebagai !!, dan ia akan gagal.

Semak mana-mana shell yang anda sedang gunakan:

echo $-

Outputnya adalah set bendera pilihan semasa, seperti himBHs. i bermaksud shell tersebut adalah interaktif dan H bermaksud pengembangan sejarah diaktifkan. Jalankan baris yang sama di dalam skrip dan kedua-dua huruf tersebut tidak akan kelihatan.

Itulah garis pemisah antara dua bahagian kerja shell anda. Pada prompt, !$ dan Ctrl-R menjimatkan ketukan kekunci pada perintah yang masih boleh anda lihat. Di dalam fail, anda menamakan perkara tersebut sebaliknya: letakkan laluan dalam pemboleh ubah, atau tangkap output dengan penggantian perintah. Skrip yang bergantung pada sejarah peribadi anda akan melakukan sesuatu yang berbeza bagi orang seterusnya yang menjalankannya, yang mana bertentangan dengan tujuan asal skrip.

Atas sebab itu, setiap contoh pada halaman ini bertujuan untuk ditaip pada prompt secara langsung. Tiada satu pun daripadanya berkelakuan sama jika ditampal ke dalam fail .sh.

FAQ

Apakah fungsi !! dalam bash?

!! dikembangkan kepada teks penuh baris arahan sebelumnya, jadi sudo !! menjalankan arahan terakhir anda semula sebagai root. Pengembangan ini bersifat tekstual dan berlaku sebelum bash menghuraikan baris tersebut, dan bash akan mencetak baris yang lengkap sejurus sebelum menjalankannya. Jika anda tidak pasti apakah baris sebelumnya, taip sudo !!:p dahulu. :p mencetak pengembangan tersebut tanpa menjalankannya, dan ia menambah baris yang dicetak ke dalam sejarah anda, supaya !! yang seterusnya akan menjalankannya.

Bagaimanakah cara menggunakan semula argumen terakhir daripada arahan sebelumnya?

Gunakan !$. Selepas sudo mkdir -p /srv/www/app/releases, baris ls -ld !$ menjadi ls -ld /srv/www/app/releases. Ia mengambil perkataan terakhir daripada baris tersebut, jadi sasaran pengalihan (redirection) di hujung baris dikira sebagai perkataan terakhir. Anda juga boleh menambah teks kepadanya: !$/config.env menambah pada laluan tersebut, kerana penentu perkataan berhenti pada /. Alternatif interaktif ialah Alt-., yang memasukkan teks yang sama ke dalam prompt anda supaya anda boleh membacanya sebelum menekan Enter.

Mengapakah bash menyatakan "event not found" apabila teks saya mengandungi tanda seru?

Tanda petikan berkembar tidak melindungi ! daripada pengembangan sejarah, jadi echo "done!" menyebabkan bash mencari event sejarah dan mencetak bash: !": event not found. Tanda petikan tunggal memang melindunginya, jadi tulis echo 'done!'. Garis miring songsang (backslash) juga menghentikan pengembangan tersebut, tetapi di dalam tanda petikan berkembar, bash akan mengekalkan garis miring songsang itu dalam output. Untuk menampal blok panjang yang mengandungi !, matikan pengembangan untuk sesi tersebut dengan set +H.

Mengapakah !! dan !$ tidak berfungsi dalam skrip shell saya?

Pengembangan sejarah hanya didayakan dalam shell interaktif. Skrip berjalan secara bukan interaktif, jadi shell tidak membina senarai sejarah, dan !! dibiarkan dalam baris sebagai teks biasa. Jalankan echo $- untuk melihat jenis shell anda: shell interaktif mencetak flag termasuk i dan H, manakala skrip tidak mencetak kedua-duanya. Dalam skrip, gunakan pemboleh ubah atau penggantian arahan (command substitution) sebagai ganti.

Bagaimanakah cara untuk memastikan kata laluan tidak disimpan dalam sejarah bash saya?

Tetapkan HISTCONTROL=ignorespace atau HISTCONTROL=ignoreboth dalam ~/.bashrc, kemudian mulakan arahan dengan satu ruang kosong dan ia tidak akan disimpan. Sahkan nilainya dengan echo "$HISTCONTROL" terlebih dahulu, kerana jika ia tidak ditetapkan, ruang kosong di hadapan tidak akan memberi kesan. Ini hanya menghalang baris tersebut daripada masuk ke ~/.bash_history. Arahan tersebut masih kelihatan dalam ps semasa ia berjalan, dan sudo merekodkan apa yang dilaksanakan. Jika rahsia sudah pun disimpan, cari nombornya dengan history, kemudian jalankan history -d <number> diikuti dengan history -w untuk menulis semula fail tersebut.