فصل ۱۷ – جست‌وجوی فایل‌ها

وقتی در سیستم لینوکس خود گردش کرده‌ایم، یک نکته کاملاً روشن شده است:
یک سیستم لینوکس معمولی تعداد بسیار زیادی فایل دارد!
و این پرسش پیش می‌آید: «چطور بین این همه فایل چیزی را که می‌خواهیم پیدا کنیم؟»

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

در این فصل، با دو ابزار اصلی برای جست‌وجوی فایل‌ها در سیستم آشنا می‌شویم:

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

در کنار آن، دو دستور کمکی دیگر نیز معرفی می‌کنیم که برای بررسی و کار با فایل‌ها مفیدند:


locate – ساده‌ترین روش برای پیدا کردن فایل

برنامه‌ی locate یک جست‌وجوی بسیار سریع درون پایگاه داده‌ای از مسیر فایل‌ها (pathnames) انجام می‌دهد
و هر نامی را که شامل رشته‌ی مورد نظر باشد، نمایش می‌دهد.

فرض کنید می‌خواهیم تمام برنامه‌هایی را پیدا کنیم که نامشان با «zip» شروع می‌شود.
از آن‌جا که به دنبال برنامه‌ها هستیم، می‌توانیم حدس بزنیم که مسیر آن‌ها در دایرکتوری‌هایی است که به bin/ ختم می‌شوند.
پس می‌توانیم این‌طور از locate استفاده کنیم:

[me@linuxbox ~]$ locate bin/zip

خروجی چیزی شبیه این خواهد بود:

/usr/bin/zip 
/usr/bin/zipcloak 
/usr/bin/zipgrep 
/usr/bin/zipinfo 
/usr/bin/zipnote 
/usr/bin/zipsplit

اگر جست‌وجوی ما ساده نباشد، می‌توانیم locate را با ابزارهای دیگر مثل grep ترکیب کنیم
تا جست‌وجوهای دقیق‌تر و پیشرفته‌تری بسازیم:

[me@linuxbox ~]$ locate zip | grep bin

خروجی:

/bin/bunzip2 
/bin/bzip2 
/bin/bzip2recover 
/bin/gunzip 
/bin/gzip 
/usr/bin/funzip 
/usr/bin/gpg-zip 
/usr/bin/preunzip 
/usr/bin/prezip 
/usr/bin/prezip-bin 
/usr/bin/unzip 
/usr/bin/unzipsfx 
/usr/bin/zip 
/usr/bin/zipcloak 
/usr/bin/zipgrep 
/usr/bin/zipinfo 
/usr/bin/zipnote 
/usr/bin/zipsplit

برنامه‌ی locate از سال‌ها پیش در لینوکس وجود داشته و نسخه‌های مختلفی از آن ساخته شده است.
در توزیع‌های امروزی دو نسخه‌ی رایج‌تر وجود دارد:

اما معمولاً هر دوی آن‌ها از طریق یک لینک نمادین به نام locate فراخوانی می‌شوند.

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

برای اطلاع از نسخه‌ی نصب‌شده‌ی locate و گزینه‌های آن، می‌توانید دستور زیر را اجرا کنید:

man locate

پایگاه دادهٔ locate از کجا می‌آید؟

شاید متوجه شده باشید که در بعضی توزیع‌ها، بلافاصله بعد از نصب سیستم، دستور locate کار نمی‌کند؛
اما اگر روز بعد دوباره امتحان کنید، ناگهان درست کار می‌کند.
علت چیست؟

پایگاه داده‌ای که locate از آن استفاده می‌کند، توسط برنامه‌ای به نام updatedb ساخته می‌شود.
این برنامه معمولاً به‌صورت خودکار و زمان‌بندی‌شده (cron job) اجرا می‌شود — یعنی وظیفه‌ای که در فواصل زمانی منظم توسط سرویس cron انجام می‌گیرد.

در اکثر سیستم‌هایی که locate نصب شده، برنامه‌ی updatedb روزی یک‌بار اجرا می‌شود.
از آن‌جا که این پایگاه داده به‌صورت لحظه‌ای به‌روزرسانی نمی‌شود، فایل‌هایی که به‌تازگی ایجاد شده‌اند ممکن است در نتایج locate دیده نشوند.

برای رفع این محدودیت، می‌توانید خودتان به‌صورت دستی پایگاه داده را به‌روزرسانی کنید.
کافی است وارد حالت کاربر ریشه (superuser) شوید و دستور زیر را اجرا کنید:

