آموزش‌های خط فرمانی

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

آموزش‌های خط فرمانی

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

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



چطور می‌توانم پارامترهای موضعی(مکانی) بعد از ‎ $9‎ را دستیابی کنم

به جای ‎$10‎ از ‎${10}‎ استفاده کنید. این روش در BASH و KornShell کار می‌کند، اما در پیاده‌سازی‌های قدیمی BourneShell کار نمی‌کند. یک روش دیگر برای دستیابی به پارامترهای مکانی بعد از ‎ $9‎ استفاده از for است، به عنوان مثال, برای به دست آوردن آخرین پارامتر:

    # Bourne
    for last
    do
        : # هیچ
    done

    echo "last argument is: $last"

برای به دست آوردن یک شناسه با عدد، می‌توانیم از شمارشگر استفاده کنیم:

    # Bourne
    n=12        # این شماره شناسه‌ای می‌باشد که مورد نظر است
    i=1
    for arg
    do
        if test $i -eq $n
        then
            argn=$arg
            break
        fi
        i=`expr $i + 1`
    done
    echo "argument number $n is: $argn"

مزیت این روش ازبین نرفتن شناسه‌ها است. اگر مشکلی نیست، فرمان shift پارامترهای مکانی قبلی را دور می‌اندازد:

    shift 11
    echo "the 12th argument is: $1"

به علاوه، bash و ksh93 با مجموعه پارامترهای مکانی به عنوان یک آرایه رفتار می‌کنند،و شما می‌توانید از ترکیب دستوری بسط پارامتر برای آدرس دادن به عناصر آن از راههای مختلف استفاده کنید :

    # Bash, ksh93
    for x in "${@:(-2)}"    # روی دو پارامتر آخر تکرار می‌کند‎
    for y in "${@:2}"       # روی تمام پارامترها تکرار می‌شود  ‎ $2‎ با شروع از ‎
           # استفاده کنیم مفید باشد shift که شاید برای موقعی که نمی‌خواهیم از

اگرچه دستیابی مستقیم به هر پارامتر مکانی به این روش امکان‌پذیر است، به ندرت لازم می‌شود. جایگزین رایج آن استفاده از getopts برای پردازش گزینه‌ها(به عنوان مثال، ‎ "-l"‎ یا ‎"-o filename"‎)، و سپس به کار بردن حلقه for یا while برای پردازش تمام شناسه‌ها یکی پس از دیگری است. یک توضیح از چگونگی پردازش شناسه‌های خط فرمان در پرسش و پاسخ شماره 35 در دسترس می‌باشد، و یک توضیح دیگر در http://www.shelldorado.com/goodcoding/cmdargs.html یافت می‌شود.


CategoryShell

پرسش و پاسخ 25 (آخرین ویرایش ‎ 2009-12-30 18:09:05 ‎ توسط MatthiasPopp)


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



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

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

    # فعال شده کار می‌کند 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)


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



من می‌خواهم به پوسته محاوره‌ای که مستعارها و توابع ویژه‌ای دارد، نه آنها که در ‎ ~/.bashrc‎ کاربر هست، متصل شوم.

فقط یک فایل شروع اولیه(start-up) متفاوت تعیین کنید:

bash --rcfile /my/custom/bashrc

نوع دیگر پرسش: من اسکریپتی دارم که یک محیط را تنظیم می‌کند، و من می‌خواهم در انتهای آن، کنترل را به کاربر بدهم.

در انتهای اسکریپت دستور ‎ exec bash‎ را برای آغاز کردن پوسته محاوره‌ای قرار بدهید. این پوسته از محیط ارث می‌برد(که شامل مستعارها نیست، اما اشکالی ندارد، زیرا مستعارها بسیار بد می‌باشند). البته، همچنین باید مطمئن شوید که اسکریپت شما در یک ترمینال اجرا می‌شود -- در غیر اینصورت، باید به عنوان مثال با استفاده از دستور ‎ exec xterm -e bash‎ آنرا ایجاد کنید.


CategoryShell

پرسش و پاسخ 23 (آخرین ویرایش ‎ 2010-08-20 12:27:03 ‎ توسط rrcs-24-39-102-30)


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



چگونه می‌توانم به جای فقط اعداد صحیح، اعداد با ممیز شناور را در محاسبه استفاده کنم؟

حسابگر داخلی BASH فقط از اعداد صحیح استفاده می‌کند:

$ echo $((10/3))
3

