AppleScriptやXojoからシェルに処理を投げると通らない場合の対処

　少し前に仕事マシンがM1 MBAに切り替わったワケですが、OSがmacOS 12 Montereyになったことで（以前からあるにはあったけどなんとかなってた）AppleScriptやXojoなどの別環境内からシェルに処理を投げた際に<strong>「ターミナルでは通っていた一部の処理が通らなくなる」</strong>現象が顕在化し、だいぶ頑張って解決したので数年後に同じことで困らないように記録を残しておきます。

<div style="padding-left:1em" class="clear">
<h3 style="border-bottom: medium solid gray">そもそもなんでそういう現象が起きるのか</h3>
　Macの「ターミナル」はbashやzshといった「シェル」を使うためのアプリです。Windowsで言うところのコマンドプロンプトですね。で、例えばOSに元々入っているバージョンとは違うPerlやPythonを使いたいとか、非標準モジュールを入れて使いたいといったような場合にはmacの場合はパッケージマネージャーの<a href="https://brew.sh/index_ja" rel="noopener" target="_blank"><strong>Homebrew</strong></a>や<a href="https://www.macports.org/" rel="noopener" target="_blank"><strong>MacPorts</strong></a>をまず入れてその後それを使ってさまざまな必要なものを入れていって環境を構築するわけですが、入れたものを有効にするために「パスを通す」作業が度々発生します。これは作業手順自体は検索すればいくらでもWebで見つかるのですが、要はシェル（bashやzsh）の環境設定ファイルである「.bash_profile」や「.zshrc」にシェルの起動時に自動実行する設定（環境変数の設定など）を記述しておく作業です。これをやることでいちいち実行ファイルのあるディレクトリに移動してからコマンドを実行するようなことをしないでも良くなるわけです。

ただ、「.bash_profile」や「.zshrc」への記述は、あくまでターミナルでシェルを起動した際に読み込まれる設定であるため、AppleScriptやXojo内からシェルに処理を投げた場合には適用されません。このため、ターミナルでは通っていた処理が通らなくなったりします。

<a href="http://densyodamasii.com/wp-content/uploads/2022/04/column86_1.png" rel=lightbox[column86]><img src="http://densyodamasii.com/wp-content/uploads/2022/04/column86_1.png" alt="設定が読み込まれなかったためエラーが発生" width="223" height="278" class="size-full wp-image-4055" thinborder" style="float:left" /></a><br /><span style="font-size:0.8em">設定が読み込まれなかったためエラーが発生</span>
</div>

<div style="padding-left:1em;padding-top:0.5em" class="clear">
<h3 style="border-bottom: medium solid gray">シェルに処理を投げる際に設定を読ませれば処理は通る</h3>
　ではどうすればよいのかと言うと、例えばApplescriptの場合なら以下のように

 
<pre class="lang:applescript decode:true " >do shell script "eval \"$(/opt/homebrew/bin/brew shellenv)\"" &amp; ";brew --version"</pre> 


　<strong>命令文の前に環境設定の記述をセミコロンで繋げて実行してやれば処理が通ります</strong>（この場合サンプルとしてHomebrewのバージョンを表示させている）。環境設定の記述はターミナルで処理が通っているのなら現在有効にしているシェルの設定ファイルのどこかに記述があるはず。
</div>

<div style="padding-left:1em;padding-top:0.5em" class="clear">
<h3 style="border-bottom: medium solid gray">bashやzshの環境設定を読み込んで実行するようにした</h3>
　ただ、上記の方法だとApplescript等のコード自体に各環境に依存する表記を追記することになるため、違うマシンにスクリプトを持って行くとたちまち動かなくなるといったような話になりそうです。それはできれば避けたいため、シェルの環境設定ファイル自体を読み込むperlスクリプトを作ってみました。

 
<pre class="lang:perl decode:true " >#! /usr/bin/perl
use utf8;
#各対象設定ファイルを読み込んで配列に収納（いらなければ配列から消す）
my @targetConfigFiles = ('.zprofile', '.zshrc', '.bash_profile', '.bashrc');
#各コンフィグ行を収納する配列を定義してpath_helper実行を最初に収納
my @eachConfigDeclaration;
push (@eachConfigDeclaration, 'eval "$(/usr/libexec/path_helper)"');
foreach $targetFile(@targetConfigFiles){
	open(IN, $targetFile);
		my @tagetFileText = &lt;IN&gt;;
		#なんか空行が入るので改行コード統一
		$eachLineTxt = join("",@tagetFileText);
		$eachLineTxt =~ s@\x0D\x0A@\x0D@g;
		$eachLineTxt =~ s@\x0A@\x0D@g;
		@tagetFileText = split("\x0D",$eachLineTxt);
		foreach (@tagetFileText){
			#exportもしくはevalで始まる行のみ抽出して@eachConfigDeclarationに収納
			if ($_ =~ /^(export|eval).+?$/){
				#セミコロンがダブるとエラーになるので行末にあったら消す
				$_ =~ s@^(.+?);$@$1@g;
				#ダブルクォートのエスケープ
				$_ =~ s@"@\"@g;
				push (@eachConfigDeclaration, $_);
			}
		}
	close (IN);
}
#連結して文字列として返す
my $joinedString = join(";",@eachConfigDeclaration);
$joinedString =~ s@"@\"@g;
print $joinedString;
exit;</pre> 


　exportもしくはevalで始まる行を抽出してセミコロンで繋げたものを返すので、あとはそれを実際に実行する処理の前に連結してシェルに処理を投げればOKです。



Applescriptに組み込んだ場合は以下のような感じ。上のperlスクリプトをshellconfigexec.plの名称でスクリプトバンドル形式ファイル内のContents/Resourcesフォルダに保存して実行しています（macOS 12 Montereyではアプリ形式で保存しようとするとエラーが出て保存できなかったりするため、スクリプトバンドル形式で保存してスクリプトメニューから実行）。

<a href="http://densyodamasii.com/wp-content/uploads/2022/04/column86_2.png" rel=lightbox[column85]><img src="http://densyodamasii.com/wp-content/uploads/2022/04/column86_2.png" alt="Applescriptに組み込んだ" width="600" height="405" class="size-full wp-image-4056 thinborder" /></a>
 
<pre class="lang:applescript decode:true " >tell application "Finder"
	--スクリプト自体のパス位置を得る
	set thisFldr to (path to me as alias) as string
	set thisFldr to POSIX path of thisFldr
	--パス内に空白文字が含まれていた場合の対策
	set thisFldr to (ASCII character (34)) &amp; thisFldr &amp; (ASCII character (34))
	--perlscriptのパスを合成
	set shellConfigExecpath to thisFldr &amp; "Contents/Resources/shellconfigexec.pl "
	--シェル初期設定読み込み
	do shell script "cd ~;perl " &amp; shellConfigExecpath
	set shellConfigExeString to result
end tell

do shell script shellConfigExeString &amp; ";brew --version"
set returndString to result
display dialog returndString</pre> 


　シェルの設定ファイルの処理を全部引っこ抜いて実行が乱暴すぎるという場合は読み込みファイルを独自定義してルートディレクトリに置いておき、それを指定して読み込ませればよいです。
</div>