少し前に仕事マシンがM1 MBAに切り替わったワケですが、OSがmacOS 12 Montereyになったことで(以前からあるにはあったけどなんとかなってた)AppleScriptやXojoなどの別環境内からシェルに処理を投げた際に「ターミナルでは通っていた一部の処理が通らなくなる」現象が顕在化し、だいぶ頑張って解決したので数年後に同じことで困らないように記録を残しておきます。
そもそもなんでそういう現象が起きるのか
Macの「ターミナル」はbashやzshといった「シェル」を使うためのアプリです。Windowsで言うところのコマンドプロンプトですね。で、例えばOSに元々入っているバージョンとは違うPerlやPythonを使いたいとか、非標準モジュールを入れて使いたいといったような場合にはmacの場合はパッケージマネージャーのHomebrewやMacPortsをまず入れてその後それを使ってさまざまな必要なものを入れていって環境を構築するわけですが、入れたものを有効にするために「パスを通す」作業が度々発生します。これは作業手順自体は検索すればいくらでもWebで見つかるのですが、要はシェル(bashやzsh)の環境設定ファイルである「.bash_profile」や「.zshrc」にシェルの起動時に自動実行する設定(環境変数の設定など)を記述しておく作業です。これをやることでいちいち実行ファイルのあるディレクトリに移動してからコマンドを実行するようなことをしないでも良くなるわけです。ただ、「.bash_profile」や「.zshrc」への記述は、あくまでターミナルでシェルを起動した際に読み込まれる設定であるため、AppleScriptやXojo内からシェルに処理を投げた場合には適用されません。このため、ターミナルでは通っていた処理が通らなくなったりします。
シェルに処理を投げる際に設定を読ませれば処理は通る
ではどうすればよいのかと言うと、例えばApplescriptの場合なら以下のようにdo shell script "eval \"$(/opt/homebrew/bin/brew shellenv)\"" & ";brew --version"
命令文の前に環境設定の記述をセミコロンで繋げて実行してやれば処理が通ります(この場合サンプルとしてHomebrewのバージョンを表示させている)。環境設定の記述はターミナルで処理が通っているのなら現在有効にしているシェルの設定ファイルのどこかに記述があるはず。
bashやzshの環境設定を読み込んで実行するようにした
ただ、上記の方法だとApplescript等のコード自体に各環境に依存する表記を追記することになるため、違うマシンにスクリプトを持って行くとたちまち動かなくなるといったような話になりそうです。それはできれば避けたいため、シェルの環境設定ファイル自体を読み込むperlスクリプトを作ってみました。#! /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 = <IN>;
#なんか空行が入るので改行コード統一
$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;
exportもしくはevalで始まる行を抽出してセミコロンで繋げたものを返すので、あとはそれを実際に実行する処理の前に連結してシェルに処理を投げればOKです。
Applescriptに組み込んだ場合は以下のような感じ。上のperlスクリプトをshellconfigexec.plの名称でスクリプトバンドル形式ファイル内のContents/Resourcesフォルダに保存して実行しています(macOS 12 Montereyではアプリ形式で保存しようとするとエラーが出て保存できなかったりするため、スクリプトバンドル形式で保存してスクリプトメニューから実行)。
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)) & thisFldr & (ASCII character (34))
--perlscriptのパスを合成
set shellConfigExecpath to thisFldr & "Contents/Resources/shellconfigexec.pl "
--シェル初期設定読み込み
do shell script "cd ~;perl " & shellConfigExecpath
set shellConfigExeString to result
end tell
do shell script shellConfigExeString & ";brew --version"
set returndString to result
display dialog returndString
シェルの設定ファイルの処理を全部引っこ抜いて実行が乱暴すぎるという場合は読み込みファイルを独自定義してルートディレクトリに置いておき、それを指定して読み込ませればよいです。