چطور از وابستگی به گیت‌هاب فرار کنیم: پشتیبان‌گیری از مخازن گیت

یکی از نکاتی که قطعی‌های اخیر گیت‌هاب گوشزد می‌کنند این است که گیت به عنوان یک سیستم مدیریت نسخهٔ غیر متمرکز طراحی شده. بسیاری از مردم به این صورت آن را مورد استفاده قرار نمی‌دهند. به جای داشتن چند بایگانی راه دور‌، داده‌ها را به یک جا می‌فرستند و در اصل بیش از اندازه به گیت‌هاب تکیه می‌کنند. کسانی را دیده‌ام که کل سازمان‌شان را بر پایهٔ گیت‌هاب بنا کرده‌اند‌، چیز‌هایی نظیر گسترش اسکریپت‌های سایت‌شان و نصب اسکریپ‌ها برای توسعه دهندگان جدید‌شان. علاوه بر این‌، dependency manager‌هایی مثل Composer و Cocoapods نیز به گیت‌هاب وابسته‌اند تا پیش‌نیاز‌هایشان را دریافت کنند. اگر گیت‌هاب Down شود‌، شما نمی‌توانید پیش‌نیاز‌های‌تان را دستکاری کنید و یا نمونه‌های جدید‌تری به آن‌ها اضافه کنید. در یک دنیای ایده‌آل این Dependency manager‌ها باید قابلیت‌ پشتیبانی توکار از mirror‌ها را داشته باشند‌، ولی در حال حاضر من چنین قابلیت‌هایی را نمی‌بینم.

چیزی که من می‌خواهم انجام دهم این است که نشان‌تان دهم که چقدر آسان است که با استفاده از سرور‌های راه دور گیت در آن واحد به دو سرور مختلف داده‌های‌تان را ارسال کنید. اگر گیت‌هاب Down شد‌، مهم نیست‌، بر فرض این که همه افراد تیم‌تان از این راهنما پیروی کرده باشند‌، کد از طریق bitbucket نیز در دسترس خواهد بود‌، یا هر جای دیگری که شما انتخاب کرده‌اید. اما این راهنما از گیت‌هاب و بیت‌باکت به عنوان mirror استفاده می‌کند.

  1. اول فرض می‌کنم که شما یک سرور راه دور برای گیت‌هاب به عنوان "origin" ایجاد کرده‌اید. آن را به github تغییر نام دهید:
git remote rename origin github
  1. یک مخزن جدید روی بیت‌باکت هم‌نام همان چیزی که در گیت‌هاب دارید بسازید.

  2. این مخزن جدید را به صورت زیر به سرور‌های راه دور‌تان اضافه کنید. ولی مطمئن باشید که اسمش bitbucket باشد و نه origin. این‌طور:

git remote add bitbucket ssh:[email protected]/username/somerepo.git

git push -u bitbucket --all
  1. و حالا با یک ادیتور از شاخهٔ ‎.git در مخزن گیت‌تان فایل config را باز کنید.

  2. نوبت این است که url‌های مربوط به هر سرور راه دور را به مجموعهٔ جدیدی به اسم origin اضافه کنیم. می‌شود چیزی شبیه این:

[remote "origin"]

url = [email protected]:username/somerepo.git

url = ssh:[email protected]/username/somerepo.git
  1. دفعهٔ بعد که از دستور git push origin استفاده کردید‌، مخزن در هر دو سرور راه دور آپلود خواهد شد.

به همین راحتی.

نسخه‌های اخیر گیت این اجازه را به شما می‌دهند که چند جا را به عنوان مخزن راه دور یک مجموعه معرفی کنید. ولی من ترجیح می‌دهم که ببینم چه کاری انجام می‌دهم.

نکته از شاهینیسم: بهتر است از یک public-key برای گیت‌هاب و بیت‌باکت استفاده کنید. این‌طوری مرحلهٔ شناسایی هر دو سرور مثل هم خواهد بود ;-)

expect دستیار باهوش شما در شل اسکریپت‌ها!

اگر تا به حال شل اسکریپت نوشته باشید‌، اگر حد اعتیادتان به آن اندازه باشد که بخواهید هر کاری را آسان‌تر کنید‌، اگر تنبلی‌تان گرفته باشد که بعد از آسان شدن کار‌هایتان با یک شل اسکریپت‌، رمز‌های جاهای مختلف را به یاد آورید تا اسکریپت‌تان درست کار کند‌‌، یا آن‌قدر به مغزتان فشار آورید که جواب صحیح یک سوال اسکریپت را پیدا کنید‌، آن وقت‌ می‌توانید درک کنید که expect چه ابزار خارق‌العاده‌ای است.

کار expect خیلی ساده است‌، بعد از اجرای اسکریپت‌تان‌، منتظر می‌ماند که اتفاق خاصی رخ دهد‌، مثلا دستوری در اسکریپت از شما رمز بخواهد‌، آن وقت expect وارد میدان می‌شود و رمز را وارد می‌کند. یعنی دیگر نیازی نیست که شما دخالتی داشته باشید. اگر فکر می‌کنید که خوب این که کار سختی نیست و از این حرف‌ها که خوب اول باید بگویم که کور خوانده‌اید و دوم این که بی‌خیال ادامهٔ این متن شوید ;-)

