python - 通过 Snakemake 进行符号链接(自动生成)目录
问题描述
我正在尝试为 Snakemake 工作流程中的别名输出目录创建一个符号链接目录结构。
让我们考虑以下示例:
很久以前,在一个遥远的星系中,有人想找到宇宙中最好的冰淇淋口味,并进行了一项调查。我们的示例工作流程旨在通过目录结构表示投票。该调查是用英语进行的(因为在那个外国星系中他们都说英语),但结果也应该被非英语人士理解。符号链接来救援。
为了让我们人类和 Snakemake 都可以解析输入,我们将它们粘贴到 YAML 文件中:
cat config.yaml
flavours:
chocolate:
- vader
- luke
- han
vanilla:
- yoda
- leia
berry:
- windu
translations:
french:
chocolat: chocolate
vanille: vanilla
baie: berry
german:
schokolade: chocolate
vanille: vanilla
beere: berry
为了创建相应的目录树,我从这个简单的 Snakefile 开始:
### Setup ###
configfile: "config.yaml"
### Targets ###
votes = ["english/" + flavour + "/" + voter
for flavour, voters in config["flavours"].items()
for voter in voters]
translations = {language + "_translation/" + translation
for language, translations in config["translations"].items()
for translation in translations.keys()}
### Commands ###
create_file_cmd = "touch '{output}'"
relative_symlink_cmd = "ln --symbolic --relative '{input}' '{output}'"
### Rules ###
rule all:
input: votes, translations
rule english:
output: "english/{flavour}/{voter}"
shell: create_file_cmd
rule translation:
input: lambda wc: "english/" + config["translations"][wc.lang][wc.trans]
output: "{lang}_translation/{trans}"
shell: relative_symlink_cmd
我相信还有更多的“pythonic”方法可以实现我想要的,但这只是一个简单的例子来说明我的问题。
使用 运行上述工作流程snakemake
,我收到以下错误:
Building DAG of jobs...
MissingInputException in line 33 of /tmp/snakemake.test/Snakefile
Missing input files for rule translation:
english/vanilla
因此,虽然 Snakemake 足够聪明地english/<flavour>
在尝试创建文件时创建目录,但在将其用作创建符号链接english/<flavour>/<voter>
的输入时,它似乎“忘记”了该目录的存在。<language>_translation/<flavour>
作为中间步骤,我将以下补丁应用于 Snakefile:
27c27
< input: votes, translations
---
> input: votes#, translations
english
现在,工作流按预期运行并创建了目录(snakemake -q
仅输出):
Job counts:
count jobs
1 all
6 english
7
现在创建了目标目录,我回到了 Snakefile 的初始版本并重新运行它:
Job counts:
count jobs
1 all
6 translation
7
ImproperOutputException in line 33 of /tmp/snakemake.test/Snakefile
Outputs of incorrect type (directories when expecting files or vice versa). Output directories must be flagged with directory(). for rule translation:
french_translation/chocolat
Exiting because a job execution failed. Look above for error message
虽然我不确定指向目录的符号链接是否符合目录的条件,但我继续并应用了一个新补丁来遵循建议:
35c35
< output: "{lang}_translation/{trans}"
---
> output: directory("{lang}_translation/{trans}")
有了这个,snakemake
最后创建了符号链接:
Job counts:
count jobs
1 all
6 translation
7
作为确认,这是生成的目录结构:
english
├── berry
│ └── windu
├── chocolate
│ ├── han
│ ├── luke
│ └── vader
└── vanilla
├── leia
└── yoda
french_translation
├── baie -> ../english/berry
├── chocolat -> ../english/chocolate
└── vanille -> ../english/vanilla
german_translation
├── beere -> ../english/berry
├── schokolade -> ../english/chocolate
└── vanille -> ../english/vanilla
9 directories, 6 files
但是,除了无法在不运行snakemake
两次(并在其间修改目标)的情况下创建此结构之外,即使简单地重新运行工作流也会导致错误:
Building DAG of jobs...
ChildIOException:
File/directory is a child to another output:
/tmp/snakemake.test/english/berry
/tmp/snakemake.test/english/berry/windu
所以我的问题是:如何在工作的 Snakefile 中实现上述逻辑?
请注意,我不是在寻求更改 YAML 文件和/或 Snakefile 中的数据表示的建议。这只是一个突出(和隔离)我在更复杂场景中遇到的问题的示例。
可悲的是,虽然到目前为止我自己无法解决这个问题,但我设法获得了一个可以工作的 GNU make 版本(即使“YAML 解析”充其量是 hackish):
### Setup ###
configfile := config.yaml
### Targets ###
votes := $(shell awk ' \
NR == 1 { next } \
/^[^ ]/ { exit } \
NF == 1 { sub(":", "", $$1); dir = "english/" $$1 "/"; next } \
{ print dir $$2 } \
' '$(configfile)')
translations := $(shell awk ' \
NR == 1 { next } \
/^[^ ]/ { trans = 1; next } \
! trans { next } \
{ sub(":", "", $$1) } \
NF == 1 { dir = $$1 "_translation/"; next } \
{ print dir $$1 } \
' '$(configfile)')
### Commands ###
create_file_cmd = touch '$@'
create_dir_cmd = mkdir --parent '$@'
relative_symlink_cmd = ln --symbolic --relative '$<' '$@'
### Rules ###
all : $(votes) $(translations)
$(sort $(dir $(votes) $(translations))) : % :
$(create_dir_cmd)
$(foreach vote, $(votes), $(eval $(vote) : | $(dir $(vote))))
$(votes) : % :
$(create_file_cmd)
translation_targets := $(shell awk ' \
NR == 1 { next } \
/^[^ ]/ { trans = 1; next } \
! trans { next } \
NF != 1 { print "english/" $$2 "/"} \
' '$(configfile)')
define translation
$(word $(1), $(translations)) : $(word $(1), $(translation_targets)) | $(dir $(word $(1), $(translations)))
$$(relative_symlink_cmd)
endef
$(foreach i, $(shell seq 1 $(words $(translations))), $(eval $(call translation, $(i))))
运行make
这个工作得很好:
mkdir --parent 'english/chocolate/'
touch 'english/chocolate/vader'
touch 'english/chocolate/luke'
touch 'english/chocolate/han'
mkdir --parent 'english/vanilla/'
touch 'english/vanilla/yoda'
touch 'english/vanilla/leia'
mkdir --parent 'english/berry/'
touch 'english/berry/windu'
mkdir --parent 'french_translation/'
ln --symbolic --relative 'english/chocolate/' 'french_translation/chocolat'
ln --symbolic --relative 'english/vanilla/' 'french_translation/vanille'
ln --symbolic --relative 'english/berry/' 'french_translation/baie'
mkdir --parent 'german_translation/'
ln --symbolic --relative 'english/chocolate/' 'german_translation/schokolade'
ln --symbolic --relative 'english/vanilla/' 'german_translation/vanille'
ln --symbolic --relative 'english/berry/' 'german_translation/beere'
生成的树与上面显示的树相同。
此外,make
再次运行也可以:
make: Nothing to be done for 'all'.
所以我真的希望解决方案不是回到老式的 GNU make 以及我多年来内化的所有不可读的黑客,而是有一种方法可以说服 Snakemake 也按照我的要求去做。;-)
以防万一:这是使用 Snakemake 版本 5.7.1 测试的。
编辑:
- 根据@MadScientist的评论修复了 GNU 发出警告。
- 由于到目前为止的一般反馈表明 Snakemake 无法做到这一点,因此我将其作为功能请求交叉发布在 Snakemake 的 GitHub 上(在赏金到期之前)。
relative_symlink_cmd
根据@Nick的评论进行了简化。
解决方案
这是解决您的第一个问题的一种方法(即,只运行一次snakemake 以获得所有所需的输出)。我使用 rule 的输出文件作为 ruleenglish
的输入translation
,并修改后一个 rule 的 shell 命令以反映这一点。以我的经验,使用目录作为输入并不适用于snakemake,如果我没记错的话,directory()
标签input
会被忽略。
相关代码更改:
relative_symlink_cmd = """ln -s \
"$(realpath --relative-to="$(dirname '{output}')" "$(dirname {input[0]})")" \
'{output}'"""
rule translation:
input: lambda wc: ["english/" + config["translations"][wc.lang][wc.trans] + "/" + voter for voter in config['flavours'][config["translations"][wc.lang][wc.trans]]]
output: directory("{lang}_translation/{trans}")
shell: relative_symlink_cmd
您的第二个问题很棘手,因为当您再次运行snakemake 时,它会将符号链接解析为其相应的源文件,这会导致ChildIOException
错误。这可以通过替换relative_symlink_cmd
为自己的目录而不是符号链接来验证,如下所示。在这种情况下,snakemake 按预期工作。
relative_symlink_cmd = """mkdir -p '{output}'"""
我不知道如何解决这个问题。
推荐阅读
- python - 如何使 Jupyter Notebook 指向与 Ubuntu 上的终端相同的 Python 安装?
- azure - 登录 Azure 应用服务运行实例
- c - 将 D 项目编译为库 - 依赖项会发生什么?
- php - 如何使用 php 从 URL 中获取第三个字符串
- mongodb - MongoDB 在 Studio 3T 中读取集合的速度很慢
- yaml - 设置 bitbucket 管道 yaml 文件
- elasticsearch - 以 24/h 时间格式设置 Logstash 输出索引
- wso2 - 如何使用接收器类型为“http-response”的另一个流的响应更新流
- javascript - 在移动 chrome 中滚动闪烁动画
- reactjs - 如何在迭代中显示 JSX 中的数据?