如何在文本文件中扩展shell变量?

30
考虑一个ASCII文本文件(假设它包含非shell脚本语言的代码): Text_File.msh:
spool on to '$LOG_FILE_PATH/logfile.log';
login 'username' 'password';
....
现在,如果这是一个shell脚本,我可以运行它作为$ sh Text_File.msh,然后shell会自动展开变量。 我的目标是让shell展开这些变量,然后按照以下方式创建一个新文件:Text_File_expanded.msh
spool on to '/expanded/path/of/the/log/file/../logfile.log';
login 'username' 'password';
....
考虑:
$ a=123
$ echo "$a"
123

所以从技术上讲,这应该可以解决问题:

$ echo "`cat Text_File.msh`" > Text_File_expanded.msh

...但是它并没有按照预期工作,输出文件与源文件完全相同。

所以我不确定如何实现这一点...我的目标是使嵌入在我的非shell脚本中的目录路径更易于维护。这些脚本不能包含任何UNIX代码,因为它们不是由UNIX shell编译的。


1
你的变量是否像上面所示一样用单引号括起来? - Guru
@Guru - 目前,在文本文件中它要么是单引号,要么没有任何引号包围。 - Kent Pawar
1
相关问题:http://superuser.com/q/235738/126693 - abyss.7
10个回答

64

这个问题在另一个帖子中已经被问过了,以下是我认为最好的答案:

export LOG_FILE_PATH=/expanded/path/of/the/log/file/../logfile.log
cat Text_File.msh | envsubst > Text_File_expanded.msh

如果在Mac上,先安装gettextbrew install gettext

参见:Forcing bash to expand variables in a string loaded from a file


1
使用 envsubst 需要记住两件事情,一是环境变量必须被 export 才能正常工作,二是它并没有预装在很多系统中。如果在 Debian/Ubuntu 上,请执行以下命令安装:sudo apt-get install gettext - Neil C. Obremski

13

这个解决方案并不优雅,但是它有效。创建一个名为shell_expansion.sh的脚本:

echo 'cat <<END_OF_TEXT' >  temp.sh
cat "$1"                 >> temp.sh
echo 'END_OF_TEXT'       >> temp.sh
bash temp.sh >> "$2"
rm temp.sh

您可以按以下方式调用此脚本:

bash shell_expansion.sh Text_File.msh Text_File_expanded.msh

1
我之前没有“导出”这些变量。尝试使用$ export LOG_FILE_PATH=/sone/path/后,它正常工作了。谢谢! :) - Kent Pawar
不需要使用临时文件,只需使用eval-请参见@ThirteenThirtySeven的答案。此外,使用临时文件的方法有些问题(多个shell_expansion.sh实例将全部写入同一文件,权限问题,错误检查等-临时文件将创建许多潜在的复杂情况,您需要准备好处理或确切知道如何避免)。 - FooF
eval比使用临时文件更危险。 - mjd2
2
我发现 echo -e "cat <<END_OF_TEXT\n$(cat [filename])\nEND_OF_TEXT" | bash 可以在不使用临时文件的情况下很好地工作。 - JacobTDC
1
@mjd2 eval比使用临时文件更危险吗?我不同意这种说法。如果保留了临时文件,那么在事后进行故障分析以确定出错原因会更容易,但它并不能提供比eval更多的安全性。 - William Pursell
显示剩余5条评论

10
如果您想将其放在一行中(我不是Bash专家,所以可能会有注意事项,但在我尝试过的所有地方都可以使用):
当test.txt包含以下内容:
${line1}
${line2}

那么:

>line1=fark
>line2=fork
>value=$(eval "echo \"$(cat test.txt)\"")
>echo "$value"
line1 says fark
line2 says fork

显然,如果您只想打印它,可以删除额外的value=$()echo "$value"


2
如果test.txt包含例如"; touch /etc/your_system_is_pwned; echo ",则会发生糟糕的事情。 - Wildcard
谢谢,这太棒了。 - ocarlsen

9
如果您可以接受Perl解决方案:
示例文件:
$ cat file.sh
spool on to '$HOME/logfile.log';
login 'username' 'password';

解决方案:

$ perl -pe 's/\$(\w+)/$ENV{$1}/g' file.sh
spool on to '/home/user/logfile.log';
login 'username' 'password';

