このページは福井県立大学の田中求之が2006年1月まで運用していた Mac のサーバ運用に関する会議室 「Web Scripter's Meeting」の記録です。情報が古くなっている可能性がありますのでご注意ください。

Applescriptで文字の検索置換をするとMacがフリーズする!?

発言者:yabuki
( Date Friday, April 21, 2000 11:51:37 )


いつもお世話になっております。

Applescriptで文字列の検索置換の方法などを、いろいろなHPで拝見します。
その方法は

set text item delimiters of AppleScript to "検索する文字列"
    set myData to every text item of myData
    set text item delimiters of AppleScript to "置換する文字列"
    set myData to myData as string
--myDataはテキストから読み出したもの

というのが一般的なようです。

これを用いて作ったドロップレットで、例えば"@"という文字列を取り除きたい場合に
myDataに"@"が3000個存在する場合は問題なく処理します。しかし、6000個の場合は
間違いなくMacがフリーズします。テキストの文字数やバイト数の問題でもなさそうだし、
テキストの読み出し、書き込みのところでもなさそうです。
やはりデリミタのところでフリーズします。デリミタで文字列のリスト型
({aaa,bbb,ccc})みたいにする時のテキスト項目区切りの制限があるのかな?
それ以外でもテキスト項目区切りにしたものにカウンター(検索して何個目)をつけて
検索置換をおこなうと、たった3個しかないのにフリーズしました。
文字の検索置換のスクリプトを作ろうとして、Macがフリーズするスクリプトを
作ってしまった。<T-T>
このような事を経験されて、解決方法を知っている人がいましたら
教えて下さい。お願い致します。

稲垣 さんからのコメント
( Friday, April 21, 2000 12:55:44 )

 多分、リスト要素の制限か何かで引っ掛かっているかもしれないですね。

 もし、標準環境以外になってもいいのであれば、Tanaka's OSAXを使うの
が非常に便利ですね。

 置換のコマンドがありますので、一行で終わってしまいますよ。

 以下のサンプルは、Tanka's OSAX 2.xを利用しています。

set myData to MT Replace myData search "検索する文字列" replace "置換する文字列"


yabuki さんからのコメント
( Friday, April 21, 2000 15:36:18 )

稲垣さん、ありがとうございます。
早速 Tanaka's OSAX で記述し直したところフリーズしませんでした。

> set myData to MT Replace myData search "検索する文字列" replace "置換する文字列"

上記の記述ではデリミタみたいにテキスト項目区切りのリストを作っていないようですが、
例えば置換する文字列の後に検索して何番目のものというカウンターを付けたり、
検索文字列のあいだにこのカウンターを挿入したりする場合には
どのようにすれば良いのでしょうか?(例えばabcを検索してab1cとかにする)
デリミタを使った時はリストにできたのでいろいろ加工する事ができたのですが...

また
> 多分、リスト要素の制限か何かで引っ掛かっているかもしれないですね。
これってなんのことですか?

自分のMac以外でもデリミタで置換するスクリプトを実行するとフリーズを起こします。
Applescriptの設定に何かあるのですか?
そこのところをお手数ですが、判りやすくご教授頂けたらと思います。

田中求之 さんからのコメント
( Friday, April 21, 2000 17:32:47 )

AppleScript では、項目数が多いリストを処理させると処理速度が遅くなって
しまうことがあります。稲垣さんがおっしゃっているのはこのことでは
ないかと思います。

>検索文字列のあいだにこのカウンターを挿入したりする場合には
>どのようにすれば良いのでしょうか?

MT Replace を使う場合にはできません。一括置換を行うだけのコマンド
ですから。その意味では、スクリプトで処理されたほうがよいと思います。

フリーズしたというのはどのようなスクリプトなのでしょうか?

yabuki さんからのコメント
( Friday, April 21, 2000 18:36:32 )

田中さん、本当にいつもお世話になります。

下記のようなスクリプトです。

(display dialog "テキストを選択し、検索する文字とカウンターの始まりの数を入力すると置換する文字(<a name=></a>)の9文字目にカウンターを入れながら置換してくれるスクリプトです。
例)カウンターが1の時、結果は<a name=\"1\"></a>となります。" buttons {"OK", "キャンセル"} ツ
  default button 1)
