یک تعداد ابزار برای این مورد در دسترس است. که استفاده از هریک به عوامل زیادی بستگی دارد، البته مهمترین عامل آن است که چه چیزی را ویرایش میکنیم.
ویرایش فایلها مستلزم دقت است. تنها ابزار استاندارد که به طور واقعی فایل را ویرایش میکند ed است. سایر روشها میتوانند به کار بروند، اما آنها با فایل موقتی و mv درگیر میشوند (یا ابزارهای غیر استاندارد، یاملحقات POSIX).
ed ویرایشگر خط فرمانی استاندارد UNIX است. در اینجا برخی ترکیب های دستوری رایج برای تعویض رشته olddomain.com با رشته newdomain.com در فایلی به نام file آمده است. هر چهار فرمان کار یکسانی را با درجات متفاوتی از قابلیت حمل و کارایی انجام میدهند:
# Bash در پوسته ed -s file <<< $'g/olddomain\\.com/s//newdomain.com/g\nw\nq' # printf با وجود Bourne در پوسته printf '%s\n' 'g/olddomain\.com/s//newdomain.com/g' w q | ed -s file printf 'g/olddomain\\.com/s//newdomain.com/g\nw\nq' | ed -s file # printf بدون وجود Bourne در پوسته ed -s file <<! g/olddomain\\.com/s//newdomain.com/g w q !
برای تعویض یک رشته در تمام فایلهای شاخه جاری، فقط یکی از موارد فوق را در حلقه قرار بدهید:
for file in ./*; do [[ -f $file ]] && ed -s "$file" <<< $'g/old/s//new/g\nw\nq' done
برای انجام این کار به صورت بازگشتی، آسانترین راه فعال نمودن
for file in ./**/*; do [[ -f $file ]] && ed -s "$file" <<< $'g/old/s//new/g\nw\nq' done
اگر شما bash نگارش 4 ندارید، میتوانید از find استفاده کنید. متأسفانه، تغذیه ورودی استاندارد ed با یکایک فایلها اندکی کسل کننده است:
find . -type f -exec bash -c 'printf "%s\n" "g/old/s//new/g" w q | ed -s "$1"' _ {} \;
اگر متغیرهای پوسته به عنوان رشتههای جایگزینی یا جستجو به کار بروند، ed مناسب نیست. نه sed، نه هیچ ابزاری که از عبارتهای منظم استفاده کند، مناسب نمیباشند. استفاده از کُد awk همراه با تغییر مسیرها و mv را در انتهای این پرسش و پاسخ ملاحظه کنید.
gsub_literal "$search" "$rep" < "$file" > tmp && mv tmp "$file"
sed ویرایشگر جریانی است، نه ویرایشگر فایل. با وجود این، اشخاص اصرار دارند آن را به طور نامناسب در هرجایی جهت ویرایش فایلها به کار ببرند. این برنامه فایلها را ویرایش نمیکند. sed گنو(و بعضی sedهای BSD) دارای گزینه -i میباشند که یک کپی ایجاد میکند و فایل اصلی را با کپی تعویض میکند. یک عملیات پر خرج، اما اگر شما از کُد غیرقابل حمل، I/O بالاسری، اثرات جانبی نامساعد(از قبیل خرابی پیوندهای نمادین) لذت میبرید، این میتواند یک انتخاب باشد:
sed -i 's/old/new/g' ./* # GNU sed -i '' 's/old/new/g' ./* # FreeBSD
افرادی از شما که پرل نگارش 5 دارند، میتوانند همان کار را با استفاده از این کُد انجام بدهند:
perl -pi -e 's/old/new/g' ./*
کاربرد بازگشتی find:
find . -type f -exec perl -pi -e 's/old/new/g' {} \; # شما هنوز + ندارد find اگر find . -type f -exec perl -pi -e 's/old/new/g' {} + # اگر دارد
اگر در عوض جایگزینی میخواهید سطرها را حذف کنید:
# را حذف میکند foo هر سطر شامل عبارت منظم پرل perl -ni -e 'print unless /foo/' ./*
برای جایگزینی تمام "unsigned" با "unsigned long" در همان مثال، در صورتی که "unsigned int" یا "unsigned long" نباشند ...:
find . -type f -exec perl -i.bak -pne \ 's/\bunsigned\b(?!\s+(int|short|long|char))/unsigned long/g' {} \;
تمام مثالهای فوق از عبارتهای منظم استفاده میکنند، به آن معنی که، دارای مشکلاتی همانند کُد sed قبلی میباشند، ایده آزمایش تعبیه متغیرهای پوسته در آنها ایدهای ترسناک است، و رفتار با یک کمیت انتخابی به عنوان رشته لفظی، در بهترین حالت نیز ناراحت کننده است.
علاوه براین، پرل، بدون آنکه استعداد نهانی تصادم با کاراکترهای علائم را داشته باشد، میتواند برای عبور دادن متغیرها به درون هم رشتههای جستجو و هم رشتههای جایگزین شونده به کار برود:
in="input (/string" out="output string" perl -pi -e $'$quoted_in=quotemeta($ENV{\'in\'}); s/$quoted_in/$ENV{\'out\'}/g' ./*
یا، سادهتر:
in=$search out=$replace perl -pi -e 's/\Q$ENV{"in"}/\Q$env{"out"}/g' ./*
اگر یک متغیر است، این کار میتواند(و باید) به طور خیلی ساده با بسط پارامتر Bash انجام بشود:
var='some string'; search=some; rep=another # Bash در پوسته var=${var//"$search"/$rep}
در POSIX خیلی دشوارتر است:
# POSIX تابع # string_rep SEARCH REPL STRING طرز استفاده # تعویض میکند STRING در REPL را با SEARCH تمام نمونههای string_rep() { # ارزش گذاری متغیرها in=$3 unset out # نباید خالی باشد SEARCH متغیر test -n "$1" || return while true; do # نباشد حلقه قطع میشود"$in" دیگر در SEARCH اگر case "$in" in *"$1"*) : ;; *) break;; esac # الحاق میکند "$out" را به REPL و SERCH راتا رسیدن به اولین نمونه "$in" محتوای out=$out${in%%"$1"*}$2 # حذف میکند "$in" و هر چه قبل از آن هست را از SEARCH اولین نمونه in=${in#*"$1"} done # پیوست و نتیجه را چاپ میکند $out مانده است به "$in" در SEARCH آنچه پس از آخرین printf '%s%s\n' "$out" "$in" } var=$(string_rep "$search" "$rep" "$var") # :توجهPOSIX راهی برای محلی نمودن متغیرها ندارد. اکثر پوستهها(حتی dash وbusybox) دارند # به هرحال اگر پوسته شما پشتیبانی میکند، با خیال راحت آنرا انجام بدهید. اما اگر # پشتیبانی نمیکند، حتی اگر تابع را به صورت var=$(string_rep ...) فراخوانی کنید، # باز هم تابع در یک پوسته فرعی اجرا خواهد شد و هیچ تخصیصی ایستادگی نخواهد نمود. #
در مثال bash، نقلقولها در اطراف "$search" از این که با متغیرها به عنوان الگوی پوسته(که همچنین به عنوان glob نیز شناخته میشوند) رفتار شود، پیشگیری میکنند. البته، اگر مطابقت الگو مورد نظر است، نقلقولها را الحاق نکنید. اگر "$rep" نقلقولی شده بود، به هرحال با نقلقولها به صورت لفظی رفتار میگردید.
بسطهای پارامترِ مانند این، به طور مفصلتر در پرسش و پاسخ شماره 100 بحث شده است.
اگر جریان است، بنابراین از
some_command | sed 's/foo/bar/g'
sed از عبارتهای منظم استفاده میکند. در مثال ما، foo و bar رشتههای لفظی هستند. اگر آنها متغیر بودند(به عنوان مثال، ورودی کاربر)، به منظور اجتناب از خطاها، آنها میبایست با دقت بسیار پوشش داده میشدند. این کار خیلی غیر عملی است، و کوشش برای انجام آن کُد شما را بینهایت مستعد باگها مینماید. تعبیه متغیرهای پوسته در فرمانهای sed هرگز ایده خوبی نیست.
شما این کار را میتوانستید در خود Bash توسط ترکیب بسط پارامتر با پرسش و پاسخ شماره 1 انجام بدهید:
search=foo; rep=bar while IFS= read -r line; do printf '%s\n' "${line//"$search"/$rep}" done < <(some_command) some_command | while IFS= read -r line; do printf '%s\n' "${line//"$search"/$rep}" done
اگر میخواهید پردازشی بیش از فقط یک جستجو و تعویض ساده انجام بدهید، شاید این بهترین گزینه باشد. توجه داشته باشید که آخرین مثال حلقه را در یک پوسته فرعی اجرا میکند. برای اطلاعات بیشتر در این مورد پرسش و پاسخ شماره 24 را ببینید.
شاید متوجه شده باشید، به هرحال آن حلقه bash بالا برای مجموعه بزرگ دادهها خیلی کُند است. بنابراین چطور موردی پیدا کنیم که بتواند رشتههای لفظی را تعویض نماید؟ خوب، میتوانستید AWK را به کار ببرید. تابع ذیل با خواندن از ورودی استاندارد و نوشتن در خروجی استاندارد، تمام نمونههای STR را با REP تعویض میکند.
# gsub_literal STR REP : نحوه استفاده # stdout و نوشتن در stdin تعویض میکند با خواندن از REP را با STR تمام نمونههای gsub_literal() { # نمیتواند خالی باشد STR [[ $1 ]] || return # string manip needed to escape '\'s, so awk doesn't expand '\n' and such awk -v str="${1//\\/\\\\}" -v rep="${2//\\/\\\\}" ' # طول رشته جستجو را به دست میآورد BEGIN { len = length(str); } { # رشته خروجی تهی out = ""; # ادامه حلقه تا موقعی که رشته جستجو در سطر هست while (i = index($0, str)) { # پیوست کردن هر چیز قبل از رشته جستجو و رشته جایگزین out = out substr($0, 1, i-1) rep; # حذف اولین رشته جستجو و هر چیز قبل از آن از سطر $0 = substr($0, i + len); } # پیوست نمودن آنچه باقی مانده است out = out $0; print out; } ' } some_command | gsub_literal "$search" "$rep" # :خلاصه شده در یک سطر some_command | awk -v s="${search//\\/\\\\}" -v r="${rep//\\/\\\\}" 'BEGIN {l=length(s)} {o="";while (i=index($0, s)) {o=o substr($0,1,i-1) r; $0=substr($0,i+l)} print o $0}'
پرسش و پاسخ 21 (آخرین ویرایش 2012-08-12 19:11:59 توسط e36freak)