برای اکثر عملیات درگیر با اعداد دارای ممیزشناور، باید از یک برنامه خارجی استفاده شود، به عنوان نمونه از bc، AWK یا dc:

$ echo "scale=3; 10/3" | bc
3.333

فرمان ‎ "scale=3"‎ به برنامه bc اعلام می‌کند که سه رقم دقت بعد از نقطه اعشاری لازم است.

همان مثال با dc( ماشین حساب شسته رفته و سبُک‌تر از bc):

$ echo "3 k 10 3 / p" | dc
3.333

k دقت اعشار را به 3 رقم تنظیم می‌کند، و p بالاترین قسمت پُشته را با یک کاراکتر سطر جدید چاپ می‌کند. به هر حال، پُشته جایگزین نمی‌شود.

اگر می‌خواهید اعداد اعشاری را مقایسه کنید(کوچکتر از، یا بزرگتر از) ، و bc گنو را دارید، می‌توانید چنین کنید:

# Bash
if (( $(bc <<< "1.4 < 2.5") )); then
  echo "1.4 is less than 2.5."
fi

اما،‎ x < y‎ توسط تمام نگارشهای bc پشتیبانی نمی‌شود:

# خیر HP-UX 10.20 این با برخی نگارشها کار می‌کند اما با
imadev:~$ bc <<< '1 < 2'
syntax error on line 1,

اگر می‌خواهید قابل حمل باشید، به مورد ماهرانه‌تری نیاز دارید:

# POSIX
case $(echo "1.4 - 2.5" | bc) in
  -*) echo "1.4 is less than 2.5";;
esac

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

میراث نگارش (Bourne):

# Bourne
case "`echo "1.4 - 2.5" | bc`" in
  -*) echo "1.4 is less than 2.5";;
esac

AWK نیز می‌تواند برای محاسبات به کار برود:

$ awk 'BEGIN {printf "%.3f\n", 10 / 3}'
3.333

در اینجا تفاوت ظریف اما مهمی بین راه حل bc و awk وجود دارد: bc فرمانها و عبارات را از ورودی استاندارد می‌خواند. از طرف دیگر awk عبارت را به عنوان بخشی از برنامه برآورد می‌کند. عبارتها در ورودی استاندارد سنجیده نمی‌شوند، یعنی فرمان ‎echo 10/3 | awk '{print $0}'‎ به جای ارزیابی، نتیجه عبارت ‎ 10/3‎ را چاپ می‌کند.

نگارشهای جدیدتر پوسته zsh و پوسته Korn یک حساب ممیز شناور داخلی، همراه با توابعی مانند‎ sin()‎ یا‎ cos()‎ در خود دارند. بنابراین بسیاری از این محاسبات، در ksh می‌توانند به طور بومی انجام شوند:

# ksh93
$ echo $((3.00000000000/7))
0.428571428571428571

مقایسه برابری دو عدد با ممیز شناور ، در واقع کار نابخردانه‌ای می‌باشد، دو محاسبه‌ای که روی کاغذ نتیجه یکسانی ارائه خواهند داد ممکن است در نتایج عددی ممیز شناور تفاوت بسیار جزئی ناشی از گِرد کردن و کوتاه‌سازی نشان بدهد. اگر می‌خواهید تعیین کنید که آیا دو عدد با ممیز شناور یکسان هستند، می‌توانید:

  • یا هر دو عدد را با دقت یکسان گِرد کنید، و بعد نتایج گِرد شده را برای برابری مقایسه کنید، یا
  • یک عدد را از دیگری تفریق نموده و مقدار خالص تفاضل را با مقدار ناچیزی به انتخاب خود مقایسه نمایید.

یکی از موارد بسیار اندکی که Bash به طور واقعی می‌تواند با اعداد ممیز شناور انجام بدهد، گرد نمودن آنها با استفاده از printf است:

# Bash 3.1
# به یکدیگر نزدیک باشند b و a ببینید اگر
# گرد کردن آنها تا دو رقم اعشار و مقایسه نتایج به عنوان رشته‌ را
a=3.002 b=2.998
printf -v a1 %.2f $a
printf -v b1 %.2f $b
if [[ $a1 = "$b1" ]]; then echo "a and b are the same, roughly"; fi

