اصولاً برای برنامهنویسی شل نیاز به تمرین میباشد. گهگاه بد نیست به کد دیگران رجوع شود و از کد آنان استفاده گردد تا کد نویسی بهینه گردد. بدین منظور برای خوانندگان محترم تکه کدی نوشتم که چندین مورد را نشانه رفته ولی برای کدخوانی نقطه قابل تاملی محسوب میگردد.
این کد از کاربر یک فایل pls که فرمت مذکور protocol-base میباشد و میتوان از روی اینترنت نیز موسیقی گوش کرد. و سپس یک دایرکتوری گرفته که فایلهای مذکور را به آن کپی میکند.
نکته: این اسکریپت فرض را بر این گذاشته که پروتکل مرتبط //:file میباشد و شما خود میتوانید پروتکلهای دیگر را به آن با دستوراتی همچون wget یا curl بیافزاید.
یک pls فایل معمولی همانند زیر میباشد:
1 2 3 4 5 6 7 8 |
[playlist] NumberOfEntries=5 Version=2 File1=file:///archives/music/latin/Tradicional/Dos Voces de America en un Canto a Cuba (Carlos Puebla & Pablo Neruda)/02 - Semifinal.mp3 File2=file:///archives/music/latin/Tradicional/Dos Voces de America en un Canto a Cuba (Carlos Puebla & Pablo Neruda)/04 - Canto A Mi Pueblo.mp3 File3=file:///archives/music/latin/Tradicional/Dos Voces de America en un Canto a Cuba (Carlos Puebla & Pablo Neruda)/06 - Y En Eso Llego Fidel.mp3 File4=file:///archives/music/latin/Tradicional/Dos Voces de America en un Canto a Cuba (Carlos Puebla & Pablo Neruda)/08 - Otra Vez La OEA.mp3 File5=file:///archives/music/latin/Tradicional/Dos Voces de America en un Canto a Cuba (Carlos Puebla & Pablo Neruda)/10 - Adelante, Companeros.mp3 |
اسکریپت مذکور:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
#!/bin/bash play_list_file=$1 if [[ $# -eq 2 ]]; then target_name=$2 elif [[ $# -eq 1 ]]; then echo "Please enter target directory:" read target_name elif [[ $# -eq 0 ]]; then echo "Please enter target directory:" read target_name echo "Please enter play list file:" read play_list_file fi; SAVEIFS=$IFS IFS=$(echo -en "\n\b") while read -r line <&3; do if [[ "$line" =~ ^File[0-9]* ]] ; then file_name=`echo $line |sed s'/File[0-9]*=//'g |sed s'/file:\/\///'g` if [[ -d "$target_name" ]]; then echo $file_name cp -v $file_name $target_name/ echo "GoodBye...!" else IFS=$SAVEIFS read -p "Your directory doesn't exists,Would you like to create this[y/n]?" -n1 yesno yesno=`echo $yesno |tr '[:upper:]' '[:lower:]'` if [[ "$yesno" = "y" ]] ;then echo "createing $target_name ..." mkdir -p $target_name IFS=$(echo -en "\n\b") else echo "GoodBye...!" exit 0; fi; fi; fi; done 3< "$play_list_file" IFS=SAVEIFS |
در گام اول کل برنامه را به دو بخش اساسی باید تقسیم کرد :
-
if های اول برنامه
-
حلقه while
متغیر IFS هم که باشد در طول برنامه توضیح داده خواهد شد.
If های اول برنامه مختص گرفتن و چک کردن آرگومانهای برنامه میباشند که یک باگ در آنها گذاشتم که باید به صورت comment برایم بگذارید.
بیایید کمی فایل Play List را بررسی کنیم : به جز ۳ خط اول که meta data محسوب میشوند بقیه خطوط شامل فایلهایی میباشند که شامل آدرس هستند. باز اگر هر خط را کمی تأمل کنیم،به سه عنصر تبدیل میشود. یکی اندیس عنصر همانند =File34 ، دیگری پروتکل کی در اینجا از //:file استفاده شده است و در آخر از آدرس فایل بر روی دیسک یا فضای مجازی که باید از آن استفاده شود. پس با توجه به این ۳ عنصر مشخص است که ما با عنصر آخر کار داریم. پس با فرمان زیر این ۳ عنصر را از هم مجزا کرده و نام و مسیر فایل را در متغیر file_name میریزیم:
1 |
file_name=`echo $line |sed s'/File[0-9]*=//'g |sed s'/file:\/\///'g` |
ناگفته نماند که کلیه خطوط حلقه توضیح داده خواهد شد. در خط بالا متغیر line شامل کل خط (۳ عنصر نام برده شده میباشد.) که آن را فیلتر کرده و از آن =File34 (با فرض اینکه خط شامل ۳۴ امین موزیک است) را جدا میکنیم. علامت * در Wildcard ها و regex ها متفاوت عمل میکند. در regex ها که استفاده کردهایم یعنی تکرار چیزی ، بدین معنی که اگر در اینجا از * استفاده نمیشد File55 بیمعنی میشد و تا یک رقم عدد معنی دار بود. ولی با علامت * تا هر رقم عدد معنی دارد.
در پایپ آخر گزینه //:file از کل رشته جدا شده و آن چیزی میخواستیم در متغیر file_name قرار میگیرد.
یک خط به بالا برگردیم به خطی که با متغیر line را درون if خود دارد. گذاشتن این خط یک مشکلی برای برنامه ایجاد میکند و آن هم مشکل performance میباشد،زیرا باید برای هر خط این شرط چک شود. اما این کد درسی است و این مشکل، مشکلی محسوب نمیشود. اگر به خاطر داشته باشید درون if ها برای چک کردن شروط ، برای عبارات ریاضی از عملگرهایی استفاده میکردیم که مخفف کلمات زیر بودند:
-
equal
-
not
-
greater
-
than
-
less
و با آنها عملگرهای زیر ساخته می شدند:
-
eq-
-
ge-
-
gt-
-
lt-
-
ne-
اما برای عبارات رشتهای از عملگرهای زیر استفاده میکردیم:
-
=
-
=!
-
n-
-
z-
نکته: ناگفته نماند کلی عملگر برای فایلها وجود دارد که از این مبحث خارج است.
اما برای چک کردن regex ها چه؟ بله، عملگر آنها فرق میکند و از دو عملگر زیر باید استفاده نمود:
-
~=
-
~!
داخل if مربوطه ذکر شده است هر خطی که با عنصر اولمان شروع شود. (علامت ^ شروع در regex است.) آنگاه این خط یک meta data نیست.
در داخل if های اول برنامه دو متغیر target_name و play_list_file به عنوان نگهدارنده دایرکتوری مقصد که قرار است فایلها در آن کپی شوند و فایل play list که قرار است از آن خوانده شود بارگذاری میشوند. در دومین if داخل حلقه چک میشود که آیا چنین دایرکتوری وجود دارد یا خیر، اگر وجود دارد شروع به کپی فایلها درون آن میکند.
اما اگر وجود نداشت چه؟ درواقع بلاک مربوط به else مشخص کننده عدم وجود دایرکتوری مربوطه است. لازم به ذکر است متغیری که قبل از حلقه ست شد را کمی آنالیز کنیم چون کمی با آن سر و کار داریم. Bash متغیری دارد با نام IFS که مخفف Internal Field Separator میباشد که مقدار پیشفرض آن در bash معادل b\ میباشد. در قبل از حلقه برای اینکه بتوان فایلها با فاصله و یا کارکترهای ویژه را کپی کرد این متغیر را دستکاری کرده و برابر n\b\ قرار میدهیم. این متغیر همانند FS برای awk کار میکند با این تفاوت که برای پارامترها کار میکند.
تمرین : بارگذاری IFS قبل از loop را حذف کنید و ببینید هنگام کپی چه پیغامی میبینید.
در بقیه برنامه برای اینکه از کاربر تاییده بگیریم تا برای او یک دایرکتوری ایجاد نماییم مجبور بودیم IFS را در حالت اولیه قرار دهیم تا بتوان بدون Enter از صفحهکلید چیزی خواند. اگر دقت کنید با p- یک promp چاپ کرده و با n1- به آن میگوییم طول ورودی ۱ باشد و محتوا را که یک کاراکتر است در متغیر yesno بریزد.
نکته: متغیر جلوی read بدون $ حاضر میشود.
در شرط if برای تست y یا n بودن میتوانستیم با یک o- بزرگ و کوچک بودن آنها را تست نمود ولی این کار را کردیم تا با tr آشنا شویم، زیرا کل این مطلب درسی است. با tr کل متغیر را چه بزرگ و چه کوچک در نهایت از آن خروجی کوچک گرفته تا در if استفاده نماییم. سپس یک دایرکتوری ساخته و متغیر IFS را آماده کپی میسازیم. در بعد از حلقه نیز متغیر IFS را همانند گذشتهاش برمیگردانیم.
اما شرط حلقه و چنین fd کمی عجیب به نظر میرسد. منظور عدد ۳ است. کلاً در تمام کتابهای سنتی یونیکس سه عدد ۰ ، ۱ و ۲ تدریس میشدند. اما خیلی وقت است که دیگر چنین نیست.
به کد زیر دقت نمایید:
1 2 3 4 |
while read line do echo $line|awk '{print $5}' done < /etc/passwd |
کد بالا کلاً از دور خارج شده است و باید انواع جدید آن استفاده کرد. برای اینکه دیگر از کد بالا استفاده ننماییم به مقاله فرید دهقان در شمارههای آتی سلام دنیا رجوع شود.
عالی بود محسن جان 😉