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

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

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

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

استفاده از find


کاربرد find

find(1)‎ یک فرمان فوق‌العاده سودمند برای اسکریپت‌های پوسته می‌باشد، اما بسیار بد درک شده است. این تا اندازه‌ای از یک ترکیب دستوری پیچیده (شاید پیچیده‌تر از تمام فرمانهای استاندارد یونیکس که در واقع زبان‌های برنامه‌نویسی مانند awk نیستند)، و تا اندازه‌ای از صفحات man بد نوشته شده آن ناشی می‌شود. (حتی صفحه man نگارش گنو تا همین 2006 دارای مثال نبود!)

ابتدایی‌ترین کاری که قبل از آنکه بیشتر پیش بروید، باید انجام بدهید، در حقیقت خواندن صفحه man سیستم خودتان در باره فرمان find است. نباید آن را حفظ کنید، یا هر قسمت را بفهمید، بلکه باید حداقل یکبار تمام بخشهای مختلف آن را نگاه کنید، به طوری که یک ایده کلی از چگونگی آن داشته باشید. سپس، شاید بخواهید برای مقایسه به OpenBSD man page نگاه کنید. گاهی اوقات، شما یک صفحه man را بیش از دیگری خواهید ‌فهمید. فقط بدانید که تمام پیاده‌سازی‌ها یکسان نیستند، ممکن است درفرمان OpenBSD ویژگی‌هایی باشد که فرمان سیستم شما فاقد آنها باشد، و بالعکس. اما بسیاری افراد خواندن صفحه‌های man در BSD را آسانتر می‌دانند، بنابراین شاید در درک مفاهیم به شما کمک کند.

اکنون بیاید کمی در مورد اینکه find چکار می‌کند، و چه وقت و چطور باید استفاده از آن را در نظر بگیرید صحبت کنیم.

1. نگاه کلی

ایده اصلی این است: find سلسله مراتب فایلها را به طور نزولی سیر می‌کند، فایلهای مسیر را با ضوابط تعیین شده مطابقت می‌دهد، و سپس با فایلهای تطابق یافته کاری انجام می‌دهد. این موضوع با اهمیتی می‌باشد، بنابراین من دوباره آن را به طور جزء به جزء می‌نویسم:

  • find سلسله مراتب فایلها را از بالا به پایین سیر می‌کند،

  • فایلهای مسیر را با ضوابط تعیین شده مطابقت می‌دهد، و سپس
  • کاری با آنها انجام می‌دهد.

اجازه بدهید چند مثال زنده ارایه کنیم. نخست:

find .
.
./bar
./bar/foo.jpg
./foo.mp3
./apple.txt

اگر شما . را برای تعیین دایرکتوری جاری استفاده نکنید، برخی نگارش‌های این فرمان فرض می‌کنند که منظور شما همان دایرکتوری جاری است، دیگران ممکن است یک خطا صادر کنند. شما همیشه باید دایرکتوری را که می‌خواهید find از آنجا به طرف پایین جستجو نماید، مشخص کنید.

اگر ضوابطی برای فایلهایی که باید مطابقت داده شوند، تعیین نکنید، find هر فایلی( شامل دایرکتوریها، لینکهای نمادین، بلوکها، و دستگاهای کاراکتری، FIFOها، سوکت‌ها -- هر چیز دیگر) را منطبق می‌داند. ما در مثال اول خود هیچ نشانه دیگری اضافه نکردیم، از اینرو، همه فایلها و دایرکتوری‌ها را به دست آوردیم.

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

find . -print

این همان خروجی را که قبلاً دیدیم تولید می‌نمود، آن را تکرار نمی‌کنم.

همچنین ممکن است شما مشاهده نموده باشید که خروجی find لزوماً مانند خروجی ‎ls(1)‎به ترتیب الفبایی مرتب نشده است. در واقع فایلها را به ترتیبی که داخل یک دایرکتوری ظاهر می‌شوند به شما ارائه می‌کند.

2. جستجو بر اساس نام

اکنون بیاید یک گزینه فیلتر کننده اِعمال کنیم. فرض کنید می‌خواهیم فقط فایلهایی را که به ‎.jpg‎ ختم می‌گردند، پیدا کنیم:

find . -name '*.jpg' -print
./bar/foo.jpg

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

در صورتیکه می‌خواستیم تمام فایلهایی را که به ‎.mp3‎ ختم می‌شوند نیز پیدا کنیم:

find . \( -name '*.mp3' -o -name '*.jpg' \) -print
./bar/foo.jpg
./foo.mp3

اندکی پیچیده‌تر از آن چه تا اینجا نشان داده‌ایم، بنابراین بیایید جزییات آن را مرور کنیم. بخش مرکزی آن این است:

-name '*.mp3' -o -name '*.jpg'

این در حقیقت می‌گوید «ما فایلهایی را می‌خواهیم که مطابق با این یا آن هستند». گزینه ‎-o‎ یک عملگر «یا»ی منطقی است، و قابل حمل‌ترین روش برای نوشتن این خواست است. (برخی نگارش‌های دیگر find از گزینه ‎-or‎ نیز پشتیبانی می‌کنند، اما موقعی که یک گزینه قابل حمل از قبل وجود دارد، استفاده از نشانه غیرقابل حمل با تایپ بیشتر برای چه؟)

ما کل عبارت «یا» را در پرانتزها پیچیده‌ایم، زیرا می‌خواهیم با آن به عنوان یک واحد منفرد رفتار بشود، مخصوصاً موقعی که شروع به زنجیر کردن آن با عبارتهای دیگر می‌نماییم(چنانکه به زودی انجام خواهیم داد). پرانتزها خودشان باید به طور دست نخورده به find تحویل ‌گردند، بنابراین ما می‌بایست آنها را با کاراکترهای \ محافظت می‌کردیم، زیرا پرانتزها نیز برای پوسته دارای معنی خاص هستند. همچنین می‌توانستیم به جای این کاراکترها، از نقل‌قول‌ها در اطراف پرانتزها استفاده کنیم.

سرانجام، ما عمل صریح ‎-print‎ را اِعمال نمودیم. همه فایل‌هایی که مطابق ضوابط ما باشند، نامشان در خروجی چاپ می‌گردد.

اگر بخواهید یک فایل مطابق با چندین ضابطه باشد، می‌توانید نشانه‌های متعدد تعیین کنید. هر گاه دو فیلتر را بدون ‎-o‎ مابین آنها با یکدیگر قرار بدهید، به طور ضمنی یک «and» منطقی میان آنها وجود دارد:

find . -name '*.mp3' -name '*.jpg' -print

این کد هیچ خروجی تولید نمی‌کند، زیرا ما تمام فایلهایی را که به ‎.mp3و  ‎.jpg‎ ختم می‌شوند، جستجو نمودیم. آشکارا، هیچ فایلی نمی‌تواند هر دو شرط را برآورده سازد، بنابراین خروجی نداشتیم.

اکنون، بیایید چنانکه پیشتر وعده کردیم، یک «یا» و یک «و» ضمنی را با یکدیگر زنجیر کنیم:

find . \( -name '*.mp3' -o -name '*.jpg' \) -name 'foo*' -print
./bar/foo.jpg
./foo.mp3

اینجا، ما همان عبارت «یا»ی مانند قبل را داریم: فایل باید دارای نامی باشد که به ‎.mp3یا.jpg‎ ختم گردد. علاوه بر این، نام آن باید با foo شروع بشود. نتایج نشان داده شده است.

توجه نمایید که ‎-name‎ تنها با نام فایل داخل دایرکتوری منطبق می‌گردد -- در مثال ما، با ‎foo.jpg‎ و نه با ‎foo/bar.jpg‎ یا ‎./foo/bar.jpg‎. به دلیل این است که فیلتر ‎-name 'foo*'‎ قابل انطباق با آن می‌باشد. اگر بخواهیم نام دایرکتوری را جستجو نماییم، باید به جای آن از فیلتر ‎-path‎ استفاده کنیم:

find . -path 'bar*' -print

هیچ خروجی تولید نمی‌کند. چرا؟ اینجا را نگاه کنید:

find . -path './bar*' -print
./bar
./bar/foo.jpg

-path‎ برای مطابقت نمودن اقلام، به نام مسیر کامل نگاه می‌کند (چیزی که اگر از ‎-print‎ استفاده گردد، یک سطر خروجی find خواهد بود). که در این حالت، ‎./‎ مقدم را شامل می‌شود.

(در این نقطه، ما باید اشاره کنیم که ‎-path‎ در بسیاری از نگارش‌های find معتبر نمی‌باشد. مخصوصاً، سولاریس فاقد آن است.)

همچنین می‌توانیم یک عبارت را منفی کنیم:

griffon:/tmp/greg$ find . ! -path '*bar*' -print
.
./foo.mp3
./apple.txt

کاراکتر ! عبارتی را که به دنبال آن می‌آید، منفی می‌کند، بنابراین هر فایلی را که در جایی از نام مسیر کامل خود دارای bar نیست، به دست آوردیم.

3. جستجو بر اساس زمان

یکی از متداول‌ترین مورد استفاده‌های find در اسکریپت‌های حفظ و نگهداری سیستم، پیدا کردن تمام فایلهایی است که قدیمی‌تر از یک موردی -- برای مثال، یک لحظه نسبی زمان از قبیل «۳۰ روز قبل»، یا برخی فایلهای دیگر، می‌باشد. اگر بخواهیم تمام فایلهای قدیمی‌تر از ۳۰ روز را پیدا کنیم( برای مثال، جهت تمیز کردن دایرکتوری موقتی)، از این استفاده می‌کنیم:

find /tmp -mtime +30 -print

