SSD Nodes Learn 🎉 VPS $4.99/মাস থেকে
নির্দেশিকা Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-08-08

Bash history expansion: !! এবং !$ ব্যবহারের নিয়ম

Bash হিস্ট্রি এক্সপ্যানশন ব্যবহার করে দ্রুত কমান্ড রান করার উপায় জানুন। !! দিয়ে আগের কমান্ড এবং !$ দিয়ে শেষ আর্গুমেন্ট ব্যবহারের নিয়মসহ 6টি প্রয়োজনীয় ট্রিকস এখানে দেওয়া হলো।

Bash history expansion কী করে

Bash history expansion আপনার শেল হিস্ট্রি থেকে একটি পূর্ববর্তী কমান্ড লাইনকে bash চালানোর আগেই পুনরায় তৈরি করে। !! পূর্ববর্তী কমান্ডটিকে পুনরাবৃত্তি করে। !$ পূর্ববর্তী কমান্ডের শেষ আর্গুমেন্টটি যুক্ত করে। এই প্রতিস্থাপনটি টেক্সট-ভিত্তিক এবং এটি সবার আগে ঘটে, bash লাইনটিকে শব্দে বিভক্ত করার আগেই। তাই যা ফিরে আসে তা হুবহু সেই ক্যারেক্টারগুলো যা আপনি প্রথমবার টাইপ করেছিলেন।

ছয়টি ফর্ম আপনার সার্ভারে করা প্রায় সব কাজ কভার করে:

  • !! পুরো পূর্ববর্তী লাইনটিকে পুনরাবৃত্তি করে এবং sudo !! সেটিকে root হিসেবে পুনরায় চালায়।
  • !$ হলো পূর্ববর্তী লাইনের শেষ আর্গুমেন্ট।
  • !* হলো পূর্ববর্তী লাইনের প্রতিটি আর্গুমেন্ট।
  • !n হিস্ট্রি এন্ট্রি নম্বর n চালায় এবং !-n বর্তমান অবস্থান থেকে উল্টো দিকে গণনা করে।
  • !string সবচেয়ে সাম্প্রতিক কমান্ডটি চালায় যা string দিয়ে শুরু হয়েছিল।
  • ^old^new পূর্ববর্তী লাইনটিকে পুনরায় চালায় যেখানে প্রথম old-কে new দিয়ে প্রতিস্থাপন করা হয়।

নিচের সবকিছু আপনার নিজের সার্ভারের একটি interactive prompt-এ টাইপ করা হয়েছে। স্ক্রিপ্টের ক্ষেত্রে history expansion বন্ধ থাকে, যা শেষ সেকশনে ব্যাখ্যা করা হয়েছে।

একটি দীর্ঘ পাথ, একবার টাইপ করুন

এটি এমন একটি ক্ষেত্র যা নিজের কার্যকারিতা নিজেই প্রমাণ করে। আপনি যখন একটি 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 হিস্ট্রিতে আপনার টাইপ করা !$ নয়, বরং expanded লাইনটি সংরক্ষণ করে। তাই লাইন 3 তার শেষ আর্গুমেন্টটি লাইন 2-এর expanded লাইন থেকে নেয় এবং লাইন 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 কমান্ডটি এক্সপ্যান্ড করা লাইনটি প্রিন্ট করে কিন্তু তা চালায় না। প্রিন্ট করা লাইনটি আপনার হিস্ট্রিতে যোগ হয়, তাই যদি লাইনটি সঠিক মনে হয়, তবে পরের লাইনে সাধারণ !! লিখলেই তা কার্যকর হবে।

শেষ আর্গুমেন্টটি পুনরায় ব্যবহার করতে !$ ব্যবহার করুন

!$ হলো পূর্ববর্তী লাইনের শেষ শব্দ। এটি এমন একটি ফর্ম যা আপনি সবচেয়ে বেশি ব্যবহার করবেন, কারণ শেষ শব্দটি সাধারণত সেই জিনিসটিই হয় যার ওপর আপনি কাজ করছেন: একটি পাথ বা একটি সার্ভিসের নাম।

