فرمان‌های پیچیده

فصل 16- فرمان‌ها، برنامه‌ها و فیلترهای خارجی

‎16.2‎- فرمان‌های پیچیده

فرمانهایی برای کاربران پیشرفته‌تر

find

‎-exec COMMAND \;‎

COMMAND را روی هر فایلی که find تطبیق بدهد، اجرا می‌کند. رشته فرمان با کاراکتر ; خاتمه می‌یابد (کاراکتر «;» برای مشخص نمودن آن که پوسته بدون تفسیر آن به عنوان یک کاراکتر خاص، به طور لفظی آنرا به find عبور بدهد، escape می‌شود).

bash$ find ~/ -name '*.txt'
/home/bozo/.kde/share/apps/karm/karmdata.txt
/home/bozo/misc/irmeyc.txt
/home/bozo/test-scripts/1.txt

اگر COMMAND شامل ‎{}‎ باشد، آنوقت find نام مسیر کامل فایل انتخاب شده را به جای ‎"{}"‎ جایگزین می‌کند.

find ~/ -name 'core*' -exec rm {} \;
#  تمام فایل‌های روگرفت حافظه(core) را از دایرکتوری خانه کاربر حذف می‌کند.

find /home/bozo/projects -mtime -1
# 
#  تمام فایلهای درخت دایرکتوری ‎/home/bozo/projects‎ را که در مدت
#+ روز گذشته (روز جاری منهای یک) ویرایش گردیده‌اند، لیست می‌کند.
#
find /home/bozo/projects -mtime 1
#       
#
#                           mtime = زمان آخرین ویرایش فایل مقصد
#  ctime = زمان آخرین تغییر وضعیت ( از طریق ‎chmod‎ یا غیر از آن)
#                                    atime = زمان آخرین دستیابی

DIR=/home/bozo/junk_files
find "$DIR" -type f -atime +5 -exec rm {} \;
# 
#  ابروها جای نگهدارنده جهت نام مسیر خروجی توسط ‎find‎ هستند.
#
#    تمام فایل‌ها در ‎/home/bozo/junk_files‎ را که ظرف «حداقل»‏
#+  ‎5‎ روز (علامت بعلاوه ‎در ‎+5‎) دستیابی نگردیده‌اند، حذف می‌کند.
#
# که در ‎"-type filetype"‎
# 
# 
# 
#
#  (صفحه ‎man‎ و ‎info‎ فرمان find دارای فهرست کامل گزینه‌ها هستند.)

find /etc -exec grep '[0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*[.][0-9][0-9]*' {} \;

# آدرس‌های ‎IP‎ ‏(xxx.xxx.xxx.xxx) را در فایل‌های دایرکتوری ‎/etc‎ جستجو می‌کند.
#          موارد نامربوطی هم یافت می‌شوند. آیا می‌توان آنها را فیلتر نمود؟

#    شاید به وسیله:

find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \
| grep '^[^.][^.]*\.[^.][^.]*\.[^.][^.]*\.[^.][^.]*$'
#
# ‎[:digit:]‎ یکی از کلاس‌های کاراکتر مطرح شده با استاندارد ‎POSIX 1003.2‎ است

# با تشکر از ‎Stéphane Chazelas‎ 

گزینه ‎-exec‎ برای فرمان find نباید با exec توکار پوسته اشتباه بشود.

مثال ‎16-3‎. Badname، از قلم انداختن نام‌فایلهای شامل کاراکترهای نامناسب و فضای سفید در دایرکتوری جاری.

#!/bin/bash
# 
# حذف نام فایلهای دایرکتوری جاری که شامل کاراکترهای نامناسب هستند.

for filename in *
do
  badname=`echo "$filename" | sed -n /[\+\{\;\"\\\=\?~\(\)\<\>\&\*\|\$]/p`
#   ‎badname=`echo "$filename" | sed -n '/[+{;"\=?~()<>&*|$]/p'`‎ نیز کار می‌کند.
#فایل‌های شامل کاراکترهای نامطبوع ‎+ { ; " \ = ? ~ ( ) < > & * | $‎ را حذف می‌کند.
#
  rm $badname 2>/dev/null
