【问题标题】:Bash parameter quotes and evalBash 参数引号和评估
【发布时间】:2012-05-22 17:41:47
【问题描述】:

我编写了一个 bash 日志库,用我公司目前正在使用的一些复杂脚本来实现。在进行日志调用时,我一直在提供脚本文件名 (${BASH_SOURCE}) 和调用脚本的行号 (${LINENO})。但是,我不想依赖用户或实现脚本来将这两个变量作为参数传递。如果这是 C/C++,我只需创建一个宏,将“__FILE__”和“__LINE__”添加到参数列表中。

我终于能够让这部分工作。以下是一些非常简化的摘要作为概念证明:

这是日志库:

# log.sh

LOG="eval _log \${BASH_SOURCE} \${LINENO}"

_log () {
    _BASH_SOURCE=`basename "${1}"` && shift
    _LINENO=${1} && shift

    echo "(${_BASH_SOURCE}:${_LINENO}) $@"
}

还有一个执行测试脚本:

# MyTest.sh

. ./log.sh

${LOG} "This is a log message"
# (test.sh:5) This is a log message

这很好用(而且,一开始我很高兴能让它工作)。然而,这有一个明显的问题:quotes 和 eval 之间的交互。如果我打电话:

${LOG} "I'm thrilled that I got this working"
# ./test.sh: eval: line 5: unexected EOF while looking for matching `''
# ./test.sh: eval: line 6: syntax error: unexpected end of file

现在,我相信我明白为什么会发生这种情况了。带引号的参数在传递给 eval 时保持不变,但此时,内容按原样放置到生成的命令字符串中。我知道我可以通过转义来解决这个问题;但是,我真的不想强制实现脚本必须这样做。在我实现这个“eval 宏”功能之前,我让用户直接调用“_log”并允许他们有选择地传入“${LINENO}”。有了这个实现,上面的失败调用(只有一个引用的句子)工作得很好。

在最基本的层面上,我真正想要的只是让脚本能够调用 [log function/macro] "String to log with special characers" 并让生成的日志消息包含调用脚本的文件名和行号,后跟日志消息。如果可能的话,我会假设我非常接近,但如果我忽略了一些需要不同方法的东西,我也对此持开放态度。我不能强迫用户转义他们的所有消息,因为这可能会导致他们不使用这个库。这是一个如此大的问题,如果我找不到解决方案,我可能会恢复到旧功能(这需要将 ${LINENO} 作为函数参数传递 - 这样的侵入性要小得多)。

TLDR:有没有办法让 eval 尊重带引号的参数中的特殊字符,而不必转义它们?

【问题讨论】:

  • 如果您使用 bash 特定的结构,您的日志记录脚本不应称为“log.sh”。它应该是“log.bash”。
  • @WilliamPursell - 我进行了一些搜索,但找不到任何关于 shell 脚本命名约定的标准。事实上,大多数人似乎完全去掉了后缀。老实说,除了“*.sh”(或者没有扩展名/后缀的脚本)之外,我从未见过任何东西。在所有的实用性上,它应该不会有任何影响,对吧?解释器要么从脚本的第一行提取,要么使用您当前的 shell。似乎它本质上只是归结为个人喜好。但是,也许“*.sh”可能暗示用户该脚本使用“sh”。
  • 唯一的实际影响是开发人员是否将您的函数与不同的外壳一起使用。使用“*.sh”名称,开发人员会合理地假定您的库可以在 zsh 脚本中获取。
  • 要点,我会记住的。谢谢!

标签: bash shell variables eval


【解决方案1】:

我建议尽可能避免使用eval。对于您的日志记录用例,您可以查看内置的 shell caller。如果您需要更多信息,可以使用变量BASH_SOURCEBASH_LINENOFUNCNAME。请注意,所有这些变量都是数组并包含完整的调用堆栈。请参阅以下示例:

#! /bin/bash       

function log() {
    echo "[$( caller )] $*" >&2
    echo "BASH_SOURCE: ${BASH_SOURCE[*]}"
    echo "BASH_LINENO: ${BASH_LINENO[*]}"
    echo "FUNCNAME: ${FUNCNAME[*]}"
}

function foobar() {
    log "failed:" "$@"
}

foobar "$@"

【讨论】:

  • 我从来不知道那些是数组!如果我知道这一点,我早就会想出一个不同的解决方案......调用者非常接近我想要的,虽然我很挑剔并且可能更喜欢 ['filename':'line']。但是,我当然可以接受来电者所拥有的。但是,因为这些内置变量都是数组,所以我有(就像你说的那样)我的堆栈跟踪可以使用。我应该能够从函数中提取必要的信息。这将使我恢复到函数调用而不是变量宏。谢谢!!!
  • 请注意:如果您想控制文件名:行的格式,您可以在数组赋值中使用调用者并分别使用文件名和行:local fl=($(caller)); echo "$(fl[1]) 处的错误:$(fl[0])"
  • @Floyd 我认为您的意思是“${fl[1]}:${fl[0]} 处的错误”,对吧?
  • 是的,它应该是大括号,而不是圆括号,它会尝试执行 fl[1] :/
【解决方案2】:

(注意:这解决了引用的直接问题,但@nosid 的回答是关于 访问调用堆栈要好得多)

稍微更改_log 的定义,从标准输入读取而不是 从位置参数获取日志消息:

_log () {
    # Set up _BASH_SOURCE and _LINENO the same way

    cat <(echo -n "$(_BASH_SOURCE:$_LINENO) ") -
}

然后使用 here doc 或 here 字符串通过标准输入传递您的日志消息:

${LOG} <<<"This is a log message"
${LOG} <<<"I'm thrilled this works, too!"
${LOG} <<HERE
Even this long
message works as
intended!
HERE

【讨论】:

  • 感谢您的意见!我可能会选择 nosid 的解决方案,因为我可以看到开发人员抱怨像“
【解决方案3】:

对于您的特定日志记录情况,您可能需要查看this function,它会打印调用者的上下文的文件函数和行号。

可重复使用的引用功能

这个函数会为你做正确的引用:

function token_quote {
  local quoted=()
  for token; do
    quoted+=( "$(printf '%q' "$token")" )
  done
  printf '%s\n' "${quoted[*]}"
}

使用示例:

使用示例:

$ token_quote token 'single token' token
token single\ token token

在上面,请注意single token 的空格被引用为\

$ set $(token_quote token 'single token' token)
$ eval printf '%s\\n' "$@"
token
single token
token
$

这表明标记中的空格被保留了。

【讨论】:

    猜你喜欢
    • 2011-09-18
    • 2018-10-08
    • 2018-10-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多