set selectfile to (choose file with prompt "文字の検索置換をしたいファイルを選択して下さい。")
open for access file (selectfile as string) with write permission
set CountByte to (get eof file (selectfile as string))
set targetStr to (read file (selectfile as string) as string to CountByte)
close access file (selectfile as string)
set inputTEXT1 to (display dialog "検索したい文字を入力して下さい。" default answer "" buttons {"OK", "キャンセル"} ツ
  default button 1)
set findStr to text returned of inputTEXT1
set beforeTEXT to "<a name=\"" as string
set afterTEXT to "\"></a>" as string
repeat
  set inputTEXT2 to text returned of (display dialog "カウンターの数を入力して下さい。" default answer "" buttons {"OK", "キャンセル"} ツ
    default button 1)
  try
    set inputTEXT2 to inputTEXT2 as integer
    if (inputTEXT2 > 0 and inputTEXT2 < 100000001) then
      set inputTEXT2 to inputTEXT2 as integer
      exit repeat
    else
      display dialog "入力できる範囲は1〜1億です。また、なにも入力しないことはできません。もう一度入力して下さい。"
    end if
  on error
    display dialog "入力できる範囲は1〜1億です。また、なにも入力しないことはできません。もう一度入力して下さい。"
  end try
end repeat
set text item delimiters of AppleScript to findStr
set targetStr to every text item of targetStr
set NumList to {}
set CurNum to 0
set Counter to (inputTEXT2 - 1)
set Text_Total to ""
repeat with i in targetStr --iとは置換しない文字です(リスト型)
  set ii to (i as string)
  set ii_Length to (get length of ii) --置換しない文字列の長さ
  if (ii_Length > 7) then
    --check1とは文字列"○</font>○○○..."の</font>がある条件の位置に存在しているかのチェック(前の○は必ず一文字の条件です)  
    set check1 to (characters from character 2 to 8 of ii) --条件の位置の文字列
    set text item delimiters of AppleScript to findStr
    set check1 to every text item of check1
    set text item delimiters of AppleScript to ""
    set check1 to check1 as string
    if (check1 = "</font>") then --条件の位置の文字列が</font>であるかのチェック
      set check2 to (character 1 of ii) --</font>の前にある一文字を取得する
      set CurNum to CurNum + (count character of (i as string)) + 1 --検索する文字列の位置
      set NumList to NumList & CurNum
      set Counter to Counter + 1 --置換する文字の回数をカウント(最後に1回多くカウントします)
      set iiNum to (count character of ii)
      set di to (characters from character 9 to (iiNum) of ii) --○</font>(8文字)から@の前までの文字列を取得する
      set di to di as string
      set i to (check2 & di & beforeTEXT & Counter & afterTEXT) --i自身を変更したい形に作りかえる
      set Text_Total to Text_Total & i --置換するたびに文字列を加えていく
    else
      set CurNum to CurNum + (count character of (i as string)) + 1 --検索する文字列の位置
      set NumList to NumList & CurNum
      set Counter to Counter + 1 --置換する文字の回数をカウント(最後に1回多くカウントします)
      set i to (i & beforeTEXT & Counter & afterTEXT) --i自身を変更したい形に作りかえる
      set Text_Total to Text_Total & i --置換するたびに文字列を加えていく
    end if
  else
    set CurNum to CurNum + (count character of (i as string)) + 1 --検索する文字列の位置
    set NumList to NumList & CurNum
    set Counter to Counter + 1 --置換する文字の回数をカウント(最後に1回多くカウントします)
    set i to (i & beforeTEXT & Counter & afterTEXT) --i自身を変更したい形に作りかえる
    set Text_Total to Text_Total & i --置換するたびに文字列を加えていく
  end if
end repeat
set Counter to Counter as string
set beforeTEXT_Length to length of beforeTEXT
set afterTEXT_Length to length of afterTEXT
set Counter_Length to length of Counter
set findNum to (count character of Text_Total)
--置き換える文字・カウンターの長さやテキストの合計文字数を求め、最後のカウンター分の文字を削ります(最後に1回多くカウントするため)
set Text_Total to (get characters 1 thru (findNum - (beforeTEXT_Length + Counter_Length + afterTEXT_Length)) of Text_Total)
set text item delimiters of AppleScript to findStr
set Text_Total to every text item of Text_Total
set text item delimiters of AppleScript to ""
set Text_Total to Text_Total as string
set eof file (selectfile as string) to 0
write Text_Total to file (selectfile as string)

