
در این آموزش، شما خواهید دید که چگونه میتوانید دستورات اسکریپتی را با استفاده از Bash ایجاد کنید. چرا Bash scripting؟ جهانی بودن Bash به ما، تستر های نفوذ، این امکان را میدهد که دستورات قدرتمند ترمینال را بدون نیاز به نصب یک کامپایلر یا یک integrated development environment (IDE) اجرا کنیم. برای توسعه یک Bash script، تنها چیزی که نیاز دارید یک ویرایشگر متن است و شما آمادهاید. کی از Bash scripts استفاده کنیم؟ این سوال مهمی است که باید قبل از شروع این فصل به آن پرداخته شود! Bash برای توسعه ابزارهای پیچیده طراحی نشده است. اگر قصد دارید چنین کاری انجام دهید، بهتر است از Python استفاده کنید (مفاهیم ابتدایی Python در بخشهای بعدی این کتاب پوشش داده شده است).
Bash برای ابزارهای کوچک و سریع است که زمانی که بخواهید زمان صرفهجویی کنید به کار میرود (برای مثال، برای جلوگیری از تکرار دستورات یکسان، آنها را در یک Bash script مینویسید). این فصل نه تنها زبان Bash scripting را به شما آموزش میدهد، بلکه فراتر از آن، ایدئولوژی برنامهنویسی را نیز به شما نشان میدهد. اگر شما به تازگی وارد دنیای برنامهنویسی شدهاید، این فصل نقطه شروع خوبی برای شماست تا درک کنید که زبانهای برنامهنویسی چگونه کار میکنند (آنها شباهتهای زیادی دارند).
در این فصل، شما خواهید آموخت:
■ Printing to the screen using Bash
■ Using variables
■ Using script parameters
■ Handling user input
■ Creating functions
■ Using conditional if statements
■ Using while and for loops
Basic Bash Scripting
Figure 2.1 تمامی دستورات را خلاصه کرده است، بنابراین میتوانید از آن به عنوان مرجع برای درک تمامی محتوای این فصل استفاده کنید. به طور خلاصه، basic Bash scripting به دستههای زیر تقسیم میشود:
■ Variables
■ Functions
■ User input
■ Script output
■ Parameters
Printing to the Screen in Bash
دو روش رایج برای نوشتن در خروجی خط فرمان ترمینال با استفاده از Bash scripting وجود دارد. روش اول ساده استفاده از دستور echo است که در فصل قبلی دیدیم (ما مقدار متن را در داخل کوتیشنهای تکی یا دوتایی قرار میدهیم):
$echo 'message to print.'
روش دوم دستور printf است؛ این دستور از echo انعطافپذیرتر است زیرا به شما این امکان را میدهد که رشتهای که میخواهید چاپ کنید را قالببندی کنید:
$printf 'message to print'
فرمول قبلی خیلی ساده است؛ در واقع، printf به شما این امکان را میدهد که رشتهها را به خوبی قالببندی کنید (نه فقط برای چاپ؛ این بیشتر از آن است). بیایید یک مثال ببینیم:
اگر بخواهیم تعداد میزبانهای زنده در یک شبکه را نمایش دهیم، میتوانیم از الگوی زیر استفاده کنیم:
root@kali:~# printf "%s %d\n" "Number of live hosts:" 15
Number of live hosts: 15
بیایید دستور را تقسیم کنیم تا بهتر متوجه شوید که چه اتفاقی میافتد:
■ %s
: یعنی ما یک رشته (متن) را در این موقعیت وارد میکنیم
■ %d
: یعنی یک عدد صحیح (عدد اعشاری) را در این موقعیت وارد میکنیم
■ \n
: یعنی پس از چاپ، میخواهیم به خط جدید برویم
همچنین توجه کنید که ما از کوتیشنهای دوتایی به جای کوتیشنهای تکی استفاده میکنیم. کوتیشنهای دوتایی به ما این امکان را میدهند که انعطاف بیشتری در دستکاری رشتهها نسبت به کوتیشنهای تکی داشته باشیم. بنابراین، بیشتر مواقع میتوانیم از کوتیشنهای دوتایی برای printf استفاده کنیم (به ندرت نیاز به استفاده از کوتیشنهای تکی داریم). برای قالببندی یک رشته با استفاده از دستور printf، میتوانید از الگوهای زیر استفاده کنید:
■ %s
: رشته (متنها)
■ %d
: عدد صحیح (اعداد)
■ %f
: اعشاری (شامل اعداد مثبت و منفی)
■ %x
: هگزادسیمال
■ \n
: خط جدید
■ \r
: بازگشت به ابتدای خط
■ \t
: تب افقی
Variables
متغیر چیست و چرا هر زبان برنامهنویسی از آن استفاده میکند؟ یک متغیر را به عنوان یک منطقه ذخیرهسازی در نظر بگیرید که میتوانید مقادیری مانند رشتهها و اعداد را در آن ذخیره کنید. هدف این است که از آنها به طور مکرر در برنامه خود استفاده کنید، و این مفهوم برای هر زبان برنامهنویسی اعمال میشود (فقط مختص Bash scripting نیست).برای اعلام یک متغیر، باید نام و مقدار آن را مشخص کنید (مقدار به طور پیشفرض یک رشته است). نام متغیر فقط میتواند شامل یک کاراکتر الفبایی یا خط زیر (_) باشد (در سایر زبانهای برنامهنویسی کنوانسیونهای نامگذاری متفاوتی وجود دارد).
برای مثال، اگر بخواهید آدرس IP روتر را در یک متغیر ذخیره کنید، ابتدا یک فایل به نام var.sh
ایجاد میکنید (فایلهای Bash script با .sh
تمام میشوند)، و داخل فایل، کد زیر را وارد میکنید:
#!/bin/bash
#Simple program with a variable
ROUTERIP="10.0.0.1"
printf "The router IP address: $ROUTERIP\n"
توضیح اولین فایل Bash script شما:
■ #!/bin/bash
به نام Bash shebang شناخته میشود؛ ما باید آن را در ابتدا قرار دهیم تا به Kali Linux بگوییم که از چه مفسری برای تجزیه فایل اسکریپت استفاده کند (ما از همین مفهوم در فصل 18، “Pentest Automation with Python”، با زبان برنامهنویسی Python استفاده خواهیم کرد). #
در خط دوم برای نشان دادن این است که این یک کامنت است (کامنتها دستوراتی هستند که نویسنده داخل کد یا اسکریپت برای مرجع بعدی میگذارد).
■ نام متغیر ROUTERIP
است و مقدار آن 10.0.0.1
است.
■ در نهایت، ما از تابع printf برای چاپ مقدار به صفحه خروجی استفاده میکنیم.
برای اجرای آن، ابتدا مطمئن شوید که مجوزهای لازم را به آن دادهاید (به خروجی زیر نگاه کنید تا ببینید اگر این کار را نکنید چه اتفاقی میافتد). از آنجایی که ما در همان دایرکتوری (/root) هستیم، از ./var.sh
برای اجرای آن استفاده خواهیم کرد:
root@kali:~# ./var.sh
bash: ./var.sh: Permission denied
root@kali:~# chmod +x var.sh
root@kali:~# ./var.sh
The router IP address: 10.0.0.1
تبریک میگوییم، شما اولین Bash script خود را ساختید! حالا فرض کنید میخواهیم این اسکریپت به طور خودکار اجرا شود بدون اینکه نیاز به مشخص کردن مسیر آن در سیستم داشته باشیم. برای این کار، باید آن را به متغیر $PATH
اضافه کنیم. در مورد ما، ما /opt
را به متغیر $PATH
اضافه خواهیم کرد تا بتوانیم اسکریپتهای شخصی خود را در این دایرکتوری ذخیره کنیم. ابتدا فایل .bashrc
را با استفاده از هر ویرایشگر متنی باز کنید. پس از بارگذاری فایل، به پایین بروید و خطی که در شکل 2.2 برجسته شده است را اضافه کنید.
تغییرات باعث میشود که /opt
به متغیر $PATH
اضافه شود. در این مرحله، فایل را ذخیره کرده و تمام ترمینالها را ببندید. دوباره پنجره ترمینال را باز کنید و فایل اسکریپت را به پوشه /opt
کپی کنید. از این پس، نیازی به وارد کردن مسیر آن نیست؛ فقط با تایپ نام اسکریپت var.sh
آن را اجرا میکنیم (دیگر نیازی به اجرای دوباره chmod
نیست؛ مجوز اجرا قبلاً تنظیم شده است):
root@kali:~# cp var.sh /opt/
root@kali:~# cd /opt
root@kali:/opt# ls -la | grep "var.sh"
-rwxr-xr-x 1 root root 110 Sep 28 11:24 var.sh
root@kali:/opt# var.sh
The router IP address: 10.0.0.1
Commands Variable
گاهی اوقات ممکن است بخواهید دستورات را اجرا کنید و خروجی آنها را در یک متغیر ذخیره کنید. بیشتر اوقات هدف این است که محتوای خروجی دستور را دستکاری کنید. در اینجا یک دستور ساده داریم که دستور ls
را اجرا میکند و نام فایلهایی را که کلمه “simple” در آنها وجود دارد با استفاده از دستور grep
فیلتر میکند. (نگران نباشید، سناریوهای پیچیدهتری را در بخشهای بعدی این فصل خواهید دید. در حال حاضر، بر روی اصول تمرکز کنید و تمرین کنید).
#!/bin/bash
LS_CMD=$(ls | grep 'simple')
printf "$LS_CMD\n"
نتایج اجرای اسکریپت به این صورت خواهد بود:
root@kali:/opt# simplels.sh
simpleadd.sh
simplels.sh
Script Parameters
گاهی اوقات، شما نیاز دارید که پارامترهایی را به اسکریپت Bash خود بدهید. برای این کار، باید هر پارامتر را با فاصله از هم جدا کنید و سپس میتوانید این پارامترها را داخل اسکریپت Bash دستکاری کنید. بیایید یک ماشین حساب ساده (simpleadd.sh
) بسازیم که دو عدد را با هم جمع کند:
#!/bin/bash
#ماشین حساب ساده که دو عدد را جمع میکند
#ذخیره اولین پارامتر در متغیر num1
NUM1=$1
#ذخیره دومین پارامتر در متغیر num2
NUM2=$2
#ذخیره نتایج جمع در متغیر total
TOTAL=$(($NUM1 + $NUM2))
echo '########################'
printf "%s %d\n" "The total is =" $TOTAL
echo '########################'
در اسکریپت قبلی میبینید که ما به پارامتر اول با استفاده از دستور $1
و پارامتر دوم با استفاده از $2
دسترسی پیدا کردیم (شما میتوانید هر تعداد پارامتر که میخواهید اضافه کنید). حالا بیایید دو عدد را با استفاده از اسکریپت جدید خود جمع کنیم (توجه کنید که من از این پس اسکریپتهایم را در پوشه /opt
ذخیره میکنم):
root@kali:/opt# simpleadd.sh 5 2
########################
The total is = 7
########################
یک محدودیت در اسکریپت قبلی وجود دارد؛ این اسکریپت فقط میتواند دو عدد را جمع کند. اگر بخواهید انعطافپذیری بیشتری داشته باشید و بتوانید از دو تا پنج عدد جمع کنید، در این صورت میتوانیم از قابلیت پارامتر پیشفرض استفاده کنیم. به عبارت دیگر، به طور پیشفرض تمام مقادیر پارامترها به صفر تنظیم میشوند و پس از وارد کردن مقدار واقعی از سوی کاربر، جمع میشوند:
#!/bin/bash
#ماشین حساب ساده که تا پنج عدد را جمع میکند
#ذخیره اولین پارامتر در متغیر num1
NUM1=${1:-0}
#ذخیره دومین پارامتر در متغیر num2
NUM2=${2:-0}
#ذخیره سومین پارامتر در متغیر num3
NUM3=${3:-0}
#ذخیره چهارمین پارامتر در متغیر num4
NUM4=${4:-0}
#ذخیره پنجمین پارامتر در متغیر num5
NUM5=${5:-0}
#ذخیره نتایج جمع در متغیر total
TOTAL=$(($NUM1 + $NUM2 + $NUM3 + $NUM4 + $NUM5))
echo '########################'
printf "%s %d\n" "The total is =" $TOTAL
echo '########################'
برای درک نحوه کارکرد این اسکریپت، بیایید به متغیر NUM1
به عنوان مثال نگاه کنیم (همین مفهوم برای پنج متغیر دیگر هم اعمال میشود). ما به آن دستور میدهیم که اولین پارامتر را از ترمینال بخواند و اگر توسط کاربر وارد نشده بود، آن را به صفر تنظیم کند، همانطور که در :-0
آمده است.
با استفاده از متغیرهای پیشفرض، ما دیگر محدود به جمع پنج عدد نیستیم؛ از این پس میتوانیم هر تعداد عدد که میخواهیم جمع کنیم، اما حداکثر تعداد آن پنج عدد خواهد بود (در مثال زیر، سه عدد را جمع میکنیم):
root@kali:~# simpleadd.sh 2 4 4
########################
The total is = 10
########################
نکته
اگر میخواهید تعداد پارامترهای وارد شده در اسکریپت را بدانید، میتوانید از $#
برای به دست آوردن تعداد کل استفاده کنید. بر اساس مثال قبلی، مقدار $#
برابر با سه خواهد بود زیرا سه آرگومان به اسکریپت ارسال کردهایم. اگر خط زیر را بعد از خط printf
اضافه کنید:
printf "%s %d\n" "The total number of params =" $#
باید در پنجره ترمینال چیزی مشابه زیر مشاهده کنید:
root@kali:~# simpleadd.sh 2 4 4
########################
The total is = 10
The total number of params = 3
########################
User Input
یکی دیگر از روشها برای تعامل با ورودیهای ارائه شده از طریق اسکریپت شل استفاده از تابع read
است. بهترین راه برای توضیح این، استفاده از مثالها است. از کاربر خواهیم خواست تا نام و نام خانوادگی خود را وارد کند و سپس نام کامل او را در صفحه نمایش چاپ میکنیم:
#!/bin/bash
read -p "Please enter your first name:" FIRSTNAME
read -p "Please enter your last name:" LASTNAME
printf "Your full name is: $FIRSTNAME $LASTNAME\n"
برای اجرای آن، فقط نام اسکریپت را وارد میکنیم (دیگر نیازی به ارسال پارامترها مانند قبل نداریم). به محض وارد کردن نام اسکریپت، پیامهایی که در اسکریپت قبلی تعریف کردهایم برای ما ظاهر میشود:
root@kali:~# nameprint.sh
Please enter your first name:Gus
Please enter your last name:Khawaja
Your full name is: Gus Khawaja
Functions
توابع روشی هستند برای سازماندهی اسکریپت Bash شما در بخشهای منطقی به جای داشتن یک ساختار بینظم (که برنامهنویسان به آن spaghetti code میگویند). بیایید برنامه ماشین حساب قبلی را دوباره سازماندهی کنیم (بازنویسی کنیم) تا بهتر به نظر برسد.
این اسکریپت Bash (در شکل 2.3) به سه بخش تقسیم میشود:
■ در بخش اول، تمام متغیرهای سراسری (global variables) ایجاد میشوند. متغیرهای سراسری در داخل هر تابعی که ایجاد کنید قابل دسترسی هستند. برای مثال، ما میتوانیم از تمام متغیرهای NUM
که در مثال اعلام شدهاند داخل تابع add
استفاده کنیم.
■ سپس، توابع را میسازیم و برنامههای خود را به بخشهای منطقی تقسیم میکنیم. تابع print_custom()
تنها متنی را چاپ میکند که به آن میدهیم. ما از $1
برای دسترسی به مقدار پارامتر که به این تابع ارسال شده (که رشته CALCULATOR
است) استفاده میکنیم.
■ در نهایت، هر تابع را به صورت متوالی فراخوانی میکنیم (هرکدام را با نامشان). ابتدا هدر را چاپ میکنیم، سپس اعداد را جمع میکنیم و در نهایت نتایج را چاپ میکنیم.
Conditions and Loops
حالا که اصول اولیه اسکریپتنویسی Bash را یاد گرفتهاید، میتوانیم تکنیکهای پیشرفتهتری را معرفی کنیم. زمانی که در اکثر زبانهای برنامهنویسی (مانند PHP، Python، C، C++، C# و غیره) و همچنین اسکریپتنویسی Bash برنامه مینویسید، با شرایط (دستورات if) و حلقهها مواجه خواهید شد، همانطور که در شکل 2.4 نشان داده شده است.
Conditions شرایط
یک دستور if به الگوی زیر عمل میکند:
if [[ مقایسه ]]
then
True, do something
else
False, Do something else
fi
اگر توجه کردهاید، بهترین راه برای توضیح این الگو از طریق مثالها است. بیایید یک برنامه بسازیم که با استفاده از Nmap به یک هاست پینگ بزند و وضعیت ماشین را بسته به شرط نمایش دهد (هاست بالا یا پایین است):
#!/bin/bash
#پینگ کردن یک هاست با استفاده از Nmap
### متغیرهای سراسری ###
#ذخیره آدرس IP
IP_ADDRESS=$1
function ping_host(){
ping_cmd=$(nmap -sn $IP_ADDRESS | grep 'Host is up' | cut -d '(' -f 1)
}
function print_status(){
if [[ -z $ping_cmd ]]
then
echo 'Host is down'
else
echo 'Host is up'
fi
}
ping_host
print_status
دستور nmap
یا یک رشته خالی باز میگرداند اگر هاست پایین باشد یا مقدار “Host is up” را بر میگرداند اگر پاسخ دهد. (سعی کنید دستور کامل nmap را در پنجره ترمینال خود اجرا کنید تا تفاوتها را ببینید. اگر این کار را انجام میدهید، $IP_ADDRESS
را با یک آدرس IP واقعی جایگزین کنید.) در شرط if
، گزینه -z
بررسی میکند که آیا رشته خالی است؛ اگر بله، سپس “Host is down” چاپ میشود و در غیر این صورت “Host is up” چاپ میشود:
root@kali:~# simpleping.sh 10.0.0.11
Host is down
root@kali:~# simpleping.sh 10.0.0.1
Host is up
سایر دستورات شرطی
اما در مورد سایر دستورات شرطی چه میشود؟ در واقع شما میتوانید مقادیر عددی، متنی یا فایلها را مقایسه کنید، همانطور که در جدولهای 2.1، 2.2 و 2.3 نشان داده شده است.
Loops
شما میتوانید حلقهها را به دو روش مختلف بنویسید: استفاده از حلقه while
یا استفاده از حلقه for
. بیشتر زبانهای برنامهنویسی از همان الگوی حلقهها استفاده میکنند. بنابراین، اگر متوجه شوید که حلقهها در Bash چگونه کار میکنند، همان مفهوم در زبانهایی مانند Python نیز قابل استفاده خواهد بود. حلقه while
به ساختار زیر عمل میکند:
while [[ شرط ]]
do
do something
done
بهترین راه برای توضیح یک حلقه از طریق یک شمارنده از 1 تا 10 است. ما برنامهای خواهیم ساخت که یک نوار پیشرفت را نمایش دهد:
#!/bin/bash
#نوار پیشرفت با حلقه while
#شمارنده
COUNTER=1
#نوار
BAR='##########'
while [[ $COUNTER -lt 11 ]]
do
#چاپ پیشرفت نوار از ایندکس صفر
echo -ne "\r${BAR:0:COUNTER}"
#خوابیدن به مدت 1 ثانیه
sleep 1
#افزایش شمارنده
COUNTER=$(( $COUNTER +1 ))
done
توجه کنید که شرط ([[ $COUNTER -lt 11 ]]
) در حلقه while
همان قوانین شرط if
را دنبال میکند. از آنجایی که میخواهیم شمارنده در عدد 10 متوقف شود، از فرمول ریاضی زیر استفاده خواهیم کرد: counter<11
. هر بار که شمارنده افزایش مییابد، پیشرفت نوار نمایش داده میشود. برای جذابتر کردن این برنامه، آن را به مدت یک ثانیه متوقف میکنیم قبل از اینکه وارد شماره بعدی شویم.
از سوی دیگر، حلقه for
به الگوی زیر عمل میکند:
for ... in [لیست موارد]
do
something
done
ما از همان مثال قبلی استفاده خواهیم کرد، اما این بار از حلقه for
استفاده میکنیم. شما متوجه خواهید شد که حلقه for
انعطافپذیرتر از حلقه while
است. (راستش، من به ندرت از حلقه while
استفاده میکنم.) همچنین، نیازی به افزایش شمارنده ایندکس ندارید؛ این کار به طور خودکار برای شما انجام میشود:
#!/bin/bash
#نوار پیشرفت با حلقه For
#نوار
BAR='##########'
for COUNTER in {1..10}
do
#چاپ پیشرفت نوار از ایندکس صفر
echo -ne "\r${BAR:0:$COUNTER}"
#خوابیدن به مدت 1 ثانیه
sleep 1
done
File Iteration تکرار فایلها
برای خواندن یک فایل متنی در Bash با استفاده از حلقه for
، شما میتوانید اینطور عمل کنید:
for line in $(cat filename)
do
do something
done
در مثال زیر، ما لیستی از آدرسهای IP را در یک فایل به نام ips.txt
ذخیره خواهیم کرد. سپس از برنامه پینگ Nmap که قبلاً ساختیم استفاده میکنیم تا بررسی کنیم که آیا هر آدرس IP بالا است یا پایین. علاوه بر این، نام DNS هر آدرس IP را نیز بررسی خواهیم کرد:
#!/bin/bash
#پینگ کردن و دریافت نام DNS از لیستی از آدرسهای IP ذخیره شده در یک فایل
#از کاربر خواسته میشود که نام فایل و مسیر آن را وارد کند.
read -p "Enter the IP addresses file name / path:" FILE_PATH_NAME
function check_host(){
#اگر مقدار آدرس IP خالی نباشد
if [[ -n $IP_ADDRESS ]]
then
ping_cmd=$(nmap -sn $IP_ADDRESS | grep 'Host is up' | cut -d '(' -f 1)
echo '------------------------------------------------'
if [[ -z $ping_cmd ]]
then
printf "$IP_ADDRESS is down\n"
else
printf "$IP_ADDRESS is up\n"
dns_name
fi
fi
}
function dns_name(){
dns_name=$(host $IP_ADDRESS)
printf "$dns_name\n"
}
#تکرار در آدرسهای IP داخل فایل
for ip in $(cat $FILE_PATH_NAME)
do
IP_ADDRESS=$ip
check_host
done
اگر با دقت این فصل را دنبال کرده باشید، باید قادر به درک تمام موارد موجود در کد قبلی باشید. تفاوت اصلی در این برنامه این است که من از فاصلهگذاری تب استفاده کردهام تا اسکریپت تمیزتر به نظر برسد. مثال قبلی بیشتر آنچه که تاکنون انجام دادهایم را پوشش میدهد، از جمله موارد زیر:
- ورودی کاربر
- اعلام متغیرها
- استفاده از توابع
- استفاده از شرایط
if
- تکرار حلقهها
- چاپ به صفحه نمایش
خلاصه
امیدوارم که تمام تمرینها را در این فصل انجام داده باشید، بهویژه اگر شما تازهکار در برنامهنویسی هستید. بسیاری از مفاهیم ذکر شده برای زبانهای برنامهنویسی مختلف قابل استفاده هستند، بنابراین تمرینها را فرصتی برای یادگیری اصول در نظر بگیرید. من شخصاً از اسکریپتنویسی Bash برای سناریوهای کوچک و سریع استفاده میکنم. اگر بخواهید برنامههای پیچیدهتری بسازید، میتوانید این کار را با استفاده از Python انجام دهید. نگران نباشید! در ان دوره های آموزشی درباره Python خواهید آموخت تا بتوانید هر موقعیتی را که میخواهید در حرفهتان به عنوان یک تستکننده نفوذ حل کنید. در نهایت، این فصل اطلاعات زیادی درباره اسکریپتنویسی Bash پوشش داد.
با این حال، اطلاعات بیشتری از آنچه در این فصل آورده شده وجود دارد. در عمل، من از موتورهای جستجوی اینترنتی برای پیدا کردن سریع منابع اسکریپتنویسی Bash استفاده میکنم. در واقع، نیازی به حفظ کردن همه چیزهایی که در این مقاله آموختید نیست.
سلام و درود
وقت بخیر
خدا قوت
ضمن تشکر از زحمات شما، پیشنهاد میگردد، مطالب و مقالات اینچنینی با توجه به زحمت کشیده شده توسط تهیه کننده بصورت فایل هم قابل دانلود بوده تا بصورت منسجم و قابل آرشیو، مطالب ارزشمند سایت مورد استفاده کاربر قرار گیرد.
با تشکر