مندرجات
find(1) یک فرمان فوقالعاده سودمند برای اسکریپتهای پوسته میباشد، اما بسیار بد درک شده است. این تا اندازهای از یک ترکیب دستوری پیچیده (شاید پیچیدهتر از تمام فرمانهای استاندارد یونیکس که در واقع زبانهای برنامهنویسی مانند awk نیستند)، و تا اندازهای از صفحات man بد نوشته شده آن ناشی میشود. (حتی صفحه man نگارش گنو تا همین 2006 دارای مثال نبود!)
ابتداییترین کاری که قبل از آنکه بیشتر پیش بروید، باید انجام بدهید، در حقیقت خواندن صفحه man سیستم خودتان در باره فرمان find است. نباید آن را حفظ کنید، یا هر قسمت را بفهمید، بلکه باید حداقل یکبار تمام بخشهای مختلف آن را نگاه کنید، به طوری که یک ایده کلی از چگونگی آن داشته باشید. سپس، شاید بخواهید برای مقایسه به OpenBSD man page نگاه کنید. گاهی اوقات، شما یک صفحه man را بیش از دیگری خواهید فهمید. فقط بدانید که تمام پیادهسازیها یکسان نیستند، ممکن است درفرمان OpenBSD ویژگیهایی باشد که فرمان سیستم شما فاقد آنها باشد، و بالعکس. اما بسیاری افراد خواندن صفحههای man در BSD را آسانتر میدانند، بنابراین شاید در درک مفاهیم به شما کمک کند.
اکنون بیاید کمی در مورد اینکه find چکار میکند، و چه وقت و چطور باید استفاده از آن را در نظر بگیرید صحبت کنیم.
ایده اصلی این است: find سلسله مراتب فایلها را به طور نزولی سیر میکند، فایلهای مسیر را با ضوابط تعیین شده مطابقت میدهد، و سپس با فایلهای تطابق یافته کاری انجام میدهد. این موضوع با اهمیتی میباشد، بنابراین من دوباره آن را به طور جزء به جزء مینویسم:
find سلسله مراتب فایلها را از بالا به پایین سیر میکند،
اجازه بدهید چند مثال زنده ارایه کنیم. نخست:
find . . ./bar ./bar/foo.jpg ./foo.mp3 ./apple.txt
اگر شما . را برای تعیین دایرکتوری جاری استفاده نکنید، برخی نگارشهای این فرمان فرض میکنند که منظور شما همان دایرکتوری جاری است، دیگران ممکن است یک خطا صادر کنند. شما همیشه باید دایرکتوری را که میخواهید find از آنجا به طرف پایین جستجو نماید، مشخص کنید.
اگر ضوابطی برای فایلهایی که باید مطابقت داده شوند، تعیین نکنید، find هر فایلی( شامل دایرکتوریها، لینکهای نمادین، بلوکها، و دستگاهای کاراکتری، FIFOها، سوکتها -- هر چیز دیگر) را منطبق میداند. ما در مثال اول خود هیچ نشانه دیگری اضافه نکردیم، از اینرو، همه فایلها و دایرکتوریها را به دست آوردیم.
بالاخره، اگر یک عمل که باید روی هر فایلِ انطباق یافته انجام بشود، تعیین نکنید، اکثر نگارشهای جدید find فرض میکنند که شما میخواهید نام آنها با یک سطر جدید بعد از هر نام در خروجی استاندارد چاپ بشود. (بعضی نگارشهای خیلی قدیمی find ممکن است هیچ کاری انجام ندهند -- در واقع خوبست که عمل مورد نظر را مشخص کنید.) اگر بخواهیم واضح باشد، باید اینطور بنویسیم:
find . -print
این همان خروجی را که قبلاً دیدیم تولید مینمود، آن را تکرار نمیکنم.
همچنین ممکن است شما مشاهده نموده باشید که خروجی find لزوماً مانند خروجی ls(1)به ترتیب الفبایی مرتب نشده است. در واقع فایلها را به ترتیبی که داخل یک دایرکتوری ظاهر میشوند به شما ارائه میکند.
اکنون بیاید یک گزینه فیلتر کننده اِعمال کنیم. فرض کنید میخواهیم فقط فایلهایی را که به .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 نیست، به دست آوردیم.
یکی از متداولترین مورد استفادههای 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 فایلها انجام میدادیم، اما در بخش آتی به «عملیات» خواهیم پرداخت.)
یک مورد استفاده رایج دیگر برای find جستجوی فایلهایی است که از حدود معینی بزرگتر هستند. برای مثال، نمونه زیر تمام فایلهای بزرگتر از ۱۰ مگابایت (10485760 بایت) را داخل /home جستجو میکند:
find /home -type f -size +10485760c -print
همانند با زمان ویرایش فایل (نشان داده شده در بخش قبل)، یک + یا - مقدمِ قبل از شناسهای که گزینه -size را دنبال میکند، قابل توجه است. یک + مقدم یعنی اندازه باید بزرگتر از اندازه تعیین شده باشد، یک - پیشتاز یعنی کوچکتر از، و بدون وجود علامت یعنی دقیقاً برابر اندازه مشخص شده.
بدون وجود کاراکتر c دنباله، شناسه باید به عنوان تعداد بلوکها تفسیر گردد، که به طور معمول 512 بایت میباشند، اگر چه، ممکن است به سیستم بستگی داشته باشد.
بعضی نگارشهای find دارای مشخص کننده مقیاس اضافهای از قبیل k برای کیلوبایت (1024 بایت) میباشند. این موارد قابل حمل نیستند، و شما اگر میخواهید از آنها استفاده کنید، برای یادگیری آنها باید به مستندات سیستم خودتان مراجعه نمایید.
یک مثال کاملتر برای پاکسازی دایرکتوری /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). ابداً هیچ چیزی در نام فایل مسئله نخواهد بود.
گاهی اوقات، شاید شما بخواهید برای هر فایلی که پردازش میکنید، دستورات مختلطی را اجرا کنید. برای مثال، ممکن است بخواهید یک بلوک کدِ پوسته اجرا کنید که نام فایل را مجزا (حذف کردن تمام دایرکتوریهای مقدم بر نام فایل) کند، تمام حروف آن را به حروف بزرگ تبدیل نماید، و سپس کار دیگری با آن انجام بدهد (شاید بعد از مقداری کنترل اضافی، که در اینجا نشان داده نشده، تغییر نام فایل). یک بلوک کدِ پوسته برای اِعمال بر یک فرمان منفرد میتواند موردی مانند این باشد:
# شامل نام فایل اولیه است $1 name=${1##*/} upper=$(echo "$name" | tr "[:lower:]" "[:upper:]") echo "$name -> $upper"
اگر بخواهیم اجرای آن را توسط find برای هر تطابق فایل داشته باشیم، آنوقت دو امکان در اختیار داریم:
ایجاد یک اسکریپت با این کد در آن، و سپس -exec آن اسکریپت.
نوشتن یک فرمان پیچیده 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 دارای معنی خاص برای پوسته باشد، آماده شکست است.)
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، آنوقت پوسته ممکن است آن فرمانها با نتایج مصیبتبار را اجرا کند.
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 را به طور مستقیم به کار ببریم، آهستهتر خواهد بود.
نیاز به آن دارد که پیادهسازی 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 . -name '*.temp' -deleteکه برای مثالهای قبلتر نیز صدق میکند. من نمیدانم آیا برای posix-find یک سوییچ -delete وجود دارد. توجه: گزینه -delete دایرکتوریها را هم حذف میکند.
(من برخی مثالها را که قصد استفاده از GNU parallel به عنوان یک جایگزین برای xargs داشتند، حذف کردهام. GNU parallel میتواند بسیار مفید باشد، اما مثالهای ارائه شده در اینجا کاملاً اشتباه بودند. صفحه مدیریت پردازش دارای تعدادی مثال مفید parallel میباشد.)
کاربران OS X: از یک باگ دیرپای در پیادهسازی Darwin از -exec … +، آگاه باشید، یعنی آن find موقعی که برنامه فراخوانی شده در -exec یک وضعیت خروج غیرصفر برگشت میدهد، خارج میشود. این مستلزم دقت است، زیرا find برنامه مشخص شده را برای آن تعداد فایل که میتواند مناسب اندازه max_args باشد فراخوانی نموده و خارج میشود، مگر اینکه فرمان موفق گردد، که در آن حالت برای مقدار بعدی فایلهای واجد شرایط همان عمل تکرار خواهد شد.
به بیان دیگر، باز هم ممکن است شما نتایجی را به دست آورید، اما نه تمام آنها را.
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 است، از بازخورد قدردانی خواهد شد.
ابزار غیر استاندارد GNU parallel در برخی حالتها میتواند مفید باشد:
find . -name '*.temp' -print0 | parallel -X -0 rm
یکبار دیگر، من مثالهایی را که ناجور و ناامن بودند حذف کردهام. در مدیریت پردازش مثالهای بهتری از GNU parallel وجود دارد.
برای صحت و امنیت، باید با GNU parallel دقیقاً مانند xargs رفتار بشود. نام فایلها را در یک جریان به آن تغذیه نکنید مگر اینکه از -print0 و -0 استفاده کنید.
بسیاری اشخاص پرسشهایی از این قبیل میپرسند، چطور میتوانم مجوزهای یک فایل را به دست آورم؟ میخواهم صحیح بودن آنها را کنترل کنم.
ابزارهای استاندارد یونیکس که بتوانند به طور معقول مجوزهای یک فایل را به شما ارائه کنند، وجود ندارد. اگر چه، پرسش واقعی در اینجا «مجوزها کدام هستند؟» نیست. در عوض، «آیا مجوزها درست هستند؟» میباشد. اگر ما بخواهیم (به عنوان مثال) بدانیم آیا مجوزهای یک فایل 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 را ببینید.
بر اساس تعریف 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 میباشد. این موضوع در بخش بعدی بیشتر بررسی میشود.
اگر فایل جاری یک دایرکتوری باشد، و عملگر -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) ارزیابی میشود.
کاربرد Find (آخرین ویرایش 2013-06-14 18:11:42 توسط 156-56-177-162)