فصل ‎10‎- دستکاری متغیرها

‎10.1‎- دستکاری رشته‌ها

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

طول رشته

‎${#string}‎

‎expr length $string‎

اینها معادل‌های ‎strlen()‎ در C هستند.

‎expr "$string" : '.*'‎

stringZ=abcABC123ABCabc

echo ${#stringZ}                 # 15
echo `expr length $stringZ`      # 15
echo `expr "$stringZ" : '.*'`    # 15

مثال ‎10-1‎. درج یک سطر خالی بین پاراگراف‌ها در یک فایل متن

#!/bin/bash
# paragraph-space.sh
# Ver. 2.1, Reldate 29Jul12 [fixup]

# .درج یک سطر خالی میان پاراگراف‌های یک فایل متنِ بدون فضای سفید
# Usage: $0 <FILENAME

MINLEN=60        #  .تغییر این مقدار؟ به نظر خودتان بستگی دارد
#  فرض کنید سطرهای کوتاهتر از ‎$MINLEN‎ کاراکتر که با یک نقطه ختم
#+می‌شوند، یک پاراگراف را خاتمه می‌دهند. تمرین‌های زیر را ببینید.

while read line  # .برای هر تعداد سطری که فایل ورودی دارد
do
  echo "$line"   # .بیرون دادن خود سطر

  len=${#line}
  if [[ "$len" -lt "$MINLEN" && "$line" =~ [*{\.}]$ ]]
# ‎if [[ "$len" -lt "$MINLEN" && "$line" =~ \[*\.\] ]]‎
#   یک به‌روزرسانی برای ‎Bash‎ نگارش قبلی این اسکریپت را نقض کرد.‏ آخ!‏
# با تشکر از ‎Halim Srama‎ برای اشاره به این مورد و پیشنهاد اصلاحیه.
    then echo            #  اضافه کردن یک سطر خالی بلافاصله بعد از
  fi                     #+  .یک سطر کوتاه خاتمه یافته با یک نقطه
done
exit

#                        :تمرین‌ها
#                        ---------
#    اسکریپت معمولاً یک سطر خالی در انتهای فایل هدف    ‎1)‎
#+                اضافه می‌کند. این مورد را اصلاح کنید. 
#    سطر ‎17‎ فقط نقطه را به عنوان خاتمه‌دهنده در نظر   ‎2)‎
#   می‌گیرد. این مورد را ویرایش کنید تا سایر کاراکترهای
#+          انتهای جمله از قبیل ‎?‎‏، ‎!‎، و ‎"‎ را نیز شامل شود.

طول رشته فرعی مطابقت یافته از ابتدای رشته

‎expr match "$string" '$substring'‎

‎$substring‎ یک عبارت منظم است.

‎expr "$string" : '$substring'‎

‎$substring‎ یک عبارت منظم است.

stringZ=abcABC123ABCabc
#       |------|
#       12345678

echo `expr match "$stringZ" 'abc[A-Z]*.2'`   # 8
echo `expr "$stringZ" : 'abc[A-Z]*.2'`       # 8

Index

‎expr index $string $substring‎

شماره اولین موقعیتی در ‎$string‎ که کاراکتری از ‎$substring‎ نخست در آنجا منطبق می‌شود.

stringZ=abcABC123ABCabc
#       123456 ...
echo `expr index "$stringZ" C12`             # 6
                                             # C موقعیت
echo `expr index "$stringZ" 1c`              # 3
# ‎'c'‎ (در موقعیت شماره ‎3‎ ) قبل از ‎'1'‎ منطبق می‌شود.

این معادل تقریبی ‎strchr()‎ در C است.

استخراج رشته فرعی

‎${string:position}‎

رشته فرعی از موقعیت ‎$position‎ تا انتهای رشته ‎$string‎ استخراج می‌کند.

اگر به جای ‎string‎ کاراکتر * یا @ قرار گیرد، آنوقت این ترکیب، پارامترهای مکانی را با شروع از پارامتر شماره ‎$position‎ استخراج می‌کند.‎[1]‎

‎${string:position:length}‎

یک رشته فرعی به طول ‎$length‎ کاراکتر از محل ‎$position‎ از رشته ‎$string‎ استخراج می‌کند.

stringZ=abcABC123ABCabc
#       0123456789.....
#       شاخص‌گذاری از صفر

echo ${stringZ:0}                    # abcABC123ABCabc
echo ${stringZ:1}                    # bcABC123ABCabc
echo ${stringZ:7}                    # 23ABCabc

echo ${stringZ:7:3}                  # 23A
                                     # .سه کاراکتر رشته فرعی


#       آیا شاخص‌گذاری از انتهای سمت راست رشته میسر است؟
    
echo ${stringZ:-4}                   #  abcABC123ABCabc
# رشته کامل، همچون در ‎${parameter:-default}‎ تعبیر می‌کند.
                                     # . . . اما

echo ${stringZ:(-4)}                 # Cabc
echo ${stringZ: -4}                  # Cabc
                                     #    .اکنون، کار می‌کند
# پرانتزها یا فاصله اضافه شده پارامتر موقعیت را پوشش می‌دهند.

#           با تشکر از ‎Dan Jacobson‎ برای روشن کردن این مطلب.

شناسه‌های position و length می‌توانند «پارامتری» بشوند، یعنی به جای یک ثابت عددی به صورت یک متغیر بیان گردند.

مثال ‎10-2‎. تولید یک رشته «تصادفی» ۸ کاراکتری

#!/bin/bash
# rand-string.sh
# .تولید یک رشته «تصادفی» ۸ کاراکتری

if [ -n "$1" ]   #     اگر شناسه خط فرمان وجود دارد
then             #+.آنوقت، تنظیم رشته ابتدایی به آن
  str0="$1"
else             # وگرنه از ‎PID‎ اسکریپت به عنوان رشته شروع استفاده شود.
  str0="$$"
fi

POS=2            # شروع از موقعیت ‎2‎ در رشته.
LEN=8            #        .استخراج ۸ کاراکتر

str1=$( echo "$str0" | md5sum | md5sum )
                 #      درهم آمیختن دوگانه  ‎^^^^^^   ^^^^^^‎
                 #+    ‎md5sum‎ با لوله‌کشی و لوله‌کشی مجدد به

randstring="${str1:$POS:$LEN}"
                 #  اینها می‌توانند پارامتر باشند  ^^^^ ^^^^

echo "$randstring"

exit $?

# bozo$ ./rand-string.sh my-password
# 1bdd88c4

#  خیر، این به عنوان یک شیوه تولید
#+ .کلمه عبور ضد-هک پیشنهاد نمی‌گردد

اگر به جای ‎string‎ کاراکتر * یا @ قرار گیرد، آنوقت این ترکیب، حداکثر به تعداد ‎$length‎ پارامتر مکانی را با شروع از پارامتر شماره ‎$position‎ استخراج می‌کند.

echo ${*:2}          # .پارامترهای مکانی دوم و بعد از آن را منعکس می‌کند
echo ${@:2}          # .مشابه مورد بالا

echo ${*:2:3}        # .با شروع از دومی، سه پارامتر مکانی نمایش می‌دهد

expr substr $string $position $length

تعداد ‎$length‎ کاراکتر از رشته ‎$string‎ با شروع از محل ‎$position‎ استخراج می‌کند.

stringZ=abcABC123ABCabc
#       123456789......
#       ‎1‎ شاخص‌گذاری از

echo `expr substr $stringZ 1 2`              # ab
echo `expr substr $stringZ 4 3`              # ABC

‎expr match "$string" '\($substring\)'‎

‎$substring‎ را از ابتدای رشته ‎$string‎ استخراج می‌کند، که در اینجا ‎$substring‎ یک عبارت منظم است.

‎expr "$string" : '\($substring\)'‎

‎$substring‎ در ابتدای رشته ‎$string‎ را استخراج می‌کند، که ‎$substring‎ یک عبارت منظم است.

stringZ=abcABC123ABCabc
#       =======	    
echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'`   # abcABC1
echo `expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)'`       # abcABC1
echo `expr "$stringZ" : '\(.......\)'`                   # abcABC1
# .تمام روایت‌های فوق نتیجه یکسانی ارایه می‌کنند

‎expr match "$string" '.*\($substring\)'‎

‎$substring‎ را از انتهای رشته ‎$string‎ استخراج می‌کند، که ‎$substring‎ در آن یک عبارت منظم است.

‎expr "$string" : '.*\($substring\)'‎

‎$substring‎ را از انتهای رشته ‎$string‎ استخراج می‌کند، که در آن ‎$substring‎ یک عبارت منظم است.

stringZ=abcABC123ABCabc
#                ======

echo `expr match "$stringZ" '.*\([A-C][A-C][A-C][a-c]*\)'`    # ABCabc
echo `expr "$stringZ" : '.*\(......\)'`                       # ABCabc

پاک کردن رشته فرعی

‎${string#substring}‎

کوتاهترین انطباق ‎$substring‎ را از جلوی ‎$string‎ حذف می‌کند.

‎${string##substring}‎

بلندترین مورد انطباق ‎$substring‎ را از جلوی رشته ‎$string‎ حذف می‌کند.

stringZ=abcABC123ABCabc
#       |----|          کوتاهترین
#       |----------|    بلندترین

echo ${stringZ#a*C}      # 123ABCabc
#         پاک کردن کوتاهترین مورد انطباق بین ‎'a'‎ و ‎'C'‎.

echo ${stringZ##a*C}     # abc
#          پاک کردن بلندترین مورد انطباق بین ‎'a'‎ و ‎'C'‎.

# .می‌توانید رشته‌های فرعی را به صورت پارامتر ارایه کنید

X='a*C'

echo ${stringZ#$X}      # 123ABCabc
echo ${stringZ##$X}     # abc
                        # .مانند مورد فوق

‎${string%substring}‎

کوتاهترین مورد انطباق ‎$substring‎ را از انتهای ‎$string‎ حذف می‌کند.

برای مثال:

#  تغییر تمام پسوندهای ‎«TXT»‎ فایلهای داخل ‎$PWD‎ با پسوند «txt»
#          به عنوان مثال، ‎"file1.TXT"‎ می‌شود ‎"file1.txt"‎ . . .

SUFF=TXT
suff=txt

for i in $(ls *.$SUFF)
do
  mv -f $i ${i%.$SUFF}.$suff
       #  بدون تغییر باقی گذاشتن هر چیزی غیر از کوتاهترین
       #+       الگوی منطبق شده از سمت راست متغیر ‎$i‎ . . .
done ### .اگر مطلوب باشد، می‌تواند در کد یک سطری خلاصه گردد

# با تشکر از ‎Rory Winston‎

‎${string%%substring}‎

طولانی‌ترین مورد تطبیق ‎$substring‎ از انتهای رشته ‎$string‎ حذف می‌شود.

stringZ=abcABC123ABCabc
#                    ||     کوتاهترین
#        |------------|      بلندترین

echo ${stringZ%b*c}      # abcABC123ABCa
# کوتاهترین مورد انطباق بین ‎'b'‎ و ‎'c'‎ با انتهای رشته ‎$stringZ‎ را پاک می‌کند.

echo ${stringZ%%b*c}     # a
#   بلندترین مورد تطبیق بین ‎'b'‎ و ‎'c'‎ با انتهای رشته ‎$stringZ‎ را پاک می‌کند.

این عامل برای تولید نام فایلها سودمند است.

مثال ‎10-3‎. تبدیل قالب فایلهای گرافیکی، با تغییر نام فایل

#!/bin/bash
#  cvt.sh:
#  تمام فایهای تصویری ‎MacPaint‎ در یک دایرکتوری را به قالب ‎"pbm"‎ تبدیل می‌کند.

#   برنامه ‎"macptopbm"‎ از بسته ‎"netpbm"‎ را به کار می‌برد، که توسط
#+     ‎Brian Henderson (bryanh@giraffe-data.com)‎ پشتیبانی می‌شود.
#              ‎Netpbm‎ یک بخش استاندارد اکثر توزیع‌های لینوکس است.

OPERATION=macptopbm
SUFFIX=pbm          # .پسوند جدید نام فایل 

if [ -n "$1" ]
then
  directory=$1      # اگر نام دایرکتوری به عنوان شناسه به اسکریپت داده شده
else
  directory=$PWD    #       .در غیر اینصورت استفاده از دایرکتوری کاری جاری
fi  
  
#   فرض می‌کند تمام فایلها در دایرکتوری هدف فایلهای تصویری
#+      ‎MacPaint‎ با یک پسوند نام فایل به صورت ‎.mac‎ هستند.

for file in $directory/*    # .جانشینی نام فایل
do
  filename=${file%.*c}      #      زدودن پسوند ‎.mac‎ از نام فایل
                            #+  (‎'.*c'‎ ‎با هر چیزی بین ‎'.'‎ و ‎'c'‎
			    #+   و شامل خود آنها، منطبق می‌شود).
  $OPERATION $file > "$filename.$SUFFIX"
                            #   .تغییر مسیر تبدیل به نام فایل جدید
  rm -f $file               #    .حذف فایلهای اصلی پس از تبدیل شدن   
  echo "$filename.$SUFFIX"  # .ثبت آنچه رخ می‌دهد در خروج استاندارد
done

exit 0

#                                  تمرین‌ها
#                                -----------
# این اسکریپت «تمام» فایلهای دایرکتوری کاری جاری را تبدیل می‌کند. طوری
#   آن را ویرایش کنید که «فقط» بر فایلهای دارای پسوند ‎".mac"‎ عمل کند.



#                   *** .و این هم یک روش دیگر برای انجام آن کار *** #

#!/bin/bash
#                             .تبدیل دسته‌ای به قالب‌های گرافیکی متفاوت
#  فرض می‌کند ‎imagemagick‎ نصب شده است (استانداردِ اکثر توزیع‌های لینوکس)‏.

INFMT=png                  #       می‌تواند tif‏، jpg‏، gif‏، و غیره باشد.
OUTFMT=pdf                 #  می‌تواند tif‏، jpg‏، gif‏، pdf‏، و غیره باشد.

for pic in *"$INFMT"
do
  p2=$(ls "$pic" | sed -e s/\.$INFMT//)
  # echo $p2
    convert "$pic" $p2.$OUTFMT
    done

exit $?

مثال ‎10-4‎. تبدیل فایلهای صوتی جریانی به ogg

#!/bin/bash
# ‎ra2ogg.sh‎: تبدیل فایلهای صوتی جریانی ‎(*.ra)‎ به ogg.

# از برنامه پخش  صوتی وتصویری ‎"mplayer"‎ استفاده می‌کند:
#      http://www.mplayerhq.hu/homepage
#   فایلهای کتابخانه ‎"ogg"‎ و ‎"oggenc"‎ را به کار می‌برد:
#      http://www.xiph.org/
#
#  این اسکریپت ممکن است به codecهای نصب شده مناسب از قبیل ‎sipr.so‎ 
#       و همچنین احتمالاً به بسته ‎compat-libstdc++‎ نیاز داشته باشد.


OFILEPREF=${1%%ra}      #     زدودن پسوند ‎"ra"‎
OFILESUFF=wav           #  پسوند برای فایل ‎wav‎
OUTFILE="$OFILEPREF""$OFILESUFF"
E_NOARGS=85

if [ -z "$1" ]          # .باید یک نام فایل برای تبدیل تعیین بشود
then
  echo "Usage: `basename $0` [filename]"
  exit $E_NOARGS
fi


##########################################################################
mplayer "$1" -ao pcm:file=$OUTFILE
oggenc "$OUTFILE"  # پسوند صحیح فایل به طور خودکار توسط ‎oggenc‎ اضافه شده.
##########################################################################

rm "$OUTFILE"      #                               حذف فایل واسطه ‎*.wav‎
                   # .اگر می‌خواهید آن را حفظ کنید،سطر فوق را توضیح کنید

exit $?

#                             نکته:
#                          --------
#     در یک وب‌سایت، معمولاً به آسانی با کلیک روی یک فایل صوتی 
#+  جریانی ‎*.ram‎  فقط آدرس ‎URL‎ فایل صوتی واقعی ‎*.ra‎ دریافت
#   می‌شود. سپس شما می‌توانید با ‎"wget"‎ یا موردی مشابه آن، خود
#+                             فایل ‎*.ra‎ را دانلود نمایید.


#                           تمرین‌ها
#                         ------------
#     مطابق معمول، اسکریپت فقط نام فایلهای ‎*.ra را تبدیل می‌کند. با اجازهِ
#  استفاده از ‎*.ram‎ و سایر نام فایلها، قابلیت انعطاف آن را افزایش بدهید.
#
#   اگر واقعاً مشتاق هستید، اسکریپت رابرای انجام دانلود
#+     .خودکار و تبدیل فایلهای صوتی جریانی گسترش بدهید
#     یک ‎URL‎ مشخص شده، دانلود دسته‌ای فایلهای صوتی جریانی
#+             (با استفاده از wget) و تبدیل درجای آنها.

یک شبیه‌سازی ساده از getopt با استفاده از ساختارهای استخراج رشته‌ فرعی.

مثال ‎10-5‎. شبیه‌سازی getopt

#!/bin/bash
#  getopt-simple.sh
#  مولف: ‎Chris Morgan‎
# با مجوز در این راهنما استفاده شده است .


getopt_simple()
{
    echo "getopt_simple()"
    echo "Parameters are '$*'"
    until [ -z "$1" ]
    do
      echo "Processing parameter of: '$1'"
      if [ ${1:0:1} = '/' ]
      then
          tmp=${1:1}               # از بین بردن ‎'/'‎ مقدم
          parameter=${tmp%%=*}     # .استخراج نام
          value=${tmp##*=}         # .استخراج کمیت 
          echo "Parameter: '$parameter', value: '$value'"
          eval $parameter=$value
      fi
      shift
    done
}

#  عبور دادن تمام گزینه‌ها به ‎getopt_simple()‎
getopt_simple $*

echo "test is '$test'"
echo "test2 is '$test2'"

exit 0  # اسکریپت ‎UseGetOpt.sh‎ را نیز ببینید، نگارش اصلاح شدهِ این اسکریپت است.

---

sh getopt_example.sh /test=value1 /test2=value2

Parameters are '/test=value1 /test2=value2'
Processing parameter of: '/test=value1'
Parameter: 'test', value: 'value1'
Processing parameter of: '/test2=value2'
Parameter: 'test2', value: 'value2'
test is 'value1'
test2 is 'value2'

تعویض رشته فرعی

‎${string/substring/replacement}‎

اولین مورد از مطابقت رشته فرعی ‎$substring‎ را با ‎$replacement‎ تعویض می‌کند. ‎[2]‎

‎${string//substring/replacement}‎

تعویض زیر-رشته ‎$substring‎ با ‎$replacement‎.

stringZ=abcABC123ABCabc

echo ${stringZ/abc/xyz}     # xyzABC123ABCabc
                            #   اولین مورد انطباق ‎'abc'‎ را با ‎'xyz'‎ تعویض می‌کند.

echo ${stringZ//abc/xyz}    # xyzABC123ABCxyz
                            # تمام موارد منطبق با ‎'abc'‎ را با ‎'xyz'‎ تعویض می‌کند.

echo  ---------------
echo "$stringZ"             # abcABC123ABCabc
echo  ---------------
                            # !خود رشته تعویض نمی‌شود

# انطباق و تعویض رشته‌ها می‌تواند پارامتری بشود؟
match=abc
repl=000
echo ${stringZ/$match/$repl}  # 000ABC123ABCabc
#              ^      ^         ^^^
echo ${stringZ//$match/$repl} # 000ABC123ABC000
# Yes!          ^      ^        ^^^         ^^^

echo

#در صورتیکه رشته ‎$replacement‎ ارایه نشود، چه اتفاقی رخ می‌دهد؟
echo ${stringZ/abc}           # ABC123ABCabc
echo ${stringZ//abc}          # ABC123ABC
#                                      .یک حذف ساده روی می‌دهد

‎${string/#substring/replacement}‎

اگر ‎$substring‎ با بخشی از ابتدای ‎‎$string‎‎ مطابقت کند، ‎$replacement‎ جایگزین ‎‎$substring‎‎ می‌گردد.

‎${string/%substring/replacement}‎‎

اگر ‎‎$substring‎‎ با قسمت انتهایی ‎‎$string‎‎ منطبق باشد، ‎$replacement‎ جایگزین ‎$substring‎ می‌شود.

stringZ=abcABC123ABCabc

echo ${stringZ/#abc/XYZ}     # XYZABC123ABCabc
                             # مورد انطباق پیشانی ‎'abc'‎ را با ‎'XYZ'‎ تعویض می‌کند.

echo ${stringZ/%abc/XYZ}     # abcABC123ABCXYZ
                             #      انطباق پایانی ‎'abc'‎ را با ‎'XYZ'‎ تعویض می‌کند.

‎10.1.1‎- دستکاری رشته‌ها بااستفاده از awk

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

مثال ‎10-6‎. روشهای جایگزین استخراج و جایگزینی رشته‌های فرعی

#!/bin/bash
# substring-extraction.sh

String=23skidoo1
#      012345678    Bash
#      123456789    awk
#            :به تفاوت سیستم شاخص‌گذاری رشته توجه نمایید
# Bash کاراکتر اول رشته را به عنوان ‎0‎ شماره‌گذاری می‌کند.
# Awk اولین کاراکتر رشته را به عنوان ‎1‎ شماره‌گذاری می‌کند.

echo ${String:2:4} # مکان سوم ‎(0-1-2)‎‏ ‎, طول 4‎ کاراکتر
                                         # skid

# معادل ‎${string:pos:length}‎ در awk برابر با ‎substr(string,pos,length)‎ است.
echo | awk '
{ print substr("'"${String}"'",3,4)      # skid
}
'
#   لوله‌کشی یک "echo" خالی به awk ورودی زائدی به آن می‌دهد، و
#+     .بنابراین ارایه کردن یک نام فایل را غیر ضروری می‌کند

echo "----"

# :و به همچنین

echo | awk '
{ print index("'"${String}"'", "skid")      # 3
}                                           # (‎skid‎ در موقعیت ‎3‎ شروع می‌شود)
'                                           # معادل ‎awk‎ از ‎"expr index"‎ ...

exit 0

‎10.1.2‎- منابع بیشتر

برای آشتایی بیشتر با دستکاری رشته در اسکریپت‌ها، به بخش ‎10.2‎ و بخش مربوط به فرمان expr مراجعه کنید.

اسکریپت‌های نمونه:

  1. مثال ‎ 16-9‎

  2. مثال ‎ 10-9‎

  3. مثال ‎ 10-10‎

  4. مثال ‎ 10-11‎

  5. مثال ‎ 10-13‎

  6. مثال ‎ A-36‎

  7. مثال ‎ A-41‎

یادداشت‌ها

[1]

این در مورد شناسه‌های خط فرمان یا پارامترهای عبور داده شده به یک تابع صدق می‌کند.

[2]

توجه نمایید که ‎$substring‎ و ‎$replacement‎ بر اساس مضمون می‌توانند به رشته‌های لفظی یا متغیرها اشاره کنند.