使用正确的引用将命令写入脚本文件

使用正确的引用将命令写入脚本文件

我遇到过几次的情况:我有一个 shell 函数,它执行一些设置,然后调用或多或少的任意命令,如下所示:

setup_and_run() {
    some_setup_commands
    "$@"
}

我想更改此函数以编写一个脚本文件,我可以使用该脚本文件在完成相同的设置后重新运行该命令。我可以执行以下操作,当命令中没有带空格或其他奇怪内容的参数时,该操作有效:

setup_and_run() {
    some_setup_commands
    cat >>rerun.sh <<EOF
some_setup_commands
$@
EOF
    "$@"
}

是否有一种标准方法可以改变这一点,以便即使某些参数具有空格或其他特殊字符,一般情况下也可以使用正确的引用编写脚本?

例如,对于 的第二个定义setup_and_run,如果我运行

$ setup_and_run ls "my cat's \"password\""
my cat's "password"

那么rerun.sh将包含

some_setup_commands
ls my cat's "password"

而我希望它包含类似的东西

some_setup_commands
ls "my cat's \"password\""

或者

some_setup_commands
ls my\ cat\'s\ \"password\"

或类似的东西。

在紧要关头我可以将其外包给Python之类的东西shlex.quote(),但我正在寻找可以在 shell 本身中完成的操作(如果可能的话),或者至少使用轻量级标准 UNIX 工具。

答案1

在 bash、ksh93 和 zsh 中,您可以使用转义%qprintf内置

#!/bin/bash
# also works in ksh93, zsh
setup_and_run() {
    some_setup_commands
    cat >>rerun.sh <<EOF
some_setup_commands
$(printf '%q ' "$@")
EOF
    "$@"
}

在 bash 中,您可以使用 将printf -v输出写入变量而不是标准输出。

#!/bin/bash
printf -v cmd '%q ' "$@"
setup_and_run() {
    some_setup_commands
    cat >>rerun.sh <<EOF
some_setup_commands
$cmd
EOF
    "$@"
}

在 zsh 中,有一种不同的方法,即q 参数扩展标志,后面j: :用空格连接数组元素。

#!/bin/zsh
setup_and_run() {
    some_setup_commands
    cat >>rerun.sh <<EOF
some_setup_commands
${(j: :)${(q)@}}
EOF
    "$@"
}

在普通的 sh 中,这更困难。引用单词的可靠方法是在单词两边加上单引号,并将其中的每个单引号替换为'\''。正确进行替换并不容易,涉及对 sed 的外部调用或循环。

#!/bin/sh
quote_simple_command () {
  quoted=
  for raw in "$@"; do
    quoted="$quoted'"
    while case "$raw" in *\'*) true;; *) false;; esac; do
      quoted="$quoted${raw%%\'*}'\\''"
      raw="${raw#*\'}"
    done
    quoted="$quoted$raw' "
  done
  printf %s "${quoted% }"
}
setup_and_run() {
    some_setup_commands
    cat >>rerun.sh <<EOF
some_setup_commands
$(quote_simple_command "$@")
EOF
    "$@"
}

相关内容