sudo systemctl status nginx
sudo systemctl reload !$

এখানে দুটি বিষয় মানুষকে অবাক করে।

প্রথমত, !$ হলো শেষ শব্দ, আপনার মনে থাকা শেষ আর্গুমেন্টটি নয়। যদি পূর্ববর্তী লাইনটি কোনো রিডাইরেকশন দিয়ে শেষ হয়, তবে শেষ শব্দটি হবে রিডাইরেক্ট টার্গেট:

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

এটি ঠিকঠাক কাজ করে। কিন্তু journalctl -u nginx > /tmp/log.txt এর পরে, !$ হলো /tmp/log.txt, nginx নয়। ইকো করা লাইনটি পড়ুন।

দ্বিতীয়ত, এই এক্সপ্যানশনটি টেক্সট-ভিত্তিক, তাই একটি ভেরিয়েবল এক্সপ্যান্ড না হওয়া অবস্থাতেই ফিরে আসে। ls $HOME/backups এর পরে, !$ আপনাকে $HOME/backups অক্ষরগুলো দেয়, যা bash পরবর্তীতে একটি সাধারণ প্যারামিটার হিসেবে পুনরায় এক্সপ্যান্ড করে। এই ক্রমটি মনে রাখুন: হিস্ট্রি এক্সপ্যানশন প্যারামিটার এক্সপ্যানশন এবং $( ) দিয়ে কমান্ড সাবস্টিটিউশন-এর আগে কাজ করে, তাই এটি কোনো ভ্যালু দেখতে পায় না, শুধুমাত্র টেক্সট দেখতে পায়।

পাশাপাশি থাকা ফর্মগুলো জেনে রাখা ভালো। !^ হলো প্রথম আর্গুমেন্ট, !:2 হলো দ্বিতীয় আর্গুমেন্ট এবং !:2-4 হলো একটি রেঞ্জ। !!:$ হলো !$ লেখার দীর্ঘ পদ্ধতি।

আপনি যদি কমান্ডটি চালানোর আগে টেক্সটটি দেখতে চান, তবে Alt-. (অথবা Esc এবং তারপর .) চাপুন। Readline পূর্ববর্তী কমান্ডের শেষ আর্গুমেন্টটি সরাসরি আপনার প্রম্পটে বসিয়ে দেবে, যেখানে আপনি এটি এডিট করতে পারবেন। তার আগের কমান্ডের শেষ আর্গুমেন্টে ফিরে যেতে এটি আবার চাপুন। আপনি Enter না চাপা পর্যন্ত কিছুই রান করবে না।

!* ব্যবহার করে প্রতিটি আর্গুমেন্ট পাস করুন

!* হলো আগের লাইনের প্রথম শব্দটি বাদে বাকি সব শব্দ।

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

"প্রথম শব্দটি বাদে" কথাটি এখানে গুরুত্বপূর্ণ কাজ করে। !* শুধুমাত্র 0 নম্বর শব্দটি বাদ দেয়, ফলে অপশনগুলো পাথ (path)-এর সাথে যুক্ত থাকে। 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 51তম এন্ট্রিটি পুনরায় চালায়। !-2 বর্তমান লাইন থেকে দুই লাইন আগের এন্ট্রিটি চালায়, তাই !-1 এবং !! একই অর্থ বহন করে।

কমান্ড ব্যবহারের আগে সংখ্যাগুলো যাচাই করে নিন। !-2 প্রতিবার কোনো কমান্ড চালানোর সাথে সাথে নতুন কোনো অবস্থানে নির্দেশ করে, এমনকি আপনি না ভেবে কোনো ls চালালেও এটি পরিবর্তিত হয়। একটি সেশনের ভেতরে পরম সংখ্যাগুলো (absolute numbers) অপরিবর্তিত থাকে, কিন্তু একই মেশিনে দ্বিতীয় সেশনে এই সংখ্যাগুলো একই থাকে না এবং পরবর্তী লগইনের পর হিস্ট্রি ফাইল লোড হলে এগুলোর মান বদলে যায়। গতকাল যে সংখ্যাটি আপনি মনে রেখেছিলেন, সেটি আজ অন্য কোনো কমান্ডকে নির্দেশ করবে।