حالا شیوهٔ عملکرد expect معلوم شد‌، بهتر است نگاهی بیاندازیم به وضعیت من. بنا به دلایل کاملا غیر عادی و نامعلومی‌، در کشوری کاملا آزاد‌، نیاز دارم که از sshuttle برای انجام برخی امور استفاده کنم. این برنامه برای انجام دادن درست کار‌هایش‌، نیاز به iptables دارد که خوب من دوست ندارم که کاربر عادی‌ام به آن دسترسی داشته باشد. پس همین دوست نداشتن من یک مرحلهٔ اضافه در فرآیند استفاده از sshuttle هزینه دارد. یعنی یک همچین چیزی:

su

Vared Kardan Ramz

cd /home/shahin/bin/sshutle

./sshuttle ...

Vared Kardan Ramz

که خوب بعد از اولین تلاش کانکت نمی‌شود و باید دوباره دستور را بزنم و منتظر بمانم تا پرامپت رمز ssh ظاهر شود و باقی قضایا. خوب کمی سخت است. البته این را هم اضافه کنم که خوب می‌دانم می‌توانم کانکت بدون رمز ssh داشته باشم‌. می‌توانم دستور را درون یک path قرار دهم. می‌توانم‌، می‌توانم‌… ولی این روشی است که من کار می‌کنم ;-)

این است که دست به دامن expect می‌شوم و همچین اسکریپتی می‌نویسم:

#!/usr/bin/expect -f



if {[llength $argv] != 5} {
    puts "usage: ssh.exp client_root_pass username password server port"
exit 1

}



set rootpass [lrange $argv 0 0]

set username [lrange $argv 1 1]

set password [lrange $argv 2 2]

set server [lrange $argv 3 3]

set port [lrange $argv 4 4]



set timeout -1



spawn su -c "/home/shahin/bin/sshuttle/sshuttle --dns -r $username@$server:$port 0/0 -vv"

match_max 100000

expect "Password:*"

send -- "$rootpass\r"



expect "*?assword:*"

send -- "$password\r"

send -- "\r"

expect eof

حالا همانطور که معلوم است کافی است همچین دستوری را تایپ کنم:

./ssh.exp client_root_pass username password server port

حالا می‌شود این اسکریپت را بدون هیچ ضرر اضافه‌ای به path اضافه کرد و حالش را برد. برای این که بفهمید این اسکریپت چطور کار می‌کند راهنما‌های زیادی در اینترنت می‌یابید. man page خود برنامه هم می‌تواند کمک خوبی باشد. ضمن این که اگر اسکریپت نویسی بدانید‌، چیز زیادی برای یاد گرفتن پیش‌رویتان نیست ;-)

۶ سال شاهینیسم

دیگر شاهین‌، شاهین قبلی نبود. نمی‌توانست باشد. همانطور که می‌خواست شده بود. آن‌هایی که نوشته‌هایش را می‌خواندند می‌دید. یاد گرفته بود به حرف‌شان بیاورد. خیلی‌هاشان مثل اطرافیانم در دنیای واقعی بودند. با همان بچه بازی‌ها. با همان نق نق‌ها. خیلی‌های دیگر عدایش را در می‌آوردند که مثلا بهترند. اما خیلی‌ها هم بودند که واقعا عالی بودند. از آن‌هایی که آرزو می‌کنی یک ساعت کنارشان بنشینی و گپ بزنی. از آن‌هایی که دوست داری اجتماعت باشند.

امروز دیگر شاهین‌، منی شده که دارد سعی می‌کند نقابش را بردارد. هر چند خوب می‌داند که… . به هر حال‌، شش سال شد. شش سال تغییرم مبارک ;-)

تولید خودکار فلش کارت انگلیسی به فارسی برای Anki با کمی شل اسکریپت

ولی خوب‌، ایمان هم کالیبرش بالاست‌. شاید هم کمال همنشین… این است که بعد از یک مدتی که خیلی ذوق داشت و همه مدل فلش کارت آماده می‌کرد‌، چون سختش بود‌، کم کم فرمت‌های آماده شده را آنقدر کم کرد که این اواخر دیگر تنها kvtml آماده می‌کرد‌. فرمتی که آماده کردنش خیلی آسان‌تر از باقی قضایا بود‌، ولی مال نرم‌افزار بسیار به درد نخور kwordquiz میزکار KDE بود! این بود که دو سه هفته پیش‌، این ایده به سرم افتاد که اول کمی از کار ایمان کم کنم‌! بندهٔ خدا زحمت زیادی می‌کشد و به هر حال حیف است که فقط مصرف کننده باشم‌. این شد که با پایتون اسکریپتی برایش نوشتم که kvtml‌ها را خیلی راحت به anki تبدیل کند‌. ذکر خیر این اسکریپت را خودش کرده‌.