# updatedb

find – روش دقیق‌تر (و سخت‌تر) برای پیدا کردن فایل‌ها

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

find ابزاری بسیار قدرتمند است و در فصل‌های آینده، هنگام یادگیری مفاهیم برنامه‌نویسی شل،
بارها با آن سر و کار خواهیم داشت.

در ساده‌ترین حالت، برای استفاده از find فقط کافی است مسیر دایرکتوری مورد نظر را مشخص کنیم.
مثلاً برای نمایش فهرست همه‌ی فایل‌ها در پوشه‌ی خانگی خودمان:

[me@linuxbox ~]$ find ~

در بیشتر سیستم‌های فعال، این دستور فهرست بسیار بلندی تولید می‌کند.
از آنجا که خروجی روی خروجی استاندارد (stdout) چاپ می‌شود، می‌توانیم آن را با pipe (|) به دستورات دیگر بدهیم.
مثلاً برای شمارش تعداد کل فایل‌ها:

[me@linuxbox ~]$ find ~ | wc -l
47068

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


آزمون‌ها (Tests)

فرض کنید می‌خواهیم فقط فهرستی از دایرکتوری‌ها به‌دست آوریم، نه فایل‌ها.
در این حالت، می‌توانیم از تست -type d استفاده کنیم:

[me@linuxbox ~]$ find ~ -type d | wc -l
1695

به‌طور مشابه، اگر بخواهیم فقط فایل‌های معمولی (regular files) را جست‌وجو کنیم:

[me@linuxbox ~]$ find ~ -type f | wc -l
38737

انواع تست‌های نوع فایل در find

حرف نوع فایل توضیح
b block special فایل‌های دستگاه بلوکی (مثل دیسک‌ها)
c character special فایل‌های دستگاه کاراکتری (مثل ترمینال‌ها)
d directory پوشه
f regular file فایل معمولی
l symbolic link لینک نمادین

جست‌وجو بر اساس نام و اندازه

می‌توانیم با افزودن چند تست دیگر، جست‌وجوی دقیق‌تری انجام دهیم.
برای مثال، فرض کنید می‌خواهیم تمام فایل‌های معمولی با پسوند .JPG را که بزرگ‌تر از ۱ مگابایت هستند، پیدا کنیم:

[me@linuxbox ~]$ find ~ -type f -name "*.JPG" -size +1M | wc -l
840

در این مثال:

علامت‌ها در مقابل اندازه معنی خاصی دارند:

حرف پس از عدد واحد اندازه را تعیین می‌کند:

واحدهای اندازه در find

کاراکتر واحد اندازه توضیح
b بلوک‌های ۵۱۲ بایتی مقدار پیش‌فرض در صورت عدم ذکر واحد
c بایت
w واژه‌های ۲ بایتی
k کیلوبایت (۱۰۲۴ بایت)
M مگابایت (۱٬۰۴۸٬۵۷۶ بایت)
G گیگابایت (۱٬۰۷۳٬۷۴۱٬۸۲۴ بایت)

به این ترتیب، دستور find ابزار قدرتمندی است که می‌تواند بر اساس نوع، اندازه، نام، زمان تغییر، مجوزها و بسیاری ویژگی‌های دیگر فایل‌ها را پیدا کند —
و این توانایی‌ها آن را به یکی از پرکاربردترین ابزارهای لینوکس تبدیل کرده است.

دستور find از مجموعه‌ی بسیار گسترده‌ای از تست‌ها (Tests) پشتیبانی می‌کند.
در جدول زیر، پرکاربردترین آن‌ها را مرور می‌کنیم.
در مواردی که ورودی عددی لازم است، همان نشانه‌گذاری‌های + و - که قبلاً توضیح داده شد (بیشتر از / کمتر از / دقیقاً برابر) نیز قابل استفاده‌اند.


📋 جدول ۱۷-۳: تست‌های متداول در find

