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

アプレットとドロップレットの違いによるエラー?(改行付き)

発言者:yabuki
( Date Monday, April 03, 2000 11:19:40 )


すいません。改行を入れるのを忘れました。(初心者なもので)

いつも拝見して勉強させて貰っています。

最近、Applescriptでフォルダ内の全てのファイル・フォルダ名を名前ごとに改行付きで、
テキスト書き出しするものを作りました。これはドロップレットで、
フォルダをドラッグ&ドロップするとテキスト書き出しするものでした。

これを作っている時に気付いたことは
フォルダ内のフォルダを見る時に書き出しのテキストのバイト数をチェック
(ようは書き出しテキストのeof取得)していたものが、0バイトに戻ってしまい、
テキストの先頭からファイル名を書き込みしてしまう点でした。
この問題はフォルダに移行する時にもう一度
書き出しテキストのeof取得をすることで解消しました。

しかし、これをドロップレットではなくダイアログでフォルダを選択するアプレットにすると、
フォルダ内のフォルダを見る所で変な動きをしてしまいます。

つまり、フォルダ内のフォルダを隅々まで見てファイル名を書き出してはくれるのですが、
フォルダに移行する時にeof取得をしてくれなくなりました。

on open ○○
---
end open

という形式のドロップレットを

set ○○ to (choose folder with prompt "フォルダを選択してください。")

というものにしただけで、同じものなのですが...?
これはバグなのかな。ご存じの方いらっしゃいましたら、教えて下さい。

田中求之 さんからのコメント
( Monday, April 03, 2000 14:03:53 )

基本的には、ドロップレットと、choose による処理で動作が代わるという
ことはないはずです。

スクリプトで再帰呼び出しをやってないか? グローバル変数やプロパティ
を使っていないか、などの具体的な処理の書き方によっては、動作が異なる
ことも考えられます。

特にプロパティに記録を残しておくようなものは、スクリプトエディタで
実行したときには、実行の度にプロパティが初期化されますので、うまく
いかないはずです。

田中求之 さんからのコメント
( Monday, April 03, 2000 14:58:53 )

もし短いスクリプトなのでしたら、スクリプトを見せていただければ
問題はわかるのではないかと思います。

yabuki さんからのコメント
( Monday, April 03, 2000 17:40:18 )

こんなスクリプトです。

set FirstObject to (choose folder with prompt "フォルダを選んで下さい")
tell application "Finder" to set SaveFolder to preferences folder as string
tell application "Finder"
  if file (SaveFolder & "Filelist.txt" as string) exists then
    open for access file (SaveFolder & "Filelist.txt" as string) with write permission
    set eof of file (SaveFolder & "Filelist.txt" as string) to 0
    close access file (SaveFolder & "Filelist.txt" as string)
  end if
end tell
set Names_File to SaveFolder & "Filelist.txt" as string
tell application "Finder"
  set CountByte to 0 as integer
  set text item delimiters of AppleScript to ":"
  set thePath0 to FirstObject as string
  if ((last character of thePath0) = ":") then
    set leg to ((length of thePath0) - 1)
    set thePath0 to (get characters 1 thru leg of thePath0)
    set text item delimiters of AppleScript to ","
    set thePath0 to every text item of thePath0
    set text item delimiters of AppleScript to ""
    set thePath0 to thePath0 as string
    set text item delimiters of AppleScript to ":"
    set thePath0 to thePath0 as string
    set theFolderName to last text item of thePath0
    repeat with thePath1 in FirstObject
      my DiveFolder(thePath1, Names_File, CountByte)
    end repeat
  else
    set FileName to last text item of thePath0
    open for access file (Names_File as string) with write permission
    write FileName & (return as string) to file (Names_File as string)
    set CountByte to (get eof file (Names_File as string))
    close access file (Names_File as string)
    display dialog CountByte
  end if
end tell

on DiveFolder(thePath1, Names_File, CountByte)
  tell application "Finder"
    set theFiles to every file of thePath1
    repeat with aFile in theFiles
      set theFileName to (name of aFile) as string
      display dialog ("「" & theFileName & "」はファイルです。") 
-- ファイル名
      display dialog (CountByte & "から書き込みます。" as string) 