معنی این اسکریپت قبلی این بود که حالا حالاها خوراک اضافه خواهم داشت‌. ولی حقیقتش کلمه‌های ایمان‌، حداقل تا این‌جایی که من دیده‌ام‌، اکثریتشان‌ در سطح من نیست‌. اگر هم باشد من خودم خیلی خیلی بیشتر از آن‌ها را در کتاب‌ها و مطالبی که روزانه می‌خوانم می‌بینم! مساله این‌جاست که از روی تنبلی‌، فقط به زور دیکشنری (mdic روی میزکار، google translate روی اینترنت و mobidic روی موبایل) ترجمه‌شان می‌کنم و بعد از چند جمله معنی‌شان از یادم می‌پرد‌. خسته کننده است‌. این طور نیست؟

خوب کمی با خودم دو دو تا چهارتا می‌کنم و یادم می‌آید که روی آن اسکریپت تبدیل kvtml دوست خوبم anki یک فرمت متنی خیلی تمیزی را به عنوان ورودی می‌گرفت و فلش کارتش می‌کرد‌. (اصلا آن اسکریپت بر همین مبنا کار می‌کند) یک همچین نحوی:

English;انگلیسی

که البته می‌توان بهش فایل‌های چند رسانه‌ای هم چسباند و یا با HTML خوشگلش کرد‌. خوب این یعنی می‌شود هر وقت که کلمه‌ای ترجمه می‌کنم‌، ترمینالم را باز کنم و مثلا از یک همچین دستوری استفاده کنم:

echo "English;انگلیسی" >> deck.txt

یا شایدم همیشه یک ادیتور زیر دستم باز بگذارم‌! ولی نه فکر کنم همان اولی بهتر است‌ (چون ترمینال در دسترس‌تر است). ولی خوب این این‌قدر‌ها هم خوشگل نیست‌. ضمن این که مثل فلش کارت‌های ایمان صدا و عکس ندارد‌. حالا عکس به کنار‌، تلفظ برای من از نان شب واجب‌تر است‌. بعضی کلمات آن‌قدر به چشمم سخت می‌آیند که اصلا نمی‌خوانمشان D:

خوب تلفظ را هم می‌شود مشکلش را با espeak حل کرد‌ (هر چند صدای روباتیکش خیلی نچسب است!). جهنم‌، شروع می‌کنم به نوشتن یک اسکریپت‌. این ایده‌، در میانهٔ راه آن‌قدر این در و آن در می‌خورد‌. آن‌قدر از ابزار‌ها و وسایلی که بقیه نوشته‌اند توش استفاده می‌شود که می‌شود اسکریپتی که اگر اینترنت دم دستش باشد‌، خودش عبارت وارد شده را با استفاده از google translate ترجمه می‌کند و تلفظ google translate را می‌چسباند تنگش و کار را تمام می‌کند‌.

چون می‌دانم همه حوصلهٔ خواندن نکات فنی اسکریپت را ندارند‌، این پست را در دو بخش می‌نویسم‌. بخش اول‌، برای کاربرانی که فقط می‌خواهند از آن استفاده کنند‌، و بخش دوم برای اشاره به نکات فنی کار ;-)

معرفی:

این اسکریپت برای اجرا به fribidi, ffmpeg, curl و html2text نیاز دارد‌. سه تای اول که عموما وضع و حال‌شان مشخص است‌. اما این آخری یک اسکریپت پایتون است که می‌توانید از مخازن‌تان نصب کنید‌. ولی من پیشنهاد می‌کنم به جای آن distribute یا pysetuptools پایتون را نصب کنید و پس از آن با دستور زیر سر و ته این اسکریپت را هم بیاورید:

sudo pip install html2text

دلیلش هم ساده است‌. مخازن پایتون خیلی سریع‌تر از مخازن توزیع‌تان به روز می‌شوند و صد البته در آخر کار مدیریت این بسته‌ها با آن‌ها آسان‌تر است‌! حالا اسکریپت را از این‌جا دانلود کنید و به صورت زیر چشم‌تان را به جمالش روشن کنید:

chmod +x fcgenerator.sh

./fcgenerator.sh -w "Some english word" -d firstDeck

مقدار Some english word که به اپراتور ‎-w نسبت داده شده‌، همان عبارت انگلیسی‌ای است که می‌خواهیم ترجمه‌اش کنیم‌. مقدار firstDeck هم اسم Deckای (مجموعه فلش کارت) که می‌خواهیم این لغت به آن اضافه شود است‌. اگر اینترنت‌تان وصل است که برنامه مثل پلنگ کارش را انجام می‌دهد و فایل‌ها را تولید می‌کند‌. اکر هم نه که می‌توانید به روش‌های دیگری کلمه را اضافه کنید‌.

مثلا فرض کنید که شما از ترجمهٔ google translate حال نمی‌کنید‌. خوب راحت است‌. کافی است که با استفاده از اپراتور ‎-t ترجمهٔ خودتان را به لغت بچسبانید‌. همین‌طور می‌توانید به جای تلفظ گوگل‌، از espeak استفاده کنید‌. برای این کار هم از اپراتور ‎-e استفاده می‌کنید. طرز کارش ساده است‌، این‌طور نیست؟