تست توضیح
-cmin n فایل‌ها یا دایرکتوری‌هایی را پیدا می‌کند که محتوایشان یا ویژگی‌هایشان دقیقاً n دقیقه پیش تغییر کرده‌اند.
برای کمتر از n دقیقه: -n، برای بیشتر از n دقیقه: +n.
-cnewer file فایل‌ها یا دایرکتوری‌هایی را پیدا می‌کند که جدیدتر از فایل مشخص‌شده هستند (از نظر زمان تغییر).
-ctime n فایل‌ها یا دایرکتوری‌هایی را پیدا می‌کند که محتوایشان یا ویژگی‌هایشان n×۲۴ ساعت پیش تغییر کرده است.
-empty فایل‌ها یا پوشه‌های خالی را پیدا می‌کند.
-group name فایل‌ها یا پوشه‌هایی را که متعلق به گروه خاصی هستند پیدا می‌کند. گروه را می‌توان با نام یا شناسه عددی گروه (GID) مشخص کرد.
-iname pattern مثل -name است ولی به حروف بزرگ و کوچک حساس نیست.
-inum n فایل‌هایی را با شمارهٔ inode مشخص‌شده پیدا می‌کند — برای یافتن تمام hard linkها به یک inode مفید است.
-mmin n فایل‌ها یا دایرکتوری‌هایی را پیدا می‌کند که محتوایشان n دقیقه پیش ویرایش شده است.
-mtime n فایل‌ها یا دایرکتوری‌هایی را پیدا می‌کند که محتوایشان n×۲۴ ساعت پیش تغییر کرده است.
-name pattern فایل‌ها یا پوشه‌هایی را می‌یابد که با الگوی wildcard داده‌شده تطابق دارند.
-newer file فایل‌ها یا پوشه‌هایی را می‌یابد که جدیدتر از فایل مشخص‌شده هستند.
این گزینه در اسکریپت‌های پشتیبان‌گیری (backup) بسیار مفید است: می‌توانید در هر بار بکاپ، تاریخ آخرین بکاپ را در فایلی ذخیره کنید و سپس با find -newer بفهمید کدام فایل‌ها از آن زمان تغییر کرده‌اند.
-nouser فایل‌ها یا پوشه‌هایی را پیدا می‌کند که هیچ مالک معتبری در سیستم ندارند — معمولاً متعلق به حساب‌های حذف‌شده یا فعالیت‌های مشکوک.
-nogroup مشابه -nouser، ولی برای گروه‌هایی که دیگر وجود ندارند.
-perm mode فایل‌ها یا پوشه‌هایی با مجوز (permission) خاص را پیدا می‌کند. حالت دسترسی می‌تواند به‌صورت عددی (مثلاً 755) یا نمادین (مثلاً u+rwx) نوشته شود.
-samefile name مانند -inum است، ولی به‌جای شماره inode، نام فایل مرجع داده می‌شود. فایل‌هایی را پیدا می‌کند که همان inode را با آن فایل به‌اشتراک می‌گذارند.
-size n فایل‌هایی را می‌یابد که اندازه‌شان برابر، بزرگ‌تر یا کوچک‌تر از n است (بسته به وجود + یا -).
-type c فایل‌هایی را می‌یابد که از نوع c هستند (مثلاً f برای فایل معمولی، d برای دایرکتوری، l برای لینک و غیره).
-user name فایل‌ها یا پوشه‌هایی را پیدا می‌کند که متعلق به کاربر مشخص‌شده هستند. نام کاربر می‌تواند username یا شناسه عددی (UID) باشد.

🔎 این فهرست کامل نیست — دستور man find جزئیات و تست‌های پیشرفته‌تری را ارائه می‌دهد،
از جمله جست‌وجو بر اساس زمان دسترسی (access time)، مجوزها، عمق دایرکتوری و ترکیب شرط‌ها با عملگرهای منطقی مثل -and, -or, ! (not).


عملگرها (Operators)

با وجود تمام تست‌هایی که دستور find ارائه می‌دهد، گاهی لازم است بتوانیم روابط منطقی بین تست‌ها را دقیق‌تر توصیف کنیم.
برای مثال، فرض کنید می‌خواهیم بررسی کنیم که آیا تمام فایل‌ها و زیرپوشه‌های یک دایرکتوری دسترسی ایمن (permissions) مناسبی دارند یا نه.

در این حالت باید:

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

مثلاً برای مثال بالا، می‌توانیم بنویسیم:

[me@linuxbox ~]$ find ~ \( -type f -not -perm 0600 \) -or \( -type d -not -perm 0700 \)

در نگاه اول شاید عجیب به نظر برسد، اما وقتی مفهوم عملگرها را بدانیم، ساده است.


📘 جدول ۱۷-۴: عملگرهای منطقی find

