I have a niche requirement to run commands stored in another config file within a wrapper script for another utility. My wrapper script (below) works for every command in that config file that does not use "runuser" and also include arguments. If a command uses runuser and my "-c" command includes arguments, the script fails.
The wrapper script
#!/bin/bash
nagios_cmd=$(grep $1 /etc/nagios/nrpe.cfg | awk -F "=" {'print $2'})
exec=$($nagios_cmd)
if [ $? -eq 0 ]
then
#exitok
echo $exec
exit 0
else
#exitcritical
echo $exec
exit 1001
fi
The config file
command[check_crsdb_state]=sudo /usr/lib64/nagios/plugins/check_crsdb_state
command[check_crsasm_state]=sudo /usr/lib64/nagios/plugins/check_crsasm_state
command[check_ora1_tablespace_apex]=sudo /usr/sbin/runuser -l oracle -c '/check_oracle_tablespace APEX 32000'
command[check_ora1_tablespace_lob1]=sudo /usr/sbin/runuser -l oracle -c '/check_oracle_tablespace LOB1 32000'
Successful Script Run
[root@quo-mai-ora1 /]# ./rmmwrapper.sh check_crsasm_state
OK - All nodes report 'Started,STABLE'
[root@quo-mai-ora1 /]#
Failure Script Run
[root@quo-mai-ora1 /]# ./rmmwrapper.sh check_ora1_tablespace_apex
APEX: -c: line 0: unexpected EOF while looking for matching `''
APEX: -c: line 1: syntax error: unexpected end of file
[root@quo-mai-ora1 /]#
Failure Script Run (with bash -x)
[root@quo-mai-ora1 /]# bash -x ./rmmwrapper.sh check_ora1_tablespace_apex
grep check_ora1_tablespace_apex /etc/nagios/nrpe.cfg
awk -F = '{print $2}'
nagios_cmd='sudo /usr/sbin/runuser -l oracle -c '\''/check_oracle_tablespace APEX 32000'\'''
sudo /usr/sbin/runuser -l oracle -c ''\''/check_oracle_tablespace' APEX '32000'\'''
APEX: -c: line 0: unexpected EOF while looking for matching `''
APEX: -c: line 1: syntax error: unexpected end of file
exec=
'[' 1 -eq 0 ']'
echo
exit 1001
[root@quo-mai-ora1 /]#
The Problem
You can see in the bash -x output, that for some reason when the $nagios_cmd gets executed, it places single quotes before the spaces separate multiple args that are supplied to that resulting script (/check_oracle_tablespace). I've tried different ways of executing $nagios_cmd (using backticks instead etc. I've tried escaping the space characters by modifying the config file to look like this:
command[check_ora1_tablespace_apex]=sudo /usr/sbin/runuser -l oracle -c '/check_oracle_tablespace\ APEX\ 32000'
I've also tried encapsulating the command after -c on runuser in double quotes instead of single, or just no quotes at all.
I'm clearly missing something fundamentally wrong with bash. How can I get the script to just execute the contents of $nagios_cmd as it appears in plain text?
CodePudding user response:
This looks like one of those rare cases where eval
is actually the right answer. Try this:
exec=$(eval "$nagios_cmd")
Explanation: bash doesn't expand variables until fairly late in the process of parsing commands, so the string in the variable isn't parsed like it would be if it were actually part of the command. In this case, the problem is that it's expanded after quotes and escapes have been parsed, so it's too late for the single-quotes around the multi-word command to have their intended effect. See BashFAQ #50: "I'm trying to put a command in a variable, but the complex cases always fail!"
What eval
does is essentially re-run the entire command parsing process from the beginning. So the variable gets expanded out to the command you want to run (including quotes, etc), and that gets parsed like it would be normally.
Note that I did put double-quotes around the variable; that's so it doesn't go through the partial-parsing process that is done to unquoted variable references, and then through the full parsing process. This one-and-a-half-times parsing process can have rare but really weird effects, so it's best avoided.
Also: eval
has a well-deserved bad reputation (I've used the phrase "massive bug magnet" to describe it). This is because what it fundamentally does is treat data (e.g. the contents of variables) as executable code, so it's easy to find that you're doing things like executing parts of your filenames as commands. But in this case, your data is supposed to be a command, and is (hopefully) trusted not to contain malicious, invalid, etc content. In this case, you want the data to be treated as executable code.