بعد از اجرای فرمان بالا اسکریپت برای ما یک فایل firstDeck.txt و یک دایرکتوری firstDeck.media ساخته‌. اگر کلمات دیگری هم داشتید و خواستید به این فلش کارت اضافه کنید‌، کافیست به دایرکتوری که این فایل‌ها در آن قرار دارند بروید و فقط مقدار ‎-w دستور را با کلمهٔ جدید‌تان عوض کنید‌.

حالا نوبت نصب Deck جدید‌تان روی Ankiاست‌. اگر Deck‌های شما هم در مسیر ‎~/.anki/decks قرار دارد (اگر توزیع‌تان خوشمزگی نکرده و جای دیگر نریخته باشد‌، بعید می‌دانم این مسیر فرق کند) می‌توانید با دستور زیر دایرکتوری مدیا را در این مسیر کپی کنید:

./fcgenerator.sh -i firstDeck

اگر هم نه که دستی کپی کنید! حالا Anki را باز کنید‌ و از منوی File گزینهٔ Import را انتخاب کنید‌. در قسمت نام Deck همان چیزی را بنویسید که برای اپراتور ‎-d قرار دادید (در این‌جا firstDeck) و سپس فایل firstDeck.txt را به آن بخورانید‌. تمام شد‌. فلش‌کارت‌های جدید‌تان آماده‌اند‌.

طرز کار اسکریپت خیلی ساده است‌. دیگر باقی‌اش به خودتان بستگی دارد که چطور از آن استفاده می‌کنید‌. می‌توانید برای راحتی کار آن را به PATHتان انتقال دهید و یا یک alias برای خیلی راحت‌تر شدنش تعریف کنید‌. یا حتی می‌توانید یک اسکریپت کوچک دیگر بنویسید که برای لیستی از لغات این فرمان را ایجاد کند و به صورت خودکار یک لیست بلند بالا از لغات + ترجمه + تلفظ‌شان داشته باشید‌! البته جنبه هم در کنارش خوب است‌. با Google طرفید‌. همچین هم زیاده روی نکنید که IPتان را به خاطر فرستادن درخواست‌های زیادی بلاک کند‌، یا کلا در گوگل ترنسلیت را گل بگیرد! ها در ضمن‌، می‌توانید با استفاده از ‎-T (به چه زبانی) و ‎-F (از چه زبانی) به جای حالت پیش‌فرض انگلیسی به فارسی از زبان‌های دیگر هم استفاده کنید.

چطور کار می‌کند؟

خوب اکثر دستورات اگر سواد شل اسکریپتینگ داشته باشید‌، سر راست‌اند‌. من در این‌جا فقط به چیز‌های جالبی که از کد‌های دیگران بهره گرفتم اشاره می‌کنم‌. یکی از خوشگل‌ترین‌شان برایم این تابع بود:

function deps(){

DEPENDENCIES=$@

deps_ok=YES

for dep in $DEPENDENCIES

do

if ! which $dep &>/dev/null; then

echo "$dep is not installed. please install it and try again."

deps_ok=NO

fi

done

if [[ "$deps_ok" == "NO" ]]; then

echo -e "Unmet dependencies ^"

echo -e "Aborting!"

exit 1

else

return 0

fi

}

خوب می‌دانم چیز ساده‌ای است. ولی حقیقتش من عقلم نمی‌رسید که می‌توان به این خوشگلی پیش‌نیاز‌های اسکریپت را بررسی کرد‌. این اسکریپت را از این لینک برداشتم و کمی دستکاری‌اش کردم تا به اسکریپت من بیاید‌.

بخش بعدی ماجرا‌، هیولا‌ترین قسمت اسکریپت است‌. Google مثل همیشه احمق‌بازی در آورده و ما را از داشتن یک API ساده محروم کرده‌. ولی خوب این مرحمت گوگل باعث نشده که این دوست ندیده و نشنیدهٔ عزیزمان دست از انگولک گوگل و نجات ما بردارد‌.

