SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-13

آموزش کار با دستورات !! و !$ در Bash

با استفاده از !! دستور قبلی را تکرار کنید و با !$ آخرین آرگومان را فراخوانی کنید. در این راهنما 6 بسط کاربردی تاریخچه Bash و نحوه استفاده از :p برای پیش‌نمایش دستورات را بیاموزید.

بسط تاریخچه در bash چه کاری انجام می‌دهد

بسط تاریخچه در bash، یک خط فرمان قبلی را از تاریخچه shell شما بازسازی می‌کند تا bash آن را اجرا کند. !! دستور قبلی را تکرار می‌کند. !$ آخرین آرگومان دستور قبلی را درج می‌کند. این جایگزینی متنی است و پیش از آنکه bash خط را به کلمات تقسیم کند، انجام می‌شود؛ بنابراین آنچه بازگردانده می‌شود، دقیقاً همان کاراکترهایی است که بار اول تایپ کرده‌اید.

شش شکل از این دستور، تقریباً تمام کارهایی را که روی سرور انجام می‌دهید پوشش می‌دهد:

  • !! کل خط قبلی را تکرار می‌کند و sudo !! همان را با دسترسی root دوباره اجرا می‌کند.
  • !$ آخرین آرگومان خط قبلی است.
  • !* تمام آرگومان‌های خط قبلی است.
  • !n ورودی تاریخچه با شماره n را اجرا می‌کند و !-n از موقعیت فعلی شما به عقب می‌شمارد.
  • !string جدیدترین دستوری را اجرا می‌کند که با string شروع شده است.
  • ^old^new خط قبلی را دوباره اجرا می‌کند، با این تفاوت که اولین old با new جایگزین می‌شود.

تمام موارد زیر در یک prompt تعاملی روی سرور شخصی شما تایپ می‌شوند. بسط تاریخچه در اسکریپت‌ها غیرفعال است که بخش آخر به توضیح آن می‌پردازد.

یک مسیر طولانی، تایپ‌شده برای یک‌بار

این موردی است که ارزش خود را ثابت می‌کند. شما در حال آماده‌سازی یک دایرکتوری برای انتشار (release) هستید و مسیر آن‌قدر طولانی است که تایپ دوبارهٔ آن احتمال بروز خطا را افزایش می‌دهد.

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

پیش از اجرای هر خط، bash خطی را که از طریق expansion تولید شده است چاپ می‌کند:

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

این echo همان بررسی شماست. پیش از آنکه خروجی دستور را بخوانید، آن را چک کنید؛ چرا که این تنها فرصت شما برای دیدن این است که bash عبارت !$ را چگونه تفسیر کرده است.

این زنجیره برقرار می‌ماند زیرا bash خط توسعه‌یافته را در تاریخچه (history) ذخیره می‌کند، نه آن !$ که شما تایپ کرده‌اید. بنابراین خط 3 آخرین آرگومان خود را از خط 2 توسعه‌یافته می‌گیرد و خط 4 آن را از خط 3 برمی‌دارد. خط 4 همچنین نشان می‌دهد که متن می‌تواند پس از یک designator بیاید: !$/config.env به مسیری تبدیل می‌شود که /config.env در انتهای آن قرار دارد، زیرا word designator در / متوقف می‌شود.

یک مورد دیگر از همان نشست. پس از ویرایش فایل، می‌خواهید دایرکتوری حاوی آن را لیست کنید:

ls -l !$:h

:h یک head modifier است. این دستور آخرین بخش یک مسیر را حذف می‌کند، همان کاری که dirname انجام می‌دهد. :t فقط آخرین بخش را نگه می‌دارد، :r پسوند را حذف می‌کند و :e فقط پسوند را نگه می‌دارد.

تکرار دستور قبلی با !! و sudo !!

شما sudo را فراموش کرده‌اید و مدیر سرویس این موضوع را اعلام می‌کند:

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

