آموزش اسکریپت نویسی

آموزش اسکریپت نویسی پوسته گنو-لینوکس

آموزش اسکریپت نویسی

آموزش اسکریپت نویسی پوسته گنو-لینوکس

پرسش و پاسخ شماره ۳۰

پرسش و پاسخ شماره ۳۰

چگونه می‌توانم تمام فایل‌های ‎ *.foo‎ را به ‎*.bar‎ تبدیل نمایم، یا فاصله‌ها را به خط زیر تبدیل کنم، و یا حروف بزرگ نام فایلها را به حروف کوچک تبدیل کنم؟

برخی توزیع‌های گنو-لینوکس دارای فرمان ‎ rename(1)‎ هستند، که می‌توانید برای این کار استفاده کنید، به هرحال نحوه ترکیب دستوری(syntax) آن از یک توزیع به دیگری تفاوت دارد، بنابراین پاسخ قابل حملی نیست....

اگر می‌خواهید یاد بگیرید که چگونه از فرمان renameخود استفاده کنید، در صورتی که یک نمونه از آن را دارید، صفحات man در سیستم خود را کنکاش کنید. غالباً، برای انجام تغییر نام‌های یکبارهِ محاوره‌ای کاملاً مناسب است، نه در اسکریپت‌های قابل حمل. ما در اینجا مثالی برای rename نمی‌آوریم زیرا خیلی مغشوش می‌شود -- دو نگارش رایج از این برنامه وجود دارد و آنها کاملاً با یکدیگر ناسازگارند.

احتمالاً می‌توانید تغییر نام انبوه غیر بازگشتی را با استفاده از یک حلقه و برخی بسط پارامترها انجام بدهید، مانند این :

# POSIX
# تغییر نام می‌دهد *.bar را به  *.foo تمام 
for f in *.foo; do mv -- "$f" "${f%.foo}.bar"; done

# POSIX
# را از تمام فایلها حذف می‌کند .zip پسوند
for file in ./*.zip; do mv "$file" "${file%.zip}"; done

‎ "--"‎ و ‎"./*"‎ برای محافظت ازنام فایلهای حیرت‌آوری که با "-" شروع می‌شوند، هستند. فقط به یکی از آنها نیاز دارید بنابراین مطلوب خود را به کار ببرید.

دراینجا مثالهای مشابهی ، با کاربرد بسط‌ پارامترهای مخصوص Bash آمده است:

# Bash
# تمام فاصله‌ها با خط زیر تعویض می‌شوند
for f in *\ *; do mv -- "$f" "${f// /_}"; done

# Bash
# تغییر می‌کنند "bar" ها به "foo" همه 
for file in ./*foo*; do mv "$file" "${file//foo/bar}"; done

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

اگر می‌خواهید به طور بازگشتی فایلها را تغییر نام بدهید، آنوقت خیلی بیشتر چالش‌انگیز می‌شود. این مثال ‎*.foo‎ را به ‎*.bar‎ تغییر نام می‌دهد:

# Bash در پوسته‎
# نیاز دارد BSD گنو یا find(1) همچنین به ‎
# تغییر می‌کنند *.bar به *.foo به طور بازگشتی تمام فایلهای 

find . -type f -name '*.foo' -print0 | while IFS= read -r -d '' f; do
  mv -- "$f" "${f%.foo}.bar"
done

این مثال به جای به کار بردن find گنو از globstar نگارش Bash 4 استفاده می‌کند:

#  فعال شده باشد،  قابل حمل نیست globstar که لازم است گزینه Bash 4 در ‎
#  تغییر نام می‌دهد "bar" را به "foo"  به طور بازگشتی تمام فایلهای ‎
#  در نام دایرکتوری نباید ظاهر شود "foo"

shopt -s globstar; for file in /path/to/**/*foo*; do mv -- "$file" "${file//foo/bar}"; done

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

جهت تکنیک‌های بیشتر برای کار با فایلهایی که کاراکترهای نامناسب در نام خود دارند پرسش و پاسخ شماره 20 را ببینید.

مهارت آمیزترین قسمت تغییر نام بازگشتی، آن است که مطمئن بشوید دایرکتوری تشکیل دهنده نام مسیر را تغییر ندهید، زیرا موردی مشابه این محکوم به شکست است:

mv "./FOO/BAR/FILE.TXT" "./foo/bar/file.txt"

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

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