# 
done

#     اکنون، پرداختن به نام فایلهای شامل هر گونه فضای سفید.
find . -name "* *" -exec rm -f {} \;
#  نام مسیر فایل که فرمان find پیدا می‌کند جانشین ‎{}‎ می‌گردد.
#  کاراکتر ‎\‎ اطمینان ایجاد می‌کند که کاراکتر ‎;‎ به طور لفظی و
#                        به عنوان انتهای فرمان تفسیر می‌گردد.
exit 0

#
# فرمان‌های زیر این سطر به علت فرمان exit اجرا نخواهند گردید.

#                                 جایگزینی برای اسکریپت بالا:
find . -name '*[+{;"\\=?~()<>&*|$ ]*' -maxdepth 0 \
-exec rm -f '{}' \;
#             گزینه ‎-maxdepth 0‎ اطمینان ایجاد می‌کند که find
#+          دایرکتوری‌های فرعی در ‎$PWD‎ را جستجو نخواهد نمود.

# (با تشکر از ‎S.C.‎)

مثال ‎16-4‎. حذف یک فایل از طریق شماره inode آن

#!/bin/bash
#  

# 
#+                                از قبیل ‎?‎ یا ‎-‎ شروع بشود.

ARGCOUNT=1      # 
E_WRONGARGS=70
E_FILE_NOT_EXIST=71
E_CHANGED_MIND=72

if [ $# -ne "$ARGCOUNT" ]
then
  echo "Usage: `basename $0` filename"
  exit $E_WRONGARGS
fi  

if [ ! -e "$1" ]
then
  echo "File \""$1"\" does not exist."
  exit $E_FILE_NOT_EXIST
fi  

inum=`ls -i | grep "$1" | awk '{print $1}'`
# 
# 
# هر فایل دارای یک ‎inode‎ است، رکوردی که اطلاعات آدرس فیزیکی را نگهداری می‌کند.
# 

echo; echo -n "Are you absolutely sure you want to delete \"$1\" (y/n)? "
# گزینه ‎-i‎ با فرمان ‎rm‎ نیز این پرسش را مطرح می‌کند.
read answer
case "$answer" in
[nN]) echo "Changed your mind, huh?"
      exit $E_CHANGED_MIND
      ;;
*)    echo "Deleting file \"$1\".";;
esac

find . -inum $inum -exec rm {} \;
# 
# 
#+             متن خروجی فرمان ‎find‎ هستند.
echo "File "\"$1"\" deleted!"

exit 0

فرمان find بدون گزینه ‎-exec‎ نیز کار می‌کند.

#!/bin/bash
#                                                یافتن فایلهای ‎suid‎ کاربر ارشد.
#یک فایل ‎suid‎ ناشناس می‌تواند بیانگر یک حفره امنیتی، یا حتی نفوذ به سیستم باشد.

directory="/usr/sbin"
#    همچنین می‌توان ‎/sbin‎‏، ‎/bin‎‏، ‎/usr/bin‎‏، ‎/usr/local/bin‎‏، وغیره را امتحان کرد.
permissions="+4000"        # ‎suid root‎ (خطرناک!)


for file in $( find "$directory" -perm "$permissions" )
do
  ls -ltF --author "$file"
done

برای اسکریپت‌هایی که از find استفاده می‌کنند، مثال ‎16-30‎‏، مثال ‎3-4‎‏، و مثال ‎11-10‎ مشاهده نمایید. صفحه man آن، جزییات بیشتری در مورد این فرمان قدرتمند و پیچیده ارایه می‌کند.


xargs