!string প্রিফিক্স ব্যবহার করে কমান্ড পুনরায় চালানো

!string কমান্ডটি এমন সর্বশেষ কমান্ডটি চালায় যা string দিয়ে শুরু হয়।

!ss

এটি উপরের তালিকা থেকে ss -tulpn কমান্ডটি পুনরায় চালায়, যা Linux সার্ভারে কোন পোর্টগুলো খোলা আছে সেকশনে বর্ণিত লিসেনিং সকেট চেক করার কমান্ড। !?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 মানে প্রতিস্থাপন (substitute) এবং g মানে পুরো লাইন জুড়ে এটি কার্যকর করা।

চালানোর আগে এক্সপ্যানশনটি প্রিন্ট করুন

দুটি অভ্যাস আপনাকে এমন দুর্ঘটনা থেকে রক্ষা করে যেখানে একটি এক্সপ্যানশন আপনার অনিচ্ছাকৃত কোনো কমান্ড চালিয়ে দেয়।

প্রথমটি হলো :p, যা আপনি ইতিমধ্যে দেখেছেন। কোনো এক্সপ্যানশনের শেষে এটি যুক্ত করলে bash সেটি না চালিয়ে ফলাফলটি প্রিন্ট করে: !!:p, অথবা !systemctl:p। প্রিন্ট করা লাইনটি আপনার হিস্ট্রিতে জমা হয়, তাই পরবর্তীতে !! চাপলে আপনি যা পড়েছেন সেটিই কার্যকর হবে।

দ্বিতীয়টি আরও কার্যকর, কারণ এটি কোনো কিছু মনে রাখা ছাড়াই প্রতিটি এক্সপ্যানশনের ক্ষেত্রে প্রযোজ্য:

shopt -s histverify

এই লাইনটি ~/.bashrc-এ রাখুন। histverify সেট করা থাকলে, Enter চাপলে কোনো এক্সপ্যানশন সরাসরি কার্যকর হয় না। Bash এক্সপ্যান্ড করা লাইনটি আপনার প্রম্পটে ফিরিয়ে দেয় যাতে আপনি সেটি পড়ে সম্পাদনা করতে পারেন, এবং দ্বিতীয়বার Enter চাপলে তবেই সেটি কার্যকর হয়। এতে একটি বাড়তি কি-স্ট্রোক লাগে, কিন্তু এটি সব ধরনের দুর্ঘটনা দূর করে। এটি readline ব্যবহার করে, তাই এটি শুধুমাত্র ইন্টারঅ্যাক্টিভ প্রম্পটে কাজ করে, অন্য কোথাও নয়।

Ctrl-R হলো কাজ করার আরেকটি উপায়, এবং এটি কোনো কিছু এক্সপ্যান্ড করে না। Ctrl-R চাপুন এবং একটি কমান্ডের অংশ টাইপ করুন। প্রম্পটটি একটি reverse-i-search প্রম্পটে পরিবর্তিত হবে এবং টাইপ করার সাথে সাথে সবচেয়ে সাম্প্রতিক মিল থাকা কমান্ডটি দেখাবে। পুরনো মিলগুলো দেখতে আবার Ctrl-R চাপুন। Enter চাপলে প্রদর্শিত লাইনটি কার্যকর হবে। Ctrl-G সার্চ বাতিল করে এবং আপনার আগের প্রম্পট ফিরিয়ে দেয়। বাম দিকের অ্যারো কি (left arrow key) সার্চ শেষ করে এবং সম্পাদনার জন্য মিল পাওয়া লাইনটি আপনার প্রম্পটে রেখে দেয়।