بش (Bash) عبارت !! را با متن خط قبلی جایگزین می‌کند، بنابراین شل دستور sudo systemctl restart nginx را اجرا می‌کند.

!! دقیقاً همان خط قبلی است، فارغ از اینکه آن خط چه بوده باشد. اینجاست که خطاهای کلاسیک رخ می‌دهند. شما تصور می‌کنید آخرین دستور همان دستوری است که با خطا مواجه شده، اما از آن زمان شما یک cd یا history اجرا کرده‌اید، یا اینکه دستور ناموفق دو خط قبل بوده است. در این حالت sudo !! دستور اشتباهی را با دسترسی root اجرا می‌کند. اگر مطمئن نیستید، ابتدا آن را چاپ کنید:

sudo !!:p

:p خط بسط‌یافته را چاپ می‌کند و آن را اجرا نمی‌کند. خط چاپ‌شده به تاریخچه (history) شما اضافه می‌شود، بنابراین اگر درست به نظر می‌رسد، یک !! ساده در خط بعدی آن را اجرا خواهد کرد.

استفاده مجدد از آخرین آرگومان با !$

!$ آخرین کلمه از خط قبلی است. این همان شکلی است که بیشترین استفاده را از آن خواهید داشت، زیرا آخرین کلمه معمولاً همان چیزی است که روی آن کار می‌کنید: یک مسیر یا نام یک سرویس.

sudo systemctl status nginx
sudo systemctl reload !$

دو نکته در اینجا کاربران را غافلگیر می‌کند.

نخست اینکه !$ آخرین کلمه است، نه آخرین آرگومانی که در ذهن داشتید. اگر خط قبلی به یک redirection ختم شود، آخرین کلمه همان مقصد redirect است:

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

این مورد به‌خوبی کار می‌کند. اما پس از journalctl -u nginx > /tmp/log.txt، مقدار !$ برابر با /tmp/log.txt است و نه nginx. خطی که نمایش داده می‌شود را بخوانید.

دوم اینکه این جایگزینی متنی است، بنابراین یک متغیر به‌صورت جایگزین‌نشده بازمی‌گردد. پس از ls $HOME/backups، عبارت !$ کاراکترهای $HOME/backups را به شما می‌دهد که bash سپس آن را دوباره به‌عنوان یک پارامتر عادی جایگزین می‌کند. این ترتیب را به خاطر بسپارید: جایگزینی تاریخچه (history expansion) پیش از جایگزینی پارامتر و پیش از جایگزینی دستور با $( ) اجرا می‌شود، بنابراین هرگز مقادیر را نمی‌بیند، بلکه فقط متن را می‌بیند.

آشنایی با اشکال مشابه نیز مفید است. !^ اولین آرگومان، !:2 دومین آرگومان و !:2-4 یک محدوده است. !!:$ روش طولانی نوشتن !$ است.

اگر ترجیح می‌دهید پیش از اجرای دستور، متن را ببینید، کلید Alt-. (یا Esc و سپس .) را فشار دهید. Readline آخرین آرگومان دستور قبلی را مستقیماً در prompt شما قرار می‌دهد تا بتوانید آن را ویرایش کنید. دوباره آن را فشار دهید تا به آخرین آرگومان دستور پیش از آن بروید. تا زمانی که Enter را فشار ندهید، هیچ دستوری اجرا نمی‌شود.

ارسال تمام آرگومان‌ها با !*

!* شامل تمام کلمات خط قبلی به جز کلمه اول است.

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

عبارت «به جز کلمه اول» نقش مهمی ایفا می‌کند. !* فقط کلمه 0 را حذف می‌کند و تغییری در بقیه ایجاد نمی‌کند، بنابراین گزینه‌ها همراه با مسیرها منتقل می‌شوند. پس از ls -l file1 file2، مقدار !* برابر با -l file1 file2 است، بنابراین sudo chmod 600 !* با خطا مواجه می‌شود، زیرا chmod مقدار -l را به عنوان یک آرگومان دریافت می‌کند. کلمات را از صفر شماره‌گذاری کنید و یک برش (slice) بگیرید: !!:2* به معنای کلمه 2 تا انتهای رشته است.