فیلتری برای تغذیه شناسه‌ها به یک فرمان، و همچنین ابزاری برای یکپارچه کردن خود فرمان‌ها است. این فرمان جریان داده‌ها را به تکه‌های به اندازه کافی کوچک جهت پردازش توسط فیلترها و فرمان‌ها تجزیه می‌کند. آن را به مثابه جایگزین قدرتمندی برای backquoteها در نظر بگیرید. در وضعیت‌هایی که در آنها جایگزینی فرمان با یک خطای «too many arguments» (شناسه‌های بسیار زیاد) ناموفق می‌گردد، اغلب جایگزین کردن xargs کار می‌کند. ‎[1]‎ به طور معمول، xargs از stdin یا از یک لوله می‌خواند، اما می‌تواند شناسه‌هایش را از یک فایل تعیین شده نیز بخواند.

فرمان پیش‌فرض برای xargs فرمان echo است. این به معنای آن است که ورودی لوله‌کشی شده به xargs ممکن است دارای کاراکترهای تعویض سطر و سایر کاراکترهای فضای سفیدی باشد که حذف بشوند.

bash$ ls -l
total 0
-rw-rw-r--    1 bozo  bozo         0 Jan 29 23:58 file1
-rw-rw-r--    1 bozo  bozo         0 Jan 29 23:58 file2


bash$ ls -l | xargs
total 0 -rw-rw-r-- 1 bozo bozo 0 Jan 29 23:58 file1 -rw-rw-r-- 1 bozo bozo 0 Jan...


bash$ find ~/mail -type f | xargs grep "Linux"
./misc:User-Agent: slrn/0.9.8.1 (Linux)
./sent-mail-jul-2005: hosted by the Linux Documentation Project.
./sent-mail-jul-2005: (Linux Documentation Project Site, rtf version)
./sent-mail-jul-2005: Subject: Criticism of Bozo's Windows/Linux article
./sent-mail-jul-2005: while mentioning that the Linux ext2/ext3 filesystem
. . .

‎ls | xargs -p -l gzip‎ همه فایل‌های دایرکتوری جاری را، در هر نوبت یک فایل و با یک اعلان قبل از هر عمل gzip می‌کند.

توجه نمایید که xargs شناسه‌های تحویل شده به آن را به صورت ترتیبی ، یکی در هر نوبت، پردازش می‌کند.

bash$ find /usr/bin | xargs file
/usr/bin:          directory
/usr/bin/foomatic-ppd-options:          perl script text executable
. . .

tip

یک گزینه جالب توجه xargs گزینه ‎-n‎ NN است، که تعداد شناسه‌های عبور داده شده را به NN محدود می‌کند.

‎ls | xargs -n 8 echo‎ فایل‌های دایرکتوری جاری را در 8 ستون فهرست می‌کند.

tip

یک گزینه سودمند دیگر ‎-0‎، در ترکیب با ‎find -print0‎ یا ‎grep -lZ‎ است. این گزینه، مدیریت شناسه‌های شامل فضای سفید یا نقل‌قول‌ها را ممکن می‌سازد.

find / -type f -print0 | xargs -0 grep -liwZ GUI | xargs -0 rm -f

grep -rliwZ GUI / | xargs -0 rm -f

هر کدام از موارد فوق هر فایل شامل GUI را حذف خواهد نمود. (با تشکر از ‎S.C.‎)

یا:

cat /proc/"$pid"/"$OPTION" | xargs -0 echo
# 
#  از اصلاحیه ‎Han Holl‎ برای ‎"get-commandline.sh"‎
#+   اسکریپت در فصل «‎/dev‎» و «‎/proc‎» آمده است.


tip

گزینه ‎-P‎ با xargs اجرای پردازش‌ها به طور موازی را مجاز می‌کند. این کار سرعت اجرا در یک ماشین با ‎CPU‎ چند هسته‌ای را افزایش می‌دهد.

#!/bin/bash

ls *gif | xargs -t -n1 -P2 gif2png
# تمام تصویرهای ‎gif‎ در دایرکتوری جاری را به ‎png‎ تبدیل می‌کند.

#                                  گزینه‌ها:
# 
#                ‎-t‎    چاپ فرمان در ‎stderr‎.
#      ‎-n1‎  حداکثر ‎1‎ شناسه در هر سطر فرمان.
# ‎-P2‎  اجرای حداکثر ‎2‎ پردازش به طور همزمان.

