Here is a minimal example:
#!/bin/bash
testFunc() (
shopt -s extglob
local posArgs=();
for((i=1; i<="$#"; i))
do
posArgs =("${!i}");
done
#<uncomment to see> echo "${posArgs[@]:0:$((${#posArgs[@]} - 1))}";
if ${posArgs[@]:0:$((${#posArgs[@]} - 1))};
then
return 0;
fi
return 1;
)
- Function body uses
()
instead of{}
to limit the scope of shopt - Function discards the last argument and executes the rest and checks exit code, if 0 returns 0, else returns 1. Quotes and command substitution are not used on purpose.
Here is what works:
testFunc "echo" "whatever" "discardThis"; #whatever
testFunc "pidof" "memcached" "discardThis"; #12435
testFunc "pwd" "discardThis"; #path/to/pwd
testFunc "pwd" "discardThis"; #path/to/pwd
testFunc "/bin/bash" "-c" "pwd" "discardThis"; #path/to/pwd
testFunc "/bin/bash" "-c" "pwd;pwd" "discardThis"; #path/to/pwd\npath/to/pwd NOTE:no space between ; and pwd
Here is what does not work:
testFunc "/bin/bash" "-c" "[[ 2 -eq 2 ]]" "discardThis"; #error
testFunc "/bin/bash" "-c" "\"[[ 2 -eq 2 ]]\"" "discardThis"; #error
testFunc "/bin/bash" "-c" "\"echo helloo\"" "discardThis"; #error
testFunc "/bin/bash" "-c" "echo helloo" "discardThis"; #exits with 0 but logs nothing
testFunc "/bin/bash" "-c" "pwd; pwd" "discardThis"; #logs pwd ONCE
testFunc "/bin/bash" "-c" "\"pwd; pwd\"" "discardThis"; #error
Above logs error like: helloo": -c: line 0: unexpected EOF while looking for matching...
Interestingly to make the above work, you can pass eval
:
testFunc "eval" "/bin/bash" "-c" "\"echo helloo\"" "discardThis"; #helloo
testFunc "eval" "/bin/bash" "-c" "\"[[ 2 -eq 2 ]]\"" "discardThis"; #exits with 0
testFunc "eval" "/bin/bash" "-c" "pwd; echo haha; pwd;" "discardThis"; #path/to/pwd\nhaha\npath/to/pwd
testFunc "eval" "/bin/bash" "-c" "pwd; pwd" "discardThis"; #Correctly logs pwd TWICE
Seems like when there is a command like echo
that expects an argument, or some other complex command eval
is needed. How can I avoid this? How can I make them work without eval.
I am not interested in implementing what I want, I am interested in why bin/bash
fails to parse a string like echo hi
.
CodePudding user response:
why bin/bash fails to parse a string like echo hi.
If you interested why the following (which was not in your examples):
testFunc "/bin/bash" "-c" "echo hi" "discardThis"
outputs nothing, then let's dive in:
${posArgs[@]:0:$((${#posArgs[@]} - 1))
The result of unquoted expansion undergoes word splitting (and filename expansion!! Lucky you, you didn't test with *
). Word splitting splits the stuff on spaces (and tabs and newlines, with default IFS), no matter what you input. The 3 words argument:
"/bin/bash" "-c" "echo hi"
become 4 words, echo hi
is split on spaces:
/bin/bash -c echo hi
Or more visible, so that you can see:
/bin/bash -c 'echo' hi
From man bash
:
-c [...] If there are arguments after the command_string, the first argument is assigned to $0 [...]
hi
is assigned to $0
for bash
, and then bash executes echo
with no arguments.
How can I avoid this?
Use quotes.
How can I make them work without eval.
Use quotes.
Check your script with shellcheck.