پیش‌بینی احتیاطی: بسیاری از مشکلاتی که به محاسبات ممیز شناور شباهت دارند در حقیقت می‌توانند فقط با اعداد صحیح حل بشوند، و بنابراین به این ابزارها نیازی نمی‌باشد-- به عنوان مثال، مشکلات مربوط به اعداد کسری. برای نمونه، جهت کنترل اینکه آیا نسبت دو عدد x و y، برابر با نسبت ‎   4:3 ‎ یا ‎ 16:9‎ می‌باشد، می‌توانید از موردی مشابه این سطرها استفاده کنید:

# Bash
# Variables x and y are integers
if (( $x*9-$y*16==0 )) ; then
   echo "16:9."
elif (( $x*3-$y*4==0 )) ; then
   echo "4:3."
else
   echo "Neither 16:9 nor 4:3."
fi

در یک بررسی استادانه‌تر، بدون استفاده از محاسبات ممیز شناور، می‌توانست تشخیص داده شود نزدیکترین نسبت 4:3 است یا 16:9. توجه نمایید که این مثال بسیار ساده‌ای می‌باشد و ظاهراً اعداد ممیز شناور و تقسیم را در گیر می‌سازد، که با اعداد صحیح و بدون تقسیم حل می‌شود. اگر امکان داشته باشد، به طور معمول تبدیل مشکل شما به محاسبات صحیح، ثمربخش‌تر از به کار بردن محاسبات ممیز شناور است.


CategoryShell

پرسش و پاسخ 22 (آخرین ویرایش ‎ed 2012-11-26 10:07:41 ‎ توسط geirha)


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



چگونه می‌توانم یک رشته را با رشته دیگری در یک متغیر، جریان داده، یک فایل، یا تمام فایلهای یک شاخه، تعویض نمایم؟

یک تعداد ابزار برای این مورد در دسترس است. که استفاده از هریک به عوامل زیادی بستگی دارد، البته مهمترین عامل آن است که چه چیزی را ویرایش می‌کنیم.

فایلها

ویرایش فایلها مستلزم دقت است. تنها ابزار استاندارد که به طور واقعی فایل را ویرایش می‌کند ed است. سایر روشها می‌توانند به کار بروند، اما آنها با فایل موقتی و mv درگیر می‌شوند (یا ابزارهای غیر استاندارد، یاملحقات POSIX).

ed ویرایشگر خط فرمانی استاندارد UNIX است. در اینجا برخی ترکیب های دستوری رایج برای تعویض رشته olddomain.com با رشته newdomain.com در فایلی به نام file آمده است. هر چهار فرمان کار یکسانی را با درجات متفاوتی از قابلیت حمل و کارایی انجام می‌دهند:

# Bash   در پوسته
ed -s file <<< $'g/olddomain\\.com/s//newdomain.com/g\nw\nq'

#  printf با وجود  Bourne  در پوسته 
printf '%s\n' 'g/olddomain\.com/s//newdomain.com/g' w q | ed -s file

printf 'g/olddomain\\.com/s//newdomain.com/g\nw\nq' | ed -s file

#  printf بدون وجود Bourne در پوسته    
ed -s file <<!
g/olddomain\\.com/s//newdomain.com/g
w
q
!

برای تعویض یک رشته در تمام فایلهای شاخه جاری، فقط یکی از موارد فوق را در حلقه قرار بدهید:

for file in ./*; do
    [[ -f $file ]] && ed -s "$file" <<< $'g/old/s//new/g\nw\nq'
done

برای انجام این کار به صورت بازگشتی، آسانترین راه فعال نمودن globstar در bash نگارش 4 (‎shopt -s globstar‎، یک ایده خوب، قرار دادن این دستور در فایل ‎~/.bashrc‎ خودتان است) و استفاده از این کد است:

for file in ./**/*; do
    [[ -f $file ]] && ed -s "$file" <<< $'g/old/s//new/g\nw\nq'
done

اگر شما bash نگارش 4 ندارید، می‌توانید از find استفاده کنید. متأسفانه، تغذیه ورودی استاندارد ed با یکایک فایلها اندکی کسل کننده است:

find . -type f -exec bash -c 'printf "%s\n" "g/old/s//new/g" w q | ed -s "$1"' _ {} \;

اگر متغیرهای پوسته به عنوان رشته‌های جایگزینی یا جستجو به کار بروند، ed مناسب نیست. نه sed، نه هیچ ابزاری که از عبارتهای منظم استفاده ‌کند، مناسب نمی‌باشند. استفاده از کُد awk همراه با تغییر مسیرها و mv را در انتهای این پرسش و پاسخ ملاحظه کنید.