#  با تشکر از ‎Roberto Polli‎ برای الهام‌بخشی.

مثال ‎16-5‎.‏  ‎Logfile‎: کاربرد xargs برای دیده‌بانی ‎log‎ سیستم

#!/bin/bash

#  با بریدن از انتهای فایل ‎/var/log/messages‎ یک
# 

# 
#  فایل ‎/var/log/messages‎ باید قابل خواندن همگانی باشد.
# 

LINES=5

( date; uname -a ) >>logfile
# 
echo ---------------------------------------------------------- >>logfile
tail -n $LINES /var/log/messages | xargs | fmt -s >>logfile
echo >>logfile
echo >>logfile

exit 0

#                     توجه:
# 
#                به طوریکه ‎Frank Wang‎ توضیح می‌دهد:
#+  
#+منبع می‌توانند برای xargs ناهنجاری ایجاد نمایند.
#
#   او پیشنهاد می‌دهد سطر زیر جایگزین سطر ‎15‎ گردد:
# 



#                        تمرین
#                       --------
#  این اسکریپت را برای پیگردی تغییرات ‎/var/log/messages‎
#+          در فاصله‌های زمانی  ‎20‎ دقیقه‌ای، ویرایش کنید.
#                 اشاره: از فرمان ‎"watch"‎ استفاده کنید.

همچون در find، یک جفت براکت کمانی به عنوان جای‌نگهدارنده‌ای برای تعویض متن به کار می‌رود.

مثال ‎16-6‎. کپی کردن فایل‌های دایرکتوری جاری به یک دایرکتوری دیگر

#!/bin/bash
# 

#         کپی (تفصیلی) تمام فایلها از دایرکتوری جاری ‎($PWD)‎
#+                     به دایرکتوری تعیین شده در خط فرمان.

E_NOARGS=85

if [ -z "$1" ]   #خروج در صورتیکه شناسه‌ای ارایه نشده باشد.
then
  echo "Usage: `basename $0` directory-to-copy-to"
  exit $E_NOARGS
fi  

ls . | xargs -i -t cp ./{} $1
# 
#  ‎-t‎ گزینه «مطول» (برونداد خط فرمان در ‎stderr‎).
#                       ‎-i‎ گزینه «تعویض رشته‌ها»‏.
#             ‎{}‎ یک جای‌نگهدارنده برای متن خروجی.
#      این مشابه استفاده از جفت براکت‌های کمانی در ‎find‎ است.
#
# لیست فایل‌ها در دایرکتوری جاری یعنی ‎(ls .)‎، خروجی ‎ls‎ را به
#+ عنوان شناسه‌ها به ‎xargs‎ ( با گزینه‌های ‎-i -t‎ ) عبور می‌دهد 
#+ سپس این شناسه‌ها ‎{}‎ به دایرکتوری جدید ‎$1‎ کپی ‎(cp)‎ می‌شود.  
#
#   پیامد کلی آن معادل دقیق ‎cp * $1‎ است، مگر اینکه فایل‌هایی
#+ دارای کاراکترهای «فضای سفید» تعبیه شده در نامشان باشند.

exit 0

مثال ‎16-7‎. کشتن پردازش‌ها بواسطه نام

#!/bin/bash
# 
# این اسکریپت را با ‎kill-process.sh‎ مقایسه کنید.

#     برای نمونه، ‎./kill-byname.sh xterm‎ را امتحان کنید و
#+ نگاه کنید، تمام ‎xterm‎ها روی میزکار شما ناپدید می‌شوند.

#                           هشدار:
#            ---------------------------------
#                        این یک اسکریپت نسبتاً خطرناک است.
#   اجرای آن از روی بی‌دقتی (مخصوصاً به عنوان ‎root‎) می‌تواند‏
#+   باعث از دست رفتن داده‌ها و سایر اثرات ناخواسته بشود.

E_BADARGS=66

if test -z "$1"    # آیا شناسه‌ای در خط فرمان ارایه نشده؟
then
  echo "Usage: `basename $0` Process(es)_to_kill"
  exit $E_BADARGS
