با برنامه cpio :
cd "$srcdir" && find . -type d -print | cpio -pdumv "$dstdir"
یا با برنامه pax:
cd "$srcdir" && find . -type d -print | pax -rwdv "$dstdir"
یا با globbing خاص پوسته zsh:
zsh -ec ' cd -- "$srcdir" dirs=(**/*(ND)) cd -- "$dstdir" mkdir -p -- $dirs'
یا با tar گنو، وترکیب دستوری بلندتر:
cd "$srcdir" && find . -type d -print | tar c --files-from - --no-recursion | tar x --directory "$dstdir"
این دستور با find لیستی از نام دایرکتوریها ایجاد میکند، به طور غیر بازگشتی فقط نام دایرکتوریها را به یک آرشیو اضافه میکند، و آنرا از طریق لوله به دومین tar میدهد که در محل مقصد آن را استخراج کند. به طوری که میتوانید ببینید، tar کمترین مناسبت را برای این منظور دارد، اما اشخاص فقط شیفته آن هستند، بنابراین فقط برای آرام کردن جمعیت علاقمندان tar در اینجا گنجانده شده. (توجه: حتی شما به هیچوجه نمیتوانید این را با یک tar معمولی یونیکس انجام بدهید. همچنین توجه کنید: چیزی به عنوان "standard tar" وجود ندارد، در نتیجه هردو برنامه tar و cpio به لطف pax به طور عمدی در POSIX از قلم افتادهاند.)
اگر نام دایرکتوریها شامل کاراکترهای سطرجدید باشد راه حل zsh فوق تقریبا شکست میخورد. دست کم در بسیاری سیستمهای مدرن BSD/GNU، آنها برای از عهده برآمدن، میتوانند با استفاده از find -print0 و یکی از موارد pax -0 یا cpio -0 یا tar --null به طور جزئی اصلاح شوند(مستندات سیستم خود را برای دیدن آنکه کدام یک از این برنامهها را دارید، و کدام ملحقات در دسترس میباشند، بررسی نمایید).
اگر میخواهید به جای فایلهای کامل، فایلهای بدون محتوا(خالی) با find(1) گنو ایجاد کنید، مورد ذیل احتمالاً سادهترین روش است.فرمان find فایلهای معمولی رابه طور مصنوعی(فایلهای خالی با همان نشانههای زمان) بازتولید میکند:
cd "$srcdir" &&
# :اول با یکی از فرمانهای فوق دایرکتوریها را ایجاد نمایید، سپس
find . -type f -exec touch -r {} "$destination"/{} \;
آگاه باشید،به هرحال، مطابق POSIX، وقتی {} به عنوان یک شناسه حضور ندارد، رفتار find مشخص نشده است. به این دلیل، راه حل پایین قابل حملتر(و احتمالاً سریعتر) از مورد قبلی است:
dstdir=whatever; export dstdir
find . -type f -exec sh -c 'for i; do touch -r "$i" "$dstdir"/"$i"; done' _ {} +
اگر برنامه find شما نمیتواند -exec + را مدیریت نماید،آنوقت میتوانید از \; به جای + در انتهای فرمان استفاده کنید. برای توضیحات UsingFind را ملاحظه نمایید.
پرسش و پاسخ 10 (آخرین ویرایش 2011-05-27 12:22:09 توسط GreyCat)
اکثر فرمانهای استاندارد یونیکس وقتی به طور غیر محاورهای به کارمیروند، خروجی را در حافظه میانجی قرار میدهند. بدین معنی که بلافاصله، هر کاراکتر(یا حتی هر سطر) را نمینویسند، بلکه به جای آن، قبل از آنکه ابداً چیزی چاپ نمایند، تعداد زیادی از کاراکترها (اغلب 4 کیلو بایت) را جمعآوری میکنند. در حالت فوق، فرمان grep خروجیاش را میانگیری میکند، و بنابراین awk ورودیاش را فقط در قطعات بزرگ دریافت میکند. میانگیری به طور عمدهای کارایی عملیات ورودی و خروجی را افزایش میدهد, و معمولاً به طریقی انجام میشود که تأثیر قابل رؤیت برای کاربر ندارد. دستور ساده tail -f از یک نشست ترمینال محاورهای به خوبی کار میکند، اما موقعی که دستور بخشی از یک خط لوله پیچیده باشد، شاید فرمان نتواند زمان واقعی(نزدیک) نیاز به خروجی نهایی را تشخیص بدهد. خوشبختانه، چند شگرد برای کنترل رفتار میانگیری ورودی-خروجی در دسترس میباشد.
مهمترین مطلب برای درک میانگیری آن است که نویسنده آنرا انجام میدهد، نه خواننده.
در این پرسش، ما لوله tail -f logfile | grep 'foo bar' | awk ... را داریم (با فرمان واقعی مشخص نشده AWK). اگر ما حقیقتاً tail -f logfile را اجرا نماییم، مشکلی وجود ندارد، به دلیل آنکه tail -f هرگز خروجیاش را بافر نمیکند. اگر tail -f logfile | grep 'foo bar' را به طور محاورهای اجرا کنیم نیز مشکلی وجود ندارد، زیرا grep نیز اگر خروجی استانداردش ترمینال باشد، خروجیاش را میانگیری نمیکند. گرچه، اگر خروجی grep به درون برنامه دیگری(ازقبیل یک فرمان AWK) لوله کشی بشود، برای بهبود کارایی، میانگیری را شروع میکند.
در این مثال عملی، برنامه grep واقعاً زائد است. ما میتوانیم آنرا حذف کنیم، و AWK را برای انجام عمل فیلتر کردن و هر چیز دیگری که انجام میدهد داشته باشیم:
tail -f logfile | awk '/foo bar/ ...'
در سایر موقعیتها، شاید این نوع یکپارچهسازی امکانپذیر نباشد. لیکن شما همواره اول باید سادهترین راه حل را جستجو نمایید.
برخی برنامههابه طور ویژهای گزینههای خاص خط فرمان را برای این قبیل مشکلات فراهم میکنند:
grep (به طور نمونه،گنو نگارش 2.5.1) |
--line-buffered |
sed (به عنوان مثال، گنو نگارش 4.0.6) |
-u,--unbuffered |
awk (برخی نگارشهای گنو) |
-W interactive, or use the fflush() function |
tcpdump و tethereal |
-l |
برای اجرای تمام خط لوله به طور بلادرنگ توسط هر فرمانی که در لوله مینویسد، باید بتوان به آن فرمان گفت که عمل میانگیری را غیر فعال کند. اگر آخرین فرمان خط لوله، در ترمینال مینویسد، به طور نوعی نیازی به ملاحظه خاصی نخواهد داشت.
اگر این متعلق به یک برنامه کاربردی C خودتان است یا به کُد منبع آن دسترسی دارید، میتوانید به این شکل میانگیری را غیرفعال کنید
setvbuf(stdout, 0, _IONBF, 0);
بسته expect یک برنامه unbuffer دارد که به طور مؤثری شگردی به برنامههای دیگر میزند که همیشه رفتاری داشته باشند چنانکه گویی به صورت محاورهای به کار رفتهاند(و اغلب میانگیری غیر فعال میشود). این هم یک مثال ساده:
tail -f logfile | unbuffer grep 'foo bar' | awk ...
برنامه expect و unbuffer ابزارهای استاندارد POSIX نیستند، اما شاید از قبل در سیستم شما نصب شده باشند.
نگارشهای اخیر coreutils گنو (از 7.5 به این طرف)با یک برنامه سودمند دلپسند به نام stdbuf همراه میباشند، که میتواند در میان سایرین برای "unbuffer" نمودن خروجی استاندارد یک فرمان به کار برود. در اینجا کاربرد اساسی آن برای مثال ما آمده است:
tail -f logfile | stdbuf -oL grep 'foo bar' | awk ...
در کُد فوق، "-oL" خروجی استاندارد را به طور سطری میانگیری میکند، حتی میتوانید از "-o0" برای غیرفعال کردن کامل آن استفاده کنید. صفحات man و info دارای تمام جزئیات میباشند.
stdbuf ابزار استاندارد POSIX نیست، اماشاید از قبل در سیستم شما نصب شده باشد(اگر از توزیعهای اخیر لینوکس استفاده میکنید،احتمالاً موجود خواهد بود).
اگر میخواهید به جای فیلتر کردن سطرهای انطباق نیافته، به سادگی عبارت مورد جستجو را متمایز(highlight) نمایید،میتوانید در عوض فیلتر tail -f از برنامه less استفاده کنید:
$ less program.log
داخل less، با دستور '/' یک جستجو را شروع کنید(مشابه جستجو در vi). یا برنامه less را با گزینه الگو -p شروع کنید.
حالا less را در وضعیت "follow" قرار بدهید، که به طور پیشفرض با shift+f به این وضعیت میرود.
وضعیت "follow" با یک وقفه که احتمالاً در سیستم شما control+c است، خاتمه مییابد. دستور '/' عبارتهای منظم را قبول میکند، بنابراین شما میتوانستید کارهایی مانند متمایز کردن تمام سطری که عبارت جستجو در آن ظاهر میشود را انجام دهید. برای جزئیات، man less را کاوش نمایید.
اگر شما از پوسته ksh یا Bash نگارش 4.0+ استفاده میکنید، برای هر کاری که شما حقیقتاً تلاش میکنید با tail -f انجام بدهید، کاربرد coproc و fflush() جهت ایجاد پردازش کمکی میتواند سودمند باشد. خوب توجه نمایید که coproc خودش میانگیری را بررسی نمیکند( در واقع مستعد مسائل میانگیری است -- از اینرو به fflush ارجاع میدهد). در اینجا فقط به coproc اشاره شد، به علت آنکه وقتی شخصی به طور پیوسته سعی در کنترل و عکسالعمل نشان دادن به رشد آهسته فایل(یا لوله) مینماید، شاید بخواهد کاری کند که از کمک پردازشها سود خواهد برد.
پرسش و پاسخ 9 (آخرین ویرایش 2012-09-14 07:43:13 توسط dslb-088-072-055-065)
90% اوقات، تمام آنچه لازم دارید یکی از اینهاست:
# :سطرهای انطباق یافته را چاپ میکند (GNU grep) grep -r -- "$search" . # :فقط نام فایلها را برگشت و چاپ میکند (GNU grep) grep -r -l -- "$search" .
اگر برنامه grep شما فاقد گزینه -r است، میتوانید از find استفاده کنید، یا اگرمیخواهید از برخورد با پیوندهای نمادین اجتناب نمایید:
find . -type f -exec grep -l -- "$search" {} \;
کاراکترهای {} با نام فایل تعویض خواهند شد.
این فرمان کندتر از آنست که لازم بشود، زیرا find فقط با یک نام فایل grep را فراخوانی میکند، که منجر به فراخوانیهای بسیار grep (یکبار برای هر فایل) میگردد. چون grep نام فایلهای چندگانه را در خط فرمان میپذیرد، به find میتواند دستور داده شود که یکمرتبه آنرا با چندین نام فایل فراخوانی کند:
find . -type f -exec grep -l -- "$search" {} +
کاراکتر '+' در انتها به find دستور میدهد grep را با هر تعداد نام فایل ممکن فراخوانی کند، صرفه جویی پردازش و اجرای سریعتر. این مثال در find پوسته POSIX کار میکند، به عنوان مثال در سولاریس به همان خوبی findخیلی اخیر گنو.
یونیکس سنتی یک برنامه کمکی به نام xargs برای همین منظور دارد:
find . -type f | xargs grep -l -- "$search"
به هرحال، اگر نام فایل شامل فاصله یا سایر فوق کاراکترها باشد، نیاز به استفاده از گزینه -print0 گنو یا BSD دارید:
find . -type f -print0 | xargs -0 grep -l -- "$search"
گزینههای -print0 و -0 اطمینان ایجاد میکنند که هر نام فایل حتی آنکه شامل فاصلهها، کاراکترهای TAB ، یا سطرهای جدید باشد، میتواند پردازش شود.
پرسش و پاسخ 8 (آخرین ویرایش 2011-05-26 13:31:47 توسط sbl-eh4-rp1)
سریعترین روش، نیازی به برنامههای خارجی ندارد(اما در پوسته بورن قابل استفاده نمیباشد)
# POSIX در پوسته
${#varname}
یا برای پوسته Bourne:
# Bourne expr "$varname" : '.*'
( برنامه expr تعداد کاراکترهای انطباق یافته با الگوی .* را چاپ میکند، که همان طول رشته است.)
یا:
# Bourne, with GNU expr(1) expr length "$varname"
(فقط expr BSD و گنو)
این روش دوم در POSIX تعریف نشده است، بنابراین قابل حمل به همه سکوها نیست. در هرحال، اگر $varname به "length" بسط یابد، روش اول با expr گنو و BSD ناموفق است.
یک روش قابل حمل این است:
expr \( "X$varname" : ".*" \) - 1
ممکن است کسی از awk استفاده کند:
# Bourne
awk -v x="$varname" 'BEGIN {print length(x)}'
باوجود اینکه آن یکی به ازای مقادیری از $varname که شامل کاراکترهای \ باشند ناموفق خواهد شد، بنابراین شاید این را ترجیح بدهید:
# Bourne with POSIX awk
awk 'BEGIN {print length(ARGV[1]);exit}' "$varname"
نیازهای مشابه:
# Korn/Bash
${#arrayname[@]}
تعداد عناصر یک آرایه را باز میگرداند.
# Korn/Bash
${#arrayname[i]}طول عضو i آرایه را برمیگرداند.
پرسش و پاسخ7 (آخرین ویرایش 2011-05-26 13:28:27 توسط sbl-eh4-rp1)
قرار دادن نام متغیرها یا هر ترکیب دستوری bash درون پارامترهای دیگر به طور کلی ایده نامساعدی است. این کار از جدایی بین کُد و داده تخطی میکند، و بدین ترتیب شما را در شیب لغزندهای به طرف باگها، مسائل امنیت، و غیره قرار میدهد. حتی وقتی میدانید که «کاملاً میفهمید»، زیرا شما به «طور دقیق میدانید و میفهمید که چه کار میکنید»، باگها برای همگی ما پیشامد میکنند و سزاوار است رعایت جدایی را تمرین کنیم تا دامنه زیانهایی را که میتوانند موجب گردند، به حداقل برسانیم.
گذشته از آن، کُد شما را نیز نامفهوم و غیر شفاف میسازد.
به طور طبیعی، در اسکریپتنویسی bash، شما ابداً نیازی به ارجاعهای غیر مستقیم ندارید. عموماً، اشخاص زمانی به این راه حل متوجه میشوند که در مورد آرایههای Bash نمیدانند یا درک نمیکنند، یا سایر ویژگیهای Bash از قبیل توابع را به طور کامل ملاحظه نکردهاند.
BASH به شما اجازه میدهد پارامتر را به طور غیرمستقیم بسط دهید--یعنی،یک متغیر ممکن است شامل نام متغیر دیگری باشد:
# Bash
realvariable=contents
ref=realvariable
echo "${!ref}" # محتویات متغیر اصلی را چاپ میکند
(ksh93) پوسته Korn تفاوت کلی دارد، ترکیب دستوری قدرتمندتر-- دستور nameref (همچنین به عنوان typeset -n نیز شناخته میشود):
# ksh93 realvariable=contents nameref ref=realvariable echo "$ref" # محتویات متغیر اصلی را چاپ میکند
متأسفانه، برای پوستههای غیر از Bash و ksh93 هیچ ترکیب دستوری برای ارزیابی متغیر مرجع شده، موجود نیست. شما باید از eval استفاده کنید، که یعنی شما، جهت اجتناب از عاقبت آن، باید متحمل اقدامات بسیار زیادی برای سترون سازی دادههای خود بشوید.
تصور یک استفاده عملی از این، که به همان آسانی کاربرد آرایه انجمنی ، انجام بشود مشکل است. اما اشخاص دائماً آنرا میپرسند(این به طور مؤثق سؤال مکرراً پرسیده شدهای میباشد).
فرمان nameref پوسته ksh93 به ما اجازه میدهد با ارجاعها به آرایهها به همان خوبی متغیرهای معمولی کار کنیم. برای مثال:
# ksh93
myfunc() {
nameref ref=$1
echo "array $1 has ${#ref[*]} elements"
}
realarray=(...)
myfunc realarray
ما از هیچ ترفندی که بتواند رونوشتی از آن توانایی در پوستههای POSIX یا Bourne ایجاد کند آگاه نمیباشیم(غیر از کاربرد eval، که اجرای آن به طور امن به شدت دشوار است). Bash تقریباً میتواند آنرا انجام بدهد -- برخی ترفندهای آرایه غیرمستقیم کار میکنند، و برخی دیگر خیر، و ما نمیدانیم که آیا ترکیب مورد بحث در انتشارهای آینده پایدار باقی میماند. پس این هک را با مسؤلیت خودتان استفاده کنید.
# Bash -- trick #1. به نظر میرسد در نگارش ۲ و بالاتر کار میکند
realarray=(...) ref=realarray; index=2
tmp="$ref[$index]"
echo "${!tmp}" # عضو آرایه با شاخص ۲ را میدهد
# Bash -- trick #2. ظاهراً در نگارش ۳ و بالاتر کار میکند
# کار نمیکند bash 2.05b در نگارش
tmp="$ref[@]"
printf "<%s> " "${!tmp}"; echo # .در تمام طول آرایه تکرار میشود
بازیابی شاخصهای آرایه به طور مستقیم با استفاده از بسط غیر مستقیم ${!var} پوسته Bash امکان پذیر نیست.
گاهی اوقات به مقصود نوشتن اطلاعات در یک مکان قابل پیکربندی به طور پویا، مایل هستید از یک متغیر به دیگری اشاره کنید. به طور نوعی موقعی این اتفاق میافتد که شما برای نوشتن تابع «قابل استفاده مجدد» تلاش مینمایید، و میخواهید خروجیاش را در یک متغیر انتخابی فراخوان کننده، به جای متغیر انتخابی تابع، قرار بدهد. (قابلیت استفاده مجدد توابع پوسته در بهترین حالت خود، مورد تردید است، پس این موردی است که غالباً نباید استفاده شود.)
اختصاص مقدار از طریق مرجع(یا اشارهگر، یا متغیر غیرمستقیم، یا هر چه شما میخواهید آنرا بنامید -- من از اینجا به بعد میخواهم از "ref" استفاده کنم) به طور گستردهتری ممکن است، اما توانایی انجام آن به شدت مختص پوسته است.
قبل از اینکه شروع کنیم، باید اشاره کنیم که شما باید مقدار ref را کنترل کنید. یعنی، شما باید فقط از آن ref که مقدار آن را خودتان داخل برنامه تخصیص دادهاید، یا از ورودی تصدیق شده، استفاده کنید. اگر یک کاربرنهایی بتواند متغیر ref را با رشتههای دلخواه اشغال کند، نتیجه میتواند تزریق پیشبینی نشده کُد باشد. در انتها مثالی از این مورد را نشان خواهیم داد.
در ksh93، فقط میتوانیم دوباره از nameref استفاده کنیم:
# ksh93 nameref ref=realvariable ref="contents" # میباشد "contents" شامل رشته realvariable اکنون
در Bash، میتوانیم از read و ترکیب دستوری here string ویژه Bash استفاده کنیم:
# Bash ref=realvariable IFS= read -r $ref <<< "contents" # میباشد "contents" شامل رشته realvariable اکنون
به هرحال، این فقط اگر سطر جدیدی در محتویات موجود نباشد، کار میکند. اگر به تخصیص دادن سطرهای چندتایی نیاز دارید به خواندن ادامه دهید.
ترفند مشابهی نیز برای متغیرهای آرایهای Bash کار میکند:
# Bash
aref=realarray
read -r -a $aref <<< "words go into array elements"
echo "${realarray[1]}" # را چاپ میکتد "go"
(یک بار دیگر، سطر جدید در ورودی این ترفند را نقض خواهد نمود. متغیر IFS برای جداکردن کلمان به کار میرود، بنابراین شما شاید به تنظیم آن احتیاج داشته یا نداشته باشید.)
ترفند دیگر مورد استفاده، فرمان printf -v در Bash است(فقط در نگارشهای اخیر در دسترس میباشد):
# یا بالاتر Bash 3.1 نگارش ref=realvariable printf -v $ref %s "contents"
اگر محتویات شامل یک رشته ثابت نباشد، بلکه ترجیحاً تولید شده به طور پویا باشد، ترفند printf -v سودمند است. از تمام امکانات قالببندی printf میتوانید استفاده کنید. این ترفند هر محتوایی را نیز برای رشته اجازه میدهد، از جمله سطرهایجدید تعبیه شده(اما بایتهای NUL خیر -هیچ نیرویی در جهان نمیتواند بایتهای تهی را به طور قابل استفادهای در رشتههای شل قرار بدهد). اگر شما در bash نگارش 3.1 یا بالاتر هستید، این بهترین ترفند است.
بازهم یک ترفند دیگر فرمان typeset پوسته Korn یا فرمان declare پوسته Bash است. اینها تقریباً همارز یکدیگر هستند. هر دوی اینها اگر داخل یک تابع به کار بروند باعث قلمرو محلی متغیر میشوند، اما اگر در خارج از تابع استفاده شوند، میتوانند متغیرهای سراسری را به کار اندازند.
# Korn تمام نگارشهای پوسته typeset $ref="contents" # Bash: declare $ref="contents"
نگارش 4.2 Bash گزینه declare -g را اضافه نموده، که میتواند حتی از داخل تابع متغیر را در زمینه سراسری قرار بدهد.
مزیت استفاده از typeset یا declare نسبت به eval آنست که سمت راست تخصیص توسط شل تفکیک نمیشود. اگر شما در اینجا از eval استفاده کنید، باید اول تمام سمت راست تخصیص راپاکیزه کنید(پوشش بدهید). این ترفند محتویات را نیز به طور دقیق حفظ مینماید،از جمله سطرهای جدید را، بنابراین، اگر در bash قبل از 3.1 (یا ksh88) میباشید بهترین ترفند است و در مورد تغییر تصادفی حوزه متغیرها نگران نباشید(به عنوان مثال، در حال استفاده از آن در داخل تابع نیستید).
در هرحال، با bash، هنوز هم شما باید در مورد سمت چپ تخصیص مراقب باشید. داخل کروشهها، بازهم بسط ها انجام میشوند، بدین معنی که با ref آلوده، declare میتواند درست به خطرناکی eval باشد:
# Bash: ref='x[$(touch evilfile; echo 0)]' ls -l evilfile # چنین فایل یا دایرکتوری موجود نیست declare "$ref=value" ls -l evilfile # ! حالا موجود است
این مشکل با typeset در mksh و pdksh نیز وجود دارد، اما ظاهراً در ksh93 نیست. این است چرای آنکه مقدار ref باید همیشه تحت کنترل شما باشد.
اگر شما از پوسته Bash یا Korn استفاده نمیکنید، میتوانید تخصیص متغیرهای مرجع شده را با استفاده ازترکیب دستوری HereDocument انجام دهید:
# Bourne ref=realvariable read $ref <<EOF contents EOF
(افسوس، با روش read ما برای گرفتن حداکثر تنها یک سطر محتوا عقبگرد میکنیم. این قابل حملترین ترفند است، اما محدود به یک سطر منفرد محتوا شده است.)
به خاطر بیاورید که، موقع استفاده از here document، اگر کلمه نگهبان(EOF در مثال ما) نقلقولی نشده باشد، آن وقت بسط پارامتر درون بدنه انجام خواهد شد. اگر نگهبان نقلقولی بشود، سپس بسط پارامتر انجام نمیشود. هر کدام که برای کار شما مناسبتر است به کار ببرید.
سرانجام، بعضی ها دقیقاً نمیتوانند با پرتاب eval داخل یک عکس نیز مخالفت کنند:
# Bourne ref=myVar eval "$ref=\$value"
این به جملهای که اجرا میشود بسط مییابد:
myVar=$value
طرف راست توسط پوسته تفکیک نمیشود، بنابراین خطر اثرات ناخواسته تفکیک این طرف وجود ندارد. در اینجا اشکال آنست که، هر فوقکاراکتر منفرد پوسته در طرف راست علامت = باید به دقت با کاراکتر گریز پوشش داده شود. در مثال نشان داده شده در اینجا، فقط یک مورد وجود داشت. در یک موقعیت پیچیدهتر، میتواند خیلی زیاد باشد.
خبر خوب آن است که اگر شما طرف راست را به طور صحیح سترون نمایید، این ترفند کاملاً قابل حمل است،هیچ مسئله ناشی از دامنه متغیر ندارد، و تمام محتویات از جمله سطرهای جدید را اجازه میدهد. خبر بد آنکه اگر شما در سترونسازی طرف راست به طور صحیح، موفق نباشید، حفره حجیم امنیتی دارید. از eval با ریسک خودتان استفاده کنید.
پرسش و پاسخ شماره ۶ (آخرین ویرایش 2012-05-28 18:03:15 توسط ormaaj)