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

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


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

پرسش و پاسخ شماره ۲۶
از چه طریق می‌توانم ترتیب سطرهای فایلی را تصادفی نمایم(بُر زدن)؟(یا یک سطر اتفاقی از فایل را انتخاب کنم، یا انتخاب یک فایل به طور تصادفی از یک شاخه.)

یک راهکار برای تصادفی نمودن سطرها در یک فایل چنین است. این روش، تولید یک عدد تصادفی است که پیشوند هر سطر می‌شود ، سپس سطرهای حاصل، مرتب می‌گردند و اعداد حذف می‌شوند.

    #bash
    randomize() {
        while IFS=\'\' read -r l ; do printf \'%d\\t%s\\n\' "$RANDOM" "$l"; done |
        sort -n |
        cut -f2-
    }

RANDOM توسط BASH و KornShell پشتیبانی می‌شود، اما در posix تعریف نگردیده است.

این روش نیز همان ایده است(چاپ اعداد تصادفی در ابتدای سطر، و مرتب نمودن سطرها نسبت به آن ستون) با استفاده از برنامه‌های دیگر:

    # Bourne
    awk \'
        BEGIN { srand() }
        { print rand() "\\t" $0 }
    \' |
    sort -n |    # مرتب نمودن عددی نسبت به اولین ستون(عدد تصادفی)‏
    cut -f2-     # حذف ستون مرتب‌سازی

این روش(احتمالاً ) سریعتر از راه حل قبلی است، اما در نگارشهای خیلی قدیمی AWK کار نمی‌کند("nawk" یا "gawk"، یا اگر موجود است ‎ /usr/xpg4/bin/awk‎ را امتحان کنید). (توجه نمایید که awk زمان epoch(مترجم: تعداد ثانیه‌های سپری شده از نیمه شب اول ژانویه سال ۱۹۷۰ که برای یونیکس به عنوان مبداء زمان تلقی می‌شود) را برای تابع ‎ srand()‎ استفاده می‌کند، که شاید برای شما به اندازه کافی تصادفی نباشد)

نگارش تعمیم یافته این پرسش می‌تواند, چگونه می‌توانم عناصر یک آرایه را بُر بزنم(درهم و برهم نمایم)؟ باشد. اگر نخواهیم از رویکرد تا اندازه‌ای بدترکیبِ مرتب‌سازی سطرها استفاده کنیم، در واقع این کار پیچیده‌تر از آنست که به نظر می‌رسد. یک روش خام نتایج جهت‌دار بدشکل به ما ارائه خواهد نمود. الگوریتم پیچیده‌تر(و صحیح) مشابه این مورد می‌باشد:

    # استفاده از متغیر آرایه سراسری. بایدتوپُر باشد(نه آرایه پراکنده)‏
    # Bash syntax.
    shuffle() {
       local i tmp size max rand

       #  جهت دار است $RANDOM به علت محدودیت دامنه $RANDOM % (i+1)
       # .با استفاده از دامنه‌ای که ضریبی از تعداد عناصر آرایه است اصلاح می‌شود
       size=${#array[*]}
       max=$(( 32768 / size * size ))

       for ((i=size-1; i>0; i--)); do
          while (( (rand=$RANDOM) >= max )); do :; done
          rand=$(( rand % (i+1) ))
          tmp=${array[i]} array[i]=${array[rand]} array[rand]=$tmp
       done
    }

این تابع عناصر یک آرایه را با استفاده از الگوریتم بُر زنی Knuth-Fisher-Yates در جا بُر می‌زند.

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

   # Bash
   n=$(wc -l < "$file")        # شمارش تعداد سطرها
   r=$((RANDOM % n + 1))       # عدد تصادفی از یک تا n
   sed -n "$r{p;q;}" "$file"   # r چاپ سطر شماره 

   #posix with awk
   awk -v n="$(wc -l<"$file")" \'BEGIN{srand();l=int((rand()*n)+1)} NR==l{print;exit}\' "$file"

( برای اطلاعات بیشتر در مورد چاپ سطر شماره n این پرسش و پاسخ را ببینید.)

مثال بعد تمامی فایل را به داخل حافظه جذب می‌کند. این رویکرد زمان بازکردن مجدد فایل را صرفه‌جویی می‌کند، اما به وضوح، حافظه بیشتری مصرف می‌کند. (احتمالاً: در سیستم‌هایی با حافظه کافی و نهانگاه دیسک مؤثر، با روشهای قبلی تمام فایل را به حافظه خوانده‌اید، مگر اینکه حافظه کافی برای انجام آن موجود نباشد،که در چنین حالتی نمی‌توانید، که می‌باید ثابت می‌شد.)

   # Bash
   unset lines i
   while IFS= read -r \'lines[i++]\'; do :; done < "$file"   # See FAQ 5
   n=${#lines[@]}
   r=$((RANDOM % n))   # see below
   echo "${lines[r]}"

توجه نمایید که ما در این مثال 1 را به عدد تصادفی اضافه نمی‌کنیم، زیرا آرایه سطرها از صفر شماره گذاری می‌شود.

همچنین، بعضی اشخاص می‌خواهند یک فایل اتفاقی از یک دایرکتوری را انتخاب کنند(برای امضاء یک e-mail، یا انتخاب یک فایل صوتی اتفاقی برای اجرا، یا یک تصویر تصادفی برای نمایش، و غیره). تکنیک مشابهی که می‌تواند به کار برود:

    # Bash
    files=(*.ogg)                  # Or *.gif, or *
    n=${#files[@]}                 # برای زیبایی گرایی
    xmms -- "${files[RANDOM % n]}" # انتخاب یک عضو تصادفی

توجه کنید که این چند نمونه اخیر از یک ضریب ساده متغیر RANDOM استفاده می‌کنند، بنابر این، نتایج جهت‌دار است. اگر این مشکلی برای برنامه شما می‌باشد، از تکنیک‌های ضد جهت مثال Knuth-Fisher-Yates فوق استفاده کنید.

دیگر برنامه‌های غیرقابل حمل:

  • shuf در Coreutils گنو(در نسخه‌های اخیر coreutils)

  • ‎ sort -R ‎گنو

در خصوص coreutils گنو، از نگارش 6.9 برنامه sort گنو دارای یک گزینه ‎ -R‎ (مستعار ‎ --random-sort‎)می‌باشد. به طور شگفت‌انگیز، فقط بامناطق عمومی کار می‌کند:

     LC_ALL=C sort -R file     # خروج سطرها به طور تصادفی 
     LC_ALL=POSIX sort -R file # خروج سطرها به طور تصادفی
     LC_ALL=en_US sort -R file # صرفنظر می‌کند -R به طور مؤثر از گزینه 

برای جزئیات بیشتر، ‎info coreutils sort‎ یا یک راهنمای معادل را ببینید.

  • > http://lists.gnu.org/archive/html/bug-bash/2010-01/msg00042.html به یک دام شگفت‌آور مرتبط با استفاده از RANDOM بدون مقدم نمودن $ در زمینه‌های محاسباتی معین، اشاره می‌کند. (خلاصه: تقریباً در هر وضعیتی باید‎ n=$((...math...)); ((array[n]++))‎ را بر ‎((array[...math...]++))‎ ترجیح بدهید.)

رفتار تشریح شده، در نگارشهای جاری mksh، ksh93، Bash، و Zsh به طور معکوس ظاهر می‌شود. بازهم موردی برای به خاطر سپردن به واسطه ماترک. -ormaaj


CategoryShell

پرسش و پاسخ 26 (آخرین ویرایش ‎ 2012-11-27 14:25:08 ‎ توسط geirha)