عملگر توضیح
-and تست‌ها در دو طرف عملگر باید هر دو درست (true) باشند تا نتیجهٔ نهایی درست شود.
می‌توان به صورت کوتاه‌شده -a نیز نوشت.
نکته: اگر هیچ عملگری بین تست‌ها ننویسیم، -and به‌صورت پیش‌فرض فرض می‌شود.
-or اگر هر یک از تست‌ها در دو طرف درست باشد، نتیجه درست می‌شود.
می‌توان به صورت کوتاه‌شده -o نیز نوشت.
-not نتیجهٔ تست بعدی را معکوس می‌کند (یعنی اگر تست نادرست بود، درست می‌شود و برعکس).
می‌توان به‌جای آن از ! استفاده کرد.
( ) تست‌ها و عملگرها را برای تعیین ترتیب اجرا (precedence) گروه‌بندی می‌کند.
به‌صورت پیش‌فرض، find از چپ به راست ارزیابی می‌کند، اما می‌توان با پرانتزها ترتیب را تغییر داد.
حتی اگر لازم نباشد، گاهی برای خوانایی بهتر دستور مفید است.
از آن‌جا که پرانتزها در پوسته معنای خاصی دارند، باید با بک‌اسلش \ از آن‌ها محافظت کرد تا به find منتقل شوند.

تحلیل دستور مثال بالا

اگر ساختار کلی دستور را نگاه کنیم، دو بخش داریم که با عملگر -or از هم جدا شده‌اند:

( expression 1 ) -or ( expression 2 )

این منطقی است، چون داریم دو نوع بررسی انجام می‌دهیم:
۱. فایل‌هایی که دسترسی مناسبی ندارند.
۲. پوشه‌هایی که دسترسی مناسبی ندارند.

چرا از -or استفاده کرده‌ایم و نه -and؟
چون هر فایل فقط می‌تواند یکی از این دو حالت را داشته باشد — یا فایل است یا دایرکتوری، نه هر دو.
پس باید بگوییم:

( فایل با مجوز اشتباه ) -or ( پوشه با مجوز اشتباه )

تعریف «مجوز اشتباه»

ما به‌طور مستقیم نمی‌توانیم «مجوز بد» را تست کنیم،
اما می‌توانیم بگوییم «مجوز غیر از خوب»، چون می‌دانیم مجوز خوب چیست.

برای فایل‌ها، مجوز خوب 0600 است، و برای دایرکتوری‌ها 0700.

پس تست‌هایمان می‌شوند:

-type f -and -not -perm 0600
-type d -and -not -perm 0700

و چون -and به‌صورت پیش‌فرض وجود دارد، می‌توانیم آن را حذف کنیم.

اگر همه را با هم ترکیب کنیم، دستور نهایی چنین است:

find ~ \( -type f -not -perm 0600 \) -or \( -type d -not -perm 0700 \)

و همان‌طور که گفته شد، پرانتزها باید با بک‌اسلش (\) محافظت شوند تا پوسته آن‌ها را تفسیر نکند.


منطق اجرای عملگرها

فرض کنید دو عبارت داریم که با یک عملگر منطقی از هم جدا شده‌اند:

expr1 -operator expr2

در همهٔ حالت‌ها، عبارت expr1 همیشه اجرا می‌شود،
اما اجرای expr2 بستگی به نتیجهٔ expr1 و نوع عملگر دارد.

جدول ۱۷-۵: منطق AND/OR در find

نتیجهٔ expr1 عملگر وضعیت اجرای expr2
True -and همیشه اجرا می‌شود
False -and اجرا نمی‌شود
True -or اجرا نمی‌شود
False -or همیشه اجرا می‌شود

چرا این رفتار مهم است؟

هدف از این منطق، بهینه‌سازی عملکرد است.
مثلاً در عبارت:

expr1 -and expr2

اگر expr1 نتیجه‌ای نادرست (false) بدهد، دیگر نیازی نیست expr2 بررسی شود، چون نتیجهٔ کلی در هر صورت نادرست خواهد بود.

به‌طور مشابه، در عبارت:

expr1 -or expr2

اگر expr1 درست باشد، دیگر نیازی به اجرای expr2 نیست، چون نتیجهٔ کلی حتماً درست است.

این رفتار علاوه بر افزایش سرعت، در کنترل ترتیب اجرای اعمال (actions) نیز نقش دارد —
که در ادامهٔ فصل هنگام بررسی عملگرهای عملیاتی (-exec, -delete, و غیره) اهمیت آن را خواهیم دید.


اعمال از پیش تعریف‌شده (Predefined Actions)

تا اینجا یاد گرفتیم که دستور find می‌تواند فهرستی از فایل‌ها را بر اساس معیارهای مختلف نمایش دهد.
اما واقعاً هدف نهایی فقط دیدن فهرست نیست — بلکه انجام کاری روی آن فایل‌هاست.