همین تله در مورد sudo نیز وجود دارد. پس از sudo chown deploy:deploy /srv/www/app، کلمه 0 برابر با sudo و کلمه 1 برابر با chown است، بنابراین !* دستور بعدی را به chown deploy:deploy /srv/www/app می‌سپارد. این نتیجه به‌ندرت همان چیزی است که شما می‌خواستید.

انتخاب یک دستور بر اساس موقعیت با !n و !-n

history فهرست را با یک شماره در ابتدای هر ورودی چاپ می‌کند.

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 را دوباره اجرا می‌کند. !-2 ورودی دو خط قبل را اجرا می‌کند، که از خطی که هم‌اکنون در حال تایپ آن هستید شمرده می‌شود، بنابراین !-1 و !! به یک معنا هستند.

پیش از استفاده از شماره‌ها، آن‌ها را بررسی کنید. !-2 هر بار که دستوری را اجرا می‌کنید به مکان جدیدی اشاره می‌کند، از جمله ls که بدون فکر کردن آن را اجرا کرده‌اید. شماره‌های مطلق در طول یک نشست ثابت هستند، اما در نشست دوم روی همان سیستم، همان شماره‌ها نیستند و پس از اینکه ورود بعدی شما فایل تاریخچه (history) را دوباره بارگذاری کند نیز تغییر می‌کنند. شماره‌ای که دیروز به خاطر سپرده‌اید، امروز به دستور متفاوتی اشاره می‌کند.

اجرای مجدد یک دستور با پیشوند !string

!string آخرین دستوری را که با string شروع می‌شود، اجرا می‌کند.

!ss

این دستور، ss -tulpn را از لیست بالا دوباره اجرا می‌کند؛ همان بررسی سوکت‌های در حال گوش دادن که در کدام پورت‌ها در سرور لینوکسی باز هستند توضیح داده شد. !?string? در هر جای خط که باشد مطابقت پیدا می‌کند، نه فقط در ابتدای آن؛ این ویژگی زمانی مفید است که آرگومان دستور را به یاد دارید اما نام خودِ دستور را فراموش کرده‌اید.

پیشوند را طولانی انتخاب کنید. !s با ss، sudo، systemctl یا shutdown مطابقت پیدا می‌کند؛ یعنی هر کدام که اخیراً اجرا شده باشد، و تا زمانی که اجرا نشود متوجه نخواهید شد کدام‌یک بوده است. !string:p دستور منطبق را بدون اجرا کردن، چاپ می‌کند. اگر هیچ موردی پیدا نشود، bash عبارت bash: !ss: event not found را چاپ کرده و هیچ دستوری را اجرا نمی‌کند که نتیجه‌ای ایمن است.

اصلاح یک غلط تایپی با ^old^new

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

شِل خط قبلی را دوباره اجرا می‌کند، با این تفاوت که اولین ngnix با nginx جایگزین می‌شود. فقط اولین مورد منطبق تغییر می‌کند. برای تغییر تمام موارد، از فرم طولانی !!:gs/ngnix/nginx/ استفاده کنید، که در آن s به معنای جایگزینی و g به معنای انجام این کار در کل خط است.

چاپ بسط پیش از اجرا

دو عادت از بروز حوادثی که در آن یک بسط (expansion) دستوری ناخواسته را اجرا می‌کند، جلوگیری می‌کنند.

عادت اول استفاده از :p است که پیش‌تر دیده‌اید. آن را به انتهای یک بسط اضافه کنید تا bash به جای اجرای دستور، نتیجه را چاپ کند: !!:p یا !systemctl:p. خط چاپ‌شده به تاریخچه (history) شما اضافه می‌شود، بنابراین !! در مرحله بعد، همان چیزی را که خوانده‌اید اجرا می‌کند.