result=$(curl -s -i --user-agent "" -d "sl=$FROMT" -d "tl=$TARGETT" --data-urlencode "text=$WORD" http://translate.google.com)

encoding=$(awk '/Content-Type: .* charset=/ {sub(/^.*charset=["'\'']?/,""); sub(/[ "'\''].*$/,""); print}' <<<"$result")

TRANSLATION=$(iconv -f $encoding <<<"$result" | awk 'BEGIN {RS="



"};/<span[^>]* id=["'\'']?result_box["'\'']?/' | html2text)

مساله ساده است‌. ما صفحه وبی داریم که می‌توانیم در جای خاصی از آن بگوییم که ترجمهٔ چه کلمه‌ای را می‌خواهیم و این صفحه در مقابل در جای خاص دیگری از خودش ترجمهٔ کلمه را به ما نشان می‌دهد‌. اسم این صفحه http://translate.google.com است که چون از متد GET (اگر درست یادم باشد) استفاده می‌کند‌، می‌توان با تغییر متغیر‌هایش نتیجهٔ کلمات مختلف را در آن دید‌. ضمن این که اضافه کنم که این دوستمان از روش جالبی برای پیدا کردن این متد‌ها (غیر از نگاه کردن به URL استفاده کرده) که هر چند به نظرم استفاده‌اش در این‌جا بی‌خود است‌، ولی کاربرد‌های باحال‌تری می‌توان برایش پیدا کرد. فعلا برای فهمیدن ماجرا از زبان خودش به صفحه‌ای که در بالا گفتم رجوع کنید‌!

در ادامه کافیست که از صفحه‌ای که گرفته‌ایم‌، آن المنت خاصی که می‌خواهیم را جدا کنیم (جایی که ترجمه نشان داده می‌شود) و به html2text بخورانیم که نهایتا فقط متن به ما نشان داده شود‌. ولی از آن‌جا که html2text فقط یک فرمت یونیکد را می‌فهمد و فرمت کدینگ صفحهٔ نتیجهٔ گوگل با توجه به زبان پاسخ متفاوت است (چرا؟) با استفاده از iconv سعی می‌کند که کدینگ درست را به خوردش بدهد‌. و حالا چون نمی‌خواهد هر بار دستی وارد کند که از کدام کدینگ تبدیل کن‌، با awk سر صفحه را می‌خواند و کدینگ صفحه را پیدا می‌کند ;-)

و آخرین بخش ماجرا مربوط می‌شود به دانلود صدا. برای این کار از این اسکریپت کمک گرفتم‌. ولی چون نمی‌خواستم نتیجهٔ کار خیلی شلخته شود (دو فرمت متفاوت در صورت استفاده از espeak و google translate در کنار هم) با ffmpeg کلیه فرمت‌ها را به ‎.ogg تغییر دادم که آقایمان نیز از ما راضی باشد.

Calculate the total CPU time.

برای دیدن در اندازهٔ بزرگ کلیک کنید

و فعلا چند اشکال به آن وارد است‌: اول این که دارم از یک برنامهٔ خیلی گنده‌، برای یک کار ساده استفاده می‌کنم‌. دوم این که در حالت شل قدرت زیادی برای انگولک خروجی Conky ندارم (مثلا آن قسمت درصد استفاده از CPU برای درصد‌های مختلف تک رقمی‌، دو رقمی و سه رقمی‌، طول‌های متفاوتی می‌گیرد و کمی قضیه را لوس می‌کند) و سوم این که اگر به ماندن در همین وضعیت رضایت دهم‌، سوادم هیچ وقت زیاد نمی‌شود‌ D:

پس تصمیم می‌گیرم که خروجی فعلی کانکی را (البته با چند تغییر کوچک) با ابزار ساده‌تری جایگزین کنم‌. خوب کار تقریبا پیچیده و سختی به نظر می‌رسد (وقتی که از لینوکس هیچ چیز ندانم) اما خوب همین Conky هم اطلاعاتش را از روی هوا نمی‌گیرد که! از طرفی مسالهٔ زبان پایش به میان می‌آید‌. به چه زبانی بنویسم؟ من که ته تهش پایتون را (آن هم دست و پا شکسته) می‌دانم‌.

بعد از کمی جستجو و دو دو تا چهار تا با دانسته‌های قبلی‌ام‌، مساله را این‌طور حل می‌کنم (از راست به چپ تصویر):

  • زمان: روی توزیعم با دستور date می‌توانم زمان حاضر را به طور کامل ببینم و البته می‌توانم آن را به صورت دلخواهم فرمت کنم‌.
  • صدا: حقیقتش کشف کردم نیازی به این ندارم و زیاد دنبال واکشی مقدارش نگشتم‌. ولی مطمئنم یک راه ساده برای فهمیدنش هست ;-)
  • مجموع آپلود/دانلود نشست حاضر (TU/TD): خوب بعد از کمی جستجو‌، با دوست قدیمی ولی ناشناخته‌ام proc آشنا شدم‌، که از قرار خیلی خیلی‌، بیشتر از چیز‌هایی که می‌خواهم را بهم می‌رساند‌. فعلا ‎/proc/net/dev جواب تا این‌جای کار است‌.
  • سرعت دانلود/آپلود (D/U): راستش را بخواهید‌، این یکی مرا خیلی در کف نگه داشت‌! فکر می‌کردم باید جایی مثلا در proc برای این قضیه باشد‌. ولی نکته‌اش این‌جا بود که من اصلا نفهمیده بودم که چه می‌خواهم‌. که البته با تقلب از روی این آموزش آن هم به صورت اتفاقی قضیه را درک کردم و شبیه سازی (البته نکاتی هم باقی می‌ماند که در ادامه مطرح می‌کنم).
  • وضعیت رم: خوب این یکی هم مثل date. می‌دانستم که دستوری به اسم free برای این‌کار ساخته شده‌. ولی باید کمی خروجی‌اش را انگولک می‌کردم و چیزی که می‌خواستم را بیرون می‌کشیدم.
  • وضعیت CPU: این یکی ترکیبی از همهٔ کار‌هاییست که در بالا باید انجام دهم به علاوه کلی چیز جدید که باید یاد بگیرم‌. فایل ‎/proc/stat وضعیت فعلی CPU را در خودش نگه می‌دارد که با اسکریپتی که در این‌جا دیدم فهمیدم که چطور وضعیت کل CPU را واکشی کنم‌. اما من وضعیت کل را نمی‌خواستم‌، بلکه به دنبال وضعیت تک تک هسته‌ها بودم که خوب باید دست و بالم رو کثیف‌تر می‌کردم ;-)