خوشبختانه find اجازه می‌دهد اعمال (Actions) خاصی را روی نتایج جست‌وجو انجام دهیم.
تعدادی از این اعمال از پیش تعریف‌شده‌اند و در کنار آن‌ها می‌توان اعمال دلخواه (User-defined) هم نوشت.
ابتدا نگاهی به چند عمل پرکاربرد بیندازیم:


📘 جدول ۱۷-۶: اعمال از پیش تعریف‌شده در find

عمل (Action) توضیح
-delete فایل یا دایرکتوری‌ای که با معیارها مطابقت دارد را حذف می‌کند.
-ls معادل اجرای دستور ls -dils برای هر فایل مطابقت‌یافته است؛ خروجی در ترمینال چاپ می‌شود.
-print مسیر کامل فایل مطابقت‌یافته را روی خروجی استاندارد (stdout) چاپ می‌کند.
این عمل به‌صورت پیش‌فرض اجرا می‌شود اگر هیچ عمل دیگری مشخص نشده باشد.
-quit بلافاصله پس از یافتن اولین نتیجه، جست‌وجو را متوقف می‌کند.

همانند تست‌ها، اعمال دیگری نیز وجود دارند؛ برای فهرست کامل باید صفحه‌ی راهنما را بخوانید:

man find

در اولین مثال این فصل، فقط نوشتیم:

find ~

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

find ~ -print

حذف فایل‌ها با find

می‌توانیم find را طوری تنظیم کنیم که فایل‌هایی را حذف کند که معیار خاصی دارند.
برای مثال، حذف تمام فایل‌هایی که پسوند .BAK دارند (که معمولاً برای نسخه‌های پشتیبان استفاده می‌شود):

find ~ -type f -name '*.BAK' -delete

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

⚠️ هشدار بسیار مهم:
همیشه پیش از اجرای دستور -delete، ابتدا همان دستور را با -print امتحان کنید تا مطمئن شوید فقط فایل‌های موردنظر انتخاب می‌شوند:

find ~ -type f -name '*.BAK' -print

وقتی خروجی را بررسی و تأیید کردید، سپس -print را با -delete جایگزین کنید.


تأثیر عملگرهای منطقی بر اجرای اعمال

به دستور زیر نگاه کنید:

find ~ -type f -name '*.BAK' -print

این دستور تمام فایل‌های معمولی (-type f) با نامی که به .BAK ختم می‌شود (-name '*.BAK') را یافته و مسیرشان را چاپ می‌کند (-print).

اما دلیل اینکه این دستور دقیقاً به این شکل کار می‌کند، به روابط منطقی بین تست‌ها و اعمال برمی‌گردد.
به‌صورت پیش‌فرض، بین هر تست و عمل در find یک رابطه‌ی -and ضمنی وجود دارد.

می‌توانیم دستور بالا را به شکل واضح‌تر بنویسیم:

find ~ -type f -and -name '*.BAK' -and -print

نحوهٔ اجرای گام‌به‌گام دستور بالا

تست / عمل چه زمانی اجرا می‌شود
-type f همیشه اجرا می‌شود (اولین تست در زنجیره‌ی -and)
-name '*.BAK' فقط اگر نتیجه‌ی -type f درست باشد
-print فقط اگر هر دو تست قبلی (-type f و -name '*.BAK') درست باشند

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


اگر ترتیب را تغییر دهیم و عمل -print را اول بنویسیم، رفتار دستور کاملاً متفاوت می‌شود:

find ~ -print -and -type f -and -name '*.BAK'

در این حالت:

نتیجه: این نسخه همهٔ فایل‌ها را چاپ می‌کند، نه فقط فایل‌های با پسوند .BAK.


🔹 جمع‌بندی:
رفتار نهایی دستور find وابسته به ترتیب تست‌ها و اعمال و روابط منطقی بین آن‌هاست.
با درک این منطق می‌توانیم دستورات پیچیده و دقیق بنویسیم — مثلاً حذف مشروط، جست‌وجوی چندمعیاره، یا پردازش گروهی فایل‌ها.


اعمال کاربر‌ساز (User-Defined Actions)

علاوه بر اعمال از پیش تعریف‌شده، می‌توانیم هر دستور دلخواهی را هم روی فایل‌های پیدا‌شده اجرا کنیم.
روش سنتی این کار استفاده از عمل -exec است.


