Here is a problem:
In my bash scripts I want to source several file with some checks, so I have:
if [ -r foo ] ; then
source foo
else
logger -t $0 -p crit "unable to source foo"
exit 1
fi
if [ -r bar ] ; then
source bar
else
logger -t $0 -p crit "unable to source bar"
exit 1
fi
# ... etc ...
Naively I tried to create a function that do:
function safe_source() {
if [ -r $1 ] ; then
source $1
else
logger -t $0 -p crit "unable to source $1"
exit 1
fi
}
safe_source foo
safe_source bar
# ... etc ...
But there is a snag there.
If one of the files foo, bar, etc. have a global such as —
declare GLOBAL_VAR=42
— it will effectively become:
function safe_source() {
# ...
declare GLOBAL_VAR=42
# ...
}
thus a global variable becomes local.
The question:
An alias in bash seems too weak for this, so must I unroll the above function, and repeat myself, or is there a more elegant approach?
… and yes, I agree that Python, Perl, Ruby would make my life easier, but when working with legacy system, one doesn’t always have the privilege of choosing the best tool.
Yes, Bash’s ‘eval’ command can make this work. ‘eval’ isn’t very elegant, and it sometimes can be difficult to understand and debug code that uses it. I usually try to avoid it, but Bash often leaves you with no other choice (like the situation that prompted your question). You’ll have to weigh the pros and cons of using ‘eval’ for yourself.
Some background on ‘eval’
If you’re not familiar with ‘eval’, it’s a Bash built-in command that expects you to pass it a string as its parameter. ‘eval’ dynamically interprets and executes your string as a command in its own right, in the current shell context and scope. Here’s a basic example of a common use (dynamic variable assignment):
See the Advanced Bash Scripting Guide for more info and examples: http://tldp.org/LDP/abs/html/internal.html#EVALREF
Solving your ‘source’ problem
To make ‘eval’ handle your sourcing issue, you’d start by rewriting your function, ‘safe_source()’. Instead of actually executing the command, ‘safe_source()’ should just PRINT the command as a string on STDOUT:
Also, you’ll need to change your function invocations, slightly, to actually execute the ‘eval’ command:
(Those are backticks/backquotes, BTW.)
How it works
In short:
It’s kind of complex. Like I said, ‘eval’ is not exactly elegant. In particular, there are a couple of special things you should notice about the changes we made:
Most of these changes are motived by the fact that ‘eval’ won’t handle newlines. It can only deal with multiple commands if we combine them into a single line delimited by semicolons, instead. The new function’s line breaks are purely a formatting convenience for the human eye.
If any of this is unclear, run your script with Bash’s ‘-x’ (debug execution) flag turned on, and that should give you a better picture of exactly what’s happening. For instance, in the function context, the function actually produces the ‘eval’ command string by executing this command:
Then, in the main context, the main script executes this:
Finally, again in the main context, the eval command executes these two commands if exists:
Good luck.