আমার কাছে কম্পিউটারের একটু কিছুই বেশি স্যাটিসফাইং (satisfying) নয় এক এলিগেন্ট (elegant) বাশ লাইন (Bash line) যা ঘন্টার পর ঘন্টা টেডিয়াস (tedious) কাজ অটোমেট করে।
আমার ল্যাপটপ বাশ স্ক্রিপ্টস (Bash scripts) দিয়ে অটোমেটিক্যালি রি-ক্রিয়েট (re-create) করার কিছু সাম্প্রতিক এক্সপ্লোরেশন (explorations) অংশ হিসেবে (পোস্ট আসছে!), আমি একটি উপায় খুঁজছিলাম আমার গিটহাব-হোস্টেড (GitHub-hosted) রিপোজিটরিগুলো সহজে একটি নতুন মেশিনে ক্লোন (clone) করার জন্য। একটু খুঁজে বের করার পর, আমি একটি ওন-লাইনার (one-liner) লিখলাম যা সেটি করল।
তারপর, সকল ডিম একই বাস্কেটে (basket) না রাখার সিপিরিটে (spirit), আমি আরেকটি ওন-লাইনার লিখলাম যা অটোমেটিক্যালি গিটল্যাব-হোস্টেড (GitLab-hosted) ব্যাকআপস (backups) তৈরি ও পুশ (push) করবে। এগুলো পাওয়া যাক।
গিটহাব রিপোজিটরিগুলো সব ক্লোন করার একটি বাশ ওন-লাইনার
সতর্কতা: আপনার পাসে গিটহাব রিপোজিটরিগুলোর একটি লিস্ট থাকতে হবে যেগুলো আপনি ক্লোন করতে চান। সেটির ভালো পার্ট হচ্ছে এটি আপনাকে ফুল এজেন্সি (full agency) দেয় শুধু যেসব রিপোজিটরি আপনি আপনার মেশিনে চান সেগুলো বাছাই করতে, পুরো হগ (whole-hog) ঢুকা না।
আপনি আপনার পাসওয়ার্ড প্রতিবার ইনপুট না দিয়ে গিটহাব রিপোজিটরি ক্লোন করতে পারেন HTTPS ব্যবহার করে আপনার ১৫-মিনিট ক্যাশেড (cached) ক্রেডেনশিয়ালস (credentials) দিয়ে অথবা, আমার পছন্দের মেথড, SSH দিয়ে গিটহাবের সাথে কানেক্ট (connect) করে। সংক্ষিপ্ততার জন্য আমি ধরে নিচ্ছি আমরা এরপর वाले মেথডটি ব্যবহার করছি, এবং আমাদের SSH কিজ (SSH keys) সেটআপ করা আছে।
একটি গিটহাব URL-র লিস্ট দেওয়া আছে gh-repos.txt ফাইলে, এইভাবে:
git@github.com:username/first-repository.git git@github.com:username/second-repository.git git@github.com:username/third-repository.git
আমরা রান করি:
xargs -n1 git clone < gh-repos.txt
এটি করেন্ট ফোল্ডারে (current folder) লিস্টের সকল রিপোজিটরি ক্লোন করবে। এই সেম ওন-লাইনার গিটল্যাব (GitLab)-ও কাজ করবে, যদি আপনি উপযুক্ত URL-গুলো সাবস্টিটিউট (substitute) করেন।
এখানে কী হচ্ছে?
এই ওন-লাইনারের দুটি হাফ (halves) আছে: ইনপুট (input), কাউন্টারইন্টুইটিভলি (counterintuitively) ডান পাশে, এবং যে পার্টটি জিনিসগুলো হ্যাপেন (happen) করায়, সেটি বাম পাশে। আমরা এই পার্টস এর অর্ডার (order) আরও ইন্টুইটিভ (intuitive) করতে পারি (হয়তো?) একই কমান্ডটি এভাবে লিখে:
< gh-repos.txt xargs -n1 git clone
আমাদের ইনপুট, gh-repos.txt-এর প্রতিটি লাইনের জন্য একটি কমান্ড রান করতে, আমরা ব্যবহার করি xargs -n1। টুল xargs ইনপুট থেকে আইটেমস (items) পড়ে এবং যেকোনো কমান্ড যা সেটি পায় সেটি এক্সিকিউট (execute) করে (যদি সেটি কোই না পায় তবে echo করে)। ডিফল্টভাবে, সেটি অনুমান করে যে আইটেমস স্পেস (spaces) দিয়ে সেপারেটেড (separated); নিউ লাইনস (new lines) ও কাজ করে এবং আমাদের লিস্ট পড়তে সহজ করে। ফ্ল্যাগ -n1 xargs-কে বলে 1 আর্গুমেন্ট (argument) ব্যবহার করতে, আমাদের কেসে, একটি লাইন প্রতি কমান্ড। আমরা আমাদের কমান্ড বিল্ড (build) করি git clone দিয়ে, যা xargs তারপর প্রতিটি লাইনের জন্য এক্সিকিউট করে। তা-দা।
গিটল্যাবে (GitLab) একাধিক রিপোজিটরি তৈরি ও পুশ করার একটি বাশ ওন-লাইনার
গিটল্যাব (GitLab), গিটহাব (GitHub)-এর ভিন্ন, আমাদের এই নিফটি (nifty) জিনিসটি করতে দেয় যেখানে আমাদের ওয়েবসাইট (website) ব্যবহার করে একটি নতুন রিপোজিটরি প্রথমে বানাতে হয় না। আমরা আমাদের টার্মিনাল থেকে একটি নতুন গিটল্যাব রিপোজিটরি তৈরি করতে পারি। নতুন তৈরি রিপোজিটริ ডিফল্টভাবে প্রাইভেট (Private) হিসেবে সেট থাকে, তাই আপনি যদি সেটি পাবলিক (Public) করতে চান গিটল্যাবে, তবে আপনাকে সেটি ম্যানুয়ালি (manually) পরে করতে হবে।
গিটল্যাব ডকস (GitLab docs) আমাদের বলে নতুন প্রজেক্ট তৈরি করতে git push --set-upstream ব্যবহার করতে, কিন্তু আমি এটাকে খুব কনভেনিয়েন্ট (convenient) পাই না গিটল্যাব ব্যাকআপ (backup) হিসেবে ব্যবহার করার জন্য। যেহেতু আমি ভবিষ্যতে আমার রিপোজিটরিগুলোর সাথে কাজ করব, আমি এক কমান্ড চাই যা গিটহাব এবং গিটল্যাব দুটোই পুশ করবে আমার পক্ষ থেকে কোনো অতিরিক্ত এফোর্ট (effort) ছাড়া।
এই বাশ ওন-লাইনারটি কাজ করার জন্য, আমাদের গিটল্যাবের জন্য রিপোজিটরি URL-র (repository URLs) একটি লিস্টও লাগবে (যারা এখনো অস্তিত্বে নেই)। আমরা সহজে এটি করতে পারি আমাদের গিটহাব রিপোজিটরি লিস্ট কপি করে, সেটি ভিম (Vim) দিয়ে খুলে, এবং একটি সার্চ-এন্ড-রিপ্লেস (search-and-replace) করে:
cp gh-repos.txt gl-repos.txt vim gl-repos.txt :%s/\<github\>/gitlab/g :wq
এটি প্রডিউস (produce) করে gl-repos.txt, যা দেখতে এই রকম:
git@gitlab.com:username/first-repository.git git@gitlab.com:username/second-repository.git git@gitlab.com:username/third-repository.git
আমরা এই রিপোজিটরিগুলো গিটল্যাবে তৈরি করতে পারি, URLs রিমোটস (remotes) হিসেবে যোগ করতে পারি, এবং আমাদের কোড নতুন রিপোজিটরিগুলোতে পুশ করতে পারি চালিয়ে:
awk -F'\/|(\.git)' '{system("cd ~/FULL/PATH/" $2 " && git remote set-url origin --add " $0 " && git push")}' gl-repos.txt
ধৈর্য ধরুন এবং আমি এক্সপ্লেইন (explain) করছি; ফিলহাল নোট করুন যে ~/FULL/PATH/ হলো আমাদের গিটহাব রিপোজিটরিগুলো ধারণকারী ডিরেক্টরির ফুল পাথ (full path) হতে হবে।
আমাদের কয়েকটা অ্যাসাম্পশন (assumptions) নোট করলেই ভালো:
- আপনার লোকাল মেশিনে যে ডিরেক্টরির নাম আছে যেটি রিপোজিটরি ধারণ করে, সেটি URL-এ রিপোজিটরির নামের সাথে একই (এটি হবে যদি উপরের ওন-লাইনার দিয়ে ক্লোন করা হয়);
- প্রতিটি রিপোজিটরি বর্তমানে চেকড আউট (checked out) আছে সেই ব্রাঞ্চে যেটি আপনি পুশ করতে চান, মানে
master।
ওন-লাইনারটি এক্সপ্যান্ড (expand) করা যেতে পারে এই অ্যাসাম্পশনগুলো হ্যান্ডেল (handle) করার জন্য, কিন্তু লেখকের হাম্বল অপিনিয়ন (humble opinion) অনুযায়ী, সেটি পয়েন্টে, আমাদের খুব বেশি একটি বাশ স্ক্রিপ্ট (Bash script) লিখতে হবে।
এখানে কী হচ্ছে?
আমাদের বাশ ওন-লাইনার gl-repos.txt ফাইলে প্রতিটি লাইন (বা URL) ইনপুট হিসেবে ব্যবহার করে। awk দিয়ে, এটি আমাদের লোকাল মেশিনে রিপোজিটরি ধারণকারী ডিরেক্টরির নাম স্প্লিট অফ (split off) করে, এবং এই ইনফরমেশন টুকরোগুলো ব্যবহার করে আমাদের বড় কমান্ডটি বিল্ড (build) করে। যদি আমরা awk-এর আউটপুট print করতাম, তবে আমরা দেখতাম:
cd ~/FULL/PATH/first-repository && git remote set-url origin --add git@gitlab.com:username/first-repository.git && git push cd ~/FULL/PATH/second-repository && git remote set-url origin --add git@gitlab.com:username/second-repository.git && git push cd ~/FULL/PATH/third-repository && git remote set-url origin --add git@gitlab.com:username/third-repository.git && git push
আসুন দেখি কীভাবে আমরা এই কমান্ডটি বিল্ড করি।
awk দিয়ে স্ট্রিংস স্প্লিট করা (Splitting strings with awk)
টুল awk ইনপুট স্প্লিট (split) করতে পারে ফিল্ড সেপারেটরস (field separators) বেসড অন। ডিফল্ট সেপারেটর হচ্ছে একটি হোয়াইটস্পেস ক্যারেক্টার (whitespace character), কিন্তু আমরা এটি চেঞ্জ (change) করতে পারি -F ফ্ল্যাগ পাস করে। সিঙ্গেল ক্যারেক্টারস (single characters) পাশাপাশি, আমরা একটি রেগুলার এক্সপ্রেশন ফিল্ড সেপারেটর (regular expression field separator)ও ব্যবহার করতে পারি। যেহেতু আমাদের রিপোজিটরি URL-গুলোর একটি সেট ফরম্যাট (set format) আছে, আমরা রিপোজিটরি নামগুলো গ্র্যাব (grab) করতে পারি স্ল্যাশ ক্যারেক্টার / এবং URL-র এন্ড (end), .git-এর মাঝপথে সাবস্ট্রিং (substring) চাই বলে।
এটি করার একটি উপায় হচ্ছে আমাদের রেজেক্স (regex) \/|(\.git) দিয়ে:
\/হচ্ছে একটি এস্কেপড (escaped)/ক্যারেক্টার;|মানে "অথবা" (or), awk-কে বলে যেকোনো এক্সপ্রেশন ম্যাচ (match) করুক;(\.git)হচ্ছে আমাদের URL-র এন্ডে একটি ক্যাপচার গ্রুপ (capture group) যা ".git" ম্যাচ করে, একটি এস্কেপড.ক্যারেক্টার সহ। এটি একটু চিট (cheat) কিনা, কারণ ".git" স্ট্রিক্টলি (strictly) কিছু স্প্লিট (split) করে না (অন্য পাশে কিছু নেই) কিন্তু এটি আমাদের জন্য একটি সহজ উপায় এই বিটটি বাদ দিতে।
একবার আমরা awk-কে বললাম কোথায় স্প্লিট করতে, আমরা সঠিক সাবস্ট্রিং (substring) গ্র্যাব (grab) করতে পারি ফিল্ড অপারেটর (field operator) দিয়ে। আমরা আমাদের ফিল্ডস (fields) রেফার (refer) করি একটি $ ক্যারেক্টার দিয়ে, তারপর ফিল্ডের কলাম নম্বর (column number) দিয়ে। আমাদের উদাহরণে, আমরা চাই দ্বিতীয় ফিল্ড, $2। এখানে সকল সাবস্ট্রিংস কেমন দেখাবে:
1: git@gitlab.com:username 2: first-repository
পুরো স্ট্রিংটি, আমাদের কেসে, পুরো URLটি, ব্যবহার করতে আমরা ফিল্ড অপারেটর $0 ব্যবহার করি। কমান্ডটি লিখতে, আমরা শুধু ফিল্ড অপারেটরগুলো সাবস্টিটিউট (substitute) করি রিপোজিটরি নাম এবং URL-র জন্য। print দিয়ে রান করা বিল্ড করার সময় সাহায্য করতে পারে স্পেসগুলো (spaces) সঠিক আছে কিনা চেক করতে।
awk -F'\/|(\.git)' '{print "cd ~/FULL/PATH/" $2 " && git remote set-url origin --add " $0 " && git push"}' gl-repos.txt
কমান্ডটি রান করা (Running the command)
আমরা আমাদের কমান্ডটি বিল্ড করি system()-র পেরেনথেসিস (parenthesis) ভেতরে। এটি ব্যবহার করে awk-এর আউটপুট হিসেবে, প্রতিটি কমান্ড রান (run) হয় যতক্ষণ না সেটি বিল্ড ও আউটপুট হয়। system() ফাংশন একটি চাইল্ড প্রসেস (child process) তৈরি করে যা আমাদের কমান্ড এক্সিকিউট (execute) করে, তারপর রিটার্ন (return) করে একবার কমান্ডটি কম্প্লিট (completed) হলে। সাদা ইংরেজিতে, এটি আমাদের গিট কমান্ডস (Git commands) প্রতিটি রিপোজিটরিতে একে একে পারফর্ম (perform) করতে দেয়, আমাদের মেইন প্রসেস (main process) থেকে ব্রেক (break) না হয়ে যেন awk আমাদের ইনপুট ফাইল দিয়ে কাজ করছে। এখানে আমাদের ফাইনাল কমান্ড (final command) আবার, সব একসাথে।
awk -F'\/|(\.git)' '{system("cd ~/FULL/PATH/" $2 " && git remote set-url origin --add " $0 " && git push")}' gl-repos.txt
আমাদের ব্যাকআপস ব্যবহার করা (Using our backups)
গিটল্যাব URLs রিমোটস হিসেবে যোগ করে, আমরা দুটি এক্সটার্নালি হোস্টেড (externally hosted) রিপোজিটরিতে পুশ করার প্রক্রিয়া সহজ করেছি। আপনি যদি git remote -v রান করেন আপনার রিপোজিটরি ডিরেক্টরিগুলোর একটিতে, তবে আপনি দেখবেন:
origin git@github.com:username/first-repository.git (fetch) origin git@github.com:username/first-repository.git (push) origin git@gitlab.com:username/first-repository.git (push)
এখন, সিম্পলি git push আর্গুমেন্টস ছাড়া চালালে করেন্ট ব্রাঞ্চ (current branch) দুটো রিমোট রিপোজিটরিতেও পুশ করবে।
আমাদের এছাড়া নোট (note) করলেও ভালো যে git pull সাধারণত শুধু চেষ্টা করবে সেই রিমোট রিপোজিটরি থেকে পুল (pull) করার জন্য যেখান থেকে আপনি অরিজিনালি ক্লোন (originally cloned) করেছিলেন (আমাদের উদাহরণে (fetch) মার্ক করা URL)। একাধিক গিট রিপোজিটরি থেকে একসাথে পুল (pull) করা সম্ভব, কিন্তু কমপ্লেক্স (complicated), এবং এই পোস্টের স্কোপ (scope)-এর বাইরে। একাধিক রিমোটসে (remotes) পুশ এবং পুল করার এক্সপ্লানেশন (explanation) দেখুন শুরু করতে, আপনি যদি কিউরিয়াস (curious) হন। গিট ডকুমেন্টেশন অন রিমোটস (Git documentation on remotes)ও হেল্পফুল (helpful) হতে পারে।
বাশ ওন-লাইনারস (Bash one-liners) এর সাকসিন্ক্টনেস (succinctness) এর ওপর আলোচনা
বাশ ওন-লাইনারস (Bash one-liners), যখন বুঝা যায়, ফান (fun) এবং হ্যান্ডি (handy) শর্টকাটস। কম্পিউটারের লিস্ট, xargs এবং awk যেসব টুলস সম্পর্কে জানা থাকলে অটোমেট (automate) করা এবং আমাদের কাজের টেডিয়াসনেস (tediousness) কমাতে পারে। যাইহোক, কিছু ডাউনসাইড (downsides) আছে।
একটি ইজি-টু-অন্ডারস্ট্যান্ড (easy-to-understand), মেইনটেইনেবল (maintainable), এবং অ্যাপ্রোচেবল (approachable) টুল হিসেবে, বাশ ওন-লাইনারস সাক (suck)। সেগুলো সাধারণত একটি বাশ স্ক্রিপ্ট (Bash script) যেটি if বা while লুপস (loops) ব্যবহার করে তার চেয়ে বেশি কমপ্লেক্স (complicated) লিখতে এবং অবশ্যই পড়তে। লাইক্লি (likely) যে যখন আমরা সেগুলো লিখি, আমরা একটি সিঙ্গেল কোট (single quote) বা ক্লোজিং পেরেনথেসিস (closing parenthesis) কোথাও মিস (miss) করব; এবং আশা করি এই পোস্ট ডেমনস্ট্রেট (demonstrate) করে, সেগুলো এক্সপ্লেইন (explain) করতে ও কফি টাইম (quite a bit) লাগে। তাহলে কেন ব্যবহার করব?
কেক (cake) বেক (bake) করার একটি রেসিপি (recipe) পড়ুন, স্টেপ বাই স্টেপ (step by step)। আপনি মেথডস (methods) এবং ইнг্রেডিয়েন্টস (ingredients) বুঝেন, এবং আপনার সাপ্লাইস (supplies) গ্যাদার (gather) করেন। তারপর, যেহেতু আপনি সেটি চিন্তা করেন, আপনি বুঝতে শুরু করেন যে আপনি যদি সকল ইнг্রেডিয়েন্টস অভেন (oven)-এ ঠিকমতো অর্ডারে (order) ফেলেন, তবে একটি কেক ইনস্ট্যান্টলি (instantly) মেটেরিয়ালাইজ (materialize) হবে। আপনি চেষ্টা করেন, এবং এটি কাজ করে!
এটা প্রিটি স্যাটিসফাইং (pretty satisfying), কি না?