عادت دوم قوی‌تر است، زیرا بدون نیاز به یادآوری شما، روی تمام بسط‌ها اعمال می‌شود:

shopt -s histverify

این خط را در ~/.bashrc قرار دهید. با تنظیم histverify، بسط هنگام فشردن کلید Enter اجرا نمی‌شود. Bash خط بسط‌یافته را دوباره در prompt شما می‌نویسد تا بتوانید آن را بخوانید و ویرایش کنید، سپس برای اجرای آن باید بار دوم کلید Enter را فشار دهید. این کار تنها یک کلید اضافه نیاز دارد و کل این دسته از حوادث را حذف می‌کند. این قابلیت به readline نیاز دارد، بنابراین فقط در prompt تعاملی کار می‌کند و در جای دیگری کاربرد ندارد.

روش دیگر کار، استفاده از Ctrl-R است که هیچ‌گاه چیزی را بسط نمی‌دهد. کلیدهای Ctrl-R را فشار دهید و بخشی از یک دستور را تایپ کنید. prompt به یک prompt از نوع reverse-i-search تغییر می‌کند و با تایپ شما، جدیدترین مورد منطبق را نشان می‌دهد. دوباره Ctrl-R را فشار دهید تا به موارد قدیمی‌تر برگردید. کلید Enter خط نمایش‌داده‌شده را اجرا می‌کند. Ctrl-G جستجو را لغو کرده و prompt اصلی شما را بازمی‌گرداند. کلید جهت‌نمای چپ (left arrow) جستجو را پایان می‌دهد و خط منطبق را برای ویرایش در prompt باقی می‌گذارد.

زمانی که می‌خواهید ابتدا دستور را ببینید از Ctrl-R استفاده کنید. زمانی که دستور مربوط به یک خط قبل است و هنوز می‌توانید آن را روی صفحه بخوانید، از !! و !$ استفاده کنید.

چرا دستور echo "done!" خطای event not found می‌دهد

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

بسط تاریخچه (History expansion) پیش از پردازش کوتیشن‌ها اجرا می‌شود و دابل کوتیشن‌ها از کاراکتر ! محافظت نمی‌کنند. تنها سینگل کوتیشن و بک‌اسلش این کار را انجام می‌دهند. بنابراین echo 'deploy done!' همان خروجی مورد انتظار شما را چاپ می‌کند. بک‌اسلش نیز از بسط جلوگیری می‌کند، اما در داخل دابل کوتیشن، bash بک‌اسلش را در خروجی باقی می‌گذارد؛ بنابراین استفاده از سینگل کوتیشن راهکار تمیزتری است.

این مشکل هنگام کار با رمزهای عبور بیشترین دردسر را ایجاد می‌کند، زیرا رمزهای عبور قوی اغلب حاوی ! هستند. دستوری مانند mysql -u app -p"S3cret!pass" یا با خطای event-not-found مواجه می‌شود، یا اگر ورودی مشابهی در تاریخچه وجود داشته باشد، به‌طور بی‌صدا رشتهٔ متفاوتی را ارسال می‌کند. از سینگل کوتیشن استفاده کنید، یا بهتر از آن، اجازه دهید ابزار مورد نظر از شما رمز عبور را بپرسد تا این دادهٔ حساس هرگز به خط فرمان وارد نشود. این عادت باید بخشی از اصول بهداشت امنیتی حساب کاربری شما باشد: برای اطلاع از نحوه مدیریت کلیدها و رمزهای عبور در یک سرور تازه، ده دقیقه اول روی یک VPS جدید را مطالعه کنید.

اگر قصد دارید بلوکی از متن را که حاوی تعداد زیادی کاراکتر ! است کپی کنید، بسط تاریخچه را برای آن نشست با دستور set +H غیرفعال و با دستور set -H دوباره فعال کنید.

متغیرهای HISTSIZE و HISTFILESIZE و محل ذخیره تاریخچه دستورات