نشانه ‎-mtime‎ یکی از سه فیلتر مورد استفاده برای بررسی نشانه‌های زمان است. یک فایل در سیستم فایل یونیکس سه نشانه زمان دارد: زمان ویرایش (mtime)، زمان دسترسی (atime)، و زمان تغییر (ctime). نشانه‌ای برای نگهداری زمان ایجاد فایل وجود ندارد.

  • ext4 و دیگران یک نشانه زمان ایجاد فایل دارند، اما من اطمینان ندارم که آیا این مورد توسط هر برنامه find یا stat پشتیبانی بشود.

اجازه بدهید برای آنها که توجه به خرج نمی‌دهند دوباره بگویم که: به طور کلی دانستن آنکه فایل چه وقت ایجاد گردیده غیرممکن است. آن اطلاعات در جایی ذخیره نمی‌شود. (به استثنای سیستم فایل‌های غیر استاندارد، که تنها در آن سیستمها این امر را ممکن می‌سازد.)

ما در مثال خود، ‎-mtime‎ را به کار بردیم، که به زمان ویرایش فایل نگاه می‌کند. این همان نشانه زمان است که موقع اجرای دستور ‎ls -l‎ می‌بینیم، و معمولاً بیش از همه مورد استفاده است. این نشانه، هر بار که با نوشتن در فایل محتویات آن تغییر می‌کند، به روزرسانی می‌شود. همچنین می‌توانستیم از ‎-atime‎برای نگاه کردن به زمان دستیابی فایل استفاده کنیم -- این مورد با دستور ‎ls -lu‎ می‌تواند دیده شود، و هر بار که محتویات فایل خوانده شود به روزرسانی می‌گردد، (مگر اینکه مدیر سیستم به طور صریح به روزرسانی atime در این سیستم فایل را خاموش نموده باشد). استفاده از ctime (زمان تغییر)، که هرگاه فوق داده‌های فایل(مجوزها، مالکیت و غیره) تغییر کند (به عنوان مثال به وسیله فرمان chmod) به روزرسانی می‌شود، کاملاً کمیاب است.

+30‎ به معنای آن است که ما تمام فایلهای قدیمی‌تر از ۳۰ روز را می‌خواهیم. اگر از این استفاده می‌کردیم:

find . -mtime 30 -print

تمام فایلهایی را که دقیقاً ۳۰ روزه هستند، به دست می‌آوردیم( با گِرد کردن به نزدیکترین روز -- برای مشخصات دقیق مستندات را ببینید). غالباً سودمند نیست. اگر چه، در صورتی که تمام فایلهایی را می‌خواستیم که در مدت ۳۰ روز اخیر ویرایش شده باشند، از این استفاده می‌کردیم:

find . -mtime -30 -print

این دستور تمام فایلهایی را که زمان ویرایش آنها کمتر از ۳۰ روز قبل است به ما ارائه می‌کند.

برخی نگارش‌های find دارای نشانه‌های ‎-mmin‎، ‎-amin‎ و ‎-cmin‎ هستند که اجازه می‌دهند زمان به جای روز بر حسب دقیقه اندازه‌گیری بشود. این مطلب به شما اجازه می‌دهد که به طور مثال تمام فایلهای ویرایش شده در ۳۰ دقیقه اخیر را پیدا کنید. این نشانه‌ها بخشی از استاندارد POSIX نیستند، اما در بسیاری از سیستم‌های گنو یا BSD موجود می‌باشند.

یک مورد استفاده رایج دیگر برای انطباق فایلها بر اساس نشانه‌های زمان آنها، موقعی است که می‌خواهیم تمام فایلهایی را پیدا کنیم که پس از آخرین زمانی که کنترل نموده‌ایم؟، ویرایش شده‌اند. منطق آن اینطور ظاهر می‌شود:

find /etc -newer /var/log/backup.timestamp -print
touch /var/log/backup.timestamp

این ساختار اصلی برای انجام پشتیبان‌گیری افزایشی سیستم می‌باشد: یافتن تمام فایلهایی که پس از آخرین بایگانی تغییر نموده‌اند و سپس به روزرسانی نشانه زمان فایل برای اجرای بعدی. (به طور واضح، به منظور داشتن یک بایگانی هدفمند، ما می‌باید کاری بیش از فقط ‎-print‎ فایلها انجام می‌دادیم، اما در بخش آتی به «عملیات» خواهیم پرداخت.)

4. جستجو بر اساس اندازه

یک مورد استفاده رایج دیگر برای find جستجوی فایلهایی است که از حدود معینی بزرگتر هستند. برای مثال، نمونه زیر تمام فایلهای بزرگتر از ۱۰ مگابایت (10485760 بایت) را داخل ‎/home‎ جستجو می‌کند:

find /home -type f -size +10485760c -print

همانند با زمان ویرایش فایل (نشان داده شده در بخش قبل)، یک + یا - مقدمِ قبل از شناسه‌ای که گزینه ‎-size‎ را دنبال می‌کند، قابل توجه است. یک + مقدم یعنی اندازه باید بزرگتر از اندازه تعیین شده باشد، یک - پیشتاز یعنی کوچکتر از، و بدون وجود علامت یعنی دقیقاً برابر اندازه مشخص شده.

بدون وجود کاراکتر c دنباله، شناسه باید به عنوان تعداد بلوک‌ها تفسیر گردد، که به طور معمول 512 بایت می‌باشند، اگر چه، ممکن است به سیستم بستگی داشته باشد.

بعضی نگارشهای find دارای مشخص کننده مقیاس اضافه‌ای از قبیل k برای کیلوبایت (1024 بایت) می‌باشند. این موارد قابل حمل نیستند، و شما اگر می‌خواهید از آنها استفاده کنید، برای یادگیری آنها باید به مستندات سیستم خودتان مراجعه نمایید.

5. عملیات

یک مثال کامل‌تر برای پاکسازی دایرکتوری ‎/tmp‎ می‌تواند چیزی مشابه این باشد:

find /tmp -type f -mtime +30 -exec rm -f {} \;

در اینجا، ما موارد جدیدی فراهم نموده‌ایم. اول از همه، ما از ‎-type f‎ برای مشخص کردن آنکه می‌خواهیم فقط فایلهای معمولی -- نه دایرکتوریها، و سوکت‌ها، و غیره -- مطابقت شوند، استفاده کردیم. پس از آن، فیلتر ‎-mtime +30‎ خودمان را داریم. به طوریکه قبلاً دیده‌ایم، دو فیلتر مجاور هم مانند این مورد به معنی وجود یک «و» تلویحی میان آنها می‌باشد، بنابراین فایل باید با هر دو معیار مطابقت داشته باشد.

اکنون، «عملیات» ما دیگر ‎-print‎ نیست، زیرا نمی‌خواهیم آنها را ببینیم. به جای آن، این مورد را تهیه کرده‌ایم:

-exec rm -f {} \;

-exec‎ یک نشانه عمل است، که می‌گوید «ما می‌خواهیم یک فرمان اجرا بشود»، فرمان به دنبال آن می‌آید، و با یک سمی‌کالن (;) خاتمه داده می‌شود. حالا، سمی‌کالن یک کاراکتر ویژه برای پوسته نیز هست، بنابراین باید آن را با کاراکتر \ محافظت کنیم، درست همانطور که قبلاً برای پرانتزها انجام دادیم. و یکبار دیگر، ما می‌توانستیم در عوض از نقل‌قول‌ها در اطراف آن استفاده نماییم، backslash یک کاراکتر کمتر است، بنابراین به طور سنتی تقدم دارد.

در فرمانِ ما، جفت ابرو یک شناسه مخصوص برای find می‌باشد. هرگاه یک دستور توسط ‎-exec‎ اجرا می‌شود، برای فایلی که انطباق یافته است، ‎{}‎ با نام فایل تعویض می‌شود. بنابراین، به فرض که ما سه فایلِ منطبق بر ضوابط، در دایرکتوری ‎/tmp‎ داشته باشیم، find می‌تواند دستورات زیر را اجرا نماید:

rm -f /tmp/foo285712
rm -f /tmp/core
rm -f /tmp/haha naughty file; "rm -rf ."      # ها ها فایل شیطانی‎

حتی اگر بعضی از کاربران با قرار دادن تروجان در نام فایلها، مانند این مثال برای ویرانگری سیستم تلاش نمایند، جایگزینی ‎{}‎ کاملاً بی‌خطر است. فرمانهایی که find اجرا می‌کند به یک پوسته عبور داده نمی‌شوند، مگر اینکه شما معین کنید. تفکیک کلمه، و تجزیه نام فایل وجود ندارد. نام فایل صِرفاً به عنوان یک شناسه به طور مستقیم به فرمان تحویل می‌شود (در مثال ما به ، rm). ابداً هیچ چیزی در نام فایل مسئله نخواهد بود.

6. عملیات پیچیده

گاهی اوقات، شاید شما بخواهید برای هر فایلی که پردازش می‌کنید، دستورات مختلطی را اجرا کنید. برای مثال، ممکن است بخواهید یک بلوک کدِ پوسته اجرا کنید که نام فایل را مجزا (حذف کردن تمام دایرکتوریهای مقدم بر نام فایل) کند، تمام حروف آن را به حروف بزرگ تبدیل نماید، و سپس کار دیگری با آن انجام بدهد (شاید بعد از مقداری کنترل اضافی، که در اینجا نشان داده نشده، تغییر نام فایل). یک بلوک کدِ پوسته برای اِعمال بر یک فرمان منفرد می‌تواند موردی مانند این باشد:

#  شامل نام فایل اولیه است ‎$1‎
name=${1##*/}
upper=$(echo "$name" | tr "[:lower:]" "[:upper:]")
echo "$name -> $upper"

اگر بخواهیم اجرای آن را توسط find برای هر تطابق فایل داشته باشیم، آنوقت دو امکان در اختیار داریم:

  1. ایجاد یک اسکریپت با این کد در آن، و سپس ‎-exec‎ آن اسکریپت.

  2. نوشتن یک فرمان پیچیده find مانند این مورد:

    find ... -exec bash -c \
         'name=${1##*/}; upper=$(echo "$name" | tr "[:lower:]" "[:upper:]");
         echo "$name -> $upper"' _ {} \;

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

  • نخست توجه نمایید که کل آن نقل‌قول منفرد شده است، بنابراین همه چیز در آن لفظی است. اگر لازم است نقل‌قول‌های منفرد خودتان را در بچه-اسکریپت تعبیه نمایید، باید ‎'\''‎ را برای نمایندگی کردن آنها به کار ببرید.

  • بچه-اسکریپت به وسیله _ و ‎{}‎ دنبال می‌شود. موقعی که ‎bash -c‎ یک فرمان اجرا می‌کند، شناسه بعدیِ پس از فرمان به عنوان ‎$0‎ به کار می‌رود (نام اسکریپت در فهرست برداری پردازش)، و شناسه‌های متعاقب آن پارامترهای مکانی (‎$1‎ و ‎$2‎ و غیره) می‌شوند. این به معنای آن است که نام فایل عبور داده شده توسط find (در جای ‎{}‎) اولین پارامتر اسکریپت می‌شود -- و در داخل بچه-اسکریپت بواسطه ‎$1‎ به آن رجوع می‌شود. (کاراکتر _ را از قلم نیاندازید و تلاش برای استفاده از ‎$0‎ در داخل بچه-اسکریپت -- نه تنها بیشتر مغشوش کننده خواهد بود، بلکه همچنین در صورتیکه نام فایل فراهم شده به عنوان شناسه توسط find دارای معنی خاص برای پوسته باشد، آماده شکست است.)

    • اگر سیستم شما دارای bash نیست، می‌توانید از پوسته دیگری استفاده کنید، مشروط بر اینکه آن پوسته از ویژگی‌هایی که در بچه-اسکریپت به کار می‌برید پشتیبانی کند. برای مثال،
      find ... -exec sh -c '..."$1"...' _ {} \;

      مخصوصاً در حالتی که پوسته sh باشد، اهمیت دارد که شناسه جایگزین شونده (placeholder) قبل از ‎{}‎ هر چیزی غیر از یک خط تیره ‎(-)‎ باشد. بعضی نگارش‌های sh خط تیره ساده در آن مکان را به جای استفاده به عنوان ‎$0‎، صرفنظر می‌کنند.

  • برای توضیح بیشتر آنکه داخل بچه-اسکریپت کدام پیش می‌رود، پرسش و پاسخ شماره 30 و پرسش و پاسخ شماره 73 را ببینید.

ما یک مثال از این پیچیدگی را بیشتر به خاطر آنکه بارها در کانال ‎#bash‎ در IRC درخواست گردیده، در اینجا قرار داده‌ایم. همچنین، یک راه حل اندکی ساده‌تر وجود دارد که به نظر می‌رسد باید کار کند -- و ممکن است در بسیاری موقعیت‌ها حقیقتاً کار کند -- که اما به طور کلی برای استفاده بسیار خطرناک می‌باشد:

  • هرگز ‎{}‎ را به طور مستقیم داخل یک بچه-اسکریپت که توسط ‎bash -c‎ یا ‎sh -c‎ اجرا می‌شود، قرار ندهید!

    # !این بد است! خطرناک! استفاده نکنید‎
     find ... -exec bash -c 'echo {}' \;

    وقتی این فرمان اجرا می‌شود، امکان دو نتیجه وجود دارد. برخی نگارش‌های find به سادگی جایگزینی ‎{}‎ را به هیچوجه انجام نخواهند داد، و شما در خروجی یک سطر نوشته شده از ‎{}‎ برای هر فایل خواهید دید. سایر نگارش‌های find علائم ‎{}‎ را با نام فایل تعویض خواهند نمود و سپس نتیجه را به عنوان کد پوسته برای تفسیر و اجرا در نظر می‌گیرند. اگر نام فایل شامل فرمانهایی باشد که پوسته می‌فهمد، از قبیل ‎; rm -rf $HOME‎، آنوقت پوسته ممکن است آن فرمانها با نتایج مصیبت‌بار را اجرا کند.

7. عملیات انبوه: xargs و ‎-print0‎ و ‎-exec +‎

xargs به طور پیش‌فرض فوق‌العاده خطرناک است، و بدون الحاقیه غیراستاندارد ‎-0‎ هرگز نباید استفاده گردد. لطفاً حذف اطلاعات مهم از این سند را مانع شوید.

xargs فرمانی است که کلمات را از ورودی استاندارد می‌خواند، و هر کلمه را به عنوان شناسه فرمان اختیاری به کار می‌برد. این فرمان کلمات را در تکه‌های بزرگ پردازش می‌کند (اگرچه شما می‌توانید تعیین کنید که در هر نوبت چه تعداد کلمه را پردازش کند)، به طوریکه آن فرمان نسبتاً دفعات کمی فراخوانی می‌شود. این مورد به واسطه پردازش فایلها به طور انبوه به جای یک به یک، می‌تواند آن فرمانهای find را که مقدار زیادی فایل را اداره می‌کنند، تسریع نماید.

برای مثال،

find . -name '*.temp' -print | xargs rm      # !این را اجرا نکنید‎

در این مثال (که اتفاقاً بسیار نامناسب است)، فرمان find یک لیست از نام فایلهای منطبق بر فیلتر ما تولید می‌کند، و آنها را با یک سطر جدید بعد از هر کدام در خروجی استاندارد می‌نویسد. xargs این نامها را در هر نوبت یک کلمه می‌خواند، و آنوقت یک یا چند فرمان بزرگ rm برای حذف فایلها تشکیل می‌دهد. در این حالت به جای فراخوانی rm به ازای هر فایل، این فرمان حداکثر چند نوبت فراخوانی خواهد شد، این مورد در سیستم‌هایی که در آنها ‎fork()‎ واقعاً آهسته است می‌تواند صرفه‌جویی چشمگیری در وقت باشد.

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

http://en.wikipedia.org/wiki/Xargs#The_separator_problem

به علت این مسائل جدی (بلکه به دلیل آنکه بازهم اشخاص قابلیت تنها یکبار انشعاب rm را می‌خواستند)، چند روش رفع و رجوع مختلفی ایجاد گردیده است. اولین مورد، شامل تغییر هر دو فرمان find و xargs می‌گردد:

find . -name '*.temp' -print0 | xargs -0 rm

با عمل ‎-print0‎، به جای قرار دادن یک سطر جدید بعد از هر نام فایل، find در عوض یک کاراکتر NUL به کار می‌برد (اسکی 00). چون NUL تنها کاراکتری است که در یک نام مسیر یونیکس معتبر نیست (‎/‎ نیز در یک نام فایل معتبر نمی‌باشد، اما در اینجا بحث در مورد نام مسیر است نه نام فایل، بنابراین لطفاً مانع قرار دادن اطلاعات نادرست در سند بشوید!)، NUL یک جداکننده معتبر بین نام‌های مسیر در جریان داده می‌باشد. نشانه متناظر ‎-0‎ (بعد از خط تیره صفر است نه حرف o) برای xargs به xargs می‌گوید به جای خواندن کلمات جدا شده با فضای سفید، در عوض کلمات جدا شده با NUL را بخواند. همچنین تجزیه خاص xargs در مورد نشانه‌های نقل‌قول و آپاستروف را خاموش می‌کند.

ویژگی ‎-print0‎ به طور نوعی در سیستم‌های GNU و BSD یافت می‌شود. در پیاده‌سازی‌های find که فاقد آن هستند، این ویژگی به این طریق می‌تواند شبیه‌سازی گردد

find . -name '*.temp' -exec printf '%s\0' {} \; | xargs -0 rm

اگر چه، این مورد دارای سه مشکل می‌باشد:

  • بازهم یک پردازش printf برای هر فایل منشعب می‌کند، بنابراین احتمالاً حتی از موقعی که به سادگی ‎-exec rm‎ را به طور مستقیم به کار ببریم، آهسته‌تر خواهد بود.

  • بازهم از ‎-exec‎ استفاده می‌کند، که احتمالاً ما سعی داریم به کار نبریم. بنابراین تایپ فرمانی که می‌خواهیم به کار ببریم در یک ‎-exec‎ نقل‌قولی شده، سریعتر از لوله‌کشی بیهوده آن به یک فرمان دیگر است.
  • نیاز به آن دارد که پیاده‌سازی xargs شما دارای نشانه ‎-0‎ باشد. غیر عادی است که در همان سیستم، xargs دارای ‎-0‎ باشد اما find فاقد ‎-print0‎ باشد.

بنابراین موردی است که احتمالاً هرگز در عمل مفید نمی‌باشد.

راه رفع و رجوع دوم به طور رایج‌تری در سیستم‌های ‎POSIX- (SUSv3 ff.)‎ و ‎SVR4-مانند و در findutils گنو از نیمه ‎'06‎ یافت می‌گردد و کلاً دست کشیدن از xargs را شامل می‌شود:

find . -name '*.temp' -exec rm {} +

علامت + (به جای ;) در انتهای عمل ‎-exec‎ به find می‌گوید از یک ویژگی xargs-مانند درونی استفاده کند که باعث می‌گردد فرمان rm به جای یکبار برای هر فایل تنها یکبار برای هر تکه از فایلها فراخوانی بشود.

متأسفانه، این مورد با ویژگی + در پوسته POSIX-مانند، که ‎{}‎ باید در انتهای فرمان ظاهر شود. کار نمی‌کند:

find . -type f -exec cp {} ../bar +          # .یک خطا تولید می‌کند‎

نکته ظریفی بود، اینطور نیست؟ اوه خب.

  • آیا این همان است -من تازه وارد هستم - زیرا این کد بدون نقص کار می‌کند؟
     find /mnt/sdb7/recipes -iname "*$i*" -type f  -exec cp -fiuRt   /mnt/sdb7/tmp2/"$i" '{}' +
    • شما به گزینه ‎-t‎ فرمان cp گنو استناد می‌کنید که به شما اجازه می‌دهد گزینه‌ها را وارونه بنویسید. بله، مشروط به اینکه در یک سیستم گنو باشید، کار می‌کند. اما من باید از تمام آن شناسه‌های دیگر (fiuR) را حذف کنم مگر اینکه شما حقیقتاً رفتار آنها را لازم داشته باشید.یک جایگزین قابل حمل برای آن، می‌تواند موردی مانند این باشد:

        find . -type f -exec sh -c 'cp -- "$@" /target' _ {} +
  • شما باید در مثال find خود با استفاده از ‎-exec rm‎ تجدید نظر کنید، چون find گنو یک گزینه اضافه برای آن دارد: ‎-delete‎ که به مراتب راحت‌تر است.
     find . -name '*.temp' -delete
    که برای مثالهای قبل‌تر نیز صدق می‌کند. من نمی‌دانم آیا برای ‎posix-find‎ یک سوییچ ‎-delete‎ وجود دارد. توجه: گزینه ‎-delete‎ دایرکتوریها را هم حذف می‌کند.
    • فقط گنو است، POSIX نیست.

(من برخی مثالها را که قصد استفاده از GNU parallel به عنوان یک جایگزین برای xargs داشتند، حذف کرده‌ام. GNU parallel می‌تواند بسیار مفید باشد، اما مثالهای ارائه شده در اینجا کاملاً اشتباه بودند. صفحه مدیریت پردازش دارای تعدادی مثال مفید parallel می‌باشد.)

7.1. باگ ناخوش‌آیند ‎OS X‎

کاربران ‎OS X‎: از یک باگ دیرپای در پیاده‌سازی Darwin از ‎-exec … +‎، آگاه باشید، یعنی آن find موقعی که برنامه فراخوانی شده در ‎-exec‎ یک وضعیت خروج غیرصفر برگشت می‌دهد، خارج می‌شود. این مستلزم دقت است، زیرا find برنامه مشخص شده را برای آن تعداد فایل که می‌تواند مناسب اندازه max_args باشد فراخوانی نموده و خارج می‌شود، مگر اینکه فرمان موفق گردد، که در آن حالت برای مقدار بعدی فایلهای واجد شرایط همان عمل تکرار خواهد شد.

به بیان دیگر، باز هم ممکن است شما نتایجی را به دست آورید، اما نه تمام آنها را.

  • از ‎OS X‎ نگارش ‎10.8.1‎ به نظر می‌رسد باگ رفع گردیده است. شما می‌توانید برای خودتان بررسی کنید:
    dir="/tmp/lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat"
    
    mkdir -p "$dir" && (cd "$dir" && touch {0001..9999})
    exec_count=$(find "$dir" -type f -exec bash -c 'echo; false' _ {} + | wc -l)
    
    if (( exec_count > 1 ))
    then echo "The bug is fixed in this version of find!"
    else echo "BUG FOUND.  Please don't trust -exec ... + in this version of find."
    fi
    اگر نگارش فعلی ‎OS X‎ شما بزرگتر از ‎10.5.4‎ و کوچکتر از ‎10.8.1‎ است، از بازخورد قدردانی خواهد شد.

8. عملیات انبوه: GNU Parallel

ابزار غیر استاندارد GNU parallel در برخی حالت‌ها می‌تواند مفید باشد:

find . -name '*.temp' -print0 | parallel -X -0 rm

یکبار دیگر، من مثالهایی را که ناجور و ناامن بودند حذف کرده‌ام. در مدیریت پردازش مثالهای بهتری از GNU parallel وجود دارد.

برای صحت و امنیت، باید با GNU parallel دقیقاً مانند xargs رفتار بشود. نام فایلها را در یک جریان به آن تغذیه نکنید مگر اینکه از ‎-print0‎ و ‎-0‎ استفاده کنید.

9. کنترل مجوزهای فایل

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

ابزارهای استاندارد یونیکس که بتوانند به طور معقول مجوزهای یک فایل را به شما ارائه کنند، وجود ندارد. اگر چه، پرسش واقعی در اینجا «مجوزها کدام هستند؟» نیست. در عوض، «آیا مجوزها درست هستند؟» می‌باشد. اگر ما بخواهیم (به عنوان مثال) بدانیم آیا مجوزهای یک فایل 0644 است، می‌توانیم از find استفاده کنیم:

find myfile -perm 0644 -print
# .اگر هر گونه خروجی ارائه کند، آنوقت فایل دارای مجوز ‏0644 می‌باشد‎

این یک هک تا اندازه‌ای بدریخت برای رفع و رجوع این واقعیت است که یونیکس دارای فرمان ‎stat(1)‎ نمی‌باشد، اما حداقل قابل حمل است.

بسیاری اوقات شما یک الگوی مشخص شناخته شده مجوزها را به خاطر ندارید -- فقط می‌خواهید اگر فایلهایی دارای بیت‌های تنظیم شده مجوزِ معینی وجود دارند، بدانید. برای مثال، برخی ابزارهای امنیتی فایلهایی را که بیت setuid تنظیم شده دارند، جستجو می‌کنند:

find /usr -perm -4000 -print

تفاوتهای ظریفی در معنای شناسه ‎-perm‎، بر اساس آنچه به دنبال آن می‌آید، وجود دارد. اگر شناسه دنبال ‎-perm‎ با یک رقم شروع بشود، آنوقت فقط فایلهایی دقیقاً با همان مجوزها تطبیق داده خواهند شد. اگر شناسه پس از ‎-perm‎ با یک - شروع شود، آنوقت find هر فایلی را که دارای تمام مجوزهای مشخص شده، و احتمالاً سایر مجوزها باشند تطبیق خواهد نمود. (ما نمونه فوق را برای یافتن فایلهایی که دارای بیت setuid تنظیم شده ‎(4000)‎ هستند، صرفنظر از سایر بیت‌های تنظیم شده دیگری که ممکن است داشته باشند، به کار بردیم.)

سرانجام، find گنو دارای یک شکل سوم است: اگر شناسه بعد از ‎-perm‎ با یک + شروع شود، آنوقت find فایلها با هر حالتی از مجوزهای مشخص شده را مطابقت می‌دهد. برای مثال، برای پیدا کردن فایلهایی که بیت setuid یا setgid (یا هر دو بیت) آنها تنظیم است:

find /usr -type f -perm +6000 -print
# FreeBSD و Darwin احتمالاً‎ .گنو find متأسفانه فقط ‎

Setuid برابر 4000 است، و setgid نیز 2000 است. اگر بخواهیم هر یک از اینها را مطابقت بدهیم باید آنها را با هم اضافه کنیم (از نظر فنی، آنها را باهم OR بیتی می‌کنیم، اما این حالت نیز همان است...) و سپس با فیلتر ‎-perm +‎ به کار می‌بریم. بعلاوه، ما ‎-type f‎ را اضافه نمودیم، به علت اینکه دایرکتوریها نیز ممکن است setgid باشند، و آنها برای ما مهم نیستند.

اگر نمی‌توانیم از فیلتر ‎-perm +‎ استفاده کنیم، آنوقت باید هر بیت مجوز را به طور صریح کنترل کنیم:

find /usr -type f \( -perm -2000 -o -perm -4000 \) -print
#  ‎-perm +6000‎ نگارش قابل حمل ‎

find گنو همچنین دارای یک روش قابل فهم‌تری از کار با مجوزها می‌باشد، در این حالت فرمان ذیل فایلهای قابل خواندن (readable) توسط سایر کاربران (other) را در ‎/usr‎ پیدا می‌کند.

find /usr -type f -perm -o=r

یک مثال دیگر از این مورد، کنترل مجوزهای برخی از فایلهای کاربر برای حصول اطمینان از آن که آنها باعث مشکلات نمی‌شوند، خواهد بود. برای نمونه، sshd از خواندن کلیدهای عمومی که برای گروه یا همه قابل نوشتن است، امتناع خواهد نمود، و ‎qmail-local‎ از پذیرفتن فایلهای ‎.qmail‎ قابل نوشتن برای گروه یا همه امتناع خواهد نمود.

find /home \( -name '.qmail*' -o -name authorized_keys \) -perm +0022 -print

مجوز نوشتن گروه 0020، و مجوز نوشتن همه 0002 است. بنابراین برای جستجوی یکی از اینها از ‎-perm +0022‎ استفاده می‌شود.

در واقع این یک کنترل کامل برای مجوز نادرست این فایلها نیست، ssh و qmail همچنین در صورتی که هر دایرکتوری در نام مسیر منتهی به آنها قابل نوشتن توسط گروه یا همه باشد (از قبیل خود ‎/home‎ ) آنها را رد می‌کند. اما این مطلب قدری فراتر از محدوده این صفحه می‌باشد. برای حالت ssh صفحه SshKeys را ببینید.

10. منطق عبارت Find و عمل ‎پیش فرض ‎"-print"‎

بر اساس تعریف POSIX از find، عبارت بعد از گزینه‌های اولیه و لیست مسیرها، ضرورتاً فقط متشکل از یک نوع عملگر می‌باشد، که صفر یا چند, عملوند می‌گیرد، و همیشه با یک ارزش صحیح ارزشیابی می‌شود.

اگر عبارتی ارائه نشده باشد، find به طور ضمنی اینطور رفتار می‌کند:

find [-H|-L] path1 [path2 ...] -print

یا، اگر سه عملگر خاص ‎-exec‎ یا ‎-ok‎ یا ‎-print‎ در جایی از عبارت حاضر نباشد، آنوقت find به طور ضمنی عبارت را اینطور بازنویسی می‌کند:

find [-H|-L] path1 [path2 ...] \( expression \) -print

در هر یک از حالت‌ها، ‎-print‎ به طور مؤثر به عنوان پیش‌فرض به انتها سنجاق میشود.

پیاده‌سازی Open BSD عملگرهای ‎-ls‎ و ‎-print0‎ را به لیست استثناهای فوق اضافه می‌کند.

find گنو عملگرهای عبارت را به چند دسته گزینه‌ها، تست‌ها، و عملیات تقسیم می‌کند.

  • گزینه‌ها مقداری ظاهر رفتار find را اصلاح می‌کنند.آنها برای منطق عبارت خیلی مهم نیستند و به طور معمول باید با یکدیگر در ابتدا قرار داده شوند. find گنو در صورتی که آنها جای دیگری قرار داده شوند یک هشدار خواهد داد. برای جزییات صفحه man را ببینید.

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

  • عملیات اساساً عملگرهایی هستند که مقداری اثر جانبی (چاپ خروجی،انجام یک عملیات سیستم فایل، وغیره) را علاوه بر داشتن یک ارزش صحیح، ارایه می‌کنند. همچنین به طور با اهمیت، آنها عملگرهایی هستند که به استثنای ‎-prune (تشریح شده در پایین) توسط find گنو متعلق به لیست عملگرهای خاص فوق که پیش‌فرض ‎-print‎ را از کار می‌اندازند، در نظر گرفته می‌شوند. این به دلیل آن است که ‎-prune‎ تنها عمل(action) در find گنو است که توسط POSIX نیز تعریف شده است در جایی که POSIX یک ‎-print‎ پیش‌فرض تعریف نمی‌کند، بنابراین find گنو از آن پیروی می‌کند. عملیات find گنو عبارتند از: ‎-delete‎ و ‎-exec‎ و ‎-execdir‎ و ‎-fls‎ و ‎-fprint‎ و ‎-fprint0‎ و ‎-fprintf‎ و ‎-ls‎ و ‎-ok‎ و ‎-okdir‎ و ‎-print‎ و ‎-print0‎ و ‎-printf‎ و ‎-prune‎ و ‎-quit‎.

موضوع با اهمیت برای آگاهی در باره عملگرهای منطقی آن است که همچون در اکثر زبانها، آنها اتصال-کوتاه هستند، و آن عملیات و آثار جانبی آنها در صورتی اِعمال می‌شوند که عملگر action به منظور ارزیابی شدن، نتیجه بشود. بدین معنی که، عملگرهای ‎-a‎ و ‎-o‎ وسیله ابتدایی «روند کنترل» find می‌باشند. اگرچه، درک کردن شرکت‌پذیری می‌تواند دشوار باشد. بر خلاف بسیاری از زبانهای برنامه‌نویسی، اما به طور رایج در زبان گزاره‌ای و نشانه‌گذاری منطقی، ‎-a‎ نسبت به ‎-o‎ پیشی می‌گیرد. به آسانی می‌توانیم آن را با «تست‌ها»ی ‎-true‎ و ‎-false‎ در find گنو (توجه نمایید که در find نگارش‌های قدیمی‌تر BSD استفاده از ‎-false‎ معادل یک ‎-not‎ می‌باشد!)، همراه با عمل ‎-printf‎، که همیشه به عنوان صحیح ارزیابی می‌گردد، امتحان کنیم. ولواینکه ‎-a‎ همیشه تلویحی است، من در مثالهای باقیمانده این بخش به طور نمایان به کار خواهم برد. فرض کنید در یک دایرکتوری خالی هستیم، به طوری که عبارت دقیقاً یکبار ارزیابی می‌شود.

 $ find . -false -o -false -a -printf 'nope\n' -o -printf 'yep\n' -o -printf 'nope\n'
yep

اندیشیدن به این منطق بر حسب لیستهای Bash می‌تواند فریبنده باشد.

 $ false || false && printf 'nope\n' || printf 'yep\n' || printf 'nope\n'
yep

اما این به یک اشتباه رایج منجر می‌گردد. در Bash، عملگرهای && و || دارای تقدم یکسان هستند. ملاحظه کنید

 $ find . -true -o -false -a -printf 'yep\n'
<بدون خروجی>

در این عبارت شرکت‌پذیری به طرف راست است. چون اولین عملوندِ تفکیک true است، دومی ارزیابی نمی‌شود (تمام ترکیب).

همواره پیگردی نمودن آن که کدام عملگرها استفاده می‌شوند، برای اینکه بازنویسی «‎-print‎ پیش‌فرض» بتواند در نظر گرفته شود، اهمیت دارد. یک مثال واقع بینانه‌تر را ملاحظه کنید:

find somedir -name '*.autosave.*' -o -name '*.bak' -a -print

بار دیگر، این شرکت‌پذیری به طرف راست است. اگر اولین عملگر غلط باشد، find بررسی می‌کند آیا دومی صحیح است، زیرا ‎-o‎ فقط موقعی اتصال کوتاه می‌شود که عملوند اول صحیح باشد. چونکه ‎-a‎ باعث می‌شود فقط در صورتی که اولی صحیح باشد، عملوند دوم آن ارزیابی گردد، عبارت فوق فقط فایلهایی را که با ‎'*.bak'‎ منطبق گردند چاپ می‌کند، بر خلاف آنچه ممکن است انتظار برود. اما چه می‌شد اگر ‎-print‎ از قلم افتاده بود؟

find somedir -name '*.autosave.*' -o -name '*.bak'

طبق قواعد فوق، این عبارت به طور تلویحی به این صورت بازنویسی می‌شود

find somedir \( -name '*.autosave.*' -o -name '*.bak' \) -a -print

که به طور غیر قابل انتظار، واقعاً دارای رفتار محسوسِ چاپ فایلهای منطبق بر هر یک از ضوابط ‎-name‎ می‌باشد. این موضوع در بخش بعدی بیشتر بررسی می‌شود.

11. ‎-prune‎

اگر فایل جاری یک دایرکتوری باشد، و عملگر ‎-prune‎ ارزیابی شود، آنوقت find به داخل آن دایرکتوری نزول نخواهد کرد. بدین معنی که، ‎-prune‎ پریدن از روی دایرکتوری‌های فرعی و محتویات آنها را میسر می‌کند.

به طوری که در بخش قبلی ذکر گردید، ‎-prune‎ عملگر ضمنی ‎-print‎ را از کار نمی‌اندازد (به موجب POSIX، با وجود آنکه توسط گنو به عنوان یک action(عمل) رده‌بندی می‌گردد). این مطلب باز هم می‌تواند به رفتار مخالف عقل سلیم دیگری منجر گردد.

 $ tree
.
├── bork
│   ├── bar
│   └── foo
│       └── bork
└── foo
    ├── bar
    ├── baz
    └── blarg
        └── bork

 $ find . -name bork -prune
./bork
./foo/blarg/bork

آنچه مورد انتظار بود نیست؟ اجازه بدهید آن را با یک ‎-print‎ بازنویسی نماییم چون کاری است که در این حالت find به طور درونی انجام می‌دهد.

find . \( -name bork -prune \) -print

اگر ‎-name‎ با ‎"bork"‎ مطابقت کند، آنوقت ‎-prune‎ بررسی می‌شود، که همیشه صحیح است. چون ‎"true and true"‎ صحیح است، و ‎-a‎ در این حالت باعث می‌گردد جزء بعدی عبارت یعنی ‎-print‎ ارزیابی شود. از این قرار، تنها فایلها یا دایرکتوریهای منطبق با ‎"bork"‎ چاپ می‌شوند، و بعلاوه، به یک دایرکتوری منطبق با ‎"bork"‎ وارد نمی‌شود، بنابراین هر فایل یا دایرکتوری تحت آن دایرکتوری چاپ نمی‌شود.

این هم آنچه تقریباً مورد نظر بود، کاربرد ‎-o‎ و یک ‎-print‎ آشکارا

 $ find . -type d -name bork -prune -o -print
.
./foo
./foo/bar
./foo/blarg
./foo/blarg/bork
./foo/baz

اکنون، اگر فایل جاری یک دایرکتوری باشد که ‎-name‎ آن با ‎"bork"‎ مطابقت کند، آنوقت: با اِعمال ‎-prune‎، به داخل دایرکتوریهای فرعی آن نزول نمی‌کند، و ‎-print‎ اِعمال نمی‌گردد. وگرنه، تمام لیست-and غلط ارزیابی می‌شود، ‎-prune‎ ارزیابی نمی‌شود، و ‎-print‎(مترجم: یعنی قسمت دوم عملگر ‎-o‎) ارزیابی می‌شود.

12. مطالعه اضافی

یادداشت‌هایی در باره Find


CategoryShell CategoryUnix

کاربرد Find (آخرین ویرایش ‎2013-06-14 18:11:42‎ توسط 156-56-177-162)


RaceCondition


یک race condition وضعیتی است که در آن دو یا چند مورد به طور همزمان در حال وقوع می‌باشند، و نتیجه نهایی به زمان‌بندی دقیق رویدادها بستگی دارد.

برای مثال، دو برنامه را در نظر بگیرید که در یک زمان اجرا می‌شوند:

  #!/bin/sh
  # برنامه اول‎
  read number < file
  number=$(($number + 1))
  echo $number > file

  #!/bin/sh
  # برنامه دوم‎
  read number < file
  number=$(($number - 1))
  echo $number > file

برنامه اول، یک عدد را از فایل می‌خواند، در حافظه یکی به آن اضافه می‌کند، و آنوقت جواب را در فایل می‌نویسد. برنامه دوم عدد را می‌خواند، یک را از آن کم می‌کند، و سپس پاسخ را در فایل می‌نویسد. فرض کنید ما عدد 42 را در فایل بگذاریم و هر دو برنامه را در یک زمان اجرا کنیم. چه اتفاقی رخ می‌دهد؟

به طور ساده‌انگارانه، انتظار خواهیم داشت مقدار نهایی در فایل 42 بشود. اگر سیستم عامل ابتدا برنامه اول را زمان‌بندی کند، آنوقت آن برنامه 43 را در فایل می‌نویسد، سپس برنامه دوم 43 را می‌خواند، یک را از آن کم می‌کند و 42 را در فایل می‌نویسد. همچنین، اگر سیستم‌عامل ابتدا برنامه دوم را در زمان‌بندی قرار دهد، انتظار خواهیم داشت فایل محتوی 41 و سپس موقعی که برنامه اول تمام می‌شود، 42 بشود. درست است؟

خوب، به طور حتم این یک پیامد محتمل می‌باشد. اما تنها نتیجه ممکن نیست، به علت اینکه برنامه‌ها atomic نیستند -- یعنی، آنها تمام مراحل خود را بدون انقطاع انجام نمی‌دهند.

یک توالی احتمالی دیگر وقوع رویدادها چنین است:

  1. برنامه اول عدد 42 را از فایل می‌خواند.
  2. برنامه دوم عدد 42 را از فایل می‌خواند.
  3. برنامه اول 1 را به عددش اضافه می‌کند، و اکنون در حافظه 43 را دارد.
  4. برنامه دوم 1 را از عددش کم می‌کند، و حالا 41 را در حافظه دارد.
  5. برنامه اول 43 را در فایل می‌نویسد.
  6. برنامه دوم 41 را در فایل می‌نویسد. نتیجه نهایی: 41

اگر دو سطر انتهایی به طور برعکس رخ بدهند، آنوقت جواب نهایی 43 خواهد شد. بنابراین، به نسبت تمایل زمان بندی سیستم‌عامل، می‌توانیم نتایج انتهایی 41, 42 یا ‏43 را داشته باشیم.

 وضعیت مسابقه در اَشکال بسیاری ظاهر می‌شود. در چندین نوع برنامه‌نویسی آنها موضوع در خورِ رسیدگی هستند. هر وقت باید کنترل کننده‌های ناهماهنگ نوشته شوند، یا در یک سیستم‌عامل چند کاربره، با سیستم‌عامل فعل و انفعال صورت بگیرد، برای اجتناب از وضعیت مسابقه، باید مراقبت فوق‌العاده انجام بشود.

  • RaceCondition (آخرین ویرایش ‎2009-02-04 20:56:56‎ توسط GreyCat)

اشکالزدایی

ادامه یادداشت قبل


6. اشکالزدایی

خیلی وقتها، خودتان را مستأصل می‌بینید که چرا، اسکریپت شما آنگونه عمل نمی‌کند، که شما می‌خواهید. حل این مسئله همواره، موضوع درک عمومی و شیوه‌های اشکال‌یابی است.

تشخیص مشکل

بدون آنکه دقیقاً بدانید مشکل چیست، به احتمال بسیار زیاد، خیلی زود نمی‌توانید چاره‌سازی نمایید. بنابراین مطمئن شوید، که به طور دقیق می‌دانید چه چیز اشتباه است. علائم و پیغام‌های خطا را بررسی و ارزیابی کنید.

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


حداقل‌سازی کد اصلی

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

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

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

به مجرد اینکه، موردی که مشکل ایجاد نموده را حذف کردید، دست بکشید(یا موردی که اضافه نمودنش دوباره آن مشکل را ظاهر می‌کند)، شما کشف کرده‌اید که مشکل در کجا قرار دارد. حتی اگر به طور دقیق به آن نرسیده‌اید، حداقل دیگر به یک اسکریپت حجیم خیره نمی‌شوید، بلکه امیدوارانه، با کوتوله‌ای نه بیش از 3 تا 7 سطر، مواجه هستید.

برای مثال، اگر اسکریپتی دارید که باز کردن فایلهای تصویری موجود در شاخه image را برحسب تاریخ برای شما انجام می‌دهد، و بنا به دلایلی، تکرار روی فایلهای دایرکتوری را نمی‌توانید به طور صحیح پیش ببرید، کافی است اسکریپت را تا اندازه این قطعه کُد کاهش بدهید:

    for image in $(ls -R "$imgFolder"); do
        echo "$image"
    done

اسکریپت واقعی شما به مراتب پیچیده‌تر از این خواهد بود، و درون حلقه for آن نیز طولانی‌تر خواهد بود. اما اصل مشکل این کُد است. وقتی مشکل را به این اندازه محدود کنید، ممکن است دیدن آنکه با چه مشکلی مواجه هستید، آسانتر باشد. دستور echo اجزاء نام فایلها را بیرون می‌دهد، به نظر می‌رسد تمام فضاهای سفید با کاراکتر سطرجدید تعویض گردیده‌اند. باید اینطور باشد، زیرا echo برای هرقسمت از نام فایل که منتهی به فضای سفید است، اجرا می‌شود، نه برای هر نام تصویر(در نتیجه، به نظر می‌رسد، خروجی نام فایلهایی که دارای فضای سفید می‌باشند، تفکیک شده‌اند). با این کُد کاهش یافته، دیدن آنکه مسبب واقعی، جمله for است که خروجی ls را به کلمات تفکیک نموده، آسانتر است. هرگز ls را در اسکریپتها به کار نبرید، مگر آنکه بخواهید خروجی آنرا به کاربر نمایش بدهید.

ما نمی‌توانیم glob بازگشتی به کار ببریم(مگر در bash نگارش 4)، بنابراین باید دستور find را برای به دست آوردن نام فایلها به کار بگیریم. یک راه اصلاح آن، چنین خواهد بود:

    find "$imgFolder" -print0 | while IFS= read -r -d '' image; do
        echo "$image"
    done

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


فعال نمودن وضعیت اشکال‌یابی BASH

اگر بازهم خطای روش‌هایتان را نمی‌بینید، شاید وضعیت اشکال‌یابی BASH برای دیدن مشکل در میان کُد به شما کمک نماید.

موقعی که BASH با گزینه ‎-x‎ اجرا می‌شود، این وضعیت فعال می‌گردد، هر دستوری را قبل از اجرا در خروجی چاپ می‌کند. همینطورهم ، بعد از هر بسط و گسترشی که انجام شده است. در نتیجه، به طور دقیق می‌توانید ببینید با اجرای هر سطر کُد، چه اتفاقی رخ می‌دهد. به نقل‌قول‌های استفاده شده خیلی دقیق توجه نمایید. BASH نقل‌قولها را برای نشان دادن آنکه دقیقاً کدام رشته به عنوان یک شناسه منفرد عبور داده شده، به کار می‌برد.

سه روش برای فعال کردن این وضعیت موجود است.

  • اجرای اسکریپت به صورت ‎ bash -x‎:

          $ bash -x ./mybrokenscript
  • ویرایش سرآیند اسکریپت:
          #!/bin/bash -x
          [.. script ..]
    
  • یا:
          #!/usr/bin/env bash
          set -x
    
  • یا اضافه نمودن ‎set -x‎ در جایی از کُد برای فعال کردن این حالت، منحصراً برای قطعه معینی از کُد:

          [..کدهای بی ارتباط..]
          set -x
          [..قطعه کد مرتبط..]
          set +x
          [..کدهای بی ارتباط..]
  • اگر ‎set -x‎ شما مقدار زیادی خروجی دارد، ویژگی دلپذیر bash نگارش 4.1 و بالاتر، متغیر BASH_XTRACEFD است. این متغیر امکان تعیین یک توصیف‌گر فایل برای هدایت خروجی اشکال‌های ‎set -x‎ به آن را فراهم می‌کند. در نسخه‌های قدیمی‌تر bash، همواره این خروجی به stderr می‌رفت، و اگر جدا کردن آن از خروجی معمولی ناممکن نبود، ولی دشوار بود. این هم یک روش دلپسند برای کاربرد آن:

        # ‎ را در یک فایل کپی می‌کند set -x ‎بخش 
        # ‎با یک نام فایل به عنوان 1$ آنرا فعال می‌کند‎
        # اگر پارامتری وجود نداشته باشد آن را غیر فعال می‌کند
        # ‎شماره  4 نباید در جای دیگری از اسکریپت استفاده شده باشد fd ‎
        setx_output()
        {
            if [[ $1 ]]; then 
               exec 4>>"$1"
               BASH_XTRACEFD=4
               set -x
            else
               set +x
               exec 4>&-
            fi
        }

اگر اسکریپت‌های پیچیده و آشفته‌ای دارید، شاید تغییر محتوی متغیر PS4 قبل از برقراری اشکال‌یابی با ‎ -x را سودمند بیابید:

      export PS4='+$BASH_SOURCE:$LINENO:$FUNCNAME: '


Step your code

اگر خروجی اشکال‌یابی از نظر شما خیلی سریع عبور می‌کند، می‌توانید کُد-مرحله‌ای را فعال کنید. کُد زیر از DEBUG دستور trap برای اطلاع به کاربر در باره دستوری که اجرا خواهد شد و انتظار برای تایید پیشرفت، استفاده می‌کند. این کُد را در محلی از اسکریپت خود که می‌خواهید مرحله‌ای بشود، قرار دهید:

    trap '(read -p "[$BASH_SOURCE:$LINENO] $BASH_COMMAND?")' DEBUG


اشکالزدای BASH

پروژه اشکالزدای Bash یک اشکال‌یاب gdb-مانند، در آدرس /http://bashdb.sourceforge.net است.

اشکال‌یاب فوق به شما کمک می‌کند در سرتاسر اسکریپت حرکت نموده و اشکالهای آن را پیگردی و پیدا کنید.


بازخوانی مستندات

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

نکته‌ها را حفظ کنید و تکرارهای راهنمایی این آموزش را خوب به خاطر بسپارید. اینها غالباً برای پرهیز از مشکلات در اسکریپت‌ها به شما کمک می‌کنند.

من این مطلب را در بخش اسکریپهای این راهنما نیز اشاره کرده‌ام، اما تکرار آن در اینجا هم با ارزش است. اول از همه، اطمینان حاصل کنید که سرآیند اسکریپت شما به راستی ‎#! /bin/bash‎ است. اگر از قلم افتاده یا اگر موردی مانند این ‎#! /bin/sh‎ است، پس شما سزاوار مشکلاتی که دارید، هستید. چون به آن معنی است که احتمالاً حتی از BASH برای اجرای اسکریپت خود استفاده نمی‌کنید. به طور وضوح علت مشکل همانست. همچنین، اطمینان حاصل کنید که کاراکترهای رفتن سر سطر(CF) در انتهای سطرها ندارید. این به سبب اسکریپتهایی است که در ویندوز نوشته شده‌اند. می‌توانید به آسانی به طور مساعدی اینها را به این صورت پاک کنید:

  •     $ tr -d '\r' < myscript > tmp && mv tmp myscript


پرسش و پاسخها و Pitfallها را بخوانید

صفحه‌های پرسش و پاسخهای رایج و دام‌های Bash تصورات غلط معمول و مشکلاتی که دیگران در اسکریپت‌های BASH با آنها روبرو شده‌اند را شرح می‌دهند. احتمال بسیار دارد، مشکل شما به شکلی در آنجا تشریح شده باشد.

برای اینکه قادر به یافتن مشکل خود در آنجا باشید، باید مسئله را به طور کاملاً واضح شناسایی کرده باشید. شما باید بدانید که در جستجوی چه چیزی می‌باشید.


از ما در IRC بپرسید

در 24 ساعت هفت روز هفته، اکثراً افرادی در کانال ‎#bash‎ حضور دارند. این کانال در شبکه freenode IRC مستقر است. برای رسیدن به ما، لازم است یک سرویس‌گیرنده IRC داشته باشید. از طریق آن به ‎irc.freenode.net‎ و ‎/join #bash ارتباط برقرار نمایید.

مطمئن شوید که می‌دانید مشکل واقعی چیست و آنرا به صورت مرحله‌ای روی کاغذ بیاورید، به طوری که خوب بتوانید آنرا شرح بدهید. ما دوست نداریم در مورد مسائل حدس بزنیم. با توضیح آنکه اسکریپت شما چه کاری باید انجام بدهد شروع کنید.

نکته دیگر، لطفاً قبل از ورود به ‎#bash‎ صفحه : XyProblem را ملاحظه نمایید.


پایان راهنمای BashGuide

هرگز این کارها را نکنید

ادامه یادداشت قبل


5. هرگز این موارد را انجام ندهید

پوسته Bash امکان انجام کارهای بسیاری برای شما فراهم می‌کند، ارائه قابلیت انعطاف‌پذیری قابل ملاحظه به شما. متأسفانه، خیلی کم شما را از سوءمصرف و دیگر رفتارهای نامطلوب، بر حذر می‌دارد. امید می‌رود، اشخاص خودشان دریابند که از برخی مسائل معین باید به هر قیمتی پرهیز نمایند.

متأسفانه بسیاری اشخاص به اندازه کافی دقیق و مراقب نیستند که بخواهند خودشان موشکافی کنند. آنها بدون اندیشیدن در مورد مسائل، می‌نویسند و بسیاری از اسکریپتهای خطرناک و مهیب به محیط‌های تولید و یا توزیع‌های لینوکس ختم می‌شود. نتیجه اینها، و حتی اسکریپتهای خیلی شخصی شما در یک شرایط اهمال اغلب می‌تواند مصیبت‌آمیز بشود.

برای پاکیزگی اسکریپت‌هایتان، و به خاطر تمام افراد بشر، هرگز هیچ موردی از سطور زیر را انجام ندهید:

  • ls -l | awk '{ print $8 }'

    • هرگز تجزیه خروجی فرمان ls را انجام ندهید! خروجی فرمان ls به چند دلیل نمی‌تواند قابل اعتماد باشد.

      1. اول، اگر نام فایلها شامل کاراکترهای پشتیبانی نشده زبان محلی شما باشد، ls نامها را خُرد خواهد نمود. در نتیجه، خروجی حاصل از تجزیه نام فایلها توسط ls، هرگز تضمین نمی‌شود که واقعاً همان نامهایی که شما قادر به یافتن آنها می‌باشید را به شما بدهد. ls ممکن است بعضی کاراکترها در نام فایل را با کاراکتر علامت سؤال تعویض نماید.

      2. دوم، ls سطرهای داده‌ها را بر اساس کاراکتر سطر جدید تفکیک می‌کند. به این طریق، هر تکه از اطلاعات یک فایل در یک سطر است. متأسفانه، نام فایلها نیز خودشان می‌توانند شامل سطر جدید باشند. این به معنی آنست که اگر شما فایلی در دایرکتوری جاری با نام شامل کاراکتر سطر جدید داشته باشید، کاملاً نتیجه تجزیه شما را درهم می‌ریزد و اسکریپت شکست می‌خورد!

      3. آخر از همه، اما نه کم اهمیت‌تر، قالب خروجی فرمان ‎ ls -l‎ تضمین نمی‌شود که در تمام پلاتفرم‌ها همسان باشد. برخی سیستم‌ها به طور پیش‌فرض شماره شناسایی گروه را از قلم می‌اندازند، و اثر گزینه ‎-g‎ را معکوس می‌نمایند. بعضی سیستم‌ها از دو فیلد برای زمان ویرایش و برخی از سه فیلد برای آن استفاده می‌کنند. در سیستم‌هایی که از سه فیلد استفاده می‌کنند، فیلد سوم می‌تواند سال یا یک ‎ HH:MM‎ الحاقی، نسبت به سن فایل، باشد.

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

  • if echo "$file| fgrep .txt; then
    ls *.txt | grep story

    • هرگز نام فایل ‌ها را با grep بررسی یا فیلتر نکنید! غیر از آنکه الگوی grep شما واقعاً هوشمند باشد، این کار احتمالاً قابل اطمینان نخواهد بود.

    • در نمونه اول مثال فوق، بررسی با هر دو مورد story.txt و story.txt.exe منطبق می‌گردد. اگر الگوهایی از grep ایجاد کنید که به اندازه کافی هوشمند باشند، احتمالاً آنها به قدری زشت، حجیم و ناخوانا می‌شوند، که با این وجود نباید از آنها استفاده کنید.

    • جایگزین آن globbing نامیده می‌شود( مترجم: من در این ترجمه گاهی به جای آن کلمه«جانشینی» را به کاربرده‌‌ام). Bash یک ویژگی به نام بسط نام‌مسیر دارد. این ویژگی به شما کمک می‌کند، که تمام فایلهایی که با یک الگوی معین مطابقت دارند را به شمار آورید. همچنین، می‌توانیداز globها جهت بررسی آنکه یک نام فایل، آیا با یک الگوی معین مطابقت می‌کند، (در یک دستور case یا ‎[[‎ ) استفاده نمایید .

  • cat file | grep pattern

    • برنامه cat را برای خوراندن محتویات یک فایل منفرد به یک فیلتر به کار نبرید. cat یک ابزار مورد استفاده برای الحاق محتویات چند فایل با یکدیگر است.

    • برای تغذیه محتویات فایلی به یک پردازش، احتمالاً می‌توانید نام فایل را به عنوان شناسه تحویل برنامه مورد نظر(مانند ‎grep 'pattern/my/file‎ یا ‎sed 'expression/my/file ‎و غیره) بدهید.

    • اگر مستندات برنامه هیچ راهی برای انجام این کار تعیین نکرده است، باید از تغییر مسیر استفاده کنید (‎read column1 column2 < /my/file‎ یا ‎tr ' ' '\n< /my/file ‎و غیره).

  • for line in $(<file); do

    • از حلقه for برای خواندن سطرهای یک فایل استفاده نکنیم. به جای آن حلقه while read را به کار ببریم.

  • for number in $(seq 1 10); do

    • به خاطر خدا و به خاطر تمام مقدسات، از برنامه seq برای شمارش استفاده نکنید.

    • Bash به اندازه کافی در انجام شمارش توانمند است. نیازی به یک برنامه خارجی(مخصوصاً یک برنامه تک سکویی) برای انجام محاسبه و ارسال آن به خروجی Bash جهت تفکیک کلمه، ندارید. ترکیب دستوری for را قبلاً آموخته‌اید!

    • باید در Bash نگارش 3 به بعد، از این: ‎for number in {1..10}‎، یا در ‎Bash 2‎ از این: ‎for ((i=1i<=10i++))‎ استفاده کنید.

    • اگر شما عملاً یک جریانی از اعداد که با کاراکتر سطر جدید از هم جدا شده‌اند، هنگام بررسی ورودی می‌خواهید، این مورد را در نظر بگیرید: ‎printf '%d\n' {1..10}

  • i=`expr $i + 1>`

    • expr یک عتیقه رُم باستان است. آن را به کار نبرید.

    • این برنامه در اسکریپت‌های نوشته شده برای پوسته‌هایی با امکانات بسیار محدود، به کار می‌رفت. اساساً با استفاده از آن در حال ایجاد یک پردازش جدید هستید که برنامه C دیگری برای انجام برخی محاسبات را برایتان فراخوانی نماید و نتایج را به صورت رشته به bashتحویل بدهد. Bash تمام اینها را خودش می‌تواند خیلی سریعتر، و به طور قابل اعتمادتر (بدون تبدیل عدد به ->رشته -> به عدد) و در همه حال بهتر، انجام بدهد.

    • شما در Bash باید از این استفاده کنید: ‎let i++ ‎ یا ‎((i++))

    • حتی پوسته POSIX بورن می‌تواند محاسبات را انجام بدهد: i=$(($i+1))‎. فقط فاقد عملگر ++ و دستور‎ ((...))‎ می‌باشد(فقط عبارت جایگزینی ‎ $((...))‎ را دارد).


ادامه دارد...

تست‌های Bash

ادامه یادداشت قبل


4. بررسی‌های Bash

فرمان test که به عنوان ‎[‎ نیز شناخته شده، یک برنامه کاربردی است که به طور معمول جایی در ‎ /usr/bin‎ یا ‎/bin‎ استقرار می‌یابد و خیلی زیاد توسط برنامه‌نویس پوسته برای اجرای آزمایش‌های معینی با متغیرها و فایلها، به کار می‌رود. در تعدادی از پوسته‌ها، از جمله Bash, دستور test به صورت دستور داخلی پوسته نیز پیاده‌سازی گردیده است.

این مورد می‌تواند نتایج شگف‌انگیزی فراهم نماید، به ویژه برای آنان که شروع به اسکریپت‌نویسی پوسته می‌نمایند و تصور می‌کنند ‎ [ ]‎ بخشی از دستور زبان پوسته است.

اگر از پوسته sh استفاده می‌کنید، انتخاب کمی دارید و استفاده از test تنها راه انجام اکثر بررسی‌هایتان می‌باشد.

گرچه اگر از Bash در اسکریپت‌نویسی استفاده می‌کنید(و من فرض می‌کنم چنین است، چون در حال خواندن این راهنما هستید)، پس می‌توانید از کلید واژه ‎[[‎ نیز استفاده کنید. هر چند بازهم از خیلی جهات همچون یک فرمان رفتار می‌کند، چندین مزیت نیز نسبت به فرمان سنتی test ارائه می‌کند.

اجازه بدهید تشریح کنم که چگونه ‎ [[‎ می‌تواند با فرمان test تعویض شود، وچطور می‌تواند به شما کمک کند از برخی اشتباهات متداول در کاربرد test پرهیز نمایید:

    $ var=''
    $ [ $var = '' ] && echo True
    -bash: [: =: unary operator expected
    $ [ "$var" = '' ] && echo True
    True
    $ [[ $var = '' ]] && echo True
    True

قسمت ‎[ $var = '' ]‎ به ‎[ = '' ]‎ بسط داده می‌شود. اولین کاری که دستور test انجام می‌دهد، شمارش شناسه‌هایش می‌باشد. چون ‎[‎ را به کار برده‌ایم، باید شناسه الزامی ] در انتها را کنار بگذاریم. در مثال اول، test دو شناسه می‌بیند: = و ''. حالا می‌داند که دو شناسه دارد، اولی باید unary operator (یک عملگر که یک عملوند می‌گیرد). اما = عملگر یگانی(unary operator) نیست(یک عملگر binary است که دو عملوند نیاز دارد)، بنابراین، test نمی‌تواند کار کند.

بله، test متغیر تهی ‎$var‎ را نمی‌بیند، زیرا BASH قبل از اینکه test حتی بتواند آن را ببیند، به هیچ بسطش داده است. نتیجه اخلاقی؟ استفاده بیشتر از نقل‌قول‌ها! کاربرد نقل‌قول‌ها در قسمت، ‎[ "$var= '' ]‎ موجب بسط آن به ‎ [ "" = '' ]‎ می‌گردد و test مشکلی ندارد.

حال آنکه، ‎[[‎ می‌تواند تمام دستور را قبل از اینکه بسط داده شود، ببیند. می‌تواند ‎$var‎ را ببیند، و نه بسط ‎$var‎ را. در نتیجه، نیازی به نقل‌قولها نمی‌باشد! ‎[[‎ مطمئن‌تر است.

    $ var=
    $ [ "$var" < a ] && echo True
    -bash: a: No such file or directory
    $ [ "$var" \< a ] && echo True
    True
    $ [[ $var < a ]] && echo True
    True

در این مثال سعی نموده‌ایم یک مقایسه رشته‌ای بین یک متغیر تهی و 'a' انجام بدهیم. شگفت‌زده می‌شویم با دیدن آنکه از اولین تلاش ما True حاصل نمی‌گردد، ولواینکه تصور می‌کردیم، می‌شود. درعوض، با خطای عجیبی که دلالت بر تلاش BASH برای باز کردن فایلی به نام ‎'a'‎ می‌نماید، مواجه می‌شویم.

ما توسط تغییر مسیر فایل گَزیده شده‌ایم. چون test دقیقاً یک برنامه کاربردی است، کاراکتر ‎<‎ در دستور ما به جای عملگر مقایسه رشته‌ای برای test،  به عنوان عملگر تغییر مسیر فایل تفسیر شده است(همانطور که باید می‌شد). BASH دستور باز کردن فایل 'a' و اتصال آن به stdin برای خواندن را دریافت نموده. برای پیش‌گیری از این مورد، لازم است, ‎ <‎ را با کاراکتر گریز پوشش دهیم، به طوری که به جای BASH برنامه test عملگر را دریافت کند. این دومین تلاش ما را تشکیل داد.

با استفاده از ‎ [[‎ می‌توانیم روی‌هم‌رفته از نابسامانی اجتناب نماییم. ‎[[‎ عملگر ‎ < را قبل از آنکه BASH آنرا برای تغییر مسیر دریافت کند، می‌بیند -- مشکل رفع می‌شود. یکبار دیگر ‎ [[‎ مطمئن‌تر است.

حتی خطرناک‌تر، استفاده از عملگر ‎ >‎ به جای عملگر ‎ <‎ مثال قبلی است. چون ‎ >‎ ماشهٔ تغییر مسیر خروجی را می‌کشد، فایلی به نام'a' ایجاد خواهد نمود. در نتیجه، هیچ پیغام خطای هشداردهنده‌ای برای ما صادر نمی‌شود که بدانیم مرتکب اشتباه شده‌ایم! به جای آن، فقط اسکریپت ما خراب می‌شود. حتی وخیم‌تر، شاید فایل مهمی را رونویسی کنیم! برای ما حدس زدن آنکه مشکل کجاست، سخت است:

    $ var=a
    $ [ "$var" > b ] && echo True || echo False
    True
    $ [[ "$var" > b ]] && echo True || echo False
    False

دو نتیجه متفاوت، شگرف. به من اعتماد کنید، وقتی می‌گویم، همیشه می‌توانید به ‎ [[‎ بیشتر از ‎ [‎ اطمینان کنید. ‎[ "$var> b ]‎ به ‎[ "a" ]‎ بسط یافته و خروجی به یک فایل جدید به نام 'b' تغییر مسیر داده می‌شود. چون ‎[ "a" ]‎ در واقع همان ‎[ -n "a" ]‎ می‌باشد و اساساً بررسی می‌شود که آیا رشته "a" غیرتهی است، نتیجه بررسی موفق است و echo True اجرا می‌شود.

با کاربرد ‎[[‎ انتظار ما که مقایسه "a" در برابر "b" است، برآورده می‌شود، و نظر به اینکه همه می‌دانیم "a" قبل از "b" مرتب می‌شود، ماشه اجرای دستور echo False کشیده می‌شود. و این چگونگی آنست که اسکریپت شما بدون پی‌بردن شما می‌تواند ناموفق بشود. هر چند که، شما یک فایل شبهه برانگیزی به نام 'b' نیز در دایرکتوری جاری خواهید داشت.

بنابراین به من باور داشته باشید، وقتی می‌گویم، ‎[[‎ مطمئن‌تر از ‎[‎ است. زیرا هر کسی به ناچار خطاهای برنامه‌نویسی را ایجاد می‌کند. افراد به طور معمول قصد ندارند، باگهایی در کدهایشان ارائه کنند. اما اتفاق می‌افتد. بنابراین مدعی نشوید که از ‎ [‎ استفاده می‌کنید و "مراقب خواهید بود که چنین اشتباهاتی مرتکب نگردید"، زیرا می‌توانم شما را مجاب کنم که مرتکب خواهید شد.

گذشته ازاین، ‎ [[‎ ویژگیهای زیر را علاوه بر ‎ [‎ ارائه می‌کند:

  • [[‎ می تواند مطابقت الگوی جانشین(glob) را انجام دهد:

    • [[ abc = a* ]]

  • [[‎ می‌تواند انطباق الگوی عبارت باقاعده(regex) را (از Bash نگارش 3.1 به بعد) انجام دهد:

    • [[ abb =~ ab+ ]]

تنها برتری test قابلیت حمل آن است.


ادامه دارد...