長くなって、申し訳有りません。
具体的にいいますと

本文<FONT COLOR=red>栄</FONT>本文---検索文字列は <FONT COLOR=red>
   ↓
本文<a name="1"></a>栄本文---置換後の結果

というものです。

何が原因なのかどうしても判りません。宜しくお願いします。

田中求之 さんからのコメント
( Friday, April 21, 2000 22:09:24 )

このスクリプトが、かならずフリーズするのですか? それとも、
処理数が増えたときにフリーズするのですか?

repeat の中でリファレンスの i を書き直しているのが気になるのと、
テキストの連結を延々とやっているのが気にはなりますが…
(細かなところまでは見てません)

田中求之 さんからのコメント
( Friday, April 21, 2000 23:59:00 )

冗長と思われる部分を書き改め、文字列の連結をリストで行うようにした
スクリプトでテストを行ってみたところ、置換箇所が多くなると、

set targetStr to every text item of targetStr

の部分で、「スタックがあふれました」というエラーになりますね。
(Stack overflow のことでしょう)

これは、スクリプト側ではなんともしようがないですね(MacOS 8.6
なんで、Stack が動的に増やされるようになったと思っていたんですが
そうじゃないみたいです)。

ということで、根本的な解決方法は、別のアプローチをとるということ
だと思います。

yabuki さんからのコメント
( Monday, April 24, 2000 10:23:01 )

いつもレスが遅くてすみません。
いろいろ調べて下さいまして、本当にありがとうございます。

>このスクリプトが、かならずフリーズするのですか? それとも、
>処理数が増えたときにフリーズするのですか?

検索置換する文字列の数が多いとフリーズしました。
(具体的に言うと検索置換する文字列が1バイト文字だと
3000個は問題なく処理をしました。しかし、6000個だと固まりました。)

また、<FONT COLOR=red>を検索して、<a name="1">こんな形に置換する場合
たった3個しかなくてもフリーズする物があります。(もちろん処理する物もある)

処理がうまくいったテキストの文字数・バイト数は3988文字・7442バイトでした。
処理がうまくいかなかったテキストの文字数・バイト数は4223文字・7909バイトでした。
検索置換する文字列の数はどちらも3個です。

この事からデリミタのリストの文字列の文字数もフリーズする原因と考えられます。
この事が「Stack overflow」 というエラーなのですかね。
ちなみに、OS7.5.5ではエラーメッセージなどでないでフリーズします。

>これは、スクリプト側ではなんともしようがないですね(MacOS 8.6
>なんで、Stack が動的に増やされるようになったと思っていたんですが
>そうじゃないみたいです)。
>
>ということで、根本的な解決方法は、別のアプローチをとるということ
>だと思います。

applescriptで文字列の検索置換する際にカウンターを付けるという事は
無理なのでしょうか?<T-T>

検索置換だけならば、稲垣さんがコメントされたようにTanka's OSAXで
問題なくできるのに...

田中さん、何か良い方法がありましたら、またご教授願えないでしょうか。
よろしくお願いいたします。

野本夏俊 さんからのコメント
( Monday, April 24, 2000 14:12:05 )

>(具体的に言うと検索置換する文字列が1バイト文字だと
>3000個は問題なく処理をしました。しかし、6000個だと固まりました。)

テキストをリストに分解する場合の限界値は4000項目程度です。
text itemsに限らず、 characters, words等でも同様です。
一度に処理する分量を抑える以外に回避方法はありません。
MT Replace では文字ではなく、バイト単位で検索置換しますので、
MT Replace "ァ" search "@" replace "x"
  →"ベ"
となります。注意が必要です。

yabuki さんからのコメント
( Monday, April 24, 2000 16:04:09 )

野本さん、コメントありがとうごさいます。

>MT Replace では文字ではなく、バイト単位で検索置換しますので、
>MT Replace "ァ" search "@" replace "x"
>  →"ベ"

