یک راهکار برای تصادفی نمودن سطرها در یک فایل چنین است. این روش، تولید یک عدد تصادفی است که پیشوند هر سطر میشود ، سپس سطرهای حاصل، مرتب میگردند و اعداد حذف میشوند.
#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)
در خصوص 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
پرسش و پاسخ 26 (آخرین ویرایش 2012-11-27 14:25:08 توسط geirha)
به جای $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 یافت میشود.
پرسش و پاسخ 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 به کار میاندازند، ضروری هستند. طبعاً آنها به قرار دادن مواردی به طور مستقیم در لولهها اقدام میکنند و ازعواقب آن سردرگم میشوند.
# 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 و بعد) میباشد، به هرحال یک روش بسیار پاکیزه و سودمند برای تعیین یک رشته کوچک لفظی برای ورودی فرمان است.
# Bash declare -i linecnt while read -r; do ((linecnt++)) done <<EOF hi ho hum EOF printf 'total number of lines: %d' "$linecnt"
# 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 را ببینید.
پرسش و پاسخ 24 (آخرین ویرایش 2012-03-05 03:38:07 توسط ormaaj)
فقط یک فایل شروع اولیه(start-up) متفاوت تعیین کنید:
bash --rcfile /my/custom/bashrc
نوع دیگر پرسش: من اسکریپتی دارم که یک محیط را تنظیم میکند، و من میخواهم در انتهای آن، کنترل را به کاربر بدهم.
در انتهای اسکریپت دستور exec bash را برای آغاز کردن پوسته محاورهای قرار بدهید. این پوسته از محیط ارث میبرد(که شامل مستعارها نیست، اما اشکالی ندارد، زیرا مستعارها بسیار بد میباشند). البته، همچنین باید مطمئن شوید که اسکریپت شما در یک ترمینال اجرا میشود -- در غیر اینصورت، باید به عنوان مثال با استفاده از دستور exec xterm -e bash آنرا ایجاد کنید.
پرسش و پاسخ 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. توجه نمایید که این مثال بسیار سادهای میباشد و ظاهراً اعداد ممیز شناور و تقسیم را در گیر میسازد، که با اعداد صحیح و بدون تقسیم حل میشود. اگر امکان داشته باشد، به طور معمول تبدیل مشکل شما به محاسبات صحیح، ثمربخشتر از به کار بردن محاسبات ممیز شناور است.
پرسش و پاسخ 22 (آخرین ویرایش ed 2012-11-26 10:07:41 توسط geirha)