Bash脚本中的文件重定向失败,但在Bash终端中却没有问题

3

在我的以 .sh 结尾的 Bash 脚本中,cmd1 可以正常工作,但是 cmd2 无法正常工作。我已经将这个脚本设置为可执行。

此外,我可以在Bash终端中正常执行cmd2。我试图制作一个最小化可复现的示例,但我的更大目标是运行一个带有命令行参数的复杂可执行文件,并将输出传递给可能存在或不存在的文件(而不是在终端中显示输出)。

将 > 替换为 >> 在脚本中也会产生相同的错误,但是在终端中却没有问题。

我的 Bash 脚本:

#!/bin/bash 
cmd1="cat test.txt"   
cmd2="cat test.txt > a" 
echo $cmd1   
$cmd1  
echo $cmd2   
$cmd2

test.txt文件中分别有两行单词"dog"和"cat",不包含引号。


你尝试使用过 eval 吗?在这里看看:https://unix.stackexchange.com/questions/23111/what-is-the-eval-command-in-bash 然后在你的脚本中尝试 eval $cmd1eval $cmd2 - Gzeh Niert
我可以尝试一下。Eval因易受攻击而声名狼藉,但程序输入将由可信用户控制。我需要更改输出位置,可能通过传递一个变量($outputFile),因为我想要自动化一个具有不同输入和输出的可执行文件。有没有更好的建议? - user3750360
3个回答

4

简短回答:请参见BashFAQ#50:我试图将命令放入变量中,但复杂情况总是失败!

长回答:Shell会在解析命令行的过程中(例如> a),对变量引用(如$cmd1)进行扩展,并在完成解析转向、引号和转义等操作后进行。实际上,它对扩展值所做的唯一操作就是单词拆分(例如将cat test.txt > a视为由"cat"、"test.txt"、">"和最后是"a"组成的字符串序列),以及通配符扩展(例如如果$cmd扩展为cat *.txt,则将*.txt部分替换为匹配文件的列表)。 (如果变量在双引号中,则跳过单词拆分和通配符扩展。)

由于这个原因,存储命令的最佳方式是:不要存储。因为变量是用来存储数据而不是命令。但是,你应该根据存储命令的原因选择其他方法。
  • If there's no real reason to store the command in a variable, then just use the command directly. For conditional redirects, just use a standard if statement:

    if [ -f a ]; then
        cat test.txt > a
    else
        cat test.txt
    fi
    
  • If you need to define the command at one point, and use it later; or want to use the same command over and over without having to write it out in full each time, use a function:

    cmd2() {
        cat test.txt > a
    }
    cmd2
    
  • It sounds like you may need to be able to define the command differently depending on some condition, you can actually do that with a function as well:

    if [ -f a ]; then
        cmd() {
            cat test.txt > a
        }
    else
        cmd() {
            cat test.txt
        }
    fi
    cmd
    
  • Alternately, you can wrap the command (without redirect) in a function, then use a conditional to control whether it redirects:

    cmd() {
        cat test.txt
    }
    if [ -f a ]; then
        cmd > a
    else
        cmd
    fi
    
  • It's also possible to wrap a conditional redirect into a function itself, then pipe output to it:

    maybe_redirect_to() {
        if [ -f "$1" ]; then
            cat > "$1"
        else
            cat
        fi
    }
    cat test.txt | maybe_redirect_to a
    

    (This creates an extra cat process that isn't really doing anything useful, but if it makes the script cleaner, I'd consider that worth it. In this particular case, you could minimize the stray cats by using maybe_redirect_to a < test.txt.)

  • As a last resort, you can store the command string in a variable, and use eval to parse it. eval basically re-runs the shell parsing process from the beginning, meaning that it'll recognize things like redirects in the string. But eval has a well-deserved reputation as a bug magnet, because it's easy for it to treat parts of the string you thought were just data as command syntax, which can cause some really weird (& dangerous) bugs.

    If you must use eval, at least double-quote the variable reference, so it runs through the parsing process just once, rather than sort-of-once-and-a-half as it would unquoted. Here's an example of what I mean:

    cmd3="echo '5 * 3 = 15'"
    eval "$cmd3"
    # prints: 5 * 3 = 15
    eval $cmd3
    # prints: 5 [list of files in the current directory] 3 = 15
    # ...unless there are any files with shell metacharacters in their names, in
    # which case something more complicated might happen.
    

BashFAQ #50讨论了一些其他可能的原因和解决方案。请注意,由于重定向解析后也会扩展数组,因此这里无法使用数组方法。


1
如果您对脚本的操作不确定,可以使用调试模式来查看是否可以确定错误。
bash -x 脚本名称 这将运行命令并显示变量评估的输出。希望这能揭示任何语法问题。

1
如果在$cmd2前加上'eval',它应该按预期工作:
#!/bin/bash
cmd2="cat test.txt > a"  
eval $cmd2

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