我今天遇到了一个奇怪的错误,当在包含带括号的目录的目录中运行脚本时,例如a()
.
最小工作示例
我设法将错误减少到以下最小工作示例:
/tmp
在其中创建一个空目录cd
:
mkdir /tmp/foo
cd /tmp/foo
创建一个名为foo.sh其中包含:
foo() {
somevar=1;
case somevar in
aha) echo "something" ;;
*) echo "other" ;;
esac;
};
运行以下命令:
eval $(/bin/cat foo.sh)
不应该有任何错误。
创建一个带括号的文件:
touch "a()"
再次运行命令:
eval $(/bin/cat foo.sh)
我现在收到错误:
bash: syntax error near unexpected token `('
为什么 bash 甚至关心目录中有哪些文件?为什么括号会导致错误?
系统信息:
$ 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
更详细的背景和原始错误:
/usr/share/modules/init/bash
我的问题来自于使用来自包的脚本environment-modules
,总结如下:
$ 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='' ;;'
答案1
这既不奇怪,也不是一个错误bash
(尽管它似乎确实是一个错误/usr/share/modules/init/bash
)。您正在将不带引号的命令替换与eval
.作为命令替换结果的字符串,由于未加引号,因此将进行分词和文件名扩展(通配符)。代码中的*)
与 filename 匹配a()
,因此在文件名扩展阶段会替换为该文件名。
在下面运行您的示例set -x
突出显示了这一点:
$ 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 `('
shell中同样的事情yash
:
$ 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
与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
和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 ")")
只有zsh
会处理这个问题,因为它不执行通配:
$ eval $(cat foo.sh)
+zsh:2> cat foo.sh
+zsh:2> eval 'foo()' '{' 'somevar=1;' case somevar in 'aha)' echo '"something"' ';;' '*)' echo '"other"' ';;' 'esac;' '};'
处理这个问题的正确方法是来源剧本foo.sh
:
. ./foo.sh
eval "$(cat foo.sh)"
据我所知,确实没有理由使用。
这也是一个代码注入漏洞:
$ touch '*) echo "hello" ;; *)'
$ eval $(cat foo.sh)
$ declare -f foo
foo ()
{
somevar=1;
case somevar in
aha)
echo "something"
;;
*)
echo "hello"
;;
*)
echo "other"
;;
esac
}
轻松破坏此命令的另一种方法没有创建一个特殊命名的文件,是将IFS
变量设置为默认字符集之外的一组字符:
$ 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'
这会破坏它,因为在评估eval
.使用 时IFS=';{} '
,每个字符都将用于将文本分割foo.sh
成单词(然后这些字符将从字符串中删除)。
甚至zsh
不能幸免:
$ 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 `)'
有关的: