
背景与场景
在 Windows 上跑数据/生信流程时,经常出现:主流程在 conda 的 Python 环境里,但其中某一步需要调用 R。常见问题包括:
- 找不到 Rscript(PATH 混乱 / 多 R 版本 / conda 里也有 R)
- R 包库冲突(加载到错误的 library,或提示包缺失)
- conda/Python 环境变量污染导致 R 运行不稳定
- 失败不可追溯(stderr 没落盘,复现困难)
Skill 做什么
sc-rscript-env-runner 把“找到并稳定调用 Rscript.exe”的流程标准化,输出一套可执行的策略与模板,包含:
- 定位可用 Rscript.exe 的优先级策略
- 运行前清理关键环境变量、重写 PATH 降低冲突
- 使用
Rscript --vanilla执行并建议 stdout/stderr 落盘,便于复现排错
使用方法(如何调用)
触发式调用(对话示例):
- “请使用
sc-rscript-env-runner,帮我在 Windows/conda 下定位可用 Rscript,并给出稳定调用模板。” - “我运行时报错 ‘未找到 Rscript’,请用
sc-rscript-env-runner给排查与解决方案(包含可复制步骤)。” - “我的 conda 和系统 R 可能冲突,请用
sc-rscript-env-runner给一个更干净的运行方式,并把 stdout/stderr 落盘。”
建议同时提供的信息:
- 当前运行方式(是否在 conda 环境内、是否有 CONDA_PREFIX)
- 报错信息原文
- 若已知 Rscript 路径则直接给绝对路径
- 希望日志/输出落盘的目录
最佳实践要点
- 优先显式指定 Rscript 绝对路径,避免依赖 PATH
- 执行使用
Rscript --vanilla,减少用户级配置带来的不确定性 - 运行前清理
PYTHONHOME/PYTHONPATH/R_LIBS/R_LIBS_USER - 固定
cwd到输出目录,stdout/stderr 分别落盘,便于复现与排错 - 若提示缺包,先确认“实际调用的是哪个 Rscript”(路径与版本)
附录 A:Skills数据
name: "sc-rscript-env-runner"
description: "在 Windows/conda 下定位并调用本地 Rscript 运行 R 脚本。用户遇到 Rscript 找不到、R 包环境冲突或需要从 Python 调用 R 时使用。"
# sc-rscript-env-runner
## 目标
把“如何在本机找到 Rscript.exe 并稳定调用它”的逻辑标准化,适用于:
- Python 脚本里需要执行一段 R 代码(写入 .R 文件后调用 Rscript)
- Windows + conda 混用导致 PATH / R_LIBS 冲突
- 机器上装了多个 R 版本,需要自动挑选可用且版本更高的 Rscript
## 触发场景(何时调用)
- 运行流程时报错 “未找到 Rscript”
- 运行 Rscript 时报包缺失、加载到错误的 R library、或 conda 的 R 与系统 R 冲突
- 需要将一段 R 计算封装到 Python step 中,并希望把 stdout/stderr 落盘便于追踪
## 参考实现(来自 Step2_scTenifoldKnk.py 的约定)
### 1) 定位 Rscript 的优先级
按以下顺序找可执行文件路径:
1. 用户显式指定(例如命令行参数 `--rscript`,且路径存在)
2. `PATH` 中的 `Rscript` 或 `Rscript.exe`(`shutil.which`)
3. Windows `where Rscript.exe` 的返回结果
4. 环境变量:
- `R_HOME` 下的 `bin\Rscript.exe` 或 `bin\x64\Rscript.exe`
- `CONDA_PREFIX` 下的 `Scripts\Rscript.exe` 或 `Library\bin\Rscript.exe`
5. 常见安装目录扫描:
- `%LOCALAPPDATA%\Programs\R\R-*\bin\Rscript.exe`(含 `bin\x64`)
- `C:\Program Files\R\R-*\bin\Rscript.exe`(含 `bin\x64`)
- `C:\Program Files (x86)\R\R-*\bin\Rscript.exe`(含 `bin\x64`)
若候选不止一个,按路径里的版本号(`R-4.3.2` 这种)做排序,选择版本更高的那个。
### 2) 清理环境变量以减少 Python/conda 对 R 的污染
调用 R 前构造新的 `env`(基于 `os.environ` 拷贝),核心策略:
- 删除这些变量(避免干扰 R 与包库解析):
- `PYTHONHOME`, `PYTHONPATH`
- `R_LIBS`, `R_LIBS_USER`
- 重写 `PATH`:
- 把 `Rscript.exe` 所在目录放到 PATH 第一位
- 从原 PATH 中移除明显属于 conda/Anaconda/Mamba/Micromamba 的路径片段
- 同时移除 `...\Library\bin`(常见 conda R 的 DLL 路径来源,易与系统 R 冲突)
### 3) 实际执行方式
- 将 R 代码写入 `run_xxx.R`
- 使用:
```text
Rscript --vanilla run_xxx.R
cwd设置为输出目录,stdout/stderr 分别写入文件,便于复现与排错
推荐复用模板(Python)
当你需要在任意 step 中运行 R 时,优先复用下列模式(与仓库现有实现对齐):
from pathlib import Path
import os
import shutil
import subprocess
def find_rscript(explicit: str | None = None) -> str | None:
if explicit:
p = Path(explicit)
if p.exists():
return str(p)
path = shutil.which("Rscript") or shutil.which("Rscript.exe")
if path:
return path
candidates: list[Path] = []
try:
p = subprocess.run(["where", "Rscript.exe"], capture_output=True, text=True, check=False)
if p.returncode == 0:
for line in (p.stdout or "").splitlines():
line = line.strip()
if line:
candidates.append(Path(line))
except Exception:
pass
r_home = os.environ.get("R_HOME")
if r_home:
candidates.append(Path(r_home) / r"bin\Rscript.exe")
candidates.append(Path(r_home) / r"bin\x64\Rscript.exe")
conda_prefix = os.environ.get("CONDA_PREFIX")
if conda_prefix:
candidates.append(Path(conda_prefix) / r"Scripts\Rscript.exe")
candidates.append(Path(conda_prefix) / r"Library\bin\Rscript.exe")
localapp = os.environ.get("LOCALAPPDATA")
if localapp:
local_r = Path(localapp) / "Programs" / "R"
if local_r.exists():
candidates.extend(local_r.glob(r"R-*\bin\Rscript.exe"))
candidates.extend(local_r.glob(r"R-*\bin\x64\Rscript.exe"))
for root in [Path(r"C:\Program Files"), Path(r"C:\Program Files (x86)")]:
r_root = root / "R"
if r_root.exists():
candidates.extend(r_root.glob(r"R-*\bin\Rscript.exe"))
candidates.extend(r_root.glob(r"R-*\bin\x64\Rscript.exe"))
candidates = [p for p in candidates if p.exists()]
return str(candidates[0]) if candidates else None
def clean_env_for_r(rscript: str) -> dict[str, str]:
env = dict(os.environ)
env.pop("PYTHONHOME", None)
env.pop("PYTHONPATH", None)
env.pop("R_LIBS", None)
env.pop("R_LIBS_USER", None)
r_bin = str(Path(rscript).resolve().parent)
path_items = (env.get("PATH") or "").split(os.pathsep)
drop_tokens = [
"miniconda",
"anaconda",
"mambaforge",
"micromamba",
"conda",
os.path.join("library", "bin").lower(),
]
kept: list[str] = []
for it in path_items:
low = it.lower()
if any(tok in low for tok in drop_tokens):
continue
kept.append(it)
env["PATH"] = os.pathsep.join([r_bin] + kept)
return env
def run_rscript(rscript: str, r_code: str, out_dir: Path) -> subprocess.CompletedProcess:
out_dir.mkdir(parents=True, exist_ok=True)
r_file = out_dir / "run.R"
r_file.write_text(r_code.lstrip(), encoding="utf-8")
p = subprocess.run(
[rscript, "--vanilla", str(r_file)],
cwd=str(out_dir),
env=clean_env_for_r(rscript),
capture_output=True,
encoding="utf-8",
errors="replace",
text=True,
check=False,
)
(out_dir / "r_stdout.txt").write_text(p.stdout or "", encoding="utf-8")
(out_dir / "r_stderr.txt").write_text(p.stderr or "", encoding="utf-8")
return p
## 运行与排错清单
- 优先用 `--rscript` 传入绝对路径,避免依赖 PATH
- `Rscript --vanilla` 可以减少用户级配置文件带来的不确定性
- 若提示找不到包(如 `scTenifoldKnk`),说明当前 R 的库路径里未安装对应包;先确认你实际调用的是哪个 Rscript(把 `rscript` 路径写入 manifest/日志)
- 若 conda 与系统 R 冲突,优先使用本技能的“清理 env + PATH 置顶 Rscript 目录”策略