gsub_literal "$search" "$rep" < "$file" > tmp && mv tmp "$file"

1. کاربرد ابزارهای غیر استاندارد

sed ویرایشگر جریانی است، نه ویرایشگر فایل. با وجود این، اشخاص اصرار دارند آن را به طور نامناسب در هرجایی جهت ویرایش فایلها به کار ببرند. این برنامه فایلها را ویرایش نمی‌کند. sed گنو(و بعضی sedهای BSD) دارای گزینه ‎ -i‎ می‌باشند که یک کپی ایجاد می‌کند و فایل اصلی را با کپی تعویض می‌کند. یک عملیات پر خرج، اما اگر شما از کُد غیرقابل حمل، I/O بالاسری، اثرات جانبی نامساعد(از قبیل خرابی پیوندهای نمادین) لذت می‌برید، این می‌تواند یک انتخاب باشد:

sed -i    's/old/new/g' ./*  # GNU
sed -i '' 's/old/new/g' ./*  # FreeBSD

افرادی از شما که پرل نگارش 5 دارند، می‌توانند همان کار را با استفاده از این کُد انجام بدهند:

perl -pi -e 's/old/new/g' ./*

کاربرد بازگشتی find:

find . -type f -exec perl -pi -e 's/old/new/g' {} \;  # شما هنوز + ندارد find اگر
find . -type f -exec perl -pi -e 's/old/new/g' {} +   # اگر دارد

اگر در عوض جایگزینی می‌خواهید سطرها را حذف کنید:

 # را حذف می‌کند foo  هر سطر شامل عبارت منظم پرل   
perl -ni -e 'print unless /foo/' ./*

برای جایگزینی تمام "unsigned" با "unsigned long" در همان مثال، در صورتی که "unsigned int" یا "unsigned long" نباشند ...:

find . -type f -exec perl -i.bak -pne \
    's/\bunsigned\b(?!\s+(int|short|long|char))/unsigned long/g' {} \;

تمام مثالهای فوق از عبارتهای منظم استفاده می‌کنند، به آن معنی که، دارای مشکلاتی همانند کُد sed قبلی می‌باشند، ایده آزمایش تعبیه متغیرهای پوسته در آنها ایده‌ای ترسناک است، و رفتار با یک کمیت انتخابی به عنوان رشته لفظی، در بهترین حالت نیز ناراحت کننده است.

علاوه براین، پرل، بدون آنکه استعداد نهانی تصادم با کاراکترهای علائم را داشته باشد، می‌تواند برای عبور دادن متغیرها به درون هم رشته‌های جستجو و هم رشته‌های جایگزین شونده به کار برود:

in="input (/string" out="output string" perl -pi -e $'$quoted_in=quotemeta($ENV{\'in\'}); s/$quoted_in/$ENV{\'out\'}/g' ./*

یا، ساده‌تر:

in=$search out=$replace perl -pi -e 's/\Q$ENV{"in"}/\Q$env{"out"}/g' ./*

متغیرها

اگر یک متغیر است، این کار می‌تواند(و باید) به طور خیلی ساده با بسط پارامتر Bash انجام بشود:

var='some string'; search=some; rep=another

 # Bash در پوسته
var=${var//"$search"/$rep}

در POSIX خیلی دشوارتر است:

 # POSIX تابع

 #  string_rep SEARCH REPL STRING  طرز استفاده  ‎
 # تعویض می‌کند STRING  در REPL را با SEARCH تمام نمونه‌های  
string_rep() {
 # ارزش گذاری متغیرها
  in=$3
  unset out

 #  نباید خالی باشد SEARCH متغیر
  test -n "$1" || return

  while true; do
    #  نباشد حلقه قطع می‌شود"$in" دیگر در SEARCH اگر 
    case "$in" in
      *"$1"*) : ;;
      *) break;;
    esac

 # الحاق می‌کند "$out" را به REPL و SERCH راتا رسیدن به اولین نمونه "$in" محتوای 
    out=$out${in%%"$1"*}$2
 #  حذف می‌کند "$in" و هر چه قبل از آن هست را از SEARCH اولین نمونه 
    in=${in#*"$1"}
  done

# پیوست و نتیجه را چاپ می‌کند $out مانده است به "$in" در SEARCH آنچه پس از آخرین 
  printf '%s%s\n' "$out" "$in"
}

var=$(string_rep "$search" "$rep" "$var")
#    :توجه  

POSIX راهی برای محلی نمودن متغیرها ندارد. اکثر پوسته‌ها(حتی dash وbusybox) دارند # به هرحال اگر پوسته شما پشتیبانی می‌کند، با خیال راحت آنرا انجام بدهید. اما اگر # پشتیبانی نمی‌کند، حتی اگر تابع را به صورت ‎ var=$(string_rep ...)‎ فراخوانی کنید، # باز هم تابع در یک پوسته فرعی اجرا خواهد شد و هیچ تخصیصی ایستادگی نخواهد نمود. #

در مثال bash، نقل‌قولها در اطراف ‎ "$search"‎ از این که با متغیرها به عنوان الگوی پوسته(که همچنین به عنوان glob نیز شناخته می‌شوند) رفتار شود، پیش‌گیری می‌کنند. البته، اگر مطابقت الگو مورد نظر است، نقل‌قولها را الحاق نکنید. اگر ‎ "$rep"‎ نقل‌قولی شده بود، به هرحال با نقل‌قولها به صورت لفظی رفتار می‌گردید.

بسط‌های پارامترِ مانند این، به طور مفصل‌تر در پرسش و پاسخ شماره 100 بحث شده است.

جریانها

اگر جریان است، بنابراین از stream editor(ویرایشگر جریانی sed) استفاده کنید:

some_command | sed 's/foo/bar/g'

sed از عبارت‌های منظم استفاده می‌کند. در مثال ما، foo و bar رشته‌های لفظی هستند. اگر آنها متغیر بودند(به عنوان مثال، ورودی کاربر)، به منظور اجتناب از خطاها، آنها می‌بایست با دقت بسیار پوشش داده می‌شدند. این کار خیلی غیر عملی است، و کوشش برای انجام آن کُد شما را بینهایت مستعد باگها می‌نماید. تعبیه متغیرهای پوسته در فرمانهای sed هرگز ایده خوبی نیست.

شما این کار را می‌توانستید در خود Bash توسط ترکیب بسط پارامتر با پرسش و پاسخ شماره 1 انجام بدهید:

search=foo; rep=bar

while IFS= read -r line; do
  printf '%s\n' "${line//"$search"/$rep}"
done < <(some_command)

some_command | while IFS= read -r line; do
  printf '%s\n' "${line//"$search"/$rep}"
done

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

شاید متوجه شده باشید، به هرحال آن حلقه bash بالا برای مجموعه بزرگ داده‌ها خیلی کُند است. بنابراین چطور موردی پیدا کنیم که بتواند رشته‌های لفظی را تعویض نماید؟ خوب، می‌توانستید AWK را به کار ببرید. تابع ذیل با خواندن از ورودی استاندارد و نوشتن در خروجی استاندارد، تمام نمونه‌های STR را با REP تعویض می‌کند.

# gsub_literal STR REP      : نحوه استفاده 
# stdout و نوشتن در stdin تعویض می‌کند با خواندن از REP را با STR تمام نمونه‌های 
gsub_literal() {
  # نمی‌تواند خالی باشد STR
  [[ $1 ]] || return

  # string manip needed to escape '\'s, so awk doesn't expand '\n' and such
  awk -v str="${1//\\/\\\\}" -v rep="${2//\\/\\\\}" '
    # طول رشته جستجو را به دست می‌آورد
    BEGIN {
      len = length(str);
    }

    {
      # رشته خروجی تهی
      out = "";

      # ادامه حلقه تا موقعی که رشته جستجو در سطر هست
      while (i = index($0, str)) {
        # پیوست کردن هر چیز قبل از رشته جستجو و رشته جایگزین 
        out = out substr($0, 1, i-1) rep;

        # حذف اولین رشته جستجو و هر چیز قبل از آن از سطر ‎
        $0 = substr($0, i + len);
      }

      # پیوست نمودن آنچه باقی مانده است
      out = out $0;

      print out;
    }
  '
}

some_command | gsub_literal "$search" "$rep"


# :خلاصه شده در یک سطر
some_command | awk -v s="${search//\\/\\\\}" -v r="${rep//\\/\\\\}" 'BEGIN {l=length(s)} {o="";while (i=index($0, s)) {o=o substr($0,1,i-1) r; $0=substr($0,i+l)} print o $0}'


CategoryShell

پرسش و پاسخ 21 (آخرین ویرایش ‎ 2012-08-12 19:11:59 ‎ توسط e36freak)