যখন আপনি আগে কমান্ডটি দেখতে চান তখন 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 ত্রুটির কারণে ব্যর্থ হয়, অথবা যদি হিস্ট্রিতে মিলে যায় এমন কোনো এন্ট্রি থাকে, তবে নীরবে ভিন্ন একটি স্ট্রিং পাঠিয়ে দেয়। সিঙ্গেল কোটেশন ব্যবহার করুন, অথবা আরও ভালো হয় যদি টুলটিকে প্রম্পট করতে দেন যাতে সিক্রেটটি কখনোই কমান্ড লাইনে না আসে। এই অভ্যাসটি আপনার অ্যাকাউন্টের নিরাপত্তার অংশ হওয়া উচিত: নতুন সার্ভারে কী (key) এবং পাসওয়ার্ড কীভাবে পরিচালনা করতে হয় তা জানতে নতুন VPS-এ প্রথম দশ মিনিট দেখুন।

আপনি যদি প্রচুর ! ক্যারেক্টার সম্বলিত কোনো টেক্সট ব্লক পেস্ট করতে চান, তবে সেই সেশনের জন্য set +H কমান্ড দিয়ে এক্সপ্যানশন বন্ধ করুন এবং কাজ শেষে set -H দিয়ে তা পুনরায় চালু করুন।

HISTSIZE, HISTFILESIZE এবং আপনার হিস্ট্রি যেখানে সংরক্ষিত থাকে

দুটি ভেরিয়েবল, এবং নামগুলো কাছাকাছি হওয়ায় অনেকেই এদের গুলিয়ে ফেলেন।

  • HISTSIZE হলো রানিং শেল মেমরিতে কতগুলো কমান্ড জমা রাখবে।
  • HISTFILESIZE হলো ডিস্কের ফাইলে কতগুলো লাইন সংরক্ষিত থাকবে, ~/.bash_history যদি না HISTFILE অন্য কিছু নির্দেশ করে।

শেল বন্ধ হওয়ার সময় ফাইলটি লেখা হয় এবং সেই মুহূর্তে এটিকে HISTFILESIZE লাইনে ট্রাঙ্কেট (truncate) করা হয়। Ubuntu-এর ডিফল্ট ~/.bashrc সাধারণত 1000 এবং 2000 সেট করা থাকে। আপনার সিস্টেমে বর্তমানে কী মান সেট করা আছে তা পরীক্ষা করুন:

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

যদি আপনার !string সার্চে পরিচিত কমান্ডগুলো খুঁজে না পান, তবে উভয় মানই বাড়িয়ে দিন। 10000 এবং 20000 মানগুলো বেশ প্রচলিত এবং এতে প্রায় কোনো বাড়তি খরচ নেই, কারণ ফাইলটি সাধারণ টেক্সট ফরম্যাটে থাকে। নেতিবাচক মান (negative value) ব্যবহারের অর্থ হলো কোনো সীমাবদ্ধতা নেই।

এক সপ্তাহ পর সার্ভারে ফিরে এলে টাইমস্ট্যাম্পগুলো বেশ কাজে দেয়:

export HISTTIMEFORMAT='%F %T '

history ব্যবহার করলে প্রতিটি এন্ট্রির আগে তারিখ ও সময় দেখা যায়, কারণ bash ফাইলে প্রতিটি কমান্ডের আগে epoch সেকেন্ড ধারণকারী একটি কমেন্ট লাইন লিখতে শুরু করে।

একটি সাধারণ অভিযোগ হলো, একাধিক টার্মিনাল ব্যবহার করলে হিস্ট্রি হারিয়ে যায়। প্রতিটি শেল তার নিজস্ব তালিকা মেমরিতে রাখে এবং বন্ধ হওয়ার সময় তা ফাইলে লেখে, তাই histappend ছাড়া সবশেষে যে শেলটি বন্ধ হয় সেটি অন্যদের সেভ করা তথ্য মুছে ফেলে। দুটি সেটিংস এই সমস্যার সমাধান করে:

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

