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

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

کاربرد خواندن فایل و پردازش خطوط آن

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

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

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

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

دستور لازم برای خواندن خطوط یک فایل متنی

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

January

February

March

.

.

.

October

November

December

با استفاده از فرمان read می‌توان خطوط را خواند و با استفاده از فرمان echo، آنچه خوانده شده در خطوط مجزا چاپ می‌شود.

به مثال زیر توجه کنید:

خروجی دستور فوق به این صورت است:

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

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

فراموش نکنید که دستور مفید cat نیز برای خواندن فایل‌های متنی طراحی شده و کار را ساده‌تر می‌کند اما برای توضیح روش خواندن فایل‌ها و کار با داده‌های خوانده شده، از حلقه و دستور read و echo استفاده کرده‌ایم تا فرآیند روشن‌تر شود.

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

January\n February\n March\n . . October\n November\n December\n

به نظر شما اگر از همان دستور قبلی استفاده کنیم، نتیجه چه خواهد بود؟

همان‌طور که در تصویر زیر مشاهده می‌کنید، کاراکتر خاص بک‌اسلش یا \ حذف می‌شود! و لذا خروجی شبیه قبل است با این تفاوت که پس از نام هر ماه، یک کاراکتر n اضافه می‌شود:

آموزش اسکریپت‌نویسی لینوکس و کار با فایل‌های متنی به عنوان ورودی

اگر نخواهید کاراکتر خاص \ حذف شود، می‌بایست تغییراتی در دستورات پردازش فایل متنی بدهید. بهتر است برای کار با فایل‌های متنی، اسکریپت‌های ساده بنویسید.

خواندن خطوط فایل با استفاده از اسکریپت

در لینوکس پسوند فایل‌های اسکریپت که حاوی دستوراتی هستند، sh است. به عنوان مثال فرض کنید که اسکریپتی با نام script1.sh ساخته‌ایم و محتویات آن به صورت زیر است:

در این اسکریپت، یک حلقه‌ی ساده داریم که در آن متغیری به اسم Counter یا شمارنده موجود است و مقدار اولیه‌ی آن عدد ۰ است و با هر بار خواندن خط جدید و انجام فرآیند، یک واحد به آن اضافه می‌شود.

اولین دستور در حلقه‌ای که با While ایجاد شده، مقداردهی به IFS است. IFS یا Internal Field Separator، رشته‌ای است که که Bash برای شناسایی محدوده‌ی لغت‌ها استفاده می‌کند. به صورت پیش‌فرض دستور read فاصله‌های ابتدا و انتها را حذف می‌کند. لذا اگر بخوانید خطوط فایل را بدون حذف شدن اسپیس‌های ابتدا و انتها بخوانید، می‌بایست مقدار IFS را یک استرینگ خالی در نظر بگیرید و این همان کاری است که در ابتدای حلقه‌ی While انجام شده است. البته می‌توانید قبل از حلقه هم به IFS مقدار خالی بدهید اما در اسکریپت‌های پیچیده‌تر، شاید مقداردهی به IFS در حلقه‌های داخلی الزامی باشد و نتیجه‌ی کار متفاوت شود.

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

اما در ادامه‌ی حلقه دو حالت ذکر شده که در صورت برقراری شرایط، پردازش در حلقه ادامه پیدا می‌کند:

  • حالت اول برقراری شرط read -r LinefromFile است به این معنی که در صورت موفقیت‌آمیز بودن خواندن خط جدید، سیگنال موفقیت به While داده می‌شود و فرآیندی که در ادامه‌ی حلقه تعریف شده، انجام می‌شود. دقت کنید که دستور read زمانی موفق است که در انتهای خط، کاراکتر ایجاد خط جدید رویت شود. اگر فایل متنی با POSIX سازگار نباشد، ممکن است در انتهای آخرین خط فایل، کاراکتر ایجاد خط جدید موجود نباشد و به جای آن کاراکتر انتهای فایل یا EOF قرار داده باشد. در نتیجه دستور read ناموفق خواهد بود و آخرین خط فایل در حلقه‌ی While پردازش نمی‌شود. لذا به شرط دوم نیاز داریم و می‌بایست یکی از دو شرط برای اجرای فرآیند حلقه برقرار باشد.
  • شرط دوم این است که فایل متنی پردازش شود و اگر در انتهای آخرین خط، کاراکتر ایجاد خط جدید موجود نبود، سیگنال موفقیت به While داده شود. در این صورت زمانی که به آخرین خط می‌رسیم، باز هم فرآیند خواندن خط انجام می‌شود و حلقه اجرا می‌شود.

