اول و پیش از همه برای درک آنکه چرا مشکل دارید، مبحث شناسهها را بخوانید، تا خوب دریابید، پوسته جملاتی را که به آن میدهید چطور میفهمد. اگر میخواهید هر کاری با شل انجام بدهید، واجب است که شما درک خوبی از این موضوع کسب نمایید.
شیوه برگزیده کار با نامهای انتخابی فایل، بازهم استفاده از find(1) میباشد:
find ... -exec command {} \;
یا، اگر به مدیریت نامفایلها به طور دسته جمعی نیاز دارید:
find ... -exec command {} +
xargs به ندرت بتواند سودمندتر از مورد فوق باشد، اما اگر شما حقیقتاً اصرار دارید، استفاده از -0 را به خاطر داشته باشید:
# نیاز دارد GNU/BSD درfind و xargs به فرمان find ... -print0 | xargs -0 command # استفاده نکنید xargs هرگز بدون گزینه 0- یا ضمائم مشابه از
یکی از اینها را به کار ببرید مگر اینکه واقعاً نمیتوانید.
روش دیگر برای کار با فایلهایی که در نامشان دارای فاصله میباشند استفاده از بسط نام فایل پوسته(globbing) است. اشکال آن، کار نکردن به طور بازگشتی است(به استثنای بسطهای zsh یا globstar نگارش 4 از bash)، اما اگر شما فقط نیاز به پردازش تمام فایلهای یک دایرکتوری منفرد دارید، به طور خارقالعادهای خوب کار میکند.
برای مثال، این کُد تمام فایلهای *.mp3 در دایرکتوری فعلی را با جایگزینی خطزیر(underscore) به جای فاصله در نامشان تغییر نام میدهد:
# Bash/ksh
for file in ./*\ *.mp3; do
mv "$file" "${file// /_}"
done
برای مثالهای بیشتر تغییر نام فایلها، پرسش و پاسخ شماره 30 را ملاحظه نمایید.
به خاطر داشته باشید، لازم است شماتمام بسطهای پارامتر را با استفاده از نقلقول دوگانه نقلقولی کنید. اگر چنین نکنید، بسط تحت تأثیر تفکیک کلمه قرار میگیرد(همچنین بخش جداسازی شناسه و تلههایBash را ببینید). همچنین، همیشه جانشینها را با "./" پیشوند کنید، در غیر این صورت، اگر فایلی با نام شامل "-" به عنوان اولین کاراکتر نام وجود داشته باشد بسطها ممکن است به طور غلط، به عنوان گزینه تفسیر گردند.
یک روش دیگر کار با نام فایلها به طور بازگشتی، شامل استفاده از گزینه -print0 فرمان find (الحاقیه GNU/BSD )، همراه با گزینه -d پوسته bash برای read میباشد:
# Bash unset a i while IFS= read -r -d $'\0' file; do a[i++]="$file" # or however you want to process each file done < <(find /tmp -type f -print0)
مثال اسبق تمام فایلهای دایرکتوری /tmp را (به طور بازگشتی) در یک آرایه حتی اگر در نام آنها سطرجدید یا فضای سفید موجود باشد، میخواند، از طریقِ اجبار read به استفاده از بایت تهی(\0) به عنوان جداکننده سطر. چون NUL بایت معتبر در نام فایل یونیکس نمیباشد، این رویکرد در کنار استفاده از find -exec مطمئنترین روش است. IFS= برای اجتناب از بریدن فضای سفید ابتدا و انتها لازم است، و -r برای اجتناب از پردازش کاراکتر
# Bash unset a i while IFS= read -r -d '' file; do a[i++]="$file" done < <(find /tmp -type f -print0)
پس چرا این کار نمیکند؟
# کار نمیکند unset a i find /tmp -type f -print0 | while IFS= read -r -d '' file; do a[i++]="$file" done
به علت وجود لوله، تمامیت حلقه while در یک پوسته فرعی انجام میشود و بنابراین تخصیصهای آرایه پس از خاتمه یافتن حلقه از دست خواهد رفت. (برای جزئیات بیشتر در این باره، پرسش و پاسخ شماره 24 را ملاحظه کنید.)
پرسش و پاسخ 20 (آخرین ویرایش 2012-06-29 11:53:50 توسط GreyCat)
برخی سیستمهای یونیکس برنامه سودمند split را برای این منظور فراهم میکنند:
split --lines 10 --numeric-suffixes input.txt output-
برای انعطاف پذیری بیشتر میتوانید از sed استفاده کنید. فرمان sed میتواند به طور مثال سطرهای محدوده 1-10 را چاپ نماید
sed -n -e '1,10p' -e '10q'
این(-n) مانع چاپ کردن هر سطر توسط sed میشود. در عوض فقط سطرهای محدوده 1 تا 10 پردازش میشوند("1,10")، و آنها چاپ ("p") میشوند. فرمان پس از خواندن سطر 10 خارج("10q") میگردد.
همچنین میتوانیم این کد را برای چاپ یک محدوده انتخابی فایل(تعیین شده با شماره سطر) به کار ببریم:
# POSIX shell
file=/etc/passwd
range=10
cur=1
last=$(wc -l < "$file") # تعداد سطرها را میشمارد
chunk=1
while [ $cur -lt $last ]
do
endofchunk=$(($cur + $range - 1))
sed -n -e "$cur,${endofchunk}p" -e "${endofchunk}q" "$file" > chunk.$(printf %04d $chunk)
chunk=$(($chunk + 1))
cur=$(($cur + $range))
done
مثال قبل محاسبات POSIX را به کار میبرد، که شلهای Bourne قدیمیتر ندارند. در آن موقعیت، مثال زیر میتواند به جای آن به کار برود:
# printf و فرض بر عدم وجود Bourne میراث پوسته
file=/etc/passwd
range=10
cur=1
last=`wc -l < "$file"` # تعداد سطرها را میشمارد
chunk=1
while test $cur -lt $last
do
endofchunk=`expr $cur + $range - 1`
sed -n -e "$cur,${endofchunk}p" -e "${endofchunk}q" "$file" > chunk.$chunk
chunk=`expr $chunk + 1`
cur=`expr $cur + $range`
done
Awk نیز میتواند برای تولید نتیجه کم و بیش مشابهی به کار برود:
awk -v range=10 '{print > FILENAME "." (int((NR -1)/ range)+1)}' file
پرسش و پاسخ 19 (آخرین ویرایش 2009-12-30 17:16:43 توسط MatthiasPopp)
به طور معمول، چند روش برای حل مشکل موجود است، هریک با مزایا و معایب خاص خود.
Bash نگارش 4 صفرهای پُرکننده(صفر قبل از عدد) و دامنه آنها را در بسط ابرویش مجاز میدارد:
# Bash 4
echo {01..10}
for i in {01..10}; do ...
تمام چاره کارهای دیگر در این صفحه Bash قبل از نگارش 4.0، یا پوستههای غیر از Bash را فرض خواهد نمود.
اگر تعداد اعداد زیاد نیست، بسط ابرو میتواند به کار برود:
# Bash
for i in 0{1,2,3,4,5,6,7,8,9} 10
do
echo $i
done
در Bash نگارش 3، میتوانید دامنهها را داخل بسط ابرو به کار ببرید(اما صفر پُرکننده را خیر). بنابراین همان کارمیتواند به طور مختصرتری به این شکل انجام بشود:
# Bash 3
for i in 0{1..9} 10
do
echo $i
done
مثال دیگر، برای خروجی 0000 تا 0034:
# Bash 3
for i in {000{0..9},00{10..34}}
do
echo $i
done
# .ابروهای خارجی فقط به جای اضافه کردن آنها یکی پس از دیگری است
# :استفاده از بسط را اجازه میدهد، به طور نمونه، مانند این
wget 'http://foo.com/adir/thepages'{000{0..9},00{10..34}}'.html'
برخی شاید روش سریع و نامنزه زیر را ترجیح بدهند(برای تولید "000" تا "015"):
# Bash 3
for i in {1000..1015}
do
echo "${i:1}" # یا "${i#1}"
# (مترجم: در اینجا از بسط پارامتر استفاده گردیده است)
done
این برای توالی وسیع کسل کننده میشود، اما روشهای دیگری نیز وجود دارد. اگر فرمان printf را(که یک فرمان داخلی Bash است و استاندارد POSIX نیز هست) دارید، میتواند برای قالببندی عدد به کار برود:
# Bash
for ((i=1; i<=10; i++))
do
printf "%02d " "$i"
done
همچنین، چون printf به طور ضمنی حلقه است، اگر شناسههایی غیر از مشخص کننده قالب داده شده باشد، میتوانید به طورهنگفتی این را مختصر نمایید:
# Bash 3
printf "%03d\n" {1..300}
اگر از قبل مقدار شروع و خاتمه را نمیدانید:
# Bash 3
# متغیرهای شامل عدد صحیح میباشند end و start
eval printf '"%03d\n"' {$start..$end}
فرمان eval در اینجا لازم است، زیرا نمیتوانید در بسط ابرو متغیرها را داشته باشید --فقط ثابتها.نقلقولهای اضافی را eval لازم دارد برای اینکه \n ما به یک n تغییر نکند. قدر مسلم روش eval راه حل شلوغی است، لطفاً برای مقاصد خطیر به جای آن از حلقه for استفاده کنید.
در پوسته Korn فرمان typeset دارای گزینهای برای تعیین تعداد ارقام عدد میباشد:
# Korn
$ typeset -Z3 i=4
$ echo $i
004
اگر فرمان seq(1) در دسترس باشد( بخشی از GNU sh-utils/coreutils میباشد)، میتوانید به این شکل از آن استفاده کنید:
seq -w 1 10
یا، برای تعداد ارقام دلخواه همراه با صفرهای پشت عدد(در اینجا: 3):
seq -f "%03g" 1 10
با ترکیب printf با seq(1)، میتوانید موردی مانند این را انجام بدهید:
# POSIX shell, GNU utilities printf "%03d\n" $(seq 300)
(که شاید اگر از Bash استفاده نمیکنید، اما seq(1) را دارید و نگارش seq(1) شما فاقد مشخص کننده قالب سَبک printf است، این روش مفید باشد. یک مجموعه عجیبی از محدودیتهامیباشد، اماگمان میکنم به طور نظری ممکن باشد. چون seq ابزار خارجی غیر استانداردی است، خوب است انتخابهای شما باز باشد.)
آگاه باشید که به هرحال آن کاربرد seq میتواند به عنوان شیوه نامساعدی مطرح شده باشد، حتی در بخش هرگز این موارد را انجام ندهید اشاره شده است.
بعضی سیستمهای مشتق شده از BSD در عوض seq(1)دارای jot(1) میباشند. مطابق رسم خیلی خوب یونیکس، این یک ترکیب دستوری کاملاً ناسازگاری دارد:
# POSIX shell, OpenBSD et al. printf "%02d\n" $(jot 10 1) # Bourne shell, OpenBSD (at least) jot -w %02d 10 1
سرانجام، مثال ذیل که با هرپوسته مشتق شده از شل Bourne (که هم expr و هم sed را دارد) با سه بایت برای هر عدد در هر سطر و صفر قبل از عدد در صورت لزوم، کار میکند:
# Bourne
i=0
while test $i -le 10
do
echo "00$i"
i=`expr $i + 1`
done |
sed 's/.*\(...\)$/\1/g'
در این مثال، تعداد '.' داخل پرانتزها در فرمان sed تعیین میکند چند بایت از فرمان echo (در انتهای هر سطر) حفظ و چاپ خواهد شد.
اما اگر به راستی میخواهید به یک فرمان خارجی یونیکس استناد کنید، در درجه اول میتوانید تمام آن را به خوبی با awk انجام بدهید:
# Bourne
# شامل یک عدد صحیح است count متغیر
awk -v count="$count" 'BEGIN {for (i=1;i<=count;i++) {printf("%03d\n",i)} }'
# بیفایده awk با سولاریس سالخورده و Bourn
awk "BEGIN {for (i=1;i<=$count;i++) {printf(\"%03d\\n\",i)} }"
اکنون، چون دلیل شماره یک پرسیدن این سؤال برای بارگیری تصویرها به طور انبوه است، میتوانید مثالهای فوق را با xargs(1) و wget(1) برای واکشیدن فایلها به کار ببرید:
almost any example above | xargs -i% wget $LOCATION/%
فرمان xargs -i% در هر نوبت سطری از ورودی را میخواند، و % انتهای فرمان را با ورودی تعویض میکند.
یا، مثال سادهتر که به کاربردن حلقه forاست:
# Bash 3
for i in {1..100}; do
wget "$prefix$(printf %03d $i).jpg"
sleep 5
done
یا، برای اجتناب از پوستههای فرعی( bash نگارش 3.1 لازم دارد):
# Bash 3.1
for i in {1..100}; do
printf -v n %03d $i
wget "$prefix$n.jpg"
sleep 5
done
پرسش و پاسخ 18 (آخرین ویرایش 2012-02-01 16:00:55 توسط GreyCat)
روش قابل حمل(POSIX یا Bourne) استفاده از چندین فرمان test (یا [) است:
# Bourne
if test A && test B || test C; then
...
در این حالت گروهبندی به صورت ضمنی است، زیرا AND یعنی(&&) تقدم بالاتری نسبت به OR یعنی(||) دارد. اگر به گروهبندی آشکار نیاز داشته باشیم، آنوقت میتوانیم از ابروها استفاده نماییم:
# Bourne(?)
if test A && { test B || test C; }; then
...
آنچه که نباید انجام بدهیم، آزمایش استفاده از عملگرهای -a یا -o فرمان test میباشد، زیرا نتیجه آن، تعریف نشده، است.
BASH و KornShell دارای فرمانهای مقایسه قدرتمندتر مختلفی با تفاوت جزئی(آسانتر) در نقلقول کردن، میباشند:
بسط محاسباتی برای عبارتهای حسابی، و
فرمان تست جدید برای بسط رشته(و فایل) .
مثالها:
# Bash/ksh
if (( (n>0 && n<10) || n == -1 ))
then echo "0 < $n < 10, or n==-1"
fi
یا
# Bash/ksh
if [[ ( -f $localconfig && -f $globalconfig ) || -n $noconfig ]]
then echo "configuration ok (or not used)"
fi
توجه نمایید که تمایز میان مقایسه رشتهای و عددی، سختگیرانه است. مثال پایین را ملاحظه کنید:
n=3
if [[ $n>0 && $n<10 ]]
then echo "$n is between 0 and 10"
else echo "ERROR: invalid number: $n"
fi
خروجی "ERROR: ...." خواهد بود، به دلیل آنکه در یک مقایسه رشتهای "3" بزرگتر از "10" است، چون "3" بعد از "1" میآید، و کاراکتر بعدی "0" در نظر گرفته نمیشود. با تعویض کروشهها با پرانتزهای دوتایی (( مثال آنطور کار میکند که انتظار میرود.
پرسش و پاسخ 17 (آخرین ویرایش 2011-07-12 02:24:26 توسط pool-173-71-205-68)
"Glob"ها الگوهای سادهای هستند که میتوانند برای مطابقت نام فایلها و رشتهها به کار بروند. به طور معمول آنها خیلی قدرتمند نمیباشند. اگر قدرت بیشتری لازم دارید، چند گزینه در دسترس است.
اگر میخواهید بر تمام فایلهای که با جانشین A یا جانشین B مطابقت دارند عمل کنید، فقط هر دو را در یک سطر قرار بدهید:
rm -- *.bak *.old
اگر میخواهید یک OR منطقی فقط در قسمتی از جانشین به کار ببرید(بزرگتر از یک کاراکتر منفرد -- برای مورد یک کاراکتری، کلاسهای کاراکتر داخل کروشه کفایت میکند)، در Bash، میتوانید از بسط ابرو استفاده کنید:
rm -- *.{bak,old}
اگر موردی بازهم بیشتر عمومی و قدرتمند نیاز دارید، در KornShell یا BASH میتوانید از جانشینهای توسعهیافته استفاده کنید. در Bash، لازم است گزینه extglob تنظیم باشد. به این ترتیب میتوند کنترل شود:
shopt extglob
و به این ترتیب تنظیم میشود:
shopt -s extglob
برای دستگرمی، تمام فایلهایی که با foo شروع میشوند و به .d ختم نمیشوند را به دایرکتوری foo_thursday.d منتقل میکنیم:
mv foo!(*.d) foo_thursday.d
یک مثال پیچیدهتر -- حذف تمام فایلهایی که نام آنها شامل Pink_Floyd و هچنین فاقد The_Final_Cut باشد:
rm !(!(*Pink_Floyd*)|*The_Final_Cut*)
ضمناً: این نوع از الگوها میتوانند با KornShell نیز به کار بروند. در آنجا نیازی به فعال کردن نیست، بلکه الگوهای پیشفرض هستند.
پرسش و پاسخ 16 (آخرین ویرایش 2009-12-30 17:14:33 توسط MatthiasPopp)