はじめに
HEASOFT(ftools)を使った解析では、ftselect, ftmgtime, ftcopy などをシェルスクリプトから呼ぶことが多い。ところが macOS では、
- ターミナルで手入力だと動く
- 同じコマンドを
.shから呼ぶとdyld: Library not loadedで落ちる
という現象が現実的に起こる。
典型例:
- 対話ターミナルでは
DYLD_LIBRARY_PATHが設定されている - しかし
#!/bin/shや#!/bin/bashで起動したスクリプト内ではDYLD_LIBRARY_PATH=(空) - その結果、
ftselectがlibcfitsioを見つけられず Abort する
この問題は 「設定ミス」ではなく macOS のセキュリティ設計に起因することが多い。
本記事では、原因の正体と、Linux/macOS両対応で破綻しない実務的な解決策をまとめる。
よくわからん(?)エラーの例。この限りではないでしょうが。
dyld[11587]: Library not loaded: /Users/birby/heasoft-6.35/heasoft-6.35/x86_64-apple-darwin23.6.0/lib/libcfitsio.10.dylib Referenced from: <0EFFAC87-5252-34FD-B2F4-032BC0EBF7F6> /Users/XXXX/work/software/heasoft-6.35.1/x86_64-apple-darwin23.6.0/bin/ftselect Reason: tried: '/Users/birby/heasoft-6.35/heasoft-6.35/x86_64-apple-darwin23.6.0/lib/libcfitsio.10.dylib' (no such file), '/System/Volumes/Preboot/Cryptexes/OS/Users/birby/heasoft-6.35/heasoft-6.35/x86_64-apple-darwin23.6.0/lib/libcfitsio.10.dylib' (no such file), '/Users/birby/heasoft-6.35/heasoft-6.35/x86_64-apple-darwin23.6.0/lib/libcfitsio.10.dylib' (no such file)
これは mac に HEASoft をバイナリで入れた場合に顕在化しやすいはずです。mac で Heasoft をバイナリで入れる方法は下記を参照ください。
xselectなどを使った時に、下記のようなよくわからんエラーが出て困る!!という人向けの解決方法についてです。
Referenced from: <4FC240B7-DAF1-3F69-9CC4-289150E9DC66> /Users/yamada/work/software/heasoft-6.35.1/x86_64-apple-darwin23.6.0/bin/ffilecat
Reason: tried: '/Users/birby/heasoft-6.35/heasoft-6.35/x86_64-apple-darwin23.6.0/lib/libcfitsio.10.dylib' (no such file)
などのエラーが出る場合です。これは、
otool -L `which xselect`
とやったときに、dylib が存在してないファイルを参照してることが原因です。
HEASoftバイナリの根本的な原因は、birbyというユーザーの直下に何かがあることを前提としてしまうところがあるので、sudoで英やっとシンボリックリンクを作成する、という邪道な解決方法もあります。。
% sudo mkdir -p /Users/birby/heasoft-6.35
% pwd
/Users/birby/heasoft-6.35
% sudo ln -s /Users/yamada/work/software/heasoft-6.35.1 heasoft-6.35
のように、無理やりリンクを貼ってしまう。主には、cfitsioが動的にリンクを呼んでるところが切れてること多い気がするので、heasoft どの version でもほぼ大丈夫なはず。
現象:なぜ HEADAS は残るのに DYLD_LIBRARY_PATH だけ消えるのか
まず観測
親シェル(zsh)では:
echo $HEADAS
echo $DYLD_LIBRARY_PATH
がどちらも表示される。しかし、同じ環境から起動した bash の中では:
env | grep -E 'HEADAS|DYLD'
が
-
HEADAS=...は残る -
DYLD_LIBRARY_PATHは出ない(消えている)
という差が出る。
これは「特殊変数」と「普通の変数」の差
-
HEADASは普通の環境変数
→ 子プロセスに素直に継承される -
DYLD_LIBRARY_PATHは macOS の動的リンカdyldを制御する “危険度の高い” 環境変数
→ 条件によって OS 側で 無効化(サニタイズ) されることがある
DYLD_* は、悪意ある dylib の注入に使えるため、macOS では「安全でない起動経路」「保護対象のバイナリ」を経由すると、起動時に落とされることがある。
その結果、「親にはあるのに子で消える」が発生する。
重要:この挙動は Linux では基本的に起きない。
したがって「macだけ壊れる」「スクリプトだけ壊れる」になる。
なぜ「ターミナルからは動く」のか
ターミナルで ftselect を手打ちすると動く理由はたいてい次のどれか:
- ターミナル(zsh)の起動時に
.zshrcが読み込まれheadas-init.shが走っている - そのときの
DYLD_LIBRARY_PATHは 同一プロセス(zsh) の中で有効 - さらに
ftselect実行はzshから直接execされるので、その環境が使える
一方、シェルスクリプトは #!/bin/sh / #!/bin/bash で 別プロセスとして起動される。
このとき macOS のサニタイズ条件に引っかかると DYLD_LIBRARY_PATH が消え、HEASOFT のバイナリが必要な dylib を見つけられなくなる。
設計指針:対話環境(.zshrc)とバッチ環境(スクリプト)を分離する
よくある誤解
.zshrc に
source $HEADAS/headas-init.sh
と書いてあるから、スクリプトでも同じ環境が使えるはず、という期待。
現実
-
.zshrcは “対話的 zsh” 用 -
#!/bin/shや#!/bin/bashのスクリプトでは読まれない - さらに macOS では
DYLD_*が “親→子”で落とされる場合がある
したがって、バッチ実行で再現性を担保したいなら:
スクリプトはスクリプト自身で環境を完結させる
が正しい。
最小で最強の対策:HEADAS から DYLD_LIBRARY_PATH を復元する
この1行(厳密には数行)が、最小のワークアラウンド:
if [ -n "${HEADAS:-}" ] && [ -z "${DYLD_LIBRARY_PATH:-}" ]; then
export DYLD_LIBRARY_PATH="$HEADAS/lib"
fi
なぜこれが効くのか
-
HEADASは普通の環境変数なので残りやすい -
HEADAS/libは HEASOFT の dylib(cfitsio など)が置かれている - つまり
DYLD_LIBRARY_PATHを 「親から受け取る」のではなく「子の中で再構成」 している
macOS は「親→子で持ち込まれる DYLD」を警戒するが、子が自分で設定した DYLDまでは(多くの場合)止めない。実務的にはこれで解決することが多い(はず)。
「全部のスクリプトに呪文を入れたくない」問題と解決策
解決策A(推奨):共通ファイルを1つ作り、各スクリプトは1行だけ読む
heasoft_env.sh を作る:
# heasoft_env.sh
case "$(uname -s 2>/dev/null)" in
Darwin)
if [ -n "${HEADAS:-}" ] && [ -z "${DYLD_LIBRARY_PATH:-}" ]; then
export DYLD_LIBRARY_PATH="$HEADAS/lib"
fi
;;
*)
# Linux等では基本的に何もしない
;;
esac
各スクリプトには「呪文」ではなく、この1行だけ:
. "$(dirname "$0")/heasoft_env.sh"
ここで重要:. と source の違い
-
.は POSIX 標準(shでも動く) -
sourceは bash/zsh の拡張(shだと死ぬ)
Linux/macOS 両対応を狙うなら . が無難。
dirname "$0" は何か
-
$0は “このスクリプトのパス” -
dirname "$0"は “そのディレクトリ” - よって、同じディレクトリにある
heasoft_env.shを確実に読める
より堅牢に絶対パス化したければ:
. "$(cd "$(dirname "$0")" && pwd)/heasoft_env.sh"
Pythonからftoolsを呼ぶ場合も同じことが起きるか?
結論:起きる。
- Pythonがどの環境で起動されたか(Terminal / cron / Jupyter / IDE)で
DYLD_LIBRARY_PATHの有無が変わる -
subprocess(..., shell=True)にすると内部で/bin/shが起動され、同じ問題が再発しやすい
Python側での堅牢パターン
import os, subprocess
env = os.environ.copy()
if "HEADAS" in env and not env.get("DYLD_LIBRARY_PATH"):
env["DYLD_LIBRARY_PATH"] = os.path.join(env["HEADAS"], "lib")
subprocess.run(["ftselect", "infile=...", "outfile=...", "expr=..."], env=env, check=True)
まとめ:再現性と保守性のベストプラクティス
-
macOSでは
DYLD_*が消える前提で設計する -
対話設定(.zshrc)とバッチ設定(スクリプト)は別物
-
対策は
-
heasoft_env.shを1つ作る - 各スクリプトは
. "$(dirname "$0")/heasoft_env.sh"を1行だけ追加
-
-
.sh→.pyの移行では “呼び出し側”の文字列も一括置換(perl + .bak)が安全 -
Pythonから呼ぶ場合も
envを明示するのが堅牢
付録A:最終形(推奨構成)
heasoft_env.sh
case "$(uname -s 2>/dev/null)" in
Darwin)
if [ -n "${HEADAS:-}" ] && [ -z "${DYLD_LIBRARY_PATH:-}" ]; then
export DYLD_LIBRARY_PATH="$HEADAS/lib"
fi
;;
esac
各スクリプトの冒頭(2行目)
#!/bin/sh
. "$(cd "$(dirname "$0")" && pwd)/heasoft_env.sh"
ここは、heasoft_env.sh を自分がおいた場所に依存して変更ください。
(小技) .sh → .py に移行したら「参照している側」も一括修正が必要
バッシュで書いていた resolve_util_ftmgtime.sh を、python で書き直して resolve_util_ftmgtime.py としたので、呼んでるファイルの中身を一括で変えたい時とかありますよね。そういう時のための小技を紹介です。
超簡潔・Python一括置換(バックアップ付き)
from pathlib import Path
OLD = "resolve_util_ftmgtime.sh"
NEW = "resolve_util_ftmgtime.py"
for p in Path(".").rglob("*.sh"):
s = p.read_text()
if OLD in s:
p.with_suffix(p.suffix + ".bak").write_text(s)
p.write_text(s.replace(OLD, NEW))
print("updated:", p)
実行
python3 replace_ftmgtime.py
これで何をしているか(1行ずつ)
-
Path(".").rglob("*.sh")
→ カレント以下の.shを全部見る -
if OLD in s:
→ 該当文字列があるファイルだけ処理 -
.bakを作ってから置換
→ 安全 -
print
→ 変更したファイルが分かる
置換結果を確認
grep -R --line-number --fixed-strings 'resolve_util_ftmgtime.sh' .
残っていなければOK。
戻したい(バックアップから復旧)
find . -type f -name "*.bak" -print0 | while IFS= read -r -d '' f; do
mv "$f" "${f%.bak}"
done
付録B:macOS のバージョン依存と dyld サニタイズの適用範囲
B.1 dyld サニタイズは「いつから」問題になる仕様か
本記事で扱っている DYLD サニタイズ(DYLD_ 環境変数の無効化)* は、
ある特定の macOS で突然導入されたものではなく、段階的に強化 されてきた仕様である。
バージョン別の整理
| macOS | 年 | dyld サニタイズの位置づけ |
|---|---|---|
| ≤ 10.7 (Lion) | ~2011 | ほぼ無し(Linux的挙動) |
| 10.8 (Mountain Lion) | 2012 | 原型導入(限定的) |
| 10.11 (El Capitan) | 2015 | SIP導入:本格化 |
| 10.13 (High Sierra) | 2017 | 仕様として定着 |
| 10.15 (Catalina) | 2019 | 一般ユーザー・研究者にも直撃 |
| 11 (Big Sur) 以降 | 2020– | 常時有効と考えてよい |
結論
現在運用されている macOS(Big Sur / Monterey / Ventura / Sonoma / Sequoia)では、DYLD_ が「消える前提」で設計しないと、再現性は担保できない* と考えてよい(気がします)。
B.2 「ターミナルでは動く」のに「スクリプトで死ぬ」理由(OS視点)
macOS の dyld サニタイズは、
- 環境変数の値そのもの
- どの起動経路でプロセスが生成されたか
の 組み合わせで発動する。
典型的な違い
| 起動経路 | DYLD_* |
|---|---|
| 対話ターミナル(zsh) | 有効なことが多い |
| bash / sh スクリプト | 落とされる場合あり |
| Python subprocess | 落ちやすい |
| cron / launchd | ほぼ常に落ちる |
| Finder / GUI | ほぼ常に落ちる |
つまり本記事で示した現象は、
「シェルの違い」ではなく「起動経路の違い」
によるものであり、Linux 的な感覚では説明できない。
付録C:ABI 問題(Intel / Apple Silicon 混在)とは何か
C.1 ABI とは何か(dyld サニタイズとの違い)
ABI(Application Binary Interface) とは、
コンパイル後のバイナリ同士が守るべき“機械語レベルの約束”
である。
具体的には:
- 関数呼び出し規約
- レジスタの使い方
- 構造体のメモリ配置
- バイナリ形式(Mach-O)
- CPU アーキテクチャ(x86_64 / arm64)
重要な切り分け
| 問題 | 正体 |
|---|---|
| DYLD_* が消える | dyld サニタイズ(セキュリティ) |
| dylib がロードできない(incompatible architecture) | ABI 問題 |
両者は原因も対処法も完全に別である。
C.2 Apple Silicon(M1/M2/M3)で ABI 問題が顕在化した理由
Intel Mac 時代
- OS:x86_64
- HEASOFT:x86_64
- 依存ライブラリ:x86_64
👉 全員同じ ABI --> 問題が見えなかった
Apple Silicon 時代
macOS は 2つの世界を同時に持つ:
| 実体 | アーキテクチャ |
|---|---|
| ネイティブ実行 | arm64 |
| 互換実行(Rosetta 2) | x86_64 |
この結果、
1つのプロセス内で arm64 と x86_64 は混在できない
という ABI の制約が、実務上のエラーとして現れる。
C.3 HEASOFT でよくある ABI 事故例
典型的な失敗パターン
- HEASOFT 本体:x86_64(Intel ビルド)
- Homebrew 依存:arm64
- 実行環境:Apple Silicon
結果:
dyld: Library not loaded: ...
Reason: incompatible architecture
これは dyld サニタイズではない。
export や source では絶対に直らない。
C.4 切り分け方法(必須)
# 実行ファイル
file $(which ftselect)
# dylib
file $HEADAS/lib/*.dylib | head
# マシン
uname -m
原則
- 全部 x86_64 → Rosetta ターミナルで統一
- 全部 arm64 → ネイティブ環境で統一
- 混在はしない
C.5 M2 以降は Intel との共存が解消されたのか?
いいえ。解消されていない。
- M1 / M2 / M3 いずれでも Rosetta 2 は存在
- Intel バイナリは今も実行可能
- dyld / ABI の制約は CPU 世代に依存しない
問題の本体は macOS の設計であり、CPU ではない。
付録D:設計指針まとめ(再現性のための要点)
dyld サニタイズ対策
- DYLD_* は 消える前提
- 親から継承せず、子で再構成
-
heasoft_env.shを単一の入口にする
ABI 対策
-
fileで必ず確認 - arm64 / x86_64 を混ぜない
- Rosetta / ネイティブを意識的に選ぶ
付録の締め
macOS における HEASOFT の不安定動作は、
- dyld サニタイズ(OSセキュリティ)
- ABI 問題(CPU/バイナリ仕様)
- の 2つを切り分けて理解すると、ほぼ理詰めで解決できる(はず)。