خوب تمام این کار‌ها را می‌شود با پایتون هم انجام داد (با هر زبان دیگری هم می‌شود) ولی‌، دم دستی‌ترین چیز ممکن shell scripting است‌. هم احتمالا کمتر از مفسر پایتون به سیستم فشار می‌آورد و هم این که اصلا چیزی در موردش نمی‌دانم (نه این که اصلا‌، ولی نه در حد یک اسکریپت نویس ;-)). این می‌شود که طی دو روز گذشته‌، تمام فکر و ذکرم را می‌گذارم روی نوشتن این اسکریپت‌، تا هم چیز یاد بگیرم‌، و هم مستقل‌تر شوم ;-)

اسکریپت حاضر است و از این‌جا قابل دریافت. در ادامه ذره‌، ذره به بررسی‌اش می‌پردازیم‌:

تاریخ

خوب اولین چیزی که لازم داشتیم‌، خروجی گرفتن زمان فعلی سیستم بود که گفتیم با دستور date قادر به انجامش هستیم‌. به صورت زیر:

date=$(date +"%a %Y.%m.%d %H:%M")

نکتهٔ این دستور آن‌جاست که من برای مقدار دهی به متغیر date، از خروجی یک دستور استفاده کرده‌ام (کاری که در ادامه خیلی زیاد انجام می‌دهم‌). برای این کار کافیست دستور مورد نظر را در ‎$(‎)‎ بنویسیم‌. به همین راحتی!

اطلاعات شبکه

اولین چیز‌هایی که می‌خواهیم‌، این است که بفهمیم چقدر دانلود یا آپلود کرده‌ایم‌. که همانطور که گفتم این اطلاعات را در فایل ‎/proc/net/dev می‌توان یافت‌. همینطور‌، با محاسبهٔ این که در یک ثانیهٔ گذشته‌، چقدر دانلود/آپلود کرده‌ایم‌، می‌توان سرعت دانلود بر ثانیه را محاسبه کرد. این است که برای تمیزی کار‌، اول یک تابع می‌نویسیم که از proc ذکر شده‌، مقدار بایت‌های ارسالی یا دریافتی را واکشی کند:

function getNetBytes

{

Brecived=$(cat $netInterface | grep "eth0" | awk '{print($2)}')

Btransmited=$(cat $netInterface | grep "eth0" | awk '{print($10)}')

}

متغیر ‎$netInterface اشاره به همان فایل ‎/proc/net/dev می‌کند که در اول اسکریپت تعریفش کرده‌ام‌. بعد هم با grep تنها خطی که مربوط به اینترفیسی که می‌خواهم است را جدا می‌کنم (eth0) و حالا با awk خانهٔ شمارهٔ ۲ را برای بایت‌های دریافت شده‌، و خانهٔ شمارهٔ ۱۰ این خروجی را برای بایت‌های فرستاده شده جدا می‌کنم‌. این کار را به روش‌های دیگری هم می‌شد انجام داد‌، ولی به نظرم با این روش‌، خط‌های کمتری تایپ کردم ;-)

حالا با دستورات زیر‌، چیز‌هایی که می‌خواهم را محاسبه می‌کنم:

dlSpeed=$(echo $(( $Brecived-$oldBRecived )) | awk '{printf( "%.2f", $1/1024)}')

upSpeed=$(echo $(( $Btransmited-$oldBTransmited )) | awk '{printf( "%.2f", $1/1024)}')

traffic=$(echo "$Brecived $Btransmited" | awk '{printf( "%.2f", ($1+$2)/1024/1024 )}')

طی این دستورات‌، از قابلیت متغیر‌گیری به صورت ‎$num و همینطور‌، پرینت فرمت شده (برای نمایش حداکثر دو رقم اعشار) awk استفاده کرده‌ام. این کار‌ها با dc هم قابل انجام بود‌، ولی خروجی به تمیزی awk نبود‌. همانطور که می‌بینید در این دستورات از متغیر‌های ‎$oldBRecived و ‎$oldBTransmited استفاده شده‌ که در واقع وضعیت یک ثانیه قبل دانلود/آپلود را در خود نگاه می‌دارند‌. تنها کافیست دستورات فوق را هر یک ثانیه اجرا کنیم تا خروجی درست را بگیریم‌. (حلقهٔ کامل این دستورات در آخر مطلب توضیح داده می‌شود)

