آموزش اسکریپت نویسی

آموزش اسکریپت نویسی پوسته گنو-لینوکس

آموزش اسکریپت نویسی

آموزش اسکریپت نویسی پوسته گنو-لینوکس

پرسش و پاسخ ۱



پرسش و پاسخ شماره ۱

چطور می‌توانم یک فایل(جریان داده، متغیر) را سطر به سطر بخوانم(و یا فیلد به فیلد)؟

استفاده از "for" را امتحان نکنید. از یک حلقه while و فرمان read استفاده کنید:

    while read -r line
    do
        echo "$line"
    done < "$file"

کاربرد گزینه ‎ -r با فرمان read از تفسیر کاراکتر ‎\‎ پیش‌گیری می‌کند(به طور معمول از این کاراکتر و کاراکتر سطر جدید برای ادامه یک سطر در سطر بعدی استفاده می‌شود). بدون این گزینه هر کاراکتر \ در ورودی، دور انداخته می‌شود. شما همیشه با فرمان read از گزینه ‎ -r‎ استفاده کنید.

line نام متغیر انتحاب شده توسط شما می‌باشد. به جای آن هر نام معتبر در پوسته را می‌توانید قرار بدهید.

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

اگر منبع ورودی شما محتویات یک متغیر/پارامتر باشد، BASH می‌تواند با استفاده از "here string" تکرار روی سطرهای آن را انجام بدهد:

    while read -r line; do
        echo "$line"
    done <<< "$var"

همین کار در هر پوسته گروه بورن با استفاده از "here document" قابل انجام است( گرچه ‎ read -r‎ مربوط به POSIX است، نه Bourne):

    while read -r line; do
        echo "$line"
    done <<EOF
