Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bash parameter quotes and eval

I've written a bash logging library to be implemented with some complex scripts that my company is currently using. I've been deadset on providing the script filename (${BASH_SOURCE}) and the line number (${LINENO}) of the calling script when making the log calls. However, I didn't want to have to rely on the user or implementing script to pass in these two variables as parameters. If this were C/C++, I would just create a macro that prepends "__FILE__" and "__LINE__" to the parameter list.

I was finally able to get this part working. Here are some much-simplified abstracts as a proof of concept:

Here's the logging library:

# log.sh

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

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

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

And an implementing test script:

# MyTest.sh

. ./log.sh

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

This works pretty well (and, I was thrilled to get it working at first). However, this has one glaring problem: the interaction between quotes and eval. If I make the call:

${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

Now, I believe that I understand why this is happening. The quoted parameters are kept intact as they're passed to eval, but at that point, the content is placed as-is into the resulting command string. I know that I can fix this by doing some escaping; however, I REALLY do not want to force the implementing scripts to have to do this. Before I implemented this "eval macro" capability, I had users make calls directly to "_log" and allowed them to optionally pass in "${LINENO}." With this implementation, The failing call above (with only a quoted sentence) worked just fine.

At the most basic level, all I really want is for a script to be able to call [log function/macro] "String to log with special characers" and have the resulting log message contain the filename and line number of the calling script, followed by the log message. If it's possible, I would assume that I'm very close, but if there's something I'm overlooking that would require a different approach, I'm open to that as well. I can't force the users to escape all of their messages, as that will likely cause them to not use this library. This is such a problem that if I can't find a solution to this, I'll likely revert to the old capability (which required ${LINENO} to be passed as a function parameter - this was much less intrusive).

TLDR: Is there any way to get eval to respect special characters within a quoted parameter, without having to escape them?

like image 208
LousyG Avatar asked May 22 '12 17:05

LousyG


2 Answers

I recommend avoiding eval if possible. For your logging use case, you could take a look at the shell builtin caller. If you need more information, you can use the variables BASH_SOURCE, BASH_LINENO and FUNCNAME. Note that all of these variables are arrays and contain the full call stack. See the following example:

#! /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 "$@"
like image 155
nosid Avatar answered Dec 07 '22 04:12

nosid


(Note: this solves the immediate problem of quoting, but @nosid's answer about accessing the call stack is much better)

Change your definition of _log slightly, to read from standard input instead of taking the log message from positional parameters:

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

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

Then pass your log message via standard input using a here doc or a here string:

${LOG} <<<"This is a log message"
${LOG} <<<"I'm thrilled this works, too!"
${LOG} <<HERE
Even this long
message works as
intended!
HERE
like image 35
chepner Avatar answered Dec 07 '22 02:12

chepner