این دو متغیر هستند و به دلیل شباهت اسمی، اغلب با هم اشتباه گرفته می‌شوند.

  • HISTSIZE مشخص می‌کند که شلِ در حال اجرا، چند دستور را در حافظه نگه می‌دارد.
  • HISTFILESIZE مشخص می‌کند که چند خط در فایلِ روی دیسک نگهداری می‌شود، ~/.bash_history مگر اینکه HISTFILE مقدار دیگری تعیین کرده باشد.

این فایل هنگام خروج از شل نوشته می‌شود و در همان لحظه به تعداد HISTFILESIZE خط کوتاه (truncate) می‌شود. تنظیمات پیش‌فرض ~/.bashrc در اوبونتو، مقادیر 1000 و 2000 هستند. بررسی کنید که تنظیمات فعلی شما چه مقداری دارند:

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

اگر در جستجوهای !string دستوراتی که می‌دانید اجرا کرده‌اید را پیدا نمی‌کنید، هر دو مقدار را افزایش دهید. مقادیر 10000 و 20000 رایج هستند و تقریباً هیچ هزینه‌ای ندارند، زیرا فایل به صورت متن ساده (plain text) ذخیره می‌شود. مقدار منفی به معنای عدم وجود هیچ‌گونه محدودیتی است.

استفاده از برچسب‌های زمانی (timestamps) زمانی که پس از یک هفته به سرور بازمی‌گردید، بسیار کمک‌کننده است:

export HISTTIMEFORMAT='%F %T '

history سپس تاریخ و زمان را در ابتدای هر ورودی چاپ می‌کند، زیرا bash پیش از هر دستور در فایل، یک خط توضیحات شامل ثانیه‌های epoch می‌نویسد.

شکایت رایج این است که وقتی از بیش از یک ترمینال استفاده می‌کنید، تاریخچه دستورات گم می‌شود. هر شل لیست مخصوص به خود را در حافظه نگه می‌دارد و هنگام خروج آن را می‌نویسد؛ بنابراین بدون histappend، آخرین شلی که بسته می‌شود، آنچه دیگران ذخیره کرده‌اند را بازنویسی می‌کند. دو تنظیم این مشکل را حل می‌کنند:

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

histappend باعث می‌شود شل به جای جایگزینی کل فایل، لیست خود را به انتهای فایل اضافه کند. history -a خطوط جدید را پس از هر prompt اضافه می‌کند، بنابراین اگر یک نشست به جای بسته شدنِ صحیح، به اجبار قطع شود (kill)، دستورات آن همچنان باقی می‌مانند. تنظیم پیش‌فرض ~/.bashrc در اوبونتو، histappend را از قبل اعمال کرده است. این موضوع هرچه تعداد ماشین‌های بیشتری داشته باشید اهمیت بیشتری پیدا می‌کند، زیرا تاریخچه به ازای هر کاربر روی هر ماشین ذخیره می‌شود؛ بنابراین وقتی در حال مدیریت چندین سرور از یک ایستگاه کاری هستید، !$ مورد نظر شما ممکن است به سادگی روی یک میزبان دیگر باشد.

دور نگه داشتن اسرار از تاریخچه bash

HISTCONTROL تعیین می‌کند که کدام خطوط اصلاً ذخیره شوند.

  • ignorespace هر خطی که با یک فاصله شروع شود را حذف می‌کند.
  • ignoredups خطی که دقیقاً مشابه خط قبلی باشد را حذف می‌کند.
  • ignoreboth هر دو مورد بالا را انجام می‌دهد.
  • erasedups تمام نسخه‌های قدیمی‌تر خطی که در حال ذخیره شدن است را پاک می‌کند.
export HISTCONTROL=ignoreboth