نکته‌ای که می‌ماند دقت این محاسبه است‌. حقیقتش همانطور که گفتم من این روش را از این‌جا [link] یاد گرفتم. اما دقیقا همان را پیاده سازی نکردم. مساله سر زمان محاسبه بود. نمی‌دانم چرا نویسنده در آن‌جا از epoch (تاریخ ۱/۱/۱۹۷۰) استفاده کرده بود (خوشحال می‌شوم اگر کسی می‌داند به من هم توضیح دهد‌.) نویسنده در آن‌جا می‌گوید مساله سر چند کیلوبایت است و از این حرف‌ها!

وضعیت RAM

گفتم که دستور free -m این‌کارها را انجام می‌دهد‌، پس:

function getRamInfo

{

totalMemory=$(free -m | grep "Mem:" | awk '{printf( "%.2f", $2/1024)}' )

usedMemory=$(free -m | grep "buffers/cache" | awk '{printf( "%.2f", $3/1024)}')

memInPercent=$(echo "$totalMemory $usedMemory" | awk '{printf( "%.0f", $2/($1/100)) }')

}

فکر نکنم نکته‌ای باقی مانده باشد که قبلا توضیح نداده باشم‌. پس دیگر توضیح نمی‌دهم!

وضعیت CPU

خوب گفتم که برای یادگیری این کار‌، از این اسکریپت[link] استفاده کردم. ولی این اسکریپت تنها وضعیت کل CPU را بر می‌گرداند‌. من آن را طوری تغییر دادم که هر بار‌، آدرس هسته‌ای که می‌خواهم اطلاعاتش را بدانم در ورودی بگیرد‌، و فقط همان را هسته را محاسبه کند و در خروجی نشان دهد:

function getCpuInfo()

{

CORE=$1

addr="^cpu${CORE}"

local CPU=(`cat /proc/stat | grep $addr`) # Get the total CPU statistics.

unset CPU[0] # Discard the "cpu" prefix.

local IDLE=${CPU[4]} # Get the idle CPU time.



# Calculate the total CPU time.

local TOTAL=0

for VALUE in "${CPU[@]}"; do

let "TOTAL=$TOTAL+$VALUE"

done



# Catch current cores last state

PREV_IDLE=$(eval echo \PREV_IDLE$CORE)

PREV_TOTAL=$(eval echo \PREV_TOTAL$CORE)



# Calculate the CPU usage since we last checked.

let "DIFF_IDLE=$IDLE-$PREV_IDLE"

let "DIFF_TOTAL=$TOTAL-$PREV_TOTAL"

let "DIFF_USAGE=(1000*($DIFF_TOTAL-$DIFF_IDLE)/$DIFF_TOTAL+5)/10"



eval \PREV_TOTAL$CORE="$TOTAL"

eval \PREV_IDLE$CORE="$IDLE"

}

برای این کار از یک متغیر برای تابع استفاده می‌کنم و مقدارش را به متغیر CORE اختصاص می‌دهم‌. با استفاده از این آدرس یک عبارت با قائده (Regex) به صورت "‎^cpu${CORE}‎" می‌سازم‌. این عبارت باقائده با خطوطی که در اولشان عبارت cpuN را دارند مطابقت می‌کند‌. و سپس با استفاده از داده‌هایی که دارم یک آرایه با استفاده از پردازش فایل ‎/proc/stat می‌سازم و آن را به متغیر CPU نسبت می‌دهم‌. خوب حالا چون اولین خانهٔ این آرایه اسم CPU را دارد‌، و به درد محاسبات بعدی نمی‌خورد‌، آن را از آرایه بیرون می‌اندازم (unset CPU[0]‎) و همین‌طور مقدار خانهٔ IDLE را هم در متغیری به همین نام ذخیره می‌کنم‌.

با جمع کردن تمامی مقادیر موجود در آرایهٔ CPU می‌توانم کل وضعیت کارکرد CPU را به دست آورم‌. این می‌شود که به صورت زیر این کار را انجام می‌دهم:

local TOTAL=0

for VALUE in "${CPU[@]}"; do

let "TOTAL=$TOTAL+$VALUE"

done

ساده است این‌طور نیست؟ آن let آن‌جا همان کار ‎(())$ را می‌کند‌. حالا وقت انجام محاسبات است‌. چون هر بار نیاز است که وضعیت قبلی CPU مورد نظر را داشته باشیم‌، پس لازم است برای هر CPU متغیر‌های مربوطه را بسازیم‌. که این کار را با دستورات زیر انجام می‌دهیم:

eval \PREV_TOTAL$CORE="$TOTAL"

eval \PREV_IDLE$CORE="$IDLE"

این دستور eval فوق‌العاده است‌. با استفاده از آدرس CPU فعلی (CORE) متغیر‌های مورد نظرم را می‌سازد و مقادیر مربوطه را درشان ذخیره می‌کند‌. اما چون نمی‌خواهم زیادی کثیف کاری شود‌، قبل از انجام محاسبات‌، متغیر‌هایی که ساخته‌ایم را به یک نام ثابت در می‌آورم تا کارم را آسان کنم:

PREV_IDLE=$(eval echo \PREV_IDLE$CORE)

PREV_TOTAL=$(eval echo \PREV_TOTAL$CORE)

