我正在使用 bash 运行一系列实验,并希望将日志文件存储在一个目录中,该目录的名称基于实验配置。配置中的某些项目是布尔值(true/false)。以下面的配置为例:
batch_size=16
fp16=false
bf16=true
checkpoint_activations=true
我想将上述配置的实验日志文件作为输入存储在具有以下名称的目录中:
output_dir="experiment_bs${batch_size}_dt${fp16 if fp16=true else bf16}_${cp if checkpoint_activations=true else empty}"
当然,我可以声明辅助变量:
data_type=""
"${fp16}" && data_type=fp16
"${bf16}" && data_type=bf16
"${cp}" && cp="_cp" || cp=""
output_dir="experiment_bs${batch_size}_dt${data_type}${cp}"
但我觉得这有点笨拙,希望参数替换在这里可能有用。"${bf16:+bf16}"
对我的情况没有帮助,因为只要定义了它,无论它的布尔值如何,它总是会打印“bf16”。
是否有任何参数替换可以应用于此用例?或者有更好的在线解决方案来解决这个问题吗?
注意:有一个特定于应用程序的原因导致我不直接data_type
在我的配置中使用。
答案1
您可以将任何您想要的 bash 命令放入其中$(...)
,这样您就可以编写:
output_dir="experiment_bs${batch_size}_dt$([[ $fp16 = true ]] && echo $fp16 || echo $bf16)_$([[ $checkpoint_activation = true ]] && echo $cp || echo empty)"
尽管为了易读性我可能会写成:
printf -v output_dir "experiment_bs%s_dt%s_%s" \
"$batch_size" \
"$([[ $fp16 = true ]] && echo "$fp16" || echo "$bf16")" \
"$([[ $checkpoint_activation = true ]] && echo "$cp" || echo empty)"
鉴于您的样本输入...
batch_size=16
fp16=false
bf16=true
checkpoint_activations=true
...以上两者都会产生值:
experiment_bs16_dttrue_empty
答案2
在 中zsh
,您可以定义一个?
实现三元运算符形式的函数(以及一个别名以防止其被视为全局模式)? condition if-yes if-no
,这让人想起 C 的condition ? if-yes : if-no
:
alias "?='?'"
'?'() if eval $1; then print -r -- $2; else print -r -- $3; fi
output_dir=experiment_bs${batch_size}_dt$(? $fp16 fp16 bf16)_$(? $cp cp)
对于 zsh 6.0+(截至 2024-02-06 尚未发布),您可以将其更改为:
alias "?='?'"
'?'() if eval $1; then REPLY=$2; else REPLY=$3; fi
output_dir=experiment_bs${batch_size}_dt${|? $fp16 fp16 bf16}_${|? $cp cp}
避免分叉进程以获取结果并允许值以换行符结尾(此功能称为瓦尔苏布(值替换)从 mksh 复制)。
请注意,三元运算符评估第一个参数中的代码来决定是否返回$2
or $3
,因此那些$fp16
/$cp
应该包含true
or false
。更改为$(? '[[ $fp16 = true ]]' fp16 bp16)
检查是否$fp16
包含true
或其他内容。
也可以看看zsh 邮件列表上的讨论对于三元运算符的一些内置方法。和这个问答关于瓦尔子有关详细信息和替代方案。
答案3
如果fp16
是一个配置变量,那么我不会这样做,"${fp16}" && data_type=fp16
因为它将该配置变量转换为命令。即使我们不考虑有人reboot
在那里放置类似内容的可能性,即使是拼写错误也会导致一些看起来奇怪的错误消息(例如“tru:找不到命令”或其他)。
话又说回来,也许这只是提醒您验证脚本获取的值,例如使用如下检查器函数:
checkbool() {
case $1 in
true|false) return 0;;
*) echo >&2 "'$1' is an invalid boolean (must be 'true' or 'false'";
exit 1;;
esac
}
checkbool "$fp16"
checkbool "$bf16"
# ...
fp16
还要考虑bf16
作为自变量是否有意义?
在:
"${fp16}" && data_type=fp16
"${bf16}" && data_type=bf16
如果 和 都fp16
为bf16
真,则后者优先。如果两者都没有设置,data_type
则留空,这可能有效也可能无效。
我不确定你的具体情况,但我想知道好吧,帖子说有一个不直接使用的理由data_type
直接作为配置变量是否会更好。data_type
,但考虑一下如果启用这两个设置或没有启用其中一个设置会发生什么可能仍然有意义。
无论如何,如果您希望参数扩展能够正常"${bf16:+bf16}"
工作,则需要使用空值作为假值,并将任何非空字符串作为真值。然后你可以这样做 例如data_type="${enable_fp16:+fp16}"
,但即使这样似乎也很难使用,因为我认为没有一个好的方法可以让空字符串变成默认值,而不泄漏其他值。例如,相反的操作"${enable_fp16:-bf16}"
会将空字符串转换为,但它也会按原样bf16
返回字符串。yes
如果您要在脚本中使用空/非空值,您是否希望向用户公开配置中的该位内部详细信息?或者写出条件语句将配置值转换为脚本实际需要的值(无论是否笨重)会更好?
我会选择这样的东西,这可能感觉很冗长,但并没有花太长时间来写,真的:
# config
batch_size=16
fp16=false
bf16=true
checkpoint_activations=true
## code
# this treats anything that's not 'true' as falsy
if [[ $fp16 = true && $bf16 != true ]]; then
data_type=fp16
elif [[ $fp16 != true && $bf16 = true ]]; then
data_type=bf16
else
echo >&2 "exactly one of fp16 and bf16 must be 'true'"
exit 1
fi
cp=
if [[ $checkpoint_activations = true ]]; then
cp=_cp
fi
# (maybe the value of $batch_size should also be checked, whatever
output_dir="experiment_bs${batch_size}_dt${data_type}${cp}"
当然,我们还可以检查每个赋值是否data_type
已设置,而不是在每个条件下检查每个输入变量的值。 (如上所述,添加第三个变量也需要更改两个现有条件。)
如果您想采用简洁的方式,Stéphane 答案中的双向选择功能也可以在 Bash 中使用,只需稍作修改即可。尽管我仍然宁愿明确地检查该值,所以可能是这样的:
choose() if [[ $1 = true ]]; then printf "%s\n" "$2"
else printf "%s\n" "$3"
fi
data_type=$(choose "$fp16" fp16 bf16)
# etc.
当然,冗长和显式的代码与紧凑和简洁的代码之间的决定始终取决于程序员。