在Bash脚本的sudo heredoc中设置变量的方法求助
这个问题的核心是here-document(即<< EOF这种输入重定向)的变量解析时机在搞鬼,我来给你拆解清楚并解决:
为什么你的代码会失效?
当你使用<< EOF而没有给EOF加引号时,当前执行sudo的shell会先解析所有的变量引用(比如${V})和命令替换(比如$(ls)),再把解析后的内容传给sudo -u other_user bash执行。
举个例子看你的第一个代码块:
sudo -u other_user bash << EOF V=test echo "${V}" EOF
当前shell会先解析${V},而此时你的当前shell里根本没设置过V变量,所以解析后echo "${V}"就变成了echo "",传给子shell后自然输出空。
第二个代码块里的$(ls)也是一样,它会先在当前用户的环境下执行ls,而不是切换到other_user后执行,然后把结果赋值给当前shell的V(但你没在当前shell定义V,最后还是输出空)。
两种有效的解决方案
方案1:用单引号包裹EOF(推荐)
给EOF加上单引号'EOF',这样当前shell会完全不解析here-document里的内容,直接把原内容传给sudo后的bash执行,变量和命令替换都会在目标用户的shell环境里处理:
基础示例修正后:
sudo -u other_user bash << 'EOF' V=test echo "${V}" EOF
执行后会输出test,符合预期。
贴近实际的场景代码修正后:
sudo -u other_user bash << 'EOF' cd V=$(ls) echo "${V}" EOF
这时候cd会切换到other_user的家目录,$(ls)会列出该目录下的文件,V会被正确赋值并输出。
方案2:转义变量和命令替换
如果你不想给EOF加引号,可以手动转义所有需要在子shell里解析的变量和命令替换,在前面加反斜杠\:
基础示例:
sudo -u other_user bash << EOF V=test echo "\${V}" EOF
实际场景示例:
sudo -u other_user bash << EOF cd V=\$(ls) echo "\${V}" EOF
这种方式也能达到效果,但如果脚本里有大量变量和命令替换,写起来会很繁琐,所以更推荐方案1。
总结
本质是要区分当前shell的解析阶段和目标用户shell的执行阶段,通过单引号包裹EOF或者转义的方式,让变量和命令替换在目标用户的shell环境里被处理,就能解决变量未设置的问题了。
内容的提问来源于stack exchange,提问作者Ryan Haining

