관리 메뉴에 들어가면 "모든 페이지의 문자열 일괄 치환" 메뉴가 있고 여기에 들어가면 아래와 같은 화면이 나온다.
![]() |
쉽게 알 수 있지만 "morning"이라는 문자열을 찾아서 "evening"으로 고치라는 예이다.
"Just test"가 체크되어 있는 경우는, 실제 저장은 하지 않고 테스트만 한다. 실수를 막기 위해서 기본적으로 체크되어 있다.
"replace" 버튼을 누르면 아래와 같이, 문자열이 있는 페이지들을 찾아서 수정해주며, 페이지 이름(첫번째 빨간 네모), 그 페이지에서 해당되는 문자열의 갯수(두번째 빨간 네모), 실제 변경되는 내용(빨간 동그라미)을 보여준다. 아래 스샷의 경우는 테스트이기 때문에 실제로 저장되지는 않는다.
![]() |
각각의 페이지는 minor edit 를 한 것으로 취급된다.
$newText =~ s/$oldStr/$newStr/g;
따라서 각각의 문자열은 Perl/정규표현식의 적용을 받기 때문에, 특수 문자(대표적으로 "/", "?", "*", ".", 등등)가 들어가면 예상하지 못한 동작을 하거나, 서버 에러가 날 수 있다. -_-;; 반대로 잘 쓰면 아주 편리하게 치환을 할 수도 있겠다. 그런데 막상 해보니 펄 코드에 직접 문자열을 적은 것과는 또 다르게 동작을 한다. 이유는 주인장도 모르겠음.
예를 들어서, 치환 코드에서 쓰이는 구분자가 "/"이기 때문에, "2/3"을 "5/6"으로 치환하려면 슬래쉬를 그대로 쓸수가 없고 "\/"처럼 넣어야 한다. $newText =~ s/2\/3/5\/6/g; 그런데, 위키에서 "2\/3" "5\/6"을 넣으면 "2/3"이 "5\/6"으로 치환되어 버린다. -_-; New string에는 "5/6"이라고 그냥 슬래쉬만 적어주어야 한다.
따라서 특수 문자가 없는 일반적인 영문이나 한글 문자열을 고치는 데는 문제가 없지만, URL이나 프로그램 코드 등을 치환할 때는 주의를 해야 한다.
게다가, 한번 바꾸면 "일괄 undo" 같은 건 없다. -_-;; 그러니 사용할 때 매우 주의를 요함. 두 시간 정도를 더 들여서 테스트 기능과 diff보기 기능을 넣은 이유도, 다수의 페이지가 잘못 치환된 경우를 상상만해도 끔찍하기 때문이다2.
"<p>".&ScriptLink("action=unlock",T('Removing edit lock')). "<p>".&ScriptLink("action=replacetext",T('Replace strings in all pages')). # 이 줄 추가 "\n";
# replacetext action # 일괄치환 use strict; sub action_replacetext { print &GetHeader("", T('Replace strings in all pages'), ""); return if (!&UserIsAdminOrError()); my ($oldStr, $newStr, $ignoreCase, $regular, $evaluate, $test); $oldStr = &GetParam("old", ""); $newStr = &GetParam("new", ""); $ignoreCase = &GetParam("p_ignore", "0"); $ignoreCase = "1" if ($ignoreCase eq "on"); $regular = &GetParam("p_regular", "0"); $regular = "1" if ($regular eq "on"); $evaluate = &GetParam("p_evaluate", "0"); $evaluate = "1" if ($evaluate eq "on"); $test = &GetParam("p_test", "0"); $test = "1" if ($test eq "on"); # 폼 출력 print &GetFormStart(); print &GetHiddenValue("action", "replacetext"),"\n"; print "<p><b>Old string:</b><br>\n"; print $q->textfield(-name=>"old",-size=>"100",-maxlength=>"255",-default=>"$oldStr"); print "<br>\n"; print $q->checkbox(-name=>"p_ignore", -override=>1, -checked=>$ignoreCase, -label=>T('Ignore case')); print $q->checkbox(-name=>"p_regular", -override=>1, -checked=>$regular, -label=>T('Use regular expression')); print "\n<p><b>New string:</b><br>\n"; print $q->textfield(-name=>"new",-size=>"100",-maxlength=>"255",-default=>"$newStr"). "\n"; print "<br>\n"; print $q->checkbox(-name=>"p_evaluate", -override=>1, -checked=>$evaluate, -label=>T('Evaluate')); print "<p>"; print $q->submit(-name=>'Replace'), "\n"; print $q->checkbox(-name=>"p_test", -override=>1, -checked=>1, -label=>T('Just test')); print $q->endform; # old string 값이 없는 경우. 제일 처음 불렸을 때 등 if ($oldStr eq '') { print &GetCommonFooter(); return; } if ($test) { print "<p>Just test ...<br>\n"; } else { print "<p>Search & replace ...<br>\n"; } my ($page, $num); $num = 0; $oldStr = "\Q$oldStr\E" if (not $regular); $oldStr = "(?i)$oldStr" if ($ignoreCase); foreach $page (&AllPagesList()) { # 모든 페이지 검사 &OpenPage($page); &OpenDefaultText(); my $newText = $Text{'text'}; my $match = 0; # 치환 시도 if ($evaluate) { $match = ($newText =~ s/$oldStr/$newStr/mgoee); } else { $match = ($newText =~ s/$oldStr/$newStr/mgo); } # 치환되는 것이 있는 경우 if ($newText ne $Text{'text'}) { $num++; print "[$num] Processing $page ... $match string(s) were found.<br>"; print &DiffToHTML(&GetDiff($Text{'text'}, $newText))."<br>"; if (!$test) { # 테스트 모드가 아닐 때는 저장 DoPostMain($newText, $page, "*", $Section{'ts'}, 0, 1, "!!"); } } } print "Completed."; print &GetCommonFooter(); } 1;
Replace strings in all pages 모든 페이지의 문자열 일괄 치환
ext2.1c - 정규표현식 사용 여부를 선택할 수 있게 함. 치환할 때 Evaluate 가능하게 함
"Evaluate" 기능이 진작에 있었으면 Nyxity님이 모놀로그 아카이브 페이지에 있는 매크로들을 일일이 찾아 바꿔주실 걸 좀 편하게 할 수 있게 할 수 있었을텐데... 담에 또 와르르 바꾸셔야 할 일 있으면 미리 말해주세요. :-)
Great functionality ... i added replace text for individual page:
...
<< $test = "1" if ($test eq "on");
# the next text is only for to save temporarily the variable $id
>> my ($id) = @_;
>> my $fname = "$DataDir/temp/replacetest";
>> if (!-f $fname || (-f $fname && -f &GetPageFile($id))) {&WriteStringToFile($fname, $id);}
>> ($status, $data) = &ReadFile($fname);
# end of save temporarily
...
<< $oldStr = "(?i)$oldStr" if ($ignoreCase);
>> foreach $page (&AllPagesList()) { # ¸ðµç ÆäÀÌÁö °Ë»ç
>> next if ($page ne $data);
I would like "save temporarily ..." on some way more clean ...
-- JuanmaMP
one line more:
<< print "Completed..";
>> if (!$test) {unlink $fname;}
(when it isn't test, unlink the temp. file).
I wonder if with the original sub is possible to obtain the variable $id after a test without make temp. file.
To be continued ... :)
--JuanmaMP
minor tweak:
+ if (!$num) {
+ print 'there is not matching';
+ }
print '
' . 'Completed ...';
What do "evaluate" do?. It says: ""Evaluate"에 체크하면 New string에 적힌 것을 Perl expression으로 해석하여 그 결과를 가지고 치환한다.
("Evaluate" New string when the check is written in Perl expression is interpreted as the substitution with the result.).
Evaluate must be check always that Use RegExp is checked too?
I don't get it ... :(
Thanks. --JuanmaMP
If checked, the value of "New String" field is interpreted as 'Perl expression' rather than simple text. This option is very similar to "eval" option of s///e operator.
Let me show some examples:
1) to replace "111" with "222"
Old string : 111
New string : 222
2) to replace "any sequence of digits" with "aaa"
Old string : \d+ -- this is regexp.
New string : aaa
check Use Regular Expression
3) to replace "any sequence of digits" with "same sequence in parentheses", that is, to enclose numbers with parentheses
Old string : \d+ -- this is regexp.
New string : "($&)" -- this is Perl expression containing a backreference $&.
check Use Regular Expression
check Evaluate
In case 3, aa11bb22 shall be aa(11)bb(22). If you don't check "Evaluate", it shall be aa"($&)"bb"($&).
Perfect with these threes examples.
Thanks. --JuanmaMP
suggestion: include "Edit Conflict", when start replace
I considered it, but it seemed to be difficult. And I couldn't imagine anyone can manage it if a conflict occurs while he/she is trying to replace several pages at once. :-) Anyway, your suggestion is right.
i am trying to improve mi newbie snippet about replace for single page. This is some help (INPUT forgets $id and this is the remember)
in gotobar, for example:
$result .= ' | ' . &ScriptLink("action=replace&id=$id", T("ReplaceIn"));
in action_replace:
@id = split ('&id=', $ENV{"HTTP_REFERER"});
foreach $page (&AllPagesList()) {
&OpenPage($page);
&OpenDefaultText();
next if ($id[1] && ($page ne $id[1]));
I wonder if "next if" for entire database is expensive load for perl.
Regards.
Thanks for the reply (I did not understand your response if the difficult is in script or is in the probability of occurrence on the users?).
Regards (again :))
I get it, difficult management for users ...
Yes, perfect and thanks (a good balance of pros and cons).
Another order of difficulty could be: Korean, English and Perl; of course, it depends of mother tongue ... :)
Hi Raymundo, how are you?
this interesting feature could choose one page or a whole site for the action "replace". Adding:
my $pagename;
$pagename = &GetParam("pagename", "");
print "
Page (blank for whole Site):
\n";
print $q->textfield(-name=>"old",-size=>"30%",-maxlength=>"255",- default=>"$pagename");
...
foreach $page (&AllPagesList()) { # 모든 페이지 검사
&OpenPage($page);
&OpenDefaultText();
if ($pagename) {
next if ($page ne $pagename);
}
...
HTH. 감사. --JuanmaMPKr
(More polished that the old proposal by me @ 2007-6-8 2:43).
감사.
it's not still a mature proposal, but if action=replace trough evaluate option offers inputs perl expressions (regexp, too?), maybe, it could implementing on action=search. Isn't it?
Wow!, I didn't notice about "*" feature.
You're right, that's a hole. That reduces the possibilities; then I wonder about a variant of search only for admins. We will see what we can do.
And thanks.
Hi Raymundo, nice to typing you; How do you do?
Replace can be extender with "wantword" utility, something like this.
(Only as simple suggestion).
- my ($oldStr, $newStr, $ignoreCase, $regular, $evaluate, $test);
+ my ($oldStr, $newStr, $ignoreCase, $wantWord, $regular, $evaluate, $test);
...
$ignoreCase = &GetParam("p_ignore", "0");
$ignoreCase = "1" if ($ignoreCase eq "on");
+ $wantWord = &GetParam("p_want", "0");
+ $wantWord = "1" if ($wantWord eq "on");
...
print '
' . $q->checkbox(-name=>"p_ignore", -override=>1, -checked=>$ignoreCase, -label=>' ' . T('Ignore case'));
+ print '
' . $q->checkbox(-name=>"p_want", -override=>1, -checked=>$wantWord, -label=>' ' . T('Want Word'));
...
$oldStr = "(?i)$oldStr" if ($ignoreCase);
+ $oldStr = "\\b$oldStr\\b" if ($wantWord);
Regards.
Hi Raymundo, one more year ... Best for 2013. Although I think "Seollal" is more important?
At least in my script, it seems required:
- $oldStr = "(?i)$oldStr" if ($ignoreCase);
+ $oldStr = "(?i)$oldStr" if (not $ignoreCase);
am I right?