この記述の仕方は使った事がないのですが、この注意とはASCIIコードのことですね。
使った事がないのであまり判らないのですが、どのような場合にこの記述が必要に
なってくるのですか?(1バイト文字と2バイト文字が混在するテキストを検索置換する場合、
この記述は使えないと思うのですが...)

> テキストをリストに分解する場合の限界値は4000項目程度です。

限界値は4000ですか。(この限界値がどれくらいなのかが判らなかったので
とても助かる情報です)
ひとつ判らないことがあるのですが、デリミタを使って検索置換する時に、
テキスト区切り項目が3000個の場合(文字数は36599)処理し、
6000個(文字数は37171)の時はダメという事は
今の説明で判るのですが、デリミタでテキスト区切りにする文字列が3個しかなくて
フリーズするというのはどういう事なのでしょうか?
(区切りが3個なので、リストは4項目のはず)

ちなみにこのフリーズするテキストは4223文字で4000を超えていますが、
どっかでテキストの文字列をテキスト区切り項目にしているのかな?
↑これが一番あやしいかも(^_^;;)

とりあえず文字数4000(限界値!?)を超えないようにチェックしてから
スクリプトを使うようにします。
ありがとうございます。

MARo さんからのコメント
( Monday, April 24, 2000 16:19:31 )

AppleScript自体で処理を行うと問題が出るのであれば、他のアプリに任せる
というのはどうでしょうか?

とはいえ、テキストエディタで処理していたのでは数十Mバイト、数百Mバイト
級のデータには対応しづらいと思います。

Mac用のGUIベースフィルタ「Filter Top」という愛用のフリーソフトがあります
このような用途には非常に適しており、かつ、大規模なデータの取り扱いも
幾度も行っていますので(サーバーのログ管理など)、お試しを。

もちろん、スクリプタブルなアプリケーションです。

→  FilterTopの説明ページ(日本語)

yabuki さんからのコメント
( Monday, April 24, 2000 17:07:58 )

>ちなみにこのフリーズするテキストは4223文字で4000を超えていますが、
>どっかでテキストの文字列をテキスト区切り項目にしているのかな?
>↑これが一番あやしいかも(^_^;;)

自分でコメントしておいてなんですが、デリミタのところではなく
検索置換し終えたテキスト(4000文字以上)の最後のところに置換された文字列が
1つ余計にできるので、

set テキスト to (get characters 1 thru "余計な文字列を削った文字数") of テキスト)

なんて事をしてフリーズしているのが判りました。
(余計な文字列を削った文字数>4000)
これも野本さんのヒントのおかげです。改めてお礼をいいます。

>Mac用のGUIベースフィルタ「Filter Top」という愛用のフリーソフトがあります
>このような用途には非常に適しており、かつ、大規模なデータの取り扱いも
>幾度も行っていますので(サーバーのログ管理など)、お試しを。

MARoさんコメントありがとうございます。
かなり便利なフリーソフトのようなので何かあったら、使わせて貰います。
(ちょっと残念なのは、つい最近ファイル名を変更するスクリプトを作ってしまったので
その前ならかなり重宝しただろうに...)

野本夏俊 さんからのコメント
( Monday, April 24, 2000 20:50:27 )

>      set di to (characters from character 9 to (iiNum) of ii) --○</font>(8文字)から@の前までの文字列を取得する
>      set di to di as string

は
set di to text 9 thru iiNum of ii
と書きます。

MT Replace がどういう処理をしているのかは僕の書いたことを参考に考えてみてください。

yabuki さんからのコメント
( Tuesday, April 25, 2000 17:11:46 )

野本さん、コメントありがとうございます。

>set di to text 9 thru iiNum of ii
>と書きます。

記述し直しました。処理速度が格段にアップしました。(^_^;;)
記述の仕方ひとつでかなり違ってくるものですね。
(私は以前の記述の方法しか知らなかったので、勉強になりました。)

AppleScript --- Tipsに処理速度を向上させる事が書かれていまして
(内容は私にはまだ難しいものでしたが...)いろいろと記述の方法を
考えて作らないといけないなぁと今頃思いました。(^_^;;)

また、初歩的な質問をしてしまうかも知れませんが、
その時はご教授願えたらと思います。