Jinsi ya kutumia Bash history expansion !! na !$
Tumia !! kurudia amri ya mwisho na !$ kupata hoja yake ya mwisho. Jifunze mbinu sita muhimu za Bash history expansion pamoja na matumizi ya :p kuchapisha amri kabla ya kuiendesha.
Kazi ya bash history expansion
Bash history expansion hujenga upya mstari wa amri uliotangulia kutoka kwenye historia ya shell yako kabla bash haijauendesha. !! hurudia amri iliyotangulia. !$ huweka hoja (argument) ya mwisho ya amri iliyotangulia. Ubadilishaji huu ni wa kimaandishi na hutokea kwanza, kabla bash haijagawanya mstari katika maneno, kwa hivyo kinachorejeshwa ni herufi kamili ulizochapa mara ya kwanza.
Aina sita hufunika karibu kila kitu utakachofanya kwenye seva:
!!hurudia mstari mzima uliotangulia, nasudo !!huuuendesha tena kama root.!$ni hoja ya mwisho ya mstari uliotangulia.!*ni hoja zote za mstari uliotangulia.!nhuendesha amri ya namba n kutoka kwenye historia, na!-nhuhesabu kinyume kutoka hapo ulipo sasa.!stringhuendesha amri ya hivi karibuni iliyoanza nastring.^old^newhuendesha tena mstari uliotangulia hukuoldya kwanza ikibadilishwa nanew.
Kila kitu hapa chini huchapwa kwenye prompt ya maingiliano (interactive prompt) kwenye seva yako mwenyewe. History expansion huzimwa kwenye scripts, jambo ambalo sehemu ya mwisho inalielezea.
Njia ndefu, iliyoandikwa mara moja
Hii ndiyo hali inayojilipa yenyewe. Unapoandaa saraka ya release, na njia ni ndefu kiasi kwamba kuiandika mara ya pili kunaweza kusababisha kosa la uchapaji.
sudo mkdir -p /srv/www/app/releases/2026-08-07
sudo chown -R deploy:deploy !$
ls -ld !$
sudo -u deploy nano !$/config.envKabla ya kuendesha kila mstari, bash huchapisha mstari uliotokana na upanuzi (expansion):
sudo chown -R deploy:deploy /srv/www/app/releases/2026-08-07Echo hiyo ni ukaguzi wako. Isome kabla ya kusoma matokeo ya amri, kwa sababu ndiyo nafasi pekee unayopata kuona kile bash iliamua !$ inamaanisha.
Mlolongo huu unashikilia kwa sababu bash huhifadhi mstari uliopanuliwa kwenye history, si !$ uliyoiandika. Kwa hivyo mstari wa 3 huchukua hoja yake ya mwisho kutoka kwenye mstari wa 2 uliopanuliwa, na mstari wa 4 huchukua kutoka kwenye mstari wa 3. Mstari wa 4 pia unaonyesha kuwa maandishi yanaweza kufuata designator: !$/config.env inakuwa njia hiyo ikiwa na /config.env mwishoni, kwa sababu neno designator huishia kwenye /.
Mfano mwingine kutoka kwenye session hiyo hiyo. Baada ya kuhariri faili unataka kuorodhesha saraka iliyo na faili hiyo:
ls -l !$:h:h ni modifier ya kichwa (head modifier). Huondoa sehemu ya mwisho ya njia, kazi sawa na ile inayofanywa na dirname. :t huweka sehemu ya mwisho pekee, :r huondoa extension, na :e huweka extension pekee.
Rudia amri ya mwisho ukitumia !! na sudo !!
Unasahau sudo, na meneja wa huduma anakuarifu hivyo:
systemctl restart nginxFailed to restart nginx.service: Interactive authentication required.sudo !!Bash inabadilisha !! na maandishi ya mstari uliopita, kwa hivyo shell inaendesha sudo systemctl restart nginx.
!! ni mstari uliopita hasa, bila kujali mstari huo ulikuwa nini. Hapa ndipo ajali ya kawaida hutokea. Unadhani amri ya mwisho ndiyo iliyoshindwa, lakini tangu wakati huo uliendesha cd, au history, au amri iliyoshindwa ilikuwa mistari miwili nyuma. sudo !! basi inaendesha amri isiyo sahihi kwa marupurupu ya root. Ichapishe kwanza wakati huna uhakika:
sudo !!:p:p inachapisha mstari uliopanuliwa na haiuendeshi. Mstari uliochapishwa unaongezwa kwenye historia yako, kwa hivyo ikiwa unasomeka kwa usahihi, !! ya kawaida kwenye mstari unaofuata inauendesha.
Tumia tena hoja ya mwisho ukitumia !$
!$ ndilo neno la mwisho la mstari uliotangulia. Hii ndiyo fomu utakayotumia mara nyingi zaidi, kwa sababu neno la mwisho kwa kawaida ndilo kitu unachokifanyia kazi: njia (path) au jina la huduma.
sudo systemctl status nginx
sudo systemctl reload !$Mambo mawili huwashangaza watu hapa.
Kwanza, !$ ni neno la mwisho, si hoja ya mwisho uliyokuwa nayo akilini. Ikiwa mstari uliotangulia uliishia na redirection, neno la mwisho ni lengo la redirection hiyo:
sudo nginx -T > /tmp/nginx-dump.conf
less !$Hiyo inafanya kazi vizuri. Lakini baada ya journalctl -u nginx > /tmp/log.txt, !$ ni /tmp/log.txt na si nginx. Soma mstari uliojitokeza.
Pili, upanuzi (expansion) huu ni wa kimaandishi, kwa hivyo variable hurudi bila kupanuliwa. Baada ya ls $HOME/backups, !$ inakupa vibambo $HOME/backups, ambavyo bash kisha huvipanua tena kama parameter ya kawaida. Zingatia mpangilio huu: upanuzi wa historia (history expansion) hufanyika kabla ya upanuzi wa parameter na kabla ya command substitution with $( ), kwa hivyo hauoni thamani, bali maandishi pekee.
Fomu za jirani zinafaa kufahamika. !^ ni hoja ya kwanza, !:2 ni ya pili, na !:2-4 ni masafa (range). !!:$ ni njia ndefu ya kuandika !$.
Ikiwa ungependa kuona maandishi hayo kabla ya kuyathibitisha, bonyeza Alt-. (au Esc kisha .). Readline huingiza hoja ya mwisho ya amri iliyotangulia moja kwa moja kwenye prompt yako, ambapo unaweza kuihariri. Bonyeza tena ili kurudi nyuma kwenye hoja ya mwisho ya amri iliyotangulia hiyo. Hakuna kinachoendeshwa hadi ubonyeze Enter.
Pitisha kila hoja kwa kutumia !*
!* ni kila neno la mstari uliotangulia isipokuwa la kwanza.
stat /srv/www/app/shared/config.env /srv/www/app/shared/secrets.env
sudo chmod 600 !*Kirai "isipokuwa la kwanza" kinafanya kazi muhimu. !* huondoa neno 0 na hakuna lingine, kwa hivyo chaguzi (options) hufuata pamoja na njia (paths). Baada ya ls -l file1 file2, !* ni -l file1 file2, kwa hivyo sudo chmod 600 !* inafeli, kwa sababu chmod hupokea -l kama hoja. Hesabu maneno kuanzia sifuri na uchukue sehemu (slice): !!:2* inamaanisha neno la 2 hadi mwisho.
Mtego uleule hutokea na sudo. Baada ya sudo chown deploy:deploy /srv/www/app, neno 0 ni sudo na neno 1 ni chown, kwa hivyo !* hukabidhi amri inayofuata chown deploy:deploy /srv/www/app. Hilo ni jambo ambalo mara chache sana utakuwa umelikusudia.
Chagua amri kwa nafasi yake kwa kutumia !n na !-n
history huchapisha orodha yenye namba mbele ya kila ingizo.
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 huendesha ingizo la 513 tena. !-2 huendesha ingizo la mistari miwili nyuma, ikianzia kwenye mstari unaoandika sasa, kwa hivyo !-1 na !! humaanisha kitu kimoja.
Kagua namba hizo mara moja kabla ya kuzitumia. !-2 huelekeza mahali pengine kila unapofanya jambo lolote, ikiwemo ls uliyoiwasha bila kufikiri. Namba kamili hubaki vilevile ndani ya kikao kimoja, lakini si namba zilezile katika kikao cha pili kwenye mashine hiyo hiyo, na si namba zilezile baada ya kuingia tena (login) na mfumo kusoma faili ya historia. Namba uliyokariri jana huelekeza kwenye amri tofauti leo.
Tekeleza upya amri kwa kutumia kiambishi awali chenye !string
!string huendesha amri ya hivi karibuni zaidi inayoanza na string.
!ssHiyo inatekeleza upya ss -tulpn kutoka kwenye orodha hapo juu, ukaguzi wa socket inayosikiliza uliotajwa katika bandari zipi ziko wazi kwenye seva ya Linux. !?string? inalinganisha sehemu yoyote kwenye mstari badala ya kuanzia mwanzo, jambo ambalo husaidia unapokumbuka hoja (argument) lakini si jina la amri.
Weka kiambishi awali kirefu. !s italinganisha ss, sudo, systemctl au shutdown, kulingana na ipi iliyoendeshwa hivi karibuni zaidi, na hutajua ni ipi hadi itakapoanza kufanya kazi. !string:p huchapisha matokeo yanayolingana bila kuyaendesha. Ikiwa hakuna kinacholingana, bash huchapisha bash: !ss: event not found na haiendeshi chochote, ambalo ni tokeo salama.
Rekebisha kosa moja la uchapaji kwa ^old^new
sudo systemctl status ngnixUnit ngnix.service could not be found.^ngnix^nginxShell huendesha upya mstari uliotangulia huku ngnix ya kwanza ikibadilishwa na nginx. Mechi ya kwanza pekee ndiyo inayobadilishwa. Ili kubadilisha kila mechi, tumia fomu ndefu ya !!:gs/ngnix/nginx/, ambapo s inamaanisha badilisha na g inamaanisha fanya hivyo kwenye mstari mzima.
Chapisha upanuzi kabla ya kuukimbiza
Tabia mbili huzuia ajali ambapo upanuzi unakimbiza kitu ambacho hukukusudia.
Ya kwanza ni :p, ambayo tayari umeiona. Iambatanishe kwenye upanuzi na bash itachapisha matokeo badala ya kuikimbiza: !!:p, au !systemctl:p. Mstari uliochapishwa unaingia kwenye historia yako, kwa hivyo !! baadaye inakimbiza kitu ulichokisoma sasa hivi.
Ya pili ni imara zaidi, kwa sababu inahusu kila upanuzi bila wewe kuhitaji kukumbuka chochote:
shopt -s histverifyWeka mstari huo kwenye ~/.bashrc. Ukiwa na histverify imewekwa, upanuzi haukimbizi unapobonyeza Enter. Bash inaandika mstari uliopanuliwa kurudi kwenye prompt yako ili uweze kuusoma na kuuhariri, na unabonyeza Enter mara ya pili ili kuukimbiza. Inagharimu mpigo mmoja wa kibodi na inaondoa aina nzima ya ajali. Inahitaji readline, kwa hivyo inafanya kazi kwenye prompt ya mwingiliano na si mahali pengine popote.
Ctrl-R ndiyo njia nyingine ya kufanya kazi, na haipanui chochote. Bonyeza Ctrl-R na uandike sehemu ya amri. Prompt inabadilika kuwa prompt ya reverse-i-search na kuonyesha mechi ya hivi karibuni zaidi unapoandika. Bonyeza Ctrl-R tena ili kurudi nyuma kwenye mechi za zamani. Enter inakimbiza mstari unaoonyeshwa. Ctrl-G inaghairi utafutaji na kurudisha prompt yako ya asili. Kitufe cha mshale wa kushoto kinamaliza utafutaji na kuacha mstari uliolingana kwenye prompt yako kwa ajili ya kuhaririwa.
Tumia Ctrl-R unapotaka kuona amri kwanza. Tumia !! na !$ wakati amri ni ya mstari mmoja uliopita na bado unaweza kuisoma kwenye skrini.
Kwa nini echo "done!" inasema event not found
echo "deploy done!"bash: !": event not foundHistory expansion hufanyika kabla ya kunukuu (quoting) kutatuliwa, na alama za kunukuu mbili (double quotes) hazilindi herufi ya !. Alama za kunukuu moja (single quotes) na backslash pekee ndizo zinazoweza kufanya hivyo. Kwa hiyo, echo 'deploy done!' huchapisha kile unachotarajia. Backslash pia husimamisha expansion, lakini ndani ya double quotes, bash huacha backslash kwenye matokeo, hivyo single quotes ndiyo suluhisho safi zaidi.
Hili ni tatizo kubwa zaidi kwa nywila (passwords), kwa sababu nywila imara mara nyingi huwa na !. Amri kama mysql -u app -p"S3cret!pass" ama itafeli kwa kosa la event-not-found, au, ikiwa kuna rekodi ya history inayolingana, itatuma kamba (string) tofauti kimyakimya. Tumia single quotes, au bora zaidi, ruhusu zana husika ikuulize nywila ili siri hiyo isiwahi kufika kwenye command line kabisa. Tabia hiyo ni sehemu ya usafi wa akaunti yako: angalia dakika kumi za kwanza kwenye VPS mpya ili kujua jinsi funguo na nywila zinavyopaswa kushughulikiwa kwenye seva mpya.
Ikiwa unakaribia kubandika (paste) maandishi yaliyojaa herufi za !, zima expansion kwa kipindi hicho cha kazi kwa kutumia set +H, na uiwashe tena kwa set -H.
HISTSIZE, HISTFILESIZE na mahali historia yako inapohifadhiwa
Kuna vigezo viwili, na watu huvichanganya kwa sababu majina yake yanafanana.
HISTSIZEni idadi ya amri ambazo shell inayofanya kazi huhifadhi kwenye kumbukumbu (RAM).HISTFILESIZEni idadi ya mistari inayohifadhiwa kwenye faili iliyo kwenye diski,~/.bash_historyisipokuwa kamaHISTFILEitaelekeza vinginevyo.
Faili huandikwa wakati shell inafungwa, na hupunguzwa hadi kufikia HISTFILESIZE mistari wakati huo. Mpangilio chaguo-msingi wa Ubuntu wa ~/.bashrc huweka 1000 na 2000. Hakiki thamani unazotumia sasa:
grep HIST ~/.bashrc
echo "$HISTSIZE $HISTFILESIZE $HISTFILE"Ongeza thamani zote mbili ikiwa utafutaji wako wa !string unakosa amri unazojua uliwahi kuziendesha. Thamani za 10000 na 20000 ni za kawaida na hazitumii rasilimali yoyote, kwa sababu faili hiyo ni matini ya kawaida (plain text). Thamani hasi inamaanisha hakuna kikomo kabisa.
Alama za muda (timestamps) husaidia unaporejea kwenye seva baada ya wiki moja:
export HISTTIMEFORMAT='%F %T 'history huchapisha tarehe na muda mbele ya kila amri, kwa sababu bash huanza kuandika mstari wa maoni wenye sekunde za epoch kabla ya kila amri kwenye faili.
Malalamiko ya kawaida ni kwamba historia hupotea unapotumia terminal zaidi ya moja. Kila shell huhifadhi orodha yake kwenye kumbukumbu na kuiandika wakati wa kufungwa, hivyo bila histappend, shell ya mwisho kufungwa hufuta kile ambacho nyingine zilihifadhi. Mipangilio miwili hutatua hili:
shopt -s histappend
export PROMPT_COMMAND='history -a'histappend huifanya shell kuongeza orodha yake mwishoni mwa faili badala ya kuibadilisha faili nzima. history -a huongeza mistari mipya baada ya kila prompt, hivyo kikao kinachokatishwa ghafla badala ya kufungwa kwa usahihi bado huacha amri zake zikiwa zimehifadhiwa. Mpangilio chaguo-msingi wa Ubuntu wa ~/.bashrc huweka histappend tayari. Hili ni muhimu zaidi kadiri unavyoendesha mashine nyingi, kwa sababu historia huhifadhiwa kwa kila mtumiaji kwenye kila mashine, hivyo unapokuwa unasimamia seva kadhaa kutoka kituo kimoja cha kazi amri ya !$ unayotafuta inaweza kuwa iko kwenye seva nyingine.
Kuweka siri nje ya bash history yako
HISTCONTROL huamua ni mistari ipi itahifadhiwa.
ignorespacehuondoa mstari wowote unaoanza na nafasi (space).ignoredupshuondoa mstari unaofanana kabisa na ule uliotangulia.ignorebothhufanya yote mawili hapo juu.erasedupshuondoa kila nakala ya zamani ya mstari unaohifadhiwa.
export HISTCONTROL=ignorebothUkiwa na ignorespace, nafasi moja ya mwanzo huzuia amri (command) isihifadhiwe kwenye orodha ya kumbukumbu (in-memory list), hivyo haifiki kwenye faili pia. Hakikisha thamani yake kabla ya kuitegemea kwa kutumia echo "$HISTCONTROL". Mtumiaji wa kawaida wa Ubuntu .bashrc huweka ignoreboth, lakini shell ya root au image ndogo inaweza kuiacha bila mpangilio, na hapo nafasi ya mwanzo haitafanya kazi na mstari utahifadhiwa kama mingine.
Kuwa mkweli kuhusu kile ambacho hatua hii inalinda. Mstari huo hauingii kwenye ~/.bash_history. Bado unaonekana kwenye matokeo ya ps wakati mchakato (process) unafanya kazi, kwa hivyo mtumiaji mwingine yeyote kwenye seva anaweza kuusoma hapo. sudo hurekodi amri iliyotekelezwa kwenye logi ya mfumo. Faili ya historia ni maandishi ya kawaida (plain text), kwa hivyo iweke katika mode 600 na kumbuka kuwa yeyote anayeweza kusoma saraka yako ya nyumbani (home directory) anaweza kusoma maelfu ya amri zako za mwisho.
Ikiwa kitu nyeti kipo tayari kwenye orodha, ondoa ingizo hilo na uandike upya faili:
history
history -d 517
history -whistory -d huondoa ingizo hilo kutoka kwenye kumbukumbu, na history -w huandika orodha ya sasa juu ya faili. history -c hufuta orodha nzima. HISTIGNORE ni kitufe kinachohusiana na kelele badala ya siri: HISTIGNORE='ls:pwd:history:clear' huweka mistari hiyo nje ya orodha ili utafutaji wako ulete matokeo muhimu.
Kwa nini upanuzi wa historia haufanyi kazi katika shell script
Upanuzi wa historia (history expansion) ni kipengele cha shell zinazotumiwa na mtumiaji moja kwa moja (interactive shells). Script huendeshwa katika shell isiyo ya interactive, ambapo orodha ya historia haijawashwa na upanuzi umezimwa, kwa hivyo !! na !$ hubaki kwenye mstari kama maandishi ya kawaida. sudo !! ndani ya script humwomba sudo aendeshe amri yenye jina halisi la !!, na inashindwa.
Kagua shell yoyote unayotumia:
echo $-Matokeo yake ni seti ya flag za chaguzi za sasa, kitu kama himBHs. i inamaanisha shell ni interactive na H inamaanisha upanuzi wa historia umewashwa. Endesha mstari huo huo ndani ya script na hakuna herufi yoyote kati ya hizo itakayokuwepo.
Hiyo ndiyo tofauti kati ya sehemu mbili za kazi yako ya shell. Kwenye prompt, !$ na Ctrl-R huokoa vibonyezo vya vitufe kwenye amri unazoweza kuziona. Kwenye faili, unataja vitu badala yake: weka njia (path) kwenye variable, au nasa matokeo kwa kutumia command substitution. Script inayotegemea historia yako binafsi ingefanya kitu tofauti kwa mtu mwingine atakayeiendesha, jambo ambalo ni kinyume na madhumuni ya script.
Kwa sababu hiyo, kila mfano kwenye ukurasa huu umekusudiwa kuandikwa kwenye prompt ya moja kwa moja. Hakuna hata mmoja utakaofanya kazi kwa njia ile ile ukibandikwa kwenye faili ya .sh.
FAQ
!! inafanya nini kwenye bash?
!! inapanuka na kuwa maandishi kamili ya mstari wa amri uliopita, kwa hivyo sudo !! huendesha amri yako ya mwisho tena kama root. Upanuzi huu ni wa kimaandishi na hutokea kabla bash haijachanganua mstari huo, na bash huchapisha mstari uliokamilika kabla ya kuuuendesha. Ikiwa huna uhakika mstari uliopita ulikuwa nini, chapa sudo !!:p kwanza. :p huchapisha upanuzi bila kuuuendesha, na huongeza mstari uliopigwa kwenye historia yako, kwa hivyo !! inayofuata huuuendesha.
Ninawezaje kutumia tena argument ya mwisho ya amri iliyopita?
Tumia !$. Baada ya sudo mkdir -p /srv/www/app/releases, mstari ls -ld !$ unakuwa ls -ld /srv/www/app/releases. Inachukua neno la mwisho la mstari, kwa hivyo lengo la redirection mwishoni huhesabiwa kama neno la mwisho. Unaweza pia kuongeza maandishi kwake: !$/config.env huongeza kwenye path, kwa sababu kiashiria cha neno huishia kwenye /. Njia mbadala ya kiingiliano ni Alt-., ambayo huingiza maandishi hayo hayo kwenye prompt yako ili uweze kuyasoma kabla ya kubonyeza Enter.
Kwa nini bash inasema "event not found" wakati maandishi yangu yana alama ya mshangao?
Alama za kunukuu (double quotes) hazilindi ! dhidi ya upanuzi wa historia, kwa hivyo echo "done!" huifanya bash kutafuta tukio la historia na kuchapisha bash: !": event not found. Alama za kunukuu moja (single quotes) huilinda, kwa hivyo andika echo 'done!'. Backslash pia husimamisha upanuzi, lakini ndani ya double quotes bash huacha backslash kwenye pato. Ili kubandika block ndefu iliyo na !, zima upanuzi kwa kipindi hicho kwa set +H.
Kwa nini !! na !$ hazifanyi kazi kwenye script yangu ya shell?
Upanuzi wa historia huwezeshwa tu katika shell za kiingiliano. Script huendeshwa bila kiingiliano, kwa hivyo shell haijengi orodha ya historia, na !! huachwa kwenye mstari kama maandishi ya kawaida. Endesha echo $- ili kuona uko katika hali gani: shell ya kiingiliano huchapisha flags ikiwemo i na H, na script haichapishi yoyote kati ya hizo. Katika scripts, tumia variable au command substitution badala yake.
Ninawezaje kuzuia nenosiri lisiingie kwenye historia yangu ya bash?
Weka HISTCONTROL=ignorespace au HISTCONTROL=ignoreboth katika ~/.bashrc, kisha anza amri kwa nafasi moja (space) na haitahifadhiwa kamwe. Thibitisha thamani hiyo kwa echo "$HISTCONTROL" kwanza, kwa sababu ikiwa haijawekwa, nafasi ya mwanzo haifanyi kazi yoyote. Hii huizuia mstari huo tu kuingia kwenye ~/.bash_history. Amri hiyo bado inaonekana kwenye ps wakati inaendeshwa, na sudo hurekodi kile ilichotekeleza. Ikiwa siri imeshahifadhiwa, tafuta namba yake kwa history, kisha endesha history -d <number> ikifuatiwa na history -w ili kuandika upya faili hiyo.