histappend শেলকে নির্দেশ দেয় যেন সে ফাইলটি প্রতিস্থাপন না করে তালিকার শেষে নতুন কমান্ডগুলো যোগ করে। history -a প্রতিটি প্রম্পটের পর নতুন লাইন অ্যাপেন্ড (append) করে, ফলে কোনো সেশন যদি সঠিকভাবে বন্ধ না হয়ে হঠাৎ বন্ধ হয়ে যায়, তবুও তার কমান্ডগুলো থেকে যায়। Ubuntu-এর ডিফল্ট ~/.bashrc-এ histappend আগেই সেট করা থাকে। আপনি যত বেশি মেশিন পরিচালনা করবেন, এটি তত বেশি গুরুত্বপূর্ণ হয়ে ওঠে, কারণ হিস্ট্রি প্রতি ইউজার প্রতি মেশিনে সংরক্ষিত থাকে। তাই আপনি যখন একটি ওয়ার্কস্টেশন থেকে একাধিক সার্ভার ম্যানেজ করেন, তখন আপনার প্রয়োজনীয় !$ হয়তো অন্য কোনো হোস্টে থাকতে পারে।

bash history থেকে গোপন তথ্য দূরে রাখা

HISTCONTROL নির্ধারণ করে কোন লাইনগুলো আদৌ সেভ করা হবে কি না।

  • ignorespace স্পেস দিয়ে শুরু হওয়া যেকোনো লাইন বাদ দেয়।
  • ignoredups আগের লাইনের সাথে হুবহু মিলে যাওয়া লাইন বাদ দেয়।
  • ignoreboth উপরের দুটি কাজই করে।
  • erasedups সেভ হতে যাওয়া লাইনের সব পুরনো কপি মুছে ফেলে।
export HISTCONTROL=ignoreboth

ignorespace সক্রিয় থাকলে, একটি লিডিং স্পেস (leading space) ব্যবহার করলে কমান্ডটি ইন-মেমোরি লিস্টে থাকে না, ফলে এটি কখনোই ফাইলে পৌঁছায় না। এর ওপর নির্ভর করার আগে echo "$HISTCONTROL" দিয়ে এর মান যাচাই করে নিন। Ubuntu-এর ডিফল্ট ইউজার .bashrc-এ ignoreboth সেট করা থাকে, কিন্তু root shell বা minimal image-এ এটি আনসেট থাকতে পারে। সেক্ষেত্রে লিডিং স্পেস কোনো কাজ করবে না এবং লাইনটি অন্য সব কমান্ডের মতোই সেভ হয়ে যাবে।

এটি আসলে কী সুরক্ষা দেয় সে বিষয়ে সচেতন থাকুন। লাইনটি ~/.bash_history-এ থাকে না ঠিকই, কিন্তু প্রসেসটি চলার সময় ps আউটপুটে এটি দৃশ্যমান থাকে, তাই ওই মেশিনের অন্য যেকোনো ইউজার তা দেখতে পাবে। sudo সিস্টেম লগে কমান্ডটি রেকর্ড করে রাখে। হিস্ট্রি ফাইলটি প্লেইন টেক্সট ফরম্যাটে থাকে, তাই এর মোড 600 রাখুন এবং মনে রাখবেন যে আপনার হোম ডিরেক্টরি পড়তে পারে এমন যেকোনো ব্যক্তি আপনার শেষ কয়েক হাজার কমান্ড দেখে ফেলতে পারবে।

যদি কোনো সংবেদনশীল তথ্য ইতিমধ্যে লিস্টে থেকে যায়, তবে এন্ট্রিটি মুছে ফেলুন এবং ফাইলটি পুনরায় লিখুন:

history
history -d 517
history -w

history -d মেমোরি থেকে সেই এন্ট্রিটি মুছে ফেলে এবং history -w বর্তমান লিস্টটি ফাইলের ওপর লিখে দেয়। history -c পুরো লিস্টটি ক্লিয়ার করে দেয়। HISTIGNORE হলো গোপন তথ্যের বদলে অপ্রয়োজনীয় তথ্য (noise) নিয়ন্ত্রণের জন্য সংশ্লিষ্ট নব (knob): HISTIGNORE='ls:pwd:history:clear' সেই লাইনগুলোকে লিস্টের বাইরে রাখে যাতে আপনার সার্চ রেজাল্টে শুধু প্রয়োজনীয় তথ্যই আসে।