بین دو شرط فوق از اپراتور منطقی || که همان یا یا OR است، استفاده می‌شود.

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

دقت کنید که می‌توانید اسکریپت فوق را در اپلیکیشن‌های ویرایش متنی ساده پیست کنید و آن را با پسوند sh در محل موردنظر ذخیره کنید. برای قابل اجرا کردن این فایل می‌بایست از فرمان chmod استفاده کنید.

آموزش اسکریپت‌نویسی لینوکس و کار با فایل‌های متنی به عنوان ورودی

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

خروجی به این صورت خواهد بود:

آموزش اسکریپت‌نویسی لینوکس و کار با فایل‌های متنی به عنوان ورودی

همان‌طور که مشاهده می‌کنید، کاراکتر ایجاد خط جدید عیناً چاپ شده و چیزی حذف نشده است.

ارسال خطوط فایل متنی به تابع در اسکریپت

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

به عنوان مثال اسکریپت زیر را در نظر بگیرید که اساساً شبیه مثال قبلی است:

در این اسکریپت قبل از ایجاد حلقه، به شمارنده مقدار ۰ داده شده و همین‌طور تابعی به اسم process_line() تعریف شده است. در واقع نمی‌توانید پس از حلقه، تابع را تعریف کنید و می‌بایست قبل از فراخوانی تابع، آن را تعریف کرده باشید.

دقت کنید که حاصل خواندن هر خط از فایل متنی، به عنوان ورودی به تابع داده می‌شود. دسترسی به این متغیر با استفاده از $1 صورت می‌گیرد. اگر دو متغیر داشته باشید و به عنوان مثال بخواهید خط بعدی به عنوان دومین متغیر به تابع ارسال شود، می‌بایست از $2 استفاده کنید و به همین ترتیب برای متغیرهای بعدی عمل کنید.

در حلقه‌ی While از دستور echo خبری نیست چرا که فرآیند چاپ کردن در خود تابع process_line() تعریف شده است. لذا با فراخوانی این تابع، عمل چاپ کردن انجام می‌شود.

اما ۳ نکته‌ی مهم در فراخوانی توابع حین اسکریپت‌نویسی:

دقت کنید که در فراخوانی توابع، نیاز به استفاده از () نیست! در حالی که در تعریف کردن تابع، می‌بایست پرانتز باز و بسته به کار رود.

نکته‌ی بسیار مهم دیگر این است که متغیرهایی که به توابع می‌فرستید را بین کوتیشن قرار دهید تا در صورت وجود فاصله یا همان اسپیس، اولین کلمه به عنوان متغیر اول و دومین کلمه به عنوان کلمه‌ی بعدی و همین‌طور سایر کلمات به عنوان متغیرهای بعدی به تابع فرستاده نشود. لذا از '$1' به جای $1 استفاده کنید.

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

در نهایت اسکریپت را ذخیره کنید و با استفاده از دستور chmod آن را به فایل اجرایی تبدیل کنید:

آموزش اسکریپت‌نویسی لینوکس و کار با فایل‌های متنی به عنوان ورودی

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

January

February

March

.

.

October

November \nMore text "at the end of the line"

December

دستور فراخوانی اسکریپت و دادن فایل فوق به عنوان ورودی:

و خروجی دستور فوق:

آموزش اسکریپت‌نویسی لینوکس و کار با فایل‌های متنی به عنوان ورودی

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