当我使用cat命令查看文件时,如何获取变量的替换结果?

3

在读取文件时,是否有一种简洁的方法可以获取变量值而不是文件中写入的变量名称?很难解释,以下是一个简单的示例:

$ cat <<EOF
$HOME
EOF
/home/myself

因为已经被 shell 扩展过了,所以 cat 返回 /home/myself。

$ echo \$HOME >/tmp/home
$ cat /tmp/home
$HOME

cat命令只是简单地读取文件,我希望$HOME能够以某种方式在cat中被扩展,因为该文件将包含变量名称(不像HOME=/home/myself)。

我的问题是是否有可能实现这一点,否则我将不得不编写一些复杂的代码。

编辑:它们是包含大量XML文件的文件。

<checkbox active="$value">

真或假


2
echo \cat /tmp/home``会起作用吗? - Beta
这些文件实际上会包含什么?只有一个变量名吗?还是你正在尝试开发某种模板引擎,将变量值插入到自由格式文本中? - sarnold
Beta@ nay...; sarnold@ 处理包含 "<checkbox active="$value">" 的大型 XML 文件,其中 $value 可以是 true 或 false。我将从 /etc 目录下的其他文件中获取这些值。 - admirabilis
4个回答

2
这在Python中很容易实现,你可以尝试一下。您可以使用re.sub函数通过调用执行转换的函数(而不是特定的字符串)来替换所有出现某个模式(如"\$\w+")的情况。对于替换函数,您可以使用os.getenv(),它接受一个变量名称并返回其值。
编辑:这是一个完整的Python脚本,实现了以上功能:
#!/usr/bin/python

import fileinput
import os
import re

def transform(match):
    return os.getenv(match.group(1)) # replace the "capture" to omit $

for line in fileinput.input(): # reads from stdin or from a file in argv
    print re.sub('\$(\w+)', transform, line), # comma to omit newline

很快就好。我会为你编写代码,你会看到它有多简单。 - John Zwinck

2

我最近发现了一种使用 envsubst 实现此目的的方法:

export MYNAME="Jason"
echo '{ "status": 200, "message": { "name": "$MYNAME" } }' > my.json.template

cat my.json.template
# outputs { "status": 200, "message": { "name": "$MYNAME" } }    

cat my.json.template | envsubst
# outputs { "status": 200, "message": { "name": "Jason" } }

如果您没有envsubst,您可以在Debian发行版上安装它:
apt-get install gettext-base

在Bash中,它也可以缩短为envsubst <my.json.template - Juliy V. Chirkov

1

cat 原始版本(1st Edition UNIX)将输入内容未经修改地复制到输出中。一开始它没有任何选项。之后,BSD 加入了一堆选项,原始的 UNIX 团队表示反对:'cat came back from Berkeley waving flags'(见:1 - passim)。它不应该用于编辑文件——那不是它的目的。(我在 BSD(Mac OS X)cat 的 man 页面上找到了该文章的参考:Rob Pike,“UNIX Style, or cat -v Considered Harmful”,USENIX Summer Conference Proceedings,1983 年。另请参见 http://quotes.cat-v.org/programming/

因此,您需要使用其他工具而不是 cat 来完成工作。我建议使用 Perl 或 Python;两者都可以轻松完成任务。或者,也可以考虑使用 sed 或者 awk

#!/usr/bin/env perl
use strict;
use warnings;
while (<>)
{
    foreach my $key (keys %ENV)
    {
        s/\$$key\b/$ENV{$key}/g;  # $envvar
        s/\${$key}/$ENV{$key}/g;  # ${envvar}
    }
    print;
}

这个循环遍历输入行,依次查找每个环境变量。另一种机制是查找可能的变量并进行相关的替换。这证明有点棘手,但可行:

#!/usr/bin/env perl
use strict;
use warnings;
while (<>)
{
    while (m/\$((\w+))/ || m/\$({(\w+)})/)
    {
        my $key = $2;
        my $var = $1;
        s/\$$var/$ENV{$key}/g if defined $ENV{$key};
    }
    print;
}

当我在捕获中包含文字 $ 时,替换操作无法正常工作。

是的,我看了一下cat命令的手册,它只是设计用来读取文件的,虽然可以通过重定向来使用它来写文件。你的脚本也非常好用,谢谢! - admirabilis

1

这样做的明显方法有很多问题:

# 这会因为某些输入而失败。 HTML肯定是个问题,因为'<'和'>'字符将被解释为文件重定向
$ while read r; do eval echo $r; done < input

以下perl应该对于简单的输入可以很好地处理问题。

$ perl -pwe 'while(($k,$v) = each %ENV ) { s/\${?$k}?/$v/ }' input

但它没有处理像${FOO-bar}这样的结构。如果您需要处理这种结构,可以转义所有shell元字符并执行while/read循环:

$ sed -e 's/\([<>&|();]\)/\\\1/g' input | while read -r l; do eval echo "$l"; done

请注意,这既不牢靠也不安全。考虑一下如下输入会发生什么:

\; rm -rf /

我说“考虑一下”。不要测试它。sed将在分号前插入反斜杠,eval将得到字符串“\\;”,这将被解释为一个反斜杠后跟一个分号,该分号终止echo,然后执行rm -rf。鉴于评估未知输入的不安全性,最好还是坚持使用像perl这样的东西,并明确替换所需的sh构造。例如:

$ perl -pwe 'while(($k,$v) = each %ENV ) { s/\${?$k}?/$v/ }; 
    s/\${[^-]*-([^}]*)}/$1/g' input

这个在处理${FOO=some-text}这样的输入时会有问题。为了可靠地获取所有sh结构(其中“:”可以是“-”、“?”、“=”、“+”、“%”、“#”或任何具有冒号前缀的相同符号(或者如果允许非posix sh语法,则可以是很多其他符号!)),您必须构建一组相当复杂的比较。


这就是我在想的。如果我使用sed命令,我可以使用所有的bash构造(我有点偏爱bash,脚本的可移植性不是个问题)。我发现你的sed命令的问题是,所有的双引号,即",都会消失。XML需要active="true",而在active=true上失败了。我需要保留XML中的引号,因为很多引号不会包含变量。我们如何改进这个sed命令? - admirabilis
@Teresa 只需要在要转义的字符列表中添加一个 ” 就可以解决问题了。但我认为最好还是使用 Perl,因为 eval 未知输入可能会带来相当严重的后果。 - William Pursell

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