শেল স্ক্রিপ্টে হিস্ট্রি এক্সপ্যানশন কেন কাজ করে না

হিস্ট্রি এক্সপ্যানশন শুধুমাত্র ইন্টারঅ্যাক্টিভ শেলের বৈশিষ্ট্য। একটি স্ক্রিপ্ট নন-ইন্টারঅ্যাক্টিভ শেলে চলে, যেখানে হিস্ট্রি লিস্ট সক্রিয় থাকে না এবং এক্সপ্যানশন বন্ধ থাকে। তাই !! এবং !$ লাইনে সাধারণ টেক্সট হিসেবেই থেকে যায়। স্ক্রিপ্টের ভেতরে sudo !! ব্যবহার করলে তা sudo-কে আক্ষরিক অর্থে !! নামের একটি কমান্ড চালানোর নির্দেশ দেয়, যা ব্যর্থ হয়।

আপনি বর্তমানে যে শেলে কাজ করছেন তা পরীক্ষা করুন:

echo $-

এর আউটপুট হলো বর্তমান অপশন ফ্ল্যাগগুলোর সেট, যা অনেকটা himBHs-এর মতো দেখাবে। i মানে হলো শেলটি ইন্টারঅ্যাক্টিভ এবং H মানে হলো হিস্ট্রি এক্সপ্যানশন সক্রিয় আছে। একই লাইন একটি স্ক্রিপ্টের ভেতরে চালিয়ে দেখুন, সেখানে এই অক্ষরগুলো থাকবে না।

আপনার শেল ব্যবহারের এই দুটি ক্ষেত্রের মধ্যে পার্থক্য এখানেই। প্রম্পটে !$ এবং Ctrl-R ব্যবহার করে আপনি আগের কমান্ডগুলো দ্রুত টাইপ করতে পারেন। কিন্তু ফাইলের ক্ষেত্রে আপনাকে ভিন্ন পদ্ধতি অবলম্বন করতে হবে: পাথটিকে একটি ভেরিয়েবলে রাখুন অথবা command substitution ব্যবহার করে আউটপুট ক্যাপচার করুন। আপনার ব্যক্তিগত হিস্ট্রির ওপর নির্ভরশীল কোনো স্ক্রিপ্ট অন্য কেউ চালালে তা ভিন্ন ফলাফল দেবে, যা স্ক্রিপ্টিংয়ের মূল উদ্দেশ্যের পরিপন্থী।

এই কারণেই, এই পৃষ্ঠার প্রতিটি উদাহরণ সরাসরি প্রম্পটে টাইপ করার জন্য দেওয়া হয়েছে। এর কোনোটিই কোনো .sh ফাইলে পেস্ট করলে একইভাবে কাজ করবে না।

FAQ

Bash-এ !! কী কাজ করে?

!! পূর্ববর্তী কমান্ড লাইনের সম্পূর্ণ টেক্সটকে প্রসারিত (expand) করে, তাই sudo !! আপনার শেষ কমান্ডটিকে root হিসেবে পুনরায় চালায়। এই প্রসারণটি টেক্সট-ভিত্তিক এবং bash লাইনটি পার্স করার আগেই ঘটে; bash লাইনটি চালানোর ঠিক আগে সম্পূর্ণ লাইনটি প্রিন্ট করে। পূর্ববর্তী লাইনটি কী ছিল তা নিশ্চিত না হলে, প্রথমে sudo !!:p টাইপ করুন। :p কমান্ডটি না চালিয়েই প্রসারণটি প্রিন্ট করে এবং প্রিন্ট করা লাইনটিকে আপনার history-তে যোগ করে, ফলে পরবর্তী !! কমান্ডটি সেটি চালিয়ে দেয়।

পূর্ববর্তী কমান্ডের শেষ আর্গুমেন্ট কীভাবে পুনরায় ব্যবহার করব?