📘 ساختار دستور -exec

-exec command {} ;

مثلاً برای اجرای عملی مشابه -delete با استفاده از rm می‌نویسیم:

-exec rm '{}' ';'

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


اجرای تعاملی (با تأیید کاربر)

می‌توانیم با استفاده از -ok به جای -exec، اجرای دستور را به‌صورت تعاملی انجام دهیم.
در این حالت، برای هر فایل قبل از اجرا از کاربر تأیید گرفته می‌شود:

find ~ -type f -name 'foo*' -ok ls -l '{}' ';'

نمونهٔ خروجی:

< ls ... /home/me/bin/foo > ? y
-rwxr-xr-x 1 me me 224 2007-10-29 18:44 /home/me/bin/foo
< ls ... /home/me/foo.txt > ? y
-rw-r--r-- 1 me me 0 2008-09-19 12:53 /home/me/foo.txt

در این مثال، find فایل‌هایی را که با «foo» شروع می‌شوند پیدا کرده
و قبل از اجرای دستور ls -l برای هر فایل، از ما تأیید می‌خواهد.


بهبود کارایی اجرای دستورات

به‌صورت پیش‌فرض، وقتی از -exec استفاده می‌کنیم،
find برای هر فایل تطبیق‌یافته یک‌بار دستور را اجرا می‌کند.
مثلاً اگر دو فایل پیدا شود، دو بار دستور ls اجرا می‌شود:

ls -l file1
ls -l file2

اما گاهی بهتر است تمام نتایج در یک دستور تجمیع شوند:

ls -l file1 file2

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


🔸 روش اول: استفاده از قابلیت جدید find

برای این کار کافی است علامت پایانی ; را با + جایگزین کنید.
در این صورت find همه‌ی مسیرها را در یک لیست واحد جمع کرده و
در یک اجرای واحد به دستور منتقل می‌کند.

مثلاً:

find ~ -type f -name 'foo*' -exec ls -l '{}' ';'

این نسخه ls را برای هر فایل جداگانه اجرا می‌کند.

اما اگر بنویسیم:

find ~ -type f -name 'foo*' -exec ls -l '{}' +

خروجی همان است،
اما دستور ls فقط یک‌بار اجرا می‌شود و تمام فایل‌ها را با هم می‌گیرد.


🔸 روش دوم: استفاده از xargs

ابزار xargs ورودی را از stdin (مثل خروجی دستور قبل از خودش) می‌گیرد
و آن را به‌صورت فهرست آرگومان‌ها به دستور بعدی منتقل می‌کند.

برای مثال:

find ~ -type f -name 'foo*' -print | xargs ls -l

در اینجا خروجی find (لیست فایل‌ها) به xargs داده می‌شود،
و xargs آن‌ها را در قالب یک خط فرمان به دستور ls اضافه و اجرا می‌کند.

خروجی مشابه است:

-rwxr-xr-x 1 me me 224 2007-10-29 18:44 /home/me/bin/foo
-rw-r--r-- 1 me me 0 2008-09-19 12:53 /home/me/foo.txt

نکتهٔ فنی: محدودیت طول خط فرمان

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

  1. دستور را با حداکثر تعداد مجاز آرگومان‌ها اجرا می‌کند،
  2. سپس این فرایند را تکرار می‌کند تا تمام ورودی‌ها پردازش شوند.

برای دیدن حداکثر اندازهٔ مجاز خط فرمان در سیستم خودتان، می‌توانید بنویسید:

xargs --show-limits

خلاصهٔ کاربردها:


کار با نام فایل‌های عجیب (Dealing With Funny Filenames)

در سیستم‌های شبه‌یونیکس، نام فایل‌ها می‌توانند شامل فاصله (space) یا حتی کاراکتر newline (خط جدید) باشند.
این موضوع می‌تواند برای ابزارهایی مثل xargs مشکل‌ساز شود؛
چون وقتی نام فایلی شامل فاصله باشد، xargs آن فاصله را به‌عنوان جداکننده‌ی آرگومان‌ها در نظر می‌گیرد،
و دستور نهایی هر بخش از نام را به‌صورت آرگومان جداگانه تفسیر می‌کند.

برای رفع این مشکل، دستورهای find و xargs راهی ارائه داده‌اند:
می‌توان از کاراکتر null (NUL) به‌جای فاصله به‌عنوان جداکننده استفاده کرد.

