我有一个古老的 bash 脚本,我想改进它。这个脚本使用了许多变量,在某些情况下,一些变量被引用但为空或未设置。这通常不是问题,因为脚本从一开始就是这样设计的。
然而,我已经开始使用外壳检查它经常抱怨缺少引号。所以我添加了引号,但遇到了一个麻烦,而我没有很好的解决方案。
有很多行看起来像这样:
cmd $param1 $param2 $param3 $param4
如果其中一个参数为空或未设置,它仍可工作,并且 cmd 获取的参数数量会减少。但如果我开始添加引号
cmd "$param1" "$param2" "$param3" "$param4"
cmd 总是会得到 4 个参数,无论其中任何一个是否为空。
为什么会这样,这非常清楚。解决方法也很清楚(在使用参数之前检查是否为空),但它很丑陋,并且需要大量额外的代码。
因此,我正在寻找一个巧妙的解决方案,要么 a)如果参数为空,则省略该参数(包括引号!) b)仅为非空变量添加引号
答案1
您使用什么 shell 来编写此脚本?我假设它是 /bin/sh
POSIX shell 中唯一类似数组的实体是位置参数。你可以这样做:
set -- # clear the positional parameters
for param in "$param1" "$param2" "$param3" "$param4"; do
if [ -n "$param" ]; then
# append this param to the positional params
set -- "$@" "$param"
fi
done
# invoke the command with the positional params, properly quoted
cmd "$@"
如果您想要更聪明地做到这一点,请将 cmd 的调用包装在一个函数中:
invoke_cmd() {
n="$#" # remember the original number of params
# append to the args list only if non-empty
for arg; do [ -n "$arg" ] && set -- "$@" "$arg"; done
# discard the original params (including the empty ones)
shift "$n"
# and invoke the cmd
cmd "$@"
}
invoke_cmd "$param1" "$param2" "$param3" "$param4"
对于实际的 bash,简化
invoke_cmd() {
local args=()
for arg; do [[ "$arg" ]] && args+=("$arg"); done
cmd "${args[@]}"
}
invoke_cmd "$param1" "$param2" "$param3" "$param4"
答案2
另一个选项(除了@glennjackman 描述的之外)是使用条件扩展,:+
仅当变量被设置并且非空时才包含一个(正确引用的)变量:
cmd ${param1:+"$param1"} ${param2:+"$param2"} ${param3:+"$param3"} ${param4:+"$param4"}
(注:条件扩展在posix 规范,在第 2.6.2 节中;但我不完全确定所有 posix shell 都会正确尊重替代值中的双引号。)
如果您愿意对脚本进行更广泛的更改,并将其限制为 bash(即如果您使用 a#!/bin/bash
或#!/usr/bin/env bash
shebang),那么在数组中积累参数通常更清晰:
cmd_parameters=() # start with an empty array
if [ something ]; then
cmd_parameters+=("$param1") # Add an element -- note that the () are critical here!
fi
if [ something_else ]; then
cmd_parameters+=("$param2")
fi
...etc
cmd "${cmd_parameters[@]}"
请注意,完全可以混合使用这些方法,包括使用:+
技巧有条件地添加数组元素:
cmd_parameters+=(${param3:+"$param3"})
和/或将数组和条件参数混合到同一个命令中:
cmd "${cmd_parameters[@]}" ${param4:+"$param4"}
此外,根据@glennjackman 对包装函数的建议,可以编写一个通用的包装函数:
conditional_args() {
# invoke a command ($1) with any empty arguments omitted
args=()
for arg; do
args+=(${arg:+"$arg"})
done
"${args[@]}" # Note that the first "arg" is actually the command itself
}
conditional_args cmd "$param1" "$param2" "$param3" "$param4"