-- テキストに書き込む先頭のバイト数
      open for access file (Names_File as string) with write permission
      write theFileName & (return as string) starting at CountByte + 1 to file (Names_File as string)
      set CountByte to (get eof file (Names_File as string))
      close access file (Names_File as string)
    end repeat
    
    set theFolders to every folder of thePath1
    repeat with aFolder in theFolders
      open for access file (Names_File as string) with write permission
      set CountByte to (get eof file (Names_File as string))
      close access file (Names_File as string)
      display dialog CountByte & "(フォルダに移行)" as string 
-- フォルダを見ている時の取得バイト数
      set theFolderName to ((name of aFolder) as string)
      display dialog ("「" & theFolderName & "」はフォルダです。")
 -- フォルダ名
      open for access file (Names_File as string) with write permission
      write theFolderName & (return as string) starting at CountByte + 1 to file (Names_File as string)
      set CountByte to (get eof file (Names_File as string))
      close access file (Names_File as string)
      DiveFolder(aFolder, Names_File, CountByte) of me 
-- 自分自身を呼び出している
    end repeat
  end tell
end DiveFolder

長くて申し訳有りません。
このスクリプトの先頭を on open FirstObject で始め、
end tell の部分を end open にすると問題なく動きます。
また、あとで気付いたのですが、選択するフォルダを新しく作ったフォルダ内に入れ、
このスクリプトで新しく作ったフォルダを選択すると問題なく動きました?

田中求之 さんからのコメント
( Monday, April 03, 2000 18:46:00 )

再帰呼び出しの部分が問題を起こしていると思います。DriveFolder から
あらたに DriveFolder が呼ばれた、その処理が終了したときに、countbyte
がどうなっているかわかりますか? ハンドラーの中でパラメータに変更
を加えても、もとの変数(ハンドラーを呼びだした方)の値は代わりません。以下のスクリプトを実行したとき、--- ! の時点で x がいくつになって
いるか分かりますか? 2 のままですよ。

set x to 2
addTen(x)
--- !

on addTen(x)
  set x to x +10
end addTen

ですので、スクリプトを問題なく動かすためには、countByte に頼るのを
やめて、ファイルに追記する処理をハンドラとして独立させておくのが
よいでしょう。

on appendToFile(Names_File, myData)
  set myRef to open for access file Names_File with write permission
  set myEof to (get eof myRef)
  write myData starting at myEof + 1 to myRef
  close access myRef
end appendToFile

というようなハンドラーを作っておけば、Names_File の末尾に myData を
追記しますので、DiveFolder ハンドラは

on DiveFolder(thePath1)
  tell application "Finder"
    set theFiles to every file of thePath1
    repeat with aFile in theFiles
      set theFileName to (name of aFile) as string
      my appendToFile(Names_File,theFileName & return)
    end repeat
    
    set theFolders to every folder of thePath1
    repeat with aFolder in theFolders
      set theFolderName to ((name of aFolder) as string)
      my appendToFile(Names_File,theFolderName & return)
      my DiveFolder(aFolder)
    end repeat
  end tell
end DiveFolder

とすっきりさせることができます(display dialog は削除)

田中求之 さんからのコメント
( Monday, April 03, 2000 18:48:06 )

なお、スクリプトの冒頭部分は何を行っているのかよくわからないので、
とりあえず、DiveFolder の問題点だけ指摘しておきました。

田中求之 さんからのコメント
( Tuesday, April 04, 2000 02:05:39 )

余計なおせっかいですが、ファイルの一覧をえたり、ファイル名を調べる
のに Finder を使うと処理速度が遅いですから、info for や list folder
といった osax (後者は MacOS 8.5 以降にしか入ってないと思います)を
使うと良いと思います。

ドロップしたフォルダのフォルダ及びファイルの一覧を作成する(ダブルク
リックしたり、スクリプトエディタで実行したときには、ダイアログで選ん
だフォルダのファイルの一覧)スクリプトは、以下のようになると思います。
(ファイルの開け閉めを少なくすることで高速化を図っています)

property saveFile : ""
property myRef : 0

on run
  set myFldr to choose folder
  open (myFldr as list)
end run

