0

Let's assume the following script:

echo "if test 1 -eq $1 ; then echo ls;fi" | tee /tmp/toto
cat /tmp/toto
$(cat /tmp/toto)

While running it with this command: ./non_secure_script 1

I get the following error: if: command not found

However, the following command run with success:

$(if test 1 -eq 1; then echo ls;fi)

Why is this happening?

Guillaume D
  • 2,202
  • 2
  • 10
  • 37
  • It's due to the order in which different components of the command line are processed. Just use `bash /tmp/toto`. Also `bash -c "$(cmd sub)"` and `eval "$(cmd sub)"` for command subs, but don't use that to execute a file. – dan Dec 16 '21 at 07:19

1 Answers1

1

Because most parsing of traditional/standard (i.e. not *csh, rc, etc) shell input, in particular recognizing reserved words like if and compound commands like if ...; then ...; fi is done before expansions like $( command ). See the sequence defined by POSIX particularly steps 2 and 3 and sections 2.2-2.4 later on the page, versus step 4 and sections 2.5-2.6 later on the page, not 'wordexp' as linked(!!!).

If you did $(/tmp/toto) or $(. /tmp/toto) -- or $(source /tmp/toto) on shells that have that as a synonym -- it would execute the if-construct in the subshell and return to the main shell only the string ls (or nothing). Since the main shell has parsed this as a simple-command (see section 2.9.1 on the page linked above) before expanding, and ls is a valid simple-command, it is executed successfully. Because word-splitting and pathname-expansion (often called globbing) are done (unless disabled) as the last steps of expansion in section 2.6, returning a string with multiple space-separated words like ls -la would work, unless they contain glob operators like ? or *, but quoting like ls -la 'file with spaces' would not work.

Your second case $(if...fi) works for the same reason; it executes the if-construct in the subshell and returns only ls to be executed in the main shell, which is a valid simple-command.

dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70