如何禁止Bash脚本对输入参数进行变量替换?
先明确下你的场景:
你的Java进程调用Bash脚本的命令是:
./spark_submit.sh ${SPARK_HOME}/bin/spark-submit --master yarn --deploy-mode cluster --conf spark.yarn.historyServer.address=${hadoopconf-yarn.resourcemanager.hostname}:18080现在要在
spark_submit.sh里逐个处理参数——用$1取值加shift的方式,但Bash会自动把${SPARK_HOME}/bin/spark-submit解析成/bin/spark-submit(如果SPARK_HOME未定义),把${hadoopconf-yarn.resourcemanager.hostname}解析成yarn.resourcemanager.hostname,而且Java没法通过加转义符传参,对吧?
给你三个实用的解决方案:
1. 用Bash的原始参数数组保存完整参数
Bash里的$@数组是个神器,它会完整保留每个参数的原始字符串,不会提前解析里面的${...}变量。如果你必须逐个处理参数,别直接用$1循环,先把所有参数存到数组里:
#!/bin/bash # 把所有原始参数存入数组 original_args=("$@") # 逐个遍历处理(这里可以加你的参数检查逻辑) for arg in "${original_args[@]}"; do echo "当前处理参数:$arg" # 你的检查代码写在这 done # 最后执行的时候直接用原始参数数组 "${original_args[@]}"
这样不管参数里的${...}是什么,都会原封不动保留,不会被Bash提前解析。
2. 反向还原被错误解析的参数
如果没法改Java的调用方式,那就在Bash脚本里做“修复”——把已经被解析错的部分还原成原始的变量形式。比如针对你提到的两个固定参数:
#!/bin/bash processed_args=() while [[ $# -gt 0 ]]; do case "$1" in # 匹配被解析后的spark-submit路径,还原成带变量的形式 /bin/spark-submit) processed_args+=('${SPARK_HOME}/bin/spark-submit') ;; # 匹配被解析后的hostname部分,还原完整变量 yarn.resourcemanager.hostname:18080) processed_args+=('${hadoopconf-yarn.resourcemanager.hostname}:18080') ;; # 其他参数直接保留 *) processed_args+=("$1") ;; esac shift done # 用处理后的参数执行命令 "${processed_args[@]}"
这个方案适合参数格式比较固定的场景,要是参数经常变,维护起来会麻烦点,但胜在不用改Java代码。
3. 让Java用参数数组传递命令(最优解)
很多Java执行外部命令的API(比如ProcessBuilder)支持传递参数数组,而不是把所有参数拼成一个字符串。这样每个参数都是独立的字符串,Bash根本不会去解析里面的变量——因为Shell根本没机会拆分或解析整个命令串。
比如Java代码可以这么写:
ProcessBuilder pb = new ProcessBuilder( "./spark_submit.sh", "${SPARK_HOME}/bin/spark-submit", "--master", "yarn", "--deploy-mode", "cluster", "--conf", "spark.yarn.historyServer.address=${hadoopconf-yarn.resourcemanager.hostname}:18080" ); pb.start();
这种方式下,spark_submit.sh收到的每个参数都是你想要的原始字符串,完全不会出现解析问题,这应该是最彻底的解决办法。
内容的提问来源于stack exchange,提问作者Gregory Danenberg