در جدول ASCII، کاراکتر null با عدد صفر (0) نمایش داده می‌شود،
در حالی که کاراکتر فاصله با عدد ۳۲ مشخص است.

در دستور find، عمل -print0 باعث می‌شود خروجی فایل‌ها با کاراکتر null جدا شود،
و در xargs، گزینه‌ی --null (یا -0) به آن می‌گوید که ورودی را با همین فرمت بخواند.

نمونه:

find ~ -iname '*.jpg' -print0 | xargs --null ls -l

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


بازگشت به محیط تمرین (A Return To The Playground)

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

ابتدا با دو دستور ساده، مجموعه‌ای از پوشه‌ها و فایل‌ها می‌سازیم:

[me@linuxbox ~]$ mkdir -p playground/dir-{001..100}
[me@linuxbox ~]$ touch playground/dir-{001..100}/file-{A..Z}

در عرض چند ثانیه، ۱۰۰ پوشه ساخته می‌شود که هرکدام شامل ۲۶ فایل خالی هستند!
این همان قدرت خط فرمان لینوکس است — سعی کنید همین کار را با رابط گرافیکی انجام دهید! 😄


توضیح ساختار

در اینجا از چند قابلیت پوسته استفاده کردیم:


جست‌وجو در Playground

در محیط تمرین خود ۱۰۰ فایل به نام file-A ساخته‌ایم.
بیایید همه‌ی آن‌ها را پیدا کنیم:

[me@linuxbox ~]$ find playground -type f -name 'file-A'

برخلاف ls، دستور find فایل‌ها را به ترتیب مرتب‌شده نمایش نمی‌دهد،
بلکه بر اساس چیدمان فیزیکی فایل‌ها در دیسک لیست را برمی‌گرداند.

می‌توانیم مطمئن شویم که واقعاً ۱۰۰ فایل پیدا شده‌اند:

[me@linuxbox ~]$ find playground -type f -name 'file-A' | wc -l
100

جست‌وجوی فایل‌ها بر اساس زمان تغییر (Modification Time)

این ویژگی هنگام پشتیبان‌گیری (backup) یا مرتب‌سازی زمانی فایل‌ها بسیار مفید است.
ابتدا یک فایل مرجع می‌سازیم که زمان فعلی را ذخیره کند:

[me@linuxbox ~]$ touch playground/timestamp

این دستور یک فایل خالی با نام timestamp می‌سازد و زمان آخرین تغییر آن را به زمان فعلی سیستم تنظیم می‌کند.

برای مشاهده‌ی جزئیات کامل این فایل از دستور stat استفاده می‌کنیم —
دستوری قدرتمندتر از ls که همه‌ی ویژگی‌های فایل را نمایش می‌دهد:

[me@linuxbox ~]$ stat playground/timestamp

نمونه‌ی خروجی:

