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

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

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

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

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



چگونه می‌توانم نام فایلهای شامل کاراکتر سطرجدید، فاصله، یا هردو را پیدا کرده و با آنها کار کنم؟

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

شیوه برگزیده کار با نامهای انتخابی فایل، بازهم استفاده از ‎find(1)‎ می‌باشد:

find ... -exec command {} \;

یا، اگر به مدیریت نام‌فایلها به طور دسته جمعی نیاز دارید:

find ... -exec command {} +

xargs به ندرت بتواند سودمندتر از مورد فوق باشد، اما اگر شما حقیقتاً اصرار دارید، استفاده از ‎ -0‎ را به خاطر داشته باشید:

# نیاز دارد GNU/BSD درfind و xargs به فرمان 
find ... -print0 | xargs -0 command

# استفاده نکنید xargs هرگز بدون گزینه 0- یا ضمائم مشابه از 

یکی از اینها را به کار ببرید مگر اینکه واقعاً نمی‌توانید.

روش دیگر برای کار با فایلهایی که در نامشان دارای فاصله می‌باشند استفاده از بسط نام فایل پوسته(globbing) است. اشکال آن، کار نکردن به طور بازگشتی است(به استثنای بسط‌های zsh یا globstar نگارش 4 از bash)، اما اگر شما فقط نیاز به پردازش تمام فایلهای یک دایرکتوری منفرد دارید، به طور خارق‌العاده‌ای خوب کار می‌کند.

برای مثال، این کُد تمام فایلهای ‎ *.mp3‎ در دایرکتوری فعلی را با جایگزینی خط‌زیر(underscore) به جای فاصله در نامشان تغییر نام می‌دهد:

# Bash/ksh
for file in ./*\ *.mp3; do
  mv "$file" "${file// /_}"
done

برای مثالهای بیشتر تغییر نام فایلها، پرسش و پاسخ شماره 30 را ملاحظه نمایید.

به خاطر داشته باشید، لازم است شماتمام بسط‌های پارامتر را با استفاده از نقل‌قول دوگانه نقل‌قولی کنید. اگر چنین نکنید، بسط تحت تأثیر تفکیک کلمه قرار می‌گیرد(همچنین بخش جداسازی شناسه و تله‌هایBash را ببینید). همچنین، همیشه جانشین‌ها را با ‎ "./"‎ پیشوند کنید، در غیر این صورت، اگر فایلی با نام شامل "-" به عنوان اولین کاراکتر نام وجود داشته باشد بسط‌ها ممکن است به طور غلط، به عنوان گزینه تفسیر گردند.

یک روش دیگر کار با نام فایلها به طور بازگشتی، شامل استفاده از گزینه ‎ -print0‎ فرمان find (الحاقیه GNU/BSD )، همراه با گزینه ‎ -d‎ پوسته bash برای read می‌باشد:

# Bash
unset a i
while IFS= read -r -d $'\0' file; do
  a[i++]="$file"        # or however you want to process each file
done < <(find /tmp -type f -print0)

مثال اسبق تمام فایلهای دایرکتوری ‎/tmp‎ را (به طور بازگشتی) در یک آرایه حتی اگر در نام آنها سطرجدید یا فضای سفید موجود باشد، می‌خواند، از طریقِ اجبار read به استفاده از بایت تهی‎(\0)‎ به عنوان جداکننده سطر. چون NUL بایت معتبر در نام فایل یونیکس نمی‌باشد، این رویکرد در کنار استفاده از find -exec مطمئن‌ترین روش است. ‎IFS=‎ برای اجتناب از بریدن فضای سفید ابتدا و انتها لازم است، و ‎ -r‎ برای اجتناب از پردازش کاراکتر \ نیاز می‌باشد. در حقیقت،‎ $'\0'‎ معادل '' است، بنابراین می‌توانستیم آن را به این شکل نیز بنویسیم:

# Bash
unset a i
while IFS= read -r -d '' file; do
  a[i++]="$file"
done < <(find /tmp -type f -print0)

پس چرا این کار نمی‌کند؟

# کار نمی‌کند
unset a i
find /tmp -type f -print0 | while IFS= read -r -d '' file; do
  a[i++]="$file"
done

به علت وجود لوله، تمامیت حلقه while در یک پوسته فرعی‎ انجام می‌شود و بنابراین تخصیص‌های آرایه پس از خاتمه یافتن حلقه از دست خواهد رفت. (برای جزئیات بیشتر در این باره، پرسش و پاسخ شماره 24 را ملاحظه کنید.)


CategoryShell

پرسش و پاسخ 20 (آخرین ویرایش ‎ 2012-06-29 11:53:50 ‎ توسط GreyCat)


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



به کدام طریقه می‌توانم یک فایل را به محدوده‌هایی از سطرها، مانند سطرهای 1-10، 11-20، 21-30 تجزیه کنم؟

برخی سیستم‌های یونیکس برنامه سودمند split را برای این منظور فراهم می‌کنند:

    split --lines 10 --numeric-suffixes input.txt output-

برای انعطاف پذیری بیشتر می‌توانید از sed استفاده کنید. فرمان sed می‌تواند به طور مثال سطرهای محدوده 1-10 را چاپ نماید

    sed -n -e '1,10p' -e '10q'

این‎(-n)‎ مانع چاپ کردن هر سطر توسط sed می‌شود. در عوض فقط سطرهای محدوده 1 تا 10 پردازش می‌شوند("1,10")، و آنها چاپ ("p") می‌شوند. فرمان پس از خواندن سطر 10 خارج("10q") می‌گردد.

همچنین می‌توانیم این کد را برای چاپ یک محدوده انتخابی فایل(تعیین شده با شماره سطر) به کار ببریم:

# POSIX shell
file=/etc/passwd
range=10
cur=1
last=$(wc -l < "$file") # تعداد سطرها را می‌شمارد
chunk=1
while [ $cur -lt $last ]
do
    endofchunk=$(($cur + $range - 1))
    sed -n -e "$cur,${endofchunk}p" -e "${endofchunk}q" "$file" > chunk.$(printf %04d $chunk)
    chunk=$(($chunk + 1))
    cur=$(($cur + $range))
done

مثال قبل محاسبات POSIX را به کار می‌برد، که شل‌های Bourne قدیمی‌تر ندارند. در آن موقعیت، مثال زیر می‌تواند به جای آن به کار برود:

#   printf و فرض بر عدم وجود  Bourne میراث پوسته     
file=/etc/passwd
range=10
cur=1
last=`wc -l < "$file"` # تعداد سطرها را می‌شمارد
chunk=1
while test $cur -lt $last
do
    endofchunk=`expr $cur + $range - 1`
    sed -n -e "$cur,${endofchunk}p" -e "${endofchunk}q" "$file" > chunk.$chunk
    chunk=`expr $chunk + 1`
    cur=`expr $cur + $range`
done

Awk نیز می‌تواند برای تولید نتیجه کم و بیش مشابهی به کار برود:

 awk -v range=10 '{print > FILENAME "." (int((NR -1)/ range)+1)}' file


CategoryShell

پرسش و پاسخ 19 (آخرین ویرایش ‎ 2009-12-30 17:16:43 ‎ توسط MatthiasPopp)


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



چگونه می‌توانم از اعدادی که با صفر شروع می‌شوند مثل 01 و 02 در یک حلقه استفاده کنم؟

به طور معمول، چند روش برای حل مشکل موجود است، هریک با مزایا و معایب خاص خود.

Bash نگارش 4 صفرهای پُرکننده(صفر قبل از عدد) و دامنه آنها را در بسط ابرویش مجاز می‌دارد:

    # Bash 4
    echo {01..10}

    for i in {01..10}; do ...

تمام چاره کارهای دیگر در این صفحه Bash قبل از نگارش 4.0، یا پوسته‌های غیر از Bash را فرض خواهد نمود.

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

    # Bash
    for i in 0{1,2,3,4,5,6,7,8,9} 10
    do
        echo $i
    done

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

    # Bash 3
    for i in 0{1..9} 10
    do
        echo $i
    done

مثال دیگر، برای خروجی 0000 تا 0034:

    # Bash 3
    for i in {000{0..9},00{10..34}}
    do
        echo $i
    done

    # .ابروهای خارجی فقط به جای اضافه کردن آنها یکی پس از دیگری است‎
    # :استفاده از بسط را اجازه می‌دهد، به طور نمونه، مانند این
    wget 'http://foo.com/adir/thepages'{000{0..9},00{10..34}}'.html'

برخی شاید روش سریع و نامنزه زیر را ترجیح بدهند(برای تولید "000" تا "015"):

    # Bash 3
    for i in {1000..1015}
    do
      echo "${i:1}"    # یا "${i#1}" 
    #  (مترجم: در اینجا از بسط پارامتر استفاده گردیده است) 
    done

این برای توالی وسیع کسل کننده می‌شود، اما روشهای دیگری نیز وجود دارد. اگر فرمان printf را(که یک فرمان داخلی Bash است و استاندارد POSIX نیز هست) دارید، می‌تواند برای قالب‌بندی عدد به کار برود:

    # Bash
    for ((i=1; i<=10; i++))
    do
        printf "%02d " "$i"
    done

همچنین، چون printf به طور ضمنی حلقه است، اگر شناسه‌هایی غیر از مشخص کننده قالب داده شده باشد، می‌توانید به طورهنگفتی این را مختصر نمایید:

   # Bash 3
   printf "%03d\n" {1..300}

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

   # Bash 3
   #  متغیرهای شامل عدد صحیح می‌باشند end و start
   eval printf '"%03d\n"' {$start..$end}

فرمان eval در اینجا لازم است، زیرا نمی‌توانید در بسط ابرو متغیرها را داشته باشید --فقط ثابت‌ها.نقل‌قولهای اضافی را eval لازم دارد برای اینکه‎ \n‎ ما به یک n تغییر نکند. قدر مسلم روش eval راه حل شلوغی است، لطفاً برای مقاصد خطیر به جای آن از حلقه for استفاده کنید.

در پوسته Korn فرمان typeset دارای گزینه‌ای برای تعیین تعداد ارقام عدد می‌باشد:

    # Korn
    $ typeset -Z3 i=4
    $ echo $i
    004

اگر فرمان ‎seq(1)‎ در دسترس باشد( بخشی از‎ GNU sh-utils/coreutils‎ می‌باشد)، می‌توانید به این شکل از آن استفاده کنید:

    seq -w 1 10

یا، برای تعداد ارقام دلخواه همراه با صفرهای پشت عدد(در اینجا: 3):

    seq -f "%03g" 1 10

با ترکیب printf با ‎seq(1)‎، می‌توانید موردی مانند این را انجام بدهید:

   # POSIX shell, GNU utilities
   printf "%03d\n" $(seq 300)

(که شاید اگر از Bash استفاده نمی‌کنید، اما ‎ seq(1)‎ را دارید و نگارش ‎seq(1)‎ شما فاقد مشخص کننده قالب سَبک printf است، این روش مفید باشد. یک مجموعه عجیبی از محدودیت‌هامی‌باشد، اماگمان می‌کنم به طور نظری ممکن باشد. چون seq ابزار خارجی غیر استانداردی است، خوب است انتخاب‌های شما باز باشد.)

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

بعضی سیستمهای مشتق شده از BSD در عوض‎ seq(1)‎دارای ‎ jot(1)‎ می‌باشند. مطابق رسم خیلی خوب یونیکس، این یک ترکیب دستوری کاملاً ناسازگاری دارد:

   # POSIX shell, OpenBSD et al.
   printf "%02d\n" $(jot 10 1)

   # Bourne shell, OpenBSD (at least)
   jot -w %02d 10 1

سرانجام، مثال ذیل که با هرپوسته مشتق شده از شل Bourne (که هم expr و هم sed را دارد) با سه بایت برای هر عدد در هر سطر و صفر قبل از عدد در صورت لزوم، کار می‌کند:

   # Bourne
   i=0
   while test $i -le 10
   do
       echo "00$i"
       i=`expr $i + 1`
   done |
       sed 's/.*\(...\)$/\1/g'

در این مثال، تعداد '.' داخل پرانتزها در فرمان sed تعیین می‌کند چند بایت از فرمان echo (در انتهای هر سطر) حفظ و چاپ خواهد شد.

اما اگر به راستی می‌خواهید به یک فرمان خارجی یونیکس استناد کنید، در درجه اول می‌توانید تمام آن را به خوبی با awk انجام بدهید:

   # Bourne
   # شامل یک عدد صحیح است count متغیر
   awk -v count="$count" 'BEGIN {for (i=1;i<=count;i++) {printf("%03d\n",i)} }'

   #  بی‌فایده awk با سولاریس سالخورده و  Bourn
   awk "BEGIN {for (i=1;i<=$count;i++) {printf(\"%03d\\n\",i)} }"


اکنون، چون دلیل شماره یک پرسیدن این سؤال برای بارگیری تصویرها به طور انبوه است، می‌توانید مثالهای فوق را با ‎ xargs(1)‎ و ‎wget(1)‎ برای واکشیدن فایلها به کار ببرید:

   almost any example above | xargs -i% wget $LOCATION/%

فرمان ‎ xargs -i%‎ در هر نوبت سطری از ورودی را می‌خواند، و % انتهای فرمان را با ورودی تعویض می‌کند.

یا، مثال ساده‌تر که به کاربردن حلقه forاست:

   # Bash 3
   for i in {1..100}; do
      wget "$prefix$(printf %03d $i).jpg"
      sleep 5
   done

یا، برای اجتناب از پوسته‌های فرعی( bash نگارش 3.1 لازم دارد):

   # Bash 3.1
   for i in {1..100}; do
      printf -v n %03d $i
      wget "$prefix$n.jpg"
      sleep 5
   done


CategoryShell

پرسش و پاسخ 18 (آخرین ویرایش ‎ 2012-02-01 16:00:55 ‎ توسط GreyCat)


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



چگونه می‌توانم عبارت‌ها را گروه‌بندی کنم، مثل ‎ (a AND b) OR c‎؟

روش قابل حمل(POSIX یا Bourne) استفاده از چندین فرمان test (یا ‎ [‎) است:

    # Bourne
    if test A && test B || test C; then
    ...

در این حالت گروه‌بندی به صورت ضمنی است، زیرا AND یعنی(&&) تقدم بالاتری نسبت به OR یعنی(||) دارد. اگر به گروه‌بندی آشکار نیاز داشته باشیم، آنوقت می‌توانیم از ابروها استفاده نماییم:

    # Bourne(?)
    if test A && { test B || test C; }; then
    ...

آنچه که نباید انجام بدهیم، آزمایش استفاده از عملگرهای ‎ -a‎ یا ‎-o‎ فرمان test می‌باشد، زیرا نتیجه آن، تعریف نشده، است.

BASH و KornShell دارای فرمانهای مقایسه قدرتمندتر مختلفی با تفاوت جزئی(آسانتر) در نقل‌قول کردن، می‌باشند:

مثالها:

    # Bash/ksh
    if (( (n>0 && n<10) || n == -1 ))
    then echo "0 < $n < 10, or n==-1"
    fi

یا

    # Bash/ksh
    if [[ ( -f $localconfig && -f $globalconfig ) || -n $noconfig ]]
    then echo "configuration ok (or not used)"
    fi

توجه نمایید که تمایز میان مقایسه رشته‌ای و عددی، سخت‌گیرانه است. مثال پایین را ملاحظه کنید:

    n=3
    if [[ $n>0 && $n<10 ]]
    then echo "$n is between 0 and 10"
    else echo "ERROR: invalid number: $n"
    fi

خروجی ‎"ERROR: ...."‎ خواهد بود، به دلیل آنکه در یک مقایسه رشته‌ای "3" بزرگتر از "10" است، چون "3" بعد از "1" می‌آید، و کاراکتر بعدی "0" در نظر گرفته نمی‌شود. با تعویض کروشه‌ها با پرانتزهای دوتایی‎ ((‎ مثال آنطور کار می‌کند که انتظار می‌رود.


CategoryShell

پرسش و پاسخ 17 (آخرین ویرایش ‎ 2011-07-12 02:24:26 ‎ توسط pool-173-71-205-68)


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



چگونه می‌توانم از AND/OR/NOT منطقی در الگو(جانشین) پوسته استفاده کنم؟

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

اگر می‌خواهید بر تمام فایلهای که با جانشین A یا جانشین B مطابقت دارند عمل کنید، فقط هر دو را در یک سطر قرار بدهید:

rm -- *.bak *.old

اگر می‌خواهید یک OR منطقی فقط در قسمتی از جانشین به کار ببرید(بزرگتر از یک کاراکتر منفرد -- برای مورد یک کاراکتری، کلاسهای کاراکتر داخل کروشه کفایت می‌کند)، در Bash، می‌توانید از بسط ابرو استفاده کنید:

rm -- *.{bak,old}

اگر موردی بازهم بیشتر عمومی و قدرتمند نیاز دارید، در KornShell یا BASH می‌توانید از جانشین‌های توسعه‌یافته استفاده کنید. در Bash، لازم است گزینه extglob تنظیم باشد. به این ترتیب می‌توند کنترل شود:

shopt extglob

و به این ترتیب تنظیم می‌شود:

shopt -s extglob

برای دست‌گرمی، تمام فایلهایی که با foo شروع می‌شوند و به ‎ .d‎ ختم نمی‌شوند را به دایرکتوری ‎ foo_thursday.d‎ منتقل می‌کنیم:

mv foo!(*.d) foo_thursday.d

یک مثال پیچیده‌تر -- حذف تمام فایلهایی که نام آنها شامل Pink_Floyd و هچنین فاقد The_Final_Cut باشد:

rm !(!(*Pink_Floyd*)|*The_Final_Cut*)

ضمناً: این نوع از الگوها می‌توانند با KornShell نیز به کار بروند. در آنجا نیازی به فعال کردن نیست، بلکه الگوهای پیش‌فرض هستند.


CategoryShell

پرسش و پاسخ 16 (آخرین ویرایش ‎ 2009-12-30 17:14:33 ‎ توسط MatthiasPopp)