谢谢@Guru - 我尝试了一下,它运行得很好。由于我需要避免对Perl的依赖,所以我不能应用这个解决方案。我不是Perl专家,但我们确定\w总是能正确捕获shell变量名吗? - Kent Pawar
好的,我谷歌搜索并得到了答案 -
  1. PERL参考:\w将匹配“单词”字符(字母数字加“_”)。
  2. UNIX shell参考: 变量的名称只能包含字母数字和“_”。
- Kent Pawar
大家好 - 我上面的评论中有错别字:"_"被认为没有标记为代码,因此被用来使文本变成斜体.. 抱歉。 - Kent Pawar
我尝试了你的脚本,它几乎完美地工作了。它将替换所有环境变量,无论它们是否被定义。我认为避免替换未定义的变量会很有用。 - aprodan

8
上述答案的一个限制是它们都需要将变量导出到环境中。以下是我想出的解决方法,可以使变量仅在当前 shell 脚本中局部使用:
#!/bin/sh
FOO=bar;
FILE=`mktemp`; # Let the shell create a temporary file
trap 'rm -f $FILE' 0 1 2 3 15;   # Clean up the temporary file 

(
  echo 'cat <<END_OF_TEXT'
  cat "$@"
  echo 'END_OF_TEXT'
) > $FILE
. $FILE

上面的例子允许在命令行中命名的文件中替换变量$FOO。我相信它可以改进,但迄今为止这对我很有用。
感谢两位先前回答者提供的思路!

感谢 @PaulGear!欢迎来到 StackOverflow。点赞,因为使用 trap 来清理临时文件。我建议进行了一些编辑,以便像我这样的新手更容易理解代码;请审核这些编辑并在必要时还原它们。谢谢。 - Kent Pawar
我不确定“0”指的是哪个信号。 - Kent Pawar
2
0 表示正常退出而不是信号。 - Paul Gear
我不确定为什么您认为在变量赋值和陷阱中需要使用分号,但在回显和连接中不需要,但是这些注释很有用,所以我不打算恢复。 - Paul Gear
谢谢,保罗。关于semis - 只是在添加注释时添加它们;并不是必要的,只是习惯力量 :) - Kent Pawar
此解决方案将替换未定义的变量。有时您希望避免这种情况并保留变量占位符。 - aprodan

2

如果你要翻译的变量数量已知且有限,那么你可以自己进行翻译:

sed "s/\$LOG_FILE_PATH/$LOG_FILE_PATH/g" input > output

同时假设变量本身已知


假设$LOG_FILE_PATH包含斜杠,它将无法工作。它将仅在模式内扩展,导致出现"替换命令中的错误标志"。我刚刚尝试了$PATH。 - George Herolyants

1

是的,eval 应该小心使用,但它为我的问题提供了这个简单的一行代码。以下是使用您的文件名的示例:

eval "echo \"$(<Text_File.msh)\""

我个人使用printf而不是echo,但那应该可以解决问题。谢谢 abyss.7 提供的链接解决了我的问题。希望能有所帮助。

1
这个解决方案可以让你在输出文件中保留相同的格式。请将以下行复制并粘贴到你的脚本中。
cat $1 | while read line
do
  eval $line
  echo $line
  eval echo $line
done | uniq | grep -v '\$'

这将逐行读取作为参数传递的文件,然后尝试打印每一行两次: - 一次不进行替换 - 一次替换变量后进行替换 然后删除重复行 然后删除包含可见变量($)的行。

0
创建一个名为test.txt的ASCII文件,并将以下内容写入其中:
Try to replace this ${myTestVariable1} 
bla bla
....

现在创建一个名为“sub.sed”的文件,其中包含变量名称,例如

's,${myTestVariable1},'"${myTestVariable1}"',g;
s,${myTestVariable2},'"${myTestVariable2}"',g;
s,${myTestVariable3},'"${myTestVariable3}"',g;
s,${myTestVariable4},'"${myTestVariable4}"',g'

打开终端并移动到包含test.txt和sub.sed的文件夹。
定义要替换的变量的值。

myTestVariable1=SomeNewText

现在调用sed替换那个变量

sed "$(eval echo $(cat sub.sed))" test.txt > test2.txt

输出结果将会是:

$cat test2.txt

Try to replace this SomeNewText 
bla bla
....

0

#logfiles.list:

$EAMSROOT/var/log/LinuxOSAgent.log
$EAMSROOT/var/log/PanacesServer.log
$EAMSROOT/var/log/PanacesStrutsGUI.log

#我的程序:

cat logfiles.list | while read line
    do
        eval Eline=$line
        echo $Eline
    done

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接