منبع اصلی نوشتار زیر در این لینک قرار دارد

پرسش و پاسخ شماره ۲۴


پرسش و پاسخ‌های رایج Bash  در Greg\'s Wiki

پرسش و پاسخ شماره ۲۴

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

در اکثر پوسته‌ها، هر یک از فرمانهای یک لوله در پوسته فرعی جداگانه‌ای اجرا می‌گردد. نه در پوسته کاری:

    # فعال شده کار می‌کند lastpipe نگارش 4.2 با bash یا ksh88/ksh93 فقط در ‎
    # در سایر پوسته‌ها صفر را چاپ می‌کند
    linecnt=0
    printf \'%s\\n\' foo bar | while read -r line
    do
        linecnt=$((linecnt+1))
    done
    echo \"total number of lines: $linecnt\"

دلیل این رفتار تعجب‌آور پنهانی، آنطور که در بالا وصف شد، آنست که هر پوسته فرعی یک محیط و زمینه متغیر جدیدی برقرار می‌کند. حلقه while فوق در پوسته فرعی جدیدی با نسخه خودش از متغیر linecnt اجرا می‌شود، که از پوسته والدش که آنرا با مقدار اولیه \'0\' ایجاد نموده اخذ کرده است. سپس این نسخه برای شمارش به کار می‌رود . وقتی حلقه while خاتمه می‌یابد، نسخه پوسته فرعی دور انداخته می‌شود، و مقدار اصلی متغیر linecnt پوسته والد(که مقدارش تغییر نکرده) در فرمان echo به کار می‌رود.

پوسته‌های متفاوت در این وضعیت رفتار متفاوتی نشان می‌دهند:

  • پوسته Bourne موقعی که ورودی یا خروجی هر چه(حلقه‌ها، case و غیره) به غیر از یک فرمان ساده , با استفاده از عملگر تغییر مسیر(< و >) یا لوله، تغییر مسیر داده می‌شود، یک پوسته فرعی ایجاد می‌کند.

  • BASH فقط اگر حلقه قسمتی از لوله باشد، یک پردازش جدید ایجاد می‌کند.

  • پوسته Korn فقط اگر حلقه قسمتی از یک لوله باشد، اما آخرین بخش آن نباشد پوسته فرعی ایجاد می‌کند. مثال read در بالا به طور واقعی در ksh88 و ksh93 کار می‌کند! (اما در mksh خیر)

  • POSIX رفتار bash را تصریح می‌کند، اما به عنوان یک توسعه اجازه می‌دهد هر قسمت یا تمام لوله بدون پوسته فرعی اجرا شود(به این ترتیب رفتار شل Korn را به خوبی مجاز می‌کند).

موارد بیشتر معیوب :

    # Bash 4
    # بدون حلقه نیز مشکل رخ می‌دهد
    printf \'%s\\n\' foo bar | mapfile -t line  
    printf \'total number of lines: %s\\n\' \"${#line[@]}\" #  صفر را چاپ می‌کند 

    f() {
        if [[ -t 0 ]]; then
            echo \"$1\"
        else
            read -r var
        fi
    };

    f \'hello\' | f
    echo \"$var\" # هیچ چاپ نمی‌کند

دوباره، در هر دو حالت لوله باعث می‌شود read یا بعضی فرمانها مشمول اجرا در پوسته فرعی شوند، بنابراین اثر آن هرگز در پردازش والد مشاهده نمی‌شود.

  • باید تأکید بشود که موضوع مختص حلقه‌ها نیست. خصوصیت عمومی تمام لوله‌ها می‌باشد، گرچه حلقه ‎ \"while/read\"‎ می‌تواند مثال استانداردی ملاحظه شود که وقتی افراد توضیحات صفحات man یا help دستور داخلی read را می‌خوانند و توجه داده می‌شوند که این دستور داده را از stdin می‌پذیرد، بارها و بارها ظاهر می‌گردد. آنها ممکن است به یاد بیاورند که داده تغییر مسیر یافته به درون دستور مرکب در سرتاسر آن دستور در دسترس است، اما درک نمی‌کنند چراتمام جایگزینی پردازشها و تغییر مسیرهای ظریفی که در سرتاسر محل‌هایی مانند FAQ #1 به کار می‌اندازند، ضروری هستند. طبعاً آنها به قرار دادن مواردی به طور مستقیم در لوله‌ها اقدام می‌کنند و ازعواقب آن سردرگم می‌شوند.