حالا این دو متغیر‌، وضعیت قبلی CPU را در خود نگه می‌دارند‌. پس نوبت این است که محاسباتم را انجام دهم:

let "DIFF_IDLE=$IDLE-$PREV_IDLE"

let "DIFF_TOTAL=$TOTAL-$PREV_TOTAL"

let "DIFF_USAGE=(1000*($DIFF_TOTAL-$DIFF_IDLE)/$DIFF_TOTAL+5)/10"

چیز خاصی نیست دیگر‌، همه چیز روشن است ;-)

پایان کار
while true;

do

getNetBytes

getRamInfo

for (( COUNT=0; COUNT < $CORES; COUNT++ ));do

getCpuInfo $COUNT

eval \cpu$COUNT=$DIFF_USAGE

done



dlSpeed=$(echo $(( $Brecived-$oldBRecived )) | awk '{printf( "%.2f", $1/1024)}')

upSpeed=$(echo $(( $Btransmited-$oldBTransmited )) | awk '{printf( "%.2f", $1/1024)}')

traffic=$(echo "$Brecived $Btransmited" | awk '{printf( "%.2f", ($1+$2)/1024/1024 )}')



printf \

"CPU: %3s,%3s,%3s,%3s | RAM: %sG/%sG (%s%%) | LAN: ↓: %4sƘ ↑: %4sƘ T↕: %6s | %s\n" \

"$cpu0" "$cpu1" "$cpu2" "$cpu3" "$usedMemory" \

"$totalMemory" "$memInPercent" "$dlSpeed" \

"$upSpeed" "$traffic" "$date"

oldBRecived=$Brecived

oldBTransmited=$Btransmited

sleep 1

done

حالا نوبت این است که کار را تمام کنم‌. یک حلقهٔ همیشه درست می‌سازم و فقط بهش می‌گویم بعد از این که همهٔ کار‌هایت را انجام دادی یک ثانیه استراحت کن (برای این که سرعت اینترنت را درست محاسبه کند‌، این مقدار ضروری است). بر همین اساس تابع‌های getNetBytes و getRamInfo را صدا می‌کنم تا متغیر‌های جدیدشان را تولید کنند‌. اما در مورد تابع getCpuInfo قضیه کمی فرق دارد‌. قرار است که چهار بار این تابع را با متغیر‌های متفاوت صدا کنیم و نتیجهٔ حاصله‌اش را در متغیرهای جداگانه ذخیره کنیم‌. این است که آن حلقهٔ for سر و کله‌اش پیدا می‌شود‌. یک حلقهٔ for به سبک C که خداییش به نظرم کامل‌ترین حلقه‌های for است D: حالا تابع را درش به صورت getCpuInfo $COUNT صدا می‌زنم و متغیر ‎$DIFF_USAGE که همان طرصد مصرف است را به یک متغیر به صورت cpuN نسبت می‌دهم‌. این N همان آدرس هسته است.

همان‌طور که می‌بینید در ادامه محاسبهٔ سرعت اینترنت می‌آید که قبلا توضیحش داده‌ام‌. printf را فعلا ول کنید و به دو خط بعدش نگاهی کنید‌. همان دو متغیری که وضعیت قبلی ترافیک را نگه می‌دارند‌. فکر کنم دیگر همه چیز روشن شده باشد‌.

حالا برسیم به آن printf. حتما می‌پرسید چرا از echo استفاده نکردم‌. echo خوب است‌، ولی خیلی ساده هم هست‌. آن مشکل کنترل فضای پرینت که در Conky گرفتارش بودم‌، این‌جا هم پیدا می‌شد‌. یک نگاهی به این خروجی بیاندازید:

CPU: 48,3,40,3 | RAM: 1.11G/1.95G (57%) | LAN: ↓:0.00Ƙ ↑:0.00Ƙ T↕:221.20 | Sat 2012.11.24 14:41

CPU: 8,4,76,4 | RAM: 1.11G/1.95G (57%) | LAN: ↓:0.00Ƙ ↑:0.00Ƙ T↕:221.20 | Sat 2012.11.24 14:41

CPU: 24,5,64,6 | RAM: 1.11G/1.95G (57%) | LAN: ↓:0.00Ƙ ↑:0.00Ƙ T↕:221.20 | Sat 2012.11.24 14:41

می‌بینید‌، هنوز هم طول خروجی کم و زیاد می‌شود که اصلا خوب نیست‌. این است که باید دنبال دستوری باشم که خروجی را آرایش کند و بیرون بدهد‌. printf سالاری که از زمان یادگیری C می‌شناسم این کار را برایم می‌کند‌. کافیست فضای متغیر‌ها را به صورت ‎%Ns که N همان طول مینیموم رشته است را مشخص کنم‌. خودش باقی کار‌ها را انجام می‌دهد‌. خوب همین دیگر‌. کارمان تمام شد‌. مرحلهٔ بعدی این است که یک خروجی json دار درست حسابی بسازم که i3 بتواند رنگی رنگی‌اش کند ;-)

برای دیدن در اندازهٔ بزرگ‌تر کلیک کنید