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?
I recommend avoiding
eval
if possible. For your logging use case, you could take a look at the shell builtincaller
. If you need more information, you can use the variablesBASH_SOURCE
,BASH_LINENO
andFUNCNAME
. Note that all of these variables are arrays and contain the full call stack. See the following example:(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:Then pass your log message via standard input using a here doc or a here string:
For your specific case of logging, you may want to look at this function which prints the caller's context's file function and line number.
Reusable quoting function
This function will do correct quoting for you:
Example usage:
Example usage:
Above, note the
single token
's space is quoted as\
.This shows that the spaces in tokens are preserved.