Workaroundها(راه های عبور )

  • اگر ورودی فایل است، یک تغییر مسیر ساده کفایت می‌کند:
        # POSIX
        while read -r line; do linecnt=$(($linecnt+1)); done < file
        echo $linecnt

    متأسفانه، این مورد در پوسته Bourne کار نمی‌کند، ‎sh(1) ‎ موروثی پوسته Bourne را برای راه عبور از آن ملاحظه کنید.

  • استفاده از گروه‌بندی دستورات و انجام همه چیز در پوسته فرعی:

        # POSIX
        linecnt=0
        cat /etc/passwd | {
        while read -r line ; do
            linecnt=$((linecnt+1))
        done
        echo \"total number of lines: $linecnt\"
        }
    این در واقع وضعیت پوسته فرعی را تغییر نمی‌دهد، اما اگر در مابقی کُد شما، چیزی از پوسته فرعی مورد نیاز نیست، آنوقت انهدام محیط موقت محلی پس از تمام شدن کار شما با آن، دقیقاً می‌تواند موردی باشد که به هر حال شما لازم دارید.
  • استفاده از جایگزینی پردازش(فقط Bash):

        # Bash
        while read -r line; do
            ((linecnt++))
        done < <(grep PATH /etc/profile)
        echo \"total number of lines: $linecnt\"
    این مورد در اصل مشابه workaround اول در بالا می‌باشد. ما بازهم فایلی را تغییر مسیر می‌دهیم، فقط در اینجا فایل یک لوله با نام (fifo) موقت است که توسط جایگزینی پردازش ما برای انتقال خروجی grep ایجاد گردیده است.
  • استفاده از لوله با نام:

        # POSIX
        mkfifo mypipe
        grep PATH /etc/profile > mypipe &
        while read -r line;do
            linecnt=$(($linecnt+1))
        done < mypipe
        echo \"total number of lines: $linecnt\"
  • استفاده از کمک پردازش(ksh و حتی pdksh و bash نسخه ۴ و oksh و mksh..):

        # ksh
        grep PATH /etc/profile |&
        while read -r -p line; do
            linecnt=$((linecnt+1))
        done
        echo \"total number of lines: $linecnt\"
  • استفاده از HereString(فقط Bash):

         read -ra words <<< \'hi ho hum\'
         printf \'total number of words: %d\' \"${#words[@]}\"

    عملگر ‎ <<<‎ مختص bash (نگارش 2.05b و بعد) می‌باشد، به هرحال یک روش بسیار پاکیزه و سودمند برای تعیین یک رشته کوچک لفظی برای ورودی فرمان است.

  • با شل POSIX، یا برای داده بلندتر چندسطری، می‌توانید به جای آن از here document استفاده کنید:
        # Bash
        declare -i linecnt
        while read -r; do
            ((linecnt++))
        done <<EOF
        hi
        ho
        hum
        EOF
        printf \'total number of lines: %d\' \"$linecnt\"
  • استفاده از lastpipe (در Bash نگارش 4.2)
         # Bash 4.2
         set +m
         shopt -s lastpipe
    
         printf \'%s\\n\' hi{,,,,,} | while read -r \"lines[x++]\"; do :; done
         printf \'total number of lines: %d\' \"${#lines[@]}\"
    Bash نگارش 4.2 رفتار فوق‌الذکرِ kshمانند در Bash را معرفی نموده. یک هُشدار آن است که کنترل job نباید فعال باشد، که به این وسیله سودمندی آن در یک پوسته محاوره‌ای محدود می‌گردد.

برای مثالهای بیشتری در ارتباط با چگونگی خواندن ورودی و خُرد کردن آن به کلمات، پرسش و پاسخ شماره 1 را ببینید.


CategoryShell

پرسش و پاسخ 24 (آخرین ویرایش ‎ 2012-03-05 03:38:07 ‎ توسط ormaaj)




به سیاره لینوکس امتیاز دهید

به اين صفحه امتياز دهيد