我正在编写一个小型实用程序脚本,可以对其进行参数化以另外time
执行它,并将其标准输出写入另一个文件。
让我们假设基本脚本很简单:
ls "$@"
那么以下将是一个幼稚的实现,这里cat
用作更复杂的输出处理的虚拟对象:
if [[ "$TIMEIT" -eq 1 ]]; then
if [[ "$LOGIT" -eq 1 ]]; then
time ls "$@" | tee >(cat)
else
time ls "$@"
fi
else
if [[ "$LOGIT" -eq 1 ]]; then
ls "$@" | tee >(cat)
else
ls "$@"
fi
fi
您可以看到问题所在,尤其是在应该添加新参数的情况下。
更灵活的重写是:
cmd="ls $@"
if [[ "$TIMEIT" -eq 1 ]]; then
cmd="time $cmd"
fi
if [[ "$LOGIT" -eq 1 ]]; then
cmd="$cmd | tee >(cat)"
fi
eval $cmd
这可行,但是一旦您将其称为:
./script.sh "file 1" file2
因为文件名不会被正确处理。
当我应用此解决方案时,我可以在命令前面添加以下内容时正确标记参数time
:
function token_quote {
local quoted=()
for token; do
quoted+=( "$(printf '%q' "$token")" )
done
printf '%s\n' "${quoted[*]}"
}
cmd=(ls "$@")
if [[ "$TIMEIT" -eq 1 ]]; then
cmd=(time "${cmd[@]}")
fi
eval "$(token_quote "${cmd[@]}")"
但是处理管道的相同方法不起作用。当我使用此解决方案对其进行调整时:
function eval_args {
local quoted=''
while (( $# )); do
if [[ $1 = '|' ]]; then
quoted+="| "
else
printf -v quoted '%s%q ' "$quoted" "$1"
fi
shift
done
eval "$quoted"
}
cmd=(ls "$@")
# prepend to array
if [[ "$TIMEIT" -eq 1 ]]; then
cmd=(time "${cmd[@]}")
fi
# append to array?
if [[ "$LOGIT" -eq 1 ]]; then
cmd=("${cmd[@]}" "|" "tee" ">(cat)")
fi
eval_args "${cmd[@]}"
它创建了一个文件>(cat)
……所以我要在这里走下兔子洞,看起来这不是要走的路。
我可以做些什么来构造命令和处理文件名中的空格/特殊字符,包括管道/进程替换?
评估
您的最后一种方法可能有效,只要您跳过它进行自定义添加并仅将其用于生成原始命令。也就是说,将 eval_args 更改为仅构建命令字符串:
实际上循环是不必要的,因为 bash 的 printf 会自动使用额外的参数重复相同的格式字符串:
Bash 5.1 有一个
${foo@Q}
修饰符,这是一个更方便的替代方案:替代评估
假设你的脚本在一个函数中有它的主要代码:
您可以有条件地为它定义一个包装函数:
(函数定义本身可以生成为字符串并进行评估。)
重新执行
在某些情况下,在新环境下重新执行整个脚本可能是有意义的:
杂项
至于重定向,您可以硬编码使用
tee
并仅交换文件名(效率不高,因为它仍然会导致 tee 运行并重复写入,但是......它可以完成工作):直接重定向到文件也是如此:
或者,您可以通过管道输入条件或包装条件的函数(由于强制,也不是 100% 有效
cat
):