# Bash
ren() {
  local newname
  newname=${1// /_}
  [ "$1" != "$newname" ] && mv -- "$1" "$newname"
}

traverse() {
  local i
  cd -- "$1" || exit 1
  for i in *; do
    [ -d "$i" ] && traverse "$i"
    ren "$i"
  done
  cd .. || exit 1
}

# main program
shopt -s nullglob
traverse /path/to/startdir

این هم روش دیگر تغییرنام بازگشتی تمام شاخه‌ها و فایلهایی که نام آنها دارای فاصله است:

find . -depth -name "* *" -exec bash -c 'dir=${1%/*} base=${1##*/}; mv "$1" "$dir/${base// /_}"' _ {} \;

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

find . -depth -name "* *" -exec bash -c 'for f; do dir=${f%/*} base=${f##*/}; mv "$f" "$dir/${base// /_}"; done' _ {} +

برای تبدیل نام فایلها به حروف کوچک، اگر شما برنامه سودمند‎ mmv(1)‎ را در سیستم خود دارید، می‌توانید این مورد را انجام بدهید:

# تمام نام فایلها را به حروف کوچک تبدیل می‌کتد
mmv "*" "#l1"

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

# Bash 4
for f in *[[:upper:]]*; do mv -- "$f" "${f,,*}"; done

در غیر اینصورت، شاید ‎tr(1)‎ سودمند باشد:

# نام فایلها را به حروف کوچک تبدیل می‌کند
# POSIX
for file in "$@"
do
    [ -f "$file" ] || continue                # صرفنظر از نامهای غیر موجود
    newname=$(echo "$file" | tr '[:upper:]' '[:lower:]')     # حالت کوچک
    [ "$file" = "$newname" ] && continue      # کاری انجام نمی‌شود
    [ -f "$newname" ] && continue             # فایهای موجود رونویسی نشوند
    mv -- "$file" "$newname"
done

ما از نماد دامنه به صورت فوق استفاده نمودیم، زیرا برنامه tr، هنگامی که در برخی مناطق از دامنه به صورت A-Z استفاده می‌شود، می‌تواند به طور بسیار عجیبی رفتار نماید:

imadev:~$ echo Hello | tr A-Z a-z
hÉMMÓ

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

imadev:~$ echo Hello | LC_ALL=C tr A-Z a-z
hello
imadev:~$ echo Hello | tr '[:upper:]' '[:lower:]'
hello
# Either way is fine here.

این روش همچنین برای تعویض تمام کاراکترهای ناخواسته در نام یک فایل، به عنوان مثال با ‎ '_'‎(خط زیر) می‌تواند به کار برود. همان اسکریپت بالا، فقط با سطر تغییر یافتهٔ ‎ "newname=..."‎.

#  تغیر نام فایلهایی که در نام خود شامل کاراکتر غیر عادی هستند
# POSIX
for file in "$@"
do
    [ -f "$file" ] || continue            # چشم‌پوشی از فایلهای غیرمعمولی، وغیره
    newname=$(echo "$file" | sed 's/[^[:alnum:]_.]/_/g')
    [ "$file" = "$newname" ] && continue  # nothing to do
    [ -f "$newname" ] && continue         # فایلهای موجود رونویسی نشوند
    mv -- "$file" "$newname"
done

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

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

# تغییر نام فایلها(نگارش کارآمدتر، کمتر قابل حمل)
# Bash
for file in "$@"; do
   [ -f "$file" ] || continue
   newname=${f//[^[:alnum:]_.]/_}
   [ "$file" = "$newname" ] && continue
   [ -f "$newname" ] && continue
   mv -- "$file" "$newname"
done

باید توجه بشود که تمام این مثالها شامل یک وضعیت مسابقه هستند-- و یک فایل موجود در صورتی که در فاصله بین فرمان‌های ‎ [ -f "$newname" ...‎ و ‎mv "$file" ...‎ ایجاد گردد، می‌تواند رونویسی بشود. به هرحال حل این موضوع فراتر از محدوده این صفحه می‌باشد.

یک مطلب نهایی در مورد تغییر حالت نام فایلها: در بسیاری از سیستم فایل‌ها، موقع استفاده از mv گنو، کوشش برای تغییرنام فایل به حالت حروف کوچک یا بزرگ معادل آن، ناموفق می‌شود. (این شامل Cygwin در سیستمهای DOS/Windows با سیستم فایل FAT یا NTFS و mv گنو در سیستمهای ‎ Mac OS X‎ با ‎HFS+‎ در وضعیت غیرحساس به حالت حروف می‌شود، همچنین در سیستمهای لینوکس که فایل‌سیستم‌های متصل شده Windows/Mac دارند، و بسیاری تنظیم‌های احتمالی دیگر.) فرمان mv گنو قبل از تلاش برای تغییرنام، مقصد هر دو نام را کنترل می‌کند، و به سبب مسیردهی سیستم‌فایل، گمان می‌کند که فایل هم‌اکنون موجود است:

mv README Readme    # ناموفق است FAT گنو در فایل‌سیستم mv با دستور

رفع و رجوع این مورد، دوبار تغییرنام فایل است: اول به یک نام موقتی که کاملاً متمایز از نام اولیه است، سپس به نام مورد نظر.

mv README tempfilename &&
mv tempfilename Readme


CategoryShell

پرسش و پاسخ 30 (آخرین ویرایش ‎ 2012-02-21 19:16:53 ‎ توسط GreyCat)


نظرات 0 + ارسال نظر
ایمیل شما بعد از ثبت نمایش داده نخواهد شد