File: `playground/timestamp'
Size: 0          Blocks: 0       IO Block: 4096 regular empty file
Device: 803h/2051d Inode: 14265061 Links: 1
Access: (0644/-rw-r--r--)  Uid: (1001/me)  Gid: (1001/me)
Access: 2008-10-08 15:15:39.000000000 -0400
Modify: 2008-10-08 15:15:39.000000000 -0400
Change: 2008-10-08 15:15:39.000000000 -0400

اگر دستور touch را دوباره اجرا کنیم، می‌بینیم که زمان‌ها به‌روزرسانی می‌شوند:

[me@linuxbox ~]$ touch playground/timestamp
[me@linuxbox ~]$ stat playground/timestamp

به‌روزرسانی فایل‌ها با find

حالا می‌خواهیم همه‌ی فایل‌های file-B را در Playground به‌روزرسانی کنیم (یعنی زمان تغییرشان را به زمان فعلی ببریم):

[me@linuxbox ~]$ find playground -type f -name 'file-B' -exec touch '{}' ';'

این دستور همه‌ی فایل‌های file-B را پیدا کرده و با دستور touch به‌روزرسانی می‌کند.

حالا برای پیدا کردن فایل‌هایی که جدیدتر از فایل مرجع timestamp هستند:

[me@linuxbox ~]$ find playground -type f -newer playground/timestamp

نتیجه شامل تمام ۱۰۰ فایل file-B خواهد بود،
چون همه‌ی آن‌ها بعد از ساخت timestamp به‌روزرسانی شدند،
و بنابراین طبق تست -newer، جدیدتر از آن فایل محسوب می‌شوند.


آزمایش دوباره‌ی مجوزهای اشتباه

در پایان، همان تستی که قبلاً برای مجوزهای نادرست (bad permissions) داشتیم را روی محیط Playground اجرا می‌کنیم:

[me@linuxbox ~]$ find playground \( -type f -not -perm 0600 \) -or \( -type d -not -perm 0700 \)

این دستور همه‌ی فایل‌ها و پوشه‌هایی را پیدا می‌کند که مجوزشان با حالت ایمن مورد نظر (۰۶۰۰ برای فایل‌ها، ۰۷۰۰ برای دایرکتوری‌ها) مطابقت ندارد.

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

ادامهٔ آزمایش در Playground

وقتی دستور زیر را اجرا می‌کنیم:

find playground \( -type f -not -perm 0600 \) -or \( -type d -not -perm 0700 \)

خروجی شامل تمام ۱۰۰ دایرکتوری و ۲۶۰۰ فایل در مسیر playground می‌شود
(به‌علاوهٔ خود پوشهٔ playground و فایل timestamp، یعنی جمعاً ۲۷۰۲ مورد)
چون هیچ‌کدام از آن‌ها مطابق تعریف ما از «مجوز ایمن» نیستند.


اعمال مجوزهای جدید با استفاده از find

با استفاده از عملگرها (operators) و اعمال (actions) حالا می‌توانیم دستور را طوری تغییر دهیم که مجوز فایل‌ها و پوشه‌ها را اصلاح کند:

[me@linuxbox ~]$ find playground \( -type f -not -perm 0600 -exec chmod 0600 '{}' ';' \) -or \( -type d -not -perm 0700 -exec chmod 0700 '{}' ';' \)

در این دستور:

در کارهای روزمره، معمولاً اجرای دو دستور جداگانه (یکی برای فایل‌ها و یکی برای پوشه‌ها) ساده‌تر است، مثل این:

find playground -type f -not -perm 0600 -exec chmod 0600 '{}' ';'
find playground -type d -not -perm 0700 -exec chmod 0700 '{}' ';'

اما این مثال ترکیبی به‌خوبی نشان می‌دهد که چگونه می‌توانیم تست‌ها، عملگرها و اعمال را با هم ترکیب کنیم تا عملیات پیچیده و هدفمند انجام دهیم.


گزینه‌ها (Options)

گزینه‌ها در دستور find برای کنترل دامنهٔ جست‌وجو (scope) استفاده می‌شوند.
می‌توان آن‌ها را همراه با تست‌ها و اعمال در یک دستور ترکیب کرد تا رفتار find دقیق‌تر شود.

در جدول زیر، متداول‌ترین گزینه‌ها آورده شده است:


📘 جدول ۱۷-۷: گزینه‌های پرکاربرد در find

گزینه توضیح
-depth باعث می‌شود find ابتدا محتوای هر دایرکتوری را بررسی کند و در انتها خود دایرکتوری را پردازش کند.
این گزینه به‌طور خودکار زمانی فعال می‌شود که از عمل -delete استفاده کنید.
-maxdepth levels بیشترین تعداد سطح‌هایی را که find در درخت دایرکتوری پایین می‌رود مشخص می‌کند.
مثلاً -maxdepth 1 فقط دایرکتوری جاری را بررسی می‌کند و وارد زیرپوشه‌ها نمی‌شود.
-mindepth levels حداقل سطحی را مشخص می‌کند که از آن به بعد تست‌ها و اعمال اجرا شوند.
مثلاً -mindepth 1 باعث می‌شود خود دایرکتوری اصلی بررسی نشود و فقط زیرپوشه‌ها پردازش شوند.
-mount به find دستور می‌دهد که وارد دایرکتوری‌هایی که روی سیستم فایل‌های دیگر (mount points) قرار دارند، نشود.
-noleaf به find می‌گوید که فرض بهینه‌سازی بر اساس ساختار فایل‌سیستم یونیکسی را نادیده بگیرد.
در زمان جست‌وجو در سیستم‌فایل‌های DOS/Windows یا CD-ROM باید از این گزینه استفاده شود.

جمع‌بندی

می‌توان گفت که دستور locate به همان اندازه ساده است که find پیچیده است!
هر دو ابزار مفیدند، اما کاربردشان متفاوت است.

اگر مرتب با فایل‌ها و ساختار سیستم لینوکس کار می‌کنید،
یادگیری و تمرین دستور find به‌مرور باعث می‌شود درک عمیق‌تری از رفتار فایل‌سیستم لینوکس پیدا کنید.


مطالعهٔ بیشتر