5

I encountered a strange bug today, when running a script in a directory containing a directory with parentheses in it, such as a().

Minimal working example

I managed to reduce the bug to the following minimal working example:

Create an empty directory in /tmp and cd into it:

mkdir /tmp/foo
cd /tmp/foo

Create a script named foo.sh in it containing:

foo() {
  somevar=1;
  case somevar in
      aha) echo "something" ;;
      *) echo "other" ;;
  esac;
};

Run the following command:

eval $(/bin/cat foo.sh)

There should not be any error.

Create a file with parentheses:

touch "a()"

Run the command again:

eval $(/bin/cat foo.sh)

I now get the error:

bash: syntax error near unexpected token `(' 

Why does bash even care about what files are in the directory? Why do parentheses cause an error?

System information:

$ bash --version
GNU bash, version 4.4.19(1)-release (x86_64-pc-linux-gnu)
Copyright © 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.1 LTS
Release:        18.04
Codename:       bionic

More detailed background and original error:

My problem came from using a script sourcing /usr/share/modules/init/bash from the environment-modules package, as summarized here:

$ dpkg -l environment-modules 
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name                                                          Version                             Architecture                        Description
+++-=============================================================-===================================-===================================-================================================================================================================================
ii  environment-modules                                           4.1.1-1                             amd64                               Modular system for handling environment variables
$ source /usr/share/modules/init/bash
$ touch "a()"
$ source /usr/share/modules/init/bash
bash: eval: line 43: syntax error near unexpected token `('
bash: eval: line 43: ` a() _mlshdbg='' ;;'
13

This is neither strange nor a bug in bash (it does seem to be a bug in /usr/share/modules/init/bash though). You are using an unquoted command substitution together with eval. The string that is the result of the command substitution will, since it is unquoted, undergo word splitting and filename expansion (globbing). The *) in the code matches the filename a(), so it is replace by this filename in the filename expansion stage.

Running your example under set -x highlights this:

$ eval $(cat foo.sh)
++ cat foo.sh
+ eval 'foo()' '{' 'somevar=1;' case somevar in 'aha)' echo '"something"' ';;' 'a()' echo '"other"' ';;' 'esac;' '};'
bash: syntax error near unexpected token `('

The same thing in the yash shell:

$ eval $(cat foo.sh)
+ cat foo.sh
+ eval 'foo()' '{' 'somevar=1;' case somevar in 'aha)' echo '"something"' ';;' 'a()' echo '"other"' ';;' 'esac;' '};'
eval:1: syntax error: `)' is missing
eval:1: syntax error: `esac' is missing
eval:1: syntax error: `}' is missing

And with ksh93:

$ eval $(cat foo.sh)
+ cat foo.sh
+ eval 'foo()' '{' somevar='1;' case somevar in 'aha)' echo '"something"' ';;' 'a()' echo '"other"' ';;' 'esac;' '};'
ksh93: eval: syntax error: `(' unexpected

And dash:

$ eval $(cat foo.sh)
+ cat foo.sh
+ eval foo() { somevar=1; case somevar in aha) echo "something" ;; a() echo "other" ;; esac; };
dash: 1: eval: Syntax error: "(" unexpected (expecting ")")

Only the zsh would handle this as it does not perform the globbing:

$ eval $(cat foo.sh)
+zsh:2> cat foo.sh
+zsh:2> eval 'foo()' '{' 'somevar=1;' case somevar in 'aha)' echo '"something"' ';;' '*)' echo '"other"' ';;' 'esac;' '};'

The correct way to handle this would be to source the foo.sh script:

. ./foo.sh

There is really no reason to use eval "$(cat foo.sh)" as far as I can see.

This is also a code injection vulnerability:

$ touch '*) echo "hello" ;; *)'
$ eval $(cat foo.sh)
$ declare -f foo
foo ()
{
    somevar=1;
    case somevar in
        aha)
            echo "something"
        ;;
        *)
            echo "hello"
        ;;
        *)
            echo "other"
        ;;
    esac
}

Another way of breaking this command easily without creating a specially named file, is to set the IFS variable to a set of characters other than the default:

$ IFS=';{} '
+ IFS=';{} '
$ eval $(cat foo.sh)
++ cat foo.sh
+ eval 'foo()' '
' somevar=1 '
' case somevar 'in
' 'aha)' echo '"something"' '' '
' '*)' echo '"other"' '' '
' esac '
' ''
bash: syntax error near unexpected token `somevar=1'

This breaks it because of the word-splitting step rather than the file globbing step in the evaluation of the arguments to eval. With IFS=';{} ', each of those characters would be used to split the text in foo.sh up into words (and those characters would then be removed from the string).

Not even zsh would be immune to this:

$ IFS=';{} '
+zsh:2> IFS=';{} '
$ eval $(cat foo.sh)
+zsh:3> cat foo.sh
+zsh:3> eval 'foo()' $'\n' 'somevar=1' $'\n' case somevar $'in\n' 'aha)' echo '"something"' '' $'\n' '*)' echo '"other"' '' $'\n' esac $'\n' '' ''
zsh: parse error near `)'

Related:

  • 1
    Thanks. I learned a bit more about bash again. :) (In particular, "set -f" can fix the error too by disable filename expansion.) I also finally upgraded to Ubuntu 18.10, which fixed the environment-modules error. – KIAaze Feb 18 at 22:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.