با فعال بودن ignorespace، یک فاصله در ابتدای دستور باعث می‌شود آن دستور در لیست حافظه قرار نگیرد و در نتیجه هرگز به فایل تاریخچه نیز منتقل نشود. پیش از تکیه بر این قابلیت، مقدار آن را با echo "$HISTCONTROL" بررسی کنید. کاربر پیش‌فرض در Ubuntu مقدار ignoreboth را برای .bashrc تنظیم می‌کند، اما در shell کاربر root یا imageهای مینیمال، ممکن است این متغیر تنظیم نشده باشد؛ در آن صورت، فاصله ابتدایی بی‌اثر بوده و خط دستور مانند سایر دستورات ذخیره می‌شود.

نسبت به آنچه این روش محافظت می‌کند، واقع‌بین باشید. این خط در ~/.bash_history ذخیره نمی‌شود. اما تا زمانی که پردازش در حال اجراست، در خروجی ps قابل مشاهده است، بنابراین هر کاربر دیگری در سیستم می‌تواند آن را بخواند. sudo نیز دستور اجرا شده را در لاگ سیستم ثبت می‌کند. فایل تاریخچه یک فایل متنی ساده است، پس دسترسی آن را روی 600 نگه دارید و به یاد داشته باشید که هر کسی که به دایرکتوری home شما دسترسی خواندن داشته باشد، می‌تواند چند هزار دستور آخر شما را مشاهده کند.

اگر مورد حساسی از قبل در لیست وجود دارد، آن ورودی را حذف کرده و فایل را بازنویسی کنید:

history
history -d 517
history -w

history -d آن ورودی را از حافظه حذف می‌کند و history -w لیست فعلی را روی فایل می‌نویسد. history -c کل لیست را پاک می‌کند. HISTIGNORE تنظیم مرتبط برای مدیریت نویز (به جای اسرار) است: HISTIGNORE='ls:pwd:history:clear' آن خطوط را از لیست دور نگه می‌دارد تا جستجوهای شما نتایج مفیدتری داشته باشند.

چرا history expansion در اسکریپت‌های shell کار نمی‌کند

قابلیت history expansion مختص shellهای تعاملی (interactive) است. اسکریپت در یک shell غیرتعاملی اجرا می‌شود که در آن لیست تاریخچه فعال نیست و expansion خاموش است؛ بنابراین !! و !$ به عنوان متن عادی در خط باقی می‌مانند. دستور sudo !! در داخل یک اسکریپت، از sudo می‌خواهد دستوری را که دقیقاً نامش !! است اجرا کند، که این کار با شکست مواجه می‌شود.

وضعیت هر shell که در آن هستید را بررسی کنید:

echo $-

خروجی، مجموعه‌ای از پرچم‌های (flags) فعال فعلی است، چیزی شبیه به himBHs. حرف i به معنای تعاملی بودن shell و H به معنای فعال بودن history expansion است. اگر همین خط را داخل یک اسکریپت اجرا کنید، هیچ‌کدام از این حروف وجود نخواهند داشت.

این همان مرز بین دو نیمهٔ کار شما با shell است. در خط فرمان، !$ و Ctrl-R باعث صرفه‌جویی در تایپ دستوراتی می‌شوند که هنوز آن‌ها را می‌بینید. در یک فایل، شما به جای آن از نام‌گذاری استفاده می‌کنید: مسیر را در یک متغیر قرار دهید، یا خروجی را با command substitution دریافت کنید. اسکریپتی که به تاریخچهٔ شخصی شما وابسته باشد، برای نفر بعدی که آن را اجرا می‌کند رفتار متفاوتی خواهد داشت، که این دقیقاً نقطهٔ مقابل هدف اصلی اسکریپت‌نویسی است.

به همین دلیل، تمام مثال‌های این صفحه برای تایپ در خط فرمان زنده (live prompt) در نظر گرفته شده‌اند. هیچ‌کدام از آن‌ها در صورت کپی شدن در یک فایل .sh رفتار مشابهی نخواهند داشت.

FAQ

در bash عبارت !! چه کاری انجام می‌دهد؟