$var
EOF

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

    # Bash
    while read -r line
    do
        [[ $line = \#* ]] && continue
        echo "$line"
    done < "$file"

اگر می‌خواهید روی فیلدهای منحصربه‌فرد هر سطر عمل کنید، می‌توانید متغیرهای اضافی برای read فراهم نمایید:

    # Input file has 3 columns separated by white space.
    while read -r first_name last_name phone; do
      ...
    done < "$file"

اگر جداکننده فیلد، فضای سفید نباشد، می‌توانید متغیر IFS (جداکننده فیلد درونی) را تنظیم کنید:

    while IFS=: read -r user pass uid gid gecos home shell; do
      ...
    done < /etc/passwd

برای فایلهایی که جداکننده فیلد آنها Tab می‌باشد، ‎ IFS=$'\t'‎ را به کار ببرید.

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

    read -r first last junk <<< 'Bob Smith 123 Main Street Elk Grove Iowa 123-555-6789'

    # first will contain "Bob", and last will contain "Smith".
    # junk holds everything else.

بعضی اشخاص از متغیر دورانداختنی _ به عنوان«متغیر ناخواسته» برای صرفنظر از فیلدها استفاده می‌کنند. این(یا در حقیقت هر متغیری) در صورتیکه دلواپس چیزی که در آن قرار می‌گیرد، نباشیم، می‌تواند بیش از یکبار در یک دستور منفرد read استفاده گردد:

    read -r _ _ first middle last _ <<< "$record"

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

فرمان read به طور پیش‌فرض هر سطر خوانده را با زدودن تمام کاراکترهای فضای سفید قبل و بعد ویرایش می‌کند(فاصله‌ها و tabها، یا هر کاراکتر فضای سفید معرفی شده درمتغیر IFS). اگر این کار مطلوب نباشد، باید متغیر IFS پاک بشود:

    # Exact lines, no trimming
    while IFS= read -r line
    do
        printf '%s\n' "$line"
    done < "$file"

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

    some command | while read -r line; do
       other commands
    done

این شیوه مخصوصاً برای پردازش خروجی find با یک بلوک از دستورها، مفید است:

    find . -type f -print0 | while IFS= read -r -d '' file; do
        mv "$file" "${file// /_}"
    done

این کُد هر دفعه یک فایل را از فرمان find می‌خواند و تغییر نام فایل را با تعویض کاراکترهای فاصله با خط زیر، انجام می‌دهد.

به کاربرد ‎ -print0‎ در فرمان find توجه نمایید، که از بایت تهی به عنوان جداکننده نام فایلها استفاده می‌کند، و ‎ -d ''‎ در فرمان read به آن دستور می‌دهد که تمام متن را تا رسیدن به بایت تهی در متغیر file بخواند. به طور پیش‌فرض، find و read ورودی خود را با سطر جدید جدا می‌کنند، به هرحال، چون نام فایلها به طور بالقوه خودشان می‌توانند محتوی سطرجدید باشند، این رفتار پیش‌فرض آن نام فایلها را در محل سطرجدید از هم جدا می‌کند و موجب نقص در بدنه حلقه می‌گردد. بعلاوه لازم است متغیر IFS به یک رشته تهی تنظیم شود، زیرا در غیر اینصورت read باز هم فضای سفیدهای قبل و بعد نامها را از بین می‌برد. برای توضیحات تفصیلی FAQ #20 را ملاحظه نمایید.

استفاده از یک لوله برای ارسال خروجی find به داخل یک حلقه while ، حلقه را در پوسته فرعی قرار می‌دهد و بنابراین بعداً، موقعی که دستورات درون بدنه حلقه سعی در تنظیم متغیرهایی نمایند که لازم است پس از آن در حلقه به کار بروند، ممکن سبب مشکلاتی بشود، برای آن وضعیت، FAQ 24 را ملاحظه کنید، یا از جایگزینی پردازش استفاده کنید، مانند:

    while read -r line; do
        other commands
    done < <(some command)

اگر می‌خواهید سطرهای فایل را به درون یک آرایه بخوانید، FAQ 5 را ببینید.

فایلهای متنی من منقطع شده‌اند! آنها فاقد سطر جدید انتهایی هستند!

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

    #  cat  شبیه سازی
    while IFS= read -r line
    do
        printf '%s\n' "$line"
    done < "$file"
    [ -n "$line" ] && printf %s "$line"

یا:

    # :این کار نمی‌کند
    printf 'line 1\ntruncated line 2' | while read -r line; do echo $line; done

    # :این یکی هم کار نمی‌کند
    printf 'line 1\ntruncated line 2' | while read -r line; do echo "$line"; done; [[ $line ]] && echo -n "$line"

    # :این کار می‌کند
    printf 'line 1\ntruncated line 2' | (while read -r line; do echo "$line"; done; [[ $line ]] && echo "$line")

برای بحث در باره آنکه چرا دومین مثال فوق آنطور که انتظار دارید کار نمی‌کند،پرسش وپاسخ ۲۴ را ملاحظه نمایید.

چطور سایر دستورات را از خوردن ورودی جلوگیری نمایم

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

    while read -r line
    do
        cat > ignoredfile
        echo "$line"
    done < "$file"

فقط محتویات سطر اول را چاپ می‌کند، مابقی محتویات به "ignoredfile" می‌روند، چون cat تمام ورودی موجود را می‌مکد.

یک عبور موقت از این امر استفاده از توصیف‌گر فایل عددی به جای ورودی استاندارد است:

    # Bash در پوسته  
    while read -r -u9 line
    do
        cat > ignoredfile
        echo "$line"
    done 9< "$file"

یا:

    # Bourne در پوسته 
    exec 9< "$file"
    while read line <&9
    do
      ...
    done
    exec 9<&-

این مثال، در هر تکرار به جای خوردن تمام ورودی حلقه، منتظر می‌ماند تا کاربر چیزی درفایل ignoredfile تایپ نماید.

شاید شما احتیاج به این داشته باشید، برای مثال به mencoder که اگر ورودی کاربر وجود داشته باشد، آن را خواهد پذیرفت، اما اگر موجود نباشد به طور بی‌صدا ادمه خواهد داد. دستورات دیگری که به این طریق عمل می‌کنند از جمله ssh و ffmpeg هستند. عبور موقت‌های اضافی برای این مورد در FAQ #89 بحث گردیده‌اند.


CategoryShell

پرسش و پاسخ 1 (آخرین ویرایش‎ 2012-03-22 11:40:30 ‎ توسط ‎ 84-73-54-61‎)

نظرات 0 + ارسال نظر
ایمیل شما بعد از ثبت نمایش داده نخواهد شد