fi


PROCESS_NAME="$1"
ps ax | grep "$PROCESS_NAME" | awk '{print $1}' | xargs -i kill {} 2&>/dev/null
# 
# 
#                                نکته‌ها:
#                    ‎-i‎ گزینه «تعویض رشته‌ها» برای ‎xargs‎ است.
#                   ابروها جای‌نگهدارنده برای جایگزینی هستند.
#             ‎2&>/dev/null‎ مانع پیغام‌ خطاهای ناخواسته می‌شود.
#
# ‎grep "$PROCESS_NAME"‎ می‌تواند با ‎pidof "$PROCESS_NAME"‎ تعویض شود؟
# 

exit $?

# فرمان ‎killall‎ دارای اثری مانند این اسکریپت است
#+         اما، به کار بردن آن خیلی آموزشی نیست.

مثال ‎16-8‎. تجزیه و تحلیل تکرار کلمه با استفاده از xargs

#!/bin/bash
# 

# جهت تجزیه سطرهای متن به کلمه‌های منفرد ‎xargs‎ را به کار می‌برد.
#      این مثال را با اسکریپت ‎wf.sh‎ در مثال ‎16-12‎ مقایسه کنید.


#  
ARGS=1
E_BADARGS=85
E_NOFILE=86

if [ $# -ne "$ARGS" ]
# 
then
  echo "Usage: `basename $0` filename"
  exit $E_BADARGS
fi

if [ ! -f "$1" ]       # 
then
  echo "File \"$1\" does not exist."
  exit $E_NOFILE
fi



#
cat "$1" | xargs -n1 | \
#   
tr A-Z a-z | \
#  
sed -e 's/\.//g'  -e 's/\,//g' -e 's/ /\
/g' | \
#  
#+ 
sort | uniq -c | sort -nr
#     
#+ 
#

#  این همان کار مثال ‎wf.sh‎ را انجام می‌دهد، اما کمی
#+   سنگین‌تر، و به طور کندتری اجرا می‌گردد (چرا؟).

exit $?

expr

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

expr 3 + 5

8 برگشت می‌دهد

expr 5 % 3

2 برگشت می‌دهد

expr 1 / 0

پیغام خطای ‎expr: division by zero‎ برگشت می‌دهد

عملیات حسابی غیر مجاز پذیرفته نیست.

expr 5 \* 3

15 برگشت می‌دهد

عملگر ضرب موقع استفاده در یک عبارت حسابی با expr بایستی escape گردد.

‎y=`expr $y + 1`‎

افزایش یک متغیر، با همان نتیجه ‎let y=y+1‎ و ‎y=$(($y+1))‎. این یک مثال از عبارت حسابی است.

‎z=`expr substr $string $position $length`‎

رشته فرعی به طول ‎$length‎ کاراکتر را با شروع از محل ‎$position‎ استخراج می‌کند.

مثال ‎16-9‎. کاربرد expr

#!/bin/bash

# نمایش برخی از کاربردهای ‎expr‎
# 

echo

# 
# 

echo "Arithmetic Operators"
echo
a=`expr 5 + 3`
echo "5 + 3 = $a"

a=`expr $a + 1`
echo
echo "a + 1 = $a"
echo "(incrementing a variable)"

a=`expr 5 % 3`
# عملگر ‎modulo‎ (باقیمانده تقسیم صحیح)
echo
echo "5 mod 3 = $a"

echo
echo

# 
# 

#  اگر صحیح باشد ‎1‎، اگر غلط باشد ‎0‎ برگشت
#+  می‌دهد، نقطه مقابل قرارداد عادی ‎Bash‎.

echo "Logical Operators"
echo

x=24
y=25
b=`expr $x = $y`         #  
echo "b = $b"            #      ( $x مساوی $y نیست )
echo

a=3
b=`expr $a \> 10`
echo 'b=`expr $a \> 10`, therefore...'
echo "If a > 10, b = 0 (false)"
echo "b = $b"            #   ( 3 بزرگتر از 10 نیست )
echo

b=`expr $a \< 10`
echo "If a < 10, b = 1 (true)"
echo "b = $b"            #    ( 3 کوچکتر از 10 است )
echo
# به ‎escape‎ کردن عملگرها توجه کنید.

b=`expr $a \<= 3`
echo "If a <= 3, b = 1 (true)"
echo "b = $b"            #   ( 3 کوچکتر یا مساوی 3 است )
# یک عملگر ‎\>=‎ (بزرگتر یا مساوی) نیز وجود دارد.


echo
echo



# 
# 

echo "String Operators"
echo

a=1234zipper43231
echo "The string being operated upon is \"$a\"."

# 
b=`expr length $a`
echo "Length of \"$a\" is $b."

# 
# 
b=`expr index $a 23`
echo "Numerical position of first \"2\" in \"$a\" is \"$b\"."

# 
b=`expr substr $a 2 6`
echo "Substring of \"$a\", starting at position 2,\
and 6 chars long is \"$b\"."


# 
#+             
#
# 
b=`expr match "$a" '[0-9]*'`       #                       
echo Number of digits at the beginning of \"$a\" is $b.
b=`expr match "$a" '\([0-9]*\)'`   # توجه کنید که پرانتز های ‎escape‎ شده
#     #+    
echo "The digits at the beginning of \"$a\" are \"$b\"."

echo

exit 0

IMPORTANT

عملگر : (null) می‌تواند جایگزین match بشود. به عنوان مثال، ‎b=`expr $a : [0-9]*`‎ معادل دقیقی برای عبارت ‎b=`expr match $a [0-9]*`‎ در مثال فوق است.

#!/bin/bash

echo
echo "String operations using \"expr \$string : \" construct"
echo "==================================================="
echo

a=1234zipper5FLIPPER43231

echo "The string being operated upon is \"`expr "$a" : '\(.*\)'`\"."
# 

# 
#+ 
#+ 
# 


# 
#+     

echo "Length of \"$a\" is `expr "$a" : '.*'`."   # 

echo "Number of digits at the beginning of \"$a\" is `expr "$a"\
 : '[0-9]*'`."

# 

echo

echo "The\
 digits at the beginning of \"$a\" are `expr "$a" : '\([0-9]*\)'`."
# 
echo "The\
 first 7 characters of \"$a\" are `expr "$a" : '\(.......\)'`."
#
# دوباره، پرانتزهای escape شده یک انطباق رشته فرعی را تحمیل می‌کنند.
#
echo "The\
 last 7 characters of \"$a\" are `expr "$a" : '.*\(.......\)'`."
#
#  (در واقع، یعنی پرش از روی یک یا چند کاراکتر تا پیدا نمودن
#                                      
echo
exit 0

اسکریپت فوق شرح می‌دهد که expr چگونه پرانتزهای escape شده ‎-- \( ... \) --‎ عملگر گروه‌بندی پشت سرهم را با عبارت منظم تحویل شده برای انطباق یک رشته فرعی به کار می‌برد. این هم یک مثال دیگر، ایندفعه از «زندگی واقعی».

#  زدودن فضای سفید از ابتدا و انتهای قالب ‎%Y-%m-%d‎ تاریخ.
LRFDATE=`expr "$LRFDATE" : '[[:space:]]*\([[:digit:]-]*\)[[:space:]]*$'`
#   از اسکریپت ‎booklistgen.sh‎ نوشته ‎Peter Knowles‎
#+برای تبدیل فایل‌ها به قالب ‎Sony Librie/PRS-50X‎.
# 

Perl‏، sed، و awk دارای وسایل به مراتب عالی‌تر تجزیه رشته هستند. یک «روتین فرعی» کوتاه sed یا awk درون یک اسکریپت (بخش ‎36.2‎ را ببینید) یک جایگزین جالب برای expr است.

برای کاربرد بیشتر expr در عملیات رشته‌ای بخش ‎10.1‎ را مشاهده نمایید.

یادداشت‌ها

[1]

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