!! به متن کامل خط فرمان قبلی بسط می‌یابد، بنابراین sudo !! آخرین دستور شما را دوباره با دسترسی root اجرا می‌کند. این بسط متنی است و پیش از آنکه bash خط را تجزیه (parse) کند رخ می‌دهد؛ bash خط نهایی را درست پیش از اجرا چاپ می‌کند. اگر از محتوای خط قبلی مطمئن نیستید، ابتدا sudo !!:p را تایپ کنید. :p بسط را بدون اجرا چاپ می‌کند و خط چاپ‌شده را به تاریخچه (history) می‌افزاید، بنابراین اجرای !! پس از آن، دستور را اجرا خواهد کرد.

چگونه آخرین آرگومان دستور قبلی را دوباره استفاده کنم؟

از !$ استفاده کنید. پس از sudo mkdir -p /srv/www/app/releases، خط ls -ld !$ به ls -ld /srv/www/app/releases تبدیل می‌شود. این عبارت آخرین «کلمه» خط را برمی‌دارد، بنابراین مقصد یک redirection که در انتهای خط باشد، آخرین کلمه محسوب می‌شود. شما همچنین می‌توانید به آن متن اضافه کنید: !$/config.env به مسیر اضافه می‌شود، زیرا تعیین‌کننده کلمه در / متوقف می‌شود. جایگزین تعاملی آن Alt-. است که همان متن را در prompt شما درج می‌کند تا بتوانید پیش از فشردن Enter آن را بخوانید.

چرا وقتی متن من شامل علامت تعجب است، bash خطای "event not found" می‌دهد؟

کوتیشن‌های دوتایی (double quotes) از ! در برابر بسط تاریخچه محافظت نمی‌کنند، بنابراین echo "done!" باعث می‌شود bash به دنبال یک رویداد تاریخچه بگردد و bash: !": event not found را چاپ کند. کوتیشن‌های تکی (single quotes) از آن محافظت می‌کنند، پس echo 'done!' را بنویسید. یک بک‌اسلش (backslash) نیز بسط را متوقف می‌کند، اما درون کوتیشن‌های دوتایی، bash بک‌اسلش را در خروجی باقی می‌گذارد. برای paste کردن یک بلوک طولانی که شامل ! است، بسط را برای آن نشست (session) با set +H غیرفعال کنید.

چرا !! و !$ در اسکریپت shell من کار نمی‌کنند؟

بسط تاریخچه فقط در shellهای تعاملی فعال است. یک اسکریپت به‌صورت غیرتعاملی اجرا می‌شود، بنابراین shell هرگز لیست تاریخچه نمی‌سازد و !! به‌عنوان متن ساده در خط باقی می‌ماند. برای دیدن نوع shell خود echo $- را اجرا کنید: یک shell تعاملی فلگ‌هایی شامل i و H را چاپ می‌کند، اما اسکریپت هیچ‌کدام را چاپ نمی‌کند. در اسکریپت‌ها، به‌جای آن از متغیر یا جایگزینی دستور (command substitution) استفاده کنید.

چگونه یک رمز عبور را از تاریخچه bash دور نگه دارم؟

HISTCONTROL=ignorespace یا HISTCONTROL=ignoreboth را در ~/.bashrc تنظیم کنید، سپس دستور را با یک فاصله (space) شروع کنید تا هرگز ذخیره نشود. ابتدا مقدار آن را با echo "$HISTCONTROL" تأیید کنید، زیرا اگر تنظیم نشده باشد، فاصله ابتدایی هیچ کاری انجام نمی‌دهد. این کار فقط خط را از ~/.bash_history دور نگه می‌دارد. دستور همچنان در ps در حین اجرا قابل مشاهده است و sudo آنچه اجرا شده را لاگ می‌کند. اگر یک رمز عبور قبلاً ذخیره شده است، شماره آن را با history پیدا کنید، سپس history -d <number> و به دنبال آن history -w را اجرا کنید تا فایل بازنویسی شود.