!$ ব্যবহার করুন। sudo mkdir -p /srv/www/app/releases-এর পরে, ls -ld !$ লাইনটি ls -ld /srv/www/app/releases-এ পরিণত হয়। এটি লাইনের শেষ word-টিকে গ্রহণ করে, তাই লাইনের শেষে থাকা কোনো redirection target-ও শেষ word হিসেবে গণ্য হয়। আপনি এর সাথে টেক্সটও যোগ করতে পারেন: !$/config.env পাথটির সাথে যোগ হয়, কারণ word designator-টি /-এ এসে থেমে যায়। এর ইন্টারঅ্যাক্টিভ বিকল্প হলো Alt-., যা একই টেক্সট আপনার প্রম্পটে ইনসার্ট করে, যাতে Enter চাপার আগে আপনি তা পড়ে দেখতে পারেন।

আমার টেক্সটে বিস্ময়সূচক চিহ্ন (exclamation mark) থাকলে bash কেন "event not found" বলে?

ডাবল কোটেশন চিহ্ন !-কে history expansion থেকে রক্ষা করে না, তাই echo "done!" লিখলে bash একটি history event খোঁজে এবং bash: !": event not found প্রিন্ট করে। সিঙ্গেল কোটেশন চিহ্ন এটিকে রক্ষা করে, তাই echo 'done!' লিখুন। একটি ব্যাকস্ল্যাশও প্রসারণ বন্ধ করে, কিন্তু ডাবল কোটেশনের ভেতরে bash ব্যাকস্ল্যাশটিকে আউটপুটে রেখে দেয়। ! সম্বলিত একটি বড় ব্লক পেস্ট করতে চাইলে, set +H কমান্ড দিয়ে সেশনের জন্য প্রসারণ বন্ধ করে নিন।

আমার শেল স্ক্রিপ্টে !! এবং !$ কাজ করে না কেন?

History expansion শুধুমাত্র ইন্টারঅ্যাক্টিভ শেলেই সক্রিয় থাকে। একটি স্ক্রিপ্ট নন-ইন্টারঅ্যাক্টিভভাবে চলে, তাই শেল কখনোই history লিস্ট তৈরি করে না এবং !! লাইনে সাধারণ টেক্সট হিসেবেই থেকে যায়। আপনি কোন মোডে আছেন তা দেখতে echo $- চালান: একটি ইন্টারঅ্যাক্টিভ শেল i এবং H সহ বিভিন্ন ফ্ল্যাগ প্রিন্ট করে, কিন্তু স্ক্রিপ্ট কোনোটিই প্রিন্ট করে না। স্ক্রিপ্টের ক্ষেত্রে, পরিবর্তে ভেরিয়েবল বা কমান্ড সাবস্টিটিউশন ব্যবহার করুন।

Bash history থেকে পাসওয়ার্ড কীভাবে দূরে রাখব?

~/.bashrc ফাইলে HISTCONTROL=ignorespace অথবা HISTCONTROL=ignoreboth সেট করুন, তারপর কমান্ডের শুরুতে একটি স্পেস দিয়ে কমান্ডটি লিখুন; এটি কখনোই সেভ হবে না। প্রথমে echo "$HISTCONTROL" দিয়ে মানটি নিশ্চিত করুন, কারণ এটি unset থাকলে শুরুতে স্পেস দিলেও কোনো কাজ হবে না। এটি শুধুমাত্র লাইনটিকে ~/.bash_history থেকে দূরে রাখে। কমান্ডটি চলার সময় ps-এ তা দৃশ্যমান থাকে এবং sudo কী এক্সিকিউট হয়েছে তা লগ করে। যদি কোনো গোপন তথ্য ইতিমধ্যে সেভ হয়ে গিয়ে থাকে, তবে history দিয়ে সেটির নম্বর খুঁজে বের করুন, তারপর history -d <number> এবং তার পরে history -w চালিয়ে ফাইলটি পুনরায় লিখুন।

#bash#shell#history#productivity#cli