on open (f)
  set saveFile to ((path to preferences) as string) & "Filelist.txt"
  
  set myRef to open for access file saveFile with write permission
  set eof of myRef to 0
  
  repeat with thisItem in f
    set myInfo to info for thisItem
    set myName to name of myInfo
    appendToFile(myName & return)
    if folder of myInfo then
      DiveFolder(thisItem as string)
    end if
  end repeat
  
  close access myRef
  
end open

on DiveFolder(tgFldr)
  set myList to list folder file tgFldr without invisibles
  repeat with thisItem in myList
    my appendToFile(thisItem & return)
    set tgInfo to info for (file (tgFldr & thisItem))
    if folder of tgInfo then
      DiveFolder(tgFldr & thisItem & ":")
    end if
  end repeat
end DiveFolder

on appendToFile(myData)
  set myEof to (get eof myRef)
  write myData starting at (myEof + 1) to myRef
end appendToFile

参考になれば幸いです

田中求之 さんからのコメント
( Tuesday, April 04, 2000 02:08:43 )

あ、ファイルを開けっ放しにしてるときには、いちいち eof を得て書き込み
位置を調整する必要はなかったですね (^_^;;

on appendToFile(myData)
  write myData to myRef
end appendToFile

だけでうまくいきますね。

yabuki さんからのコメント
( Tuesday, April 04, 2000 11:43:14 )

返事が遅れてすみませんでした。

田中さんに記述してもらったスクリプトを勉強しよう思い、
記述されたものをコピーして、構文確認をしたところ

my appendToFile(thisItem & return)

のところで、ひっかかってしまいました。
これはMacOSの違いのせいか?もしくはTanaka's osaxが古いものなのか?

とても参考になるスクリプトを記述して頂いて、たいへん恐縮なのですが...
これでは、スクリプトの動きが掴めません。何が原因なのか教えて頂けないでしょうか?

使っているOSは7.5.5で、Tanaka's osax 2.0を使わせてもらっています。

田中求之 さんからのコメント
( Tuesday, April 04, 2000 13:15:38 )

>使っているOSは7.5.5

あ、すみません。先のサンプルは、MacOS 8.5 以降でしか動かないスクリ
プトになってます(list folder コマンドは 7.5 の時点ではなかったはず
です)。

なお、スクリプトは Tanaka's osax は使用していません。純正のセットだけで
書いたスクリプトになっています。

7.5.5 の場合には、最初に yabuki さんが書かれていたような、Finder を
利用したスクリプトに変更しなければならないと思います。今は出張で時間が
とれないので、必要なら今週末にでも変更したスクリプトを載せます。
(どなたか、代わりにフォローしていただけるとありがたいのですが)

田中求之 さんからのコメント
( Saturday, April 08, 2000 14:56:29 )

修正版です。たぶん、7.5 でも動くと思います。

property myRef : 0

on run
  set myFldr to choose folder
  open (myFldr as list)
end run

on open (f)
  -- 記録用ファイルを開いて初期化する
  -- myRef にはファイルリファレンスが記録されるので、書き込みではこれを用いる
  tell application "Finder"
    set saveFile to (preferences folder as string) & "Filelist.txt"
  end tell
  set myRef to open for access file saveFile with write permission
  set eof of myRef to 0
  
  -- ドロップされたファイル/フォルダーを順に処理する
  -- ドロップレットの場合は、複数のアイテムのドロップに備える必要がある
  repeat with thisItem in f
    set myInfo to info for thisItem
    set myName to name of myInfo
    write (myName & return) to myRef
    if folder of myInfo then
      --  フォルダーの場合は DiveFolder ハンドラで処理
      DiveFolder(thisItem as string)
    end if
  end repeat
  
  -- 記録用ファイルを閉じる
  close access myRef
  
end open

on DiveFolder(tgFldr)
  -- フォルダーの中のファイル名一覧を書き出す
  
  -- ファイル名一覧
  try
    tell application "Finder"
      set myList to (name of files in folder tgFldr) as list
    end tell
    repeat with thisItem in myList
      write (thisItem & return) to myRef
    end repeat
  on error
    -- ファイルは存在しない
  end try
  
  -- フォルダ一覧
  try
    tell application "Finder"
      set myList to (name of folders in folder tgFldr) as list
    end tell
    repeat with thisItem in myList
      write (thisItem & return) to myRef
      DiveFolder(tgFldr & thisItem & ":")
    end repeat
  on error
    -- フォルダーは存在しない
  end try
  
end DiveFolder

yabuki さんからのコメント
( Monday, April 10, 2000 10:36:54 )

田中さん、問題なく動きました。
フォルダを選択する場合とドロップレットとして使う場合(しかも複数の処理可能)に
対応できて、本当に助かります。
また、分かりやすい解説文を書いて頂き、大変参考になりました。
ありがとうごさいました。

この話とは全然関係ないですが、アプレット1から別のアプレット2を処理させて、
その結果を用いてアプレット1が処理するという物
(複数のアプレットを使う物は初めて)を作っているのですが、
アプレット2が処理を終える前にアプレット1が処理を始めてしまい問題が起きます。
いまは、とりあえずアプレット1にダイアログの表示を入れ、
アプレット2の処理が終わるのを待っている事でしのいでいます。

サブスクリプトの終了(ファイルを閉じている)のステータスを条件文として
記述したいのですが、そのようなものがAppleScriptにありますか?
もしご存じでしたら、またご教授頂きたいのですが、お願い致します。

田中求之 さんからのコメント
( Monday, April 10, 2000 18:12:04 )

>アプレット2が処理を終える前にアプレット1が処理を始めてしまい問題が>起きます。

アプレット2をどのように呼びだしていますか? 単純に run とか
launch で起動している場合には、おっしゃるようなことが起きますが、
アプレット2の中のハンドラーを呼ぶようにしておけば、リプライが
返ってくるまで1は停止しますよ。

ただし、この方法を有効にするには、アプレット2は「実行後に終了しない」
ものにしておき、かつ、処理の内容を run ハンドラとは別の独立したハンドラ
にしておく必要がありますが。

yabuki さんからのコメント
( Tuesday, April 11, 2000 15:24:38 )

>アプレット2をどのように呼びだしていますか? 単純に run とか
>launch で起動している場合には、おっしゃるようなことが起きますが、
>アプレット2の中のハンドラーを呼ぶようにしておけば、リプライが
>返ってくるまで1は停止しますよ。

下記のように記述しています。

open selection using file (FilePath as string)
--FilePathはアプレット2のファイルパスです。

アプレット2は独自にある処理をしてくれるもので、
それをアプレット1に上記のような記述をして動かしています。
何か根本的に間違った記述をしていますかね?

田中求之 さんからのコメント
( Tuesday, April 11, 2000 15:51:13 )

間違ってはいませんが、連携プレーをさせるには、他の方法がよいと思います。
以下に簡単なサンプルを示します。アプレット1はアプレット2を呼びだして、
アプレット2に指定したファイルを Finder で表示させるという処理を行わせます。

アプレット1のスクリプト:
on run
  -- 処理するファイルの選択
  set myFile to choose file
  
  -- myApp2 を使って、Finder 上で表示・選択させる  
  tell application "myApp2"
    showThis(myFile)
  end tell
  
  -- myApp2 の実行結果を元に情報表示を行う
  tell application "Finder"
    open information window of selection
  end tell
  
  -- myApp2 を終了する
  tell application "myApp2" to quit
end run

アプレット2(名前は myApp2 )のスクリプト。このスクリプトを
アプリケーションとして保存する際には、「実行後、自動的に終了
しない」と「初期画面を表示しない」の2つのオプションにチェック
を入れる必要がある。

on showThis(tgFile)
  tell application "Finder"
    activate
    reveal tgFile
  end tell
end showThis

こういった感じで、アプレット2のハンドラーを呼びだす形にしておけば、
ハンドラーの処理が終わるまでアプレット1の実行は停止しますので、
連携プレーが可能になります。

yabuki さんからのコメント
( Thursday, April 13, 2000 12:56:45 )

レスが遅くなりましてすみませんでした。
記述して頂いたサンプルスクリプトを基に、自分のアプレットを改良してみて
うまくいきました。本当にありがとうございました。
また、大変勉強になりました。