通过在bash中连接字符串来构建命令


13

我有一个bash脚本,可以在一次执行之前根据一些参数在字符串中构建命令行。应该将连接到命令字符串的部分用管道分开,以利于通过每个组件的“数据流”。

一个非常简化的示例:

#!/bin/bash
part1=gzip -c
part2=some_other_command
cmd="cat infile"

if [ ! "$part1" = "" ]
then
    cmd+=" | $part1"
fi


if [ ! "$part2" = "" ]
then
    cmd+=" | $part2"
fi


cmd+="> outfile"
#show command. It looks ok
echo $cmd
#run the command. fails with pipes
$cmd

由于某些原因,管道似乎无法正常工作。当我运行此脚本时,我得到的错误消息通常与命令的第一部分(在第一个管道之前)有关。

所以我的问题是,是否可以通过这种方式构建命令,什么是最好的方式?


错误消息是什么?
CameronNemo 2014年

在我的脚本中(比这种简化要复杂一些),我得到“找不到文件”
Lennart Rolland 2014年

可以安全地假设infile当前目录中存在该目录吗?
saiarcot895 2014年

是。在我的代码中,它是wget -O-而不是文件。实际上,如果我只是复制连接的字符串并在终端中将其pasete,它将运行良好
Lennart Rolland

Answers:


17

这完全取决于何时对事物进行评估。键入时$cmd,该行的其余部分将作为参数传递给中的第一个单词$cmd

walt@spong:~(0)$ a="cat /etc/passwd"
walt@spong:~(0)$ b="| wc -l"
walt@spong:~(0)$ c="$a $b"
walt@spong:~(0)$ echo $c
cat /etc/passwd | wc -l
walt@spong:~(0)$ $c
cat: invalid option -- 'l'
Try 'cat --help' for more information.
walt@spong:~(1)$ eval $c
62
walt@spong:~(0)$ a="echo /etc/passwd"
walt@spong:~(0)$ c="$a $b"
walt@spong:~(0)$ echo $c
echo /etc/passwd | wc -l
walt@spong:~(0)$ $c
/etc/passwd | wc -l
walt@spong:~(0)$ $c |od -bc
0000000 057 145 164 143 057 160 141 163 163 167 144 040 174 040 167 143  
          /   e   t   c   /   p   a   s   s   w   d       |       w   c  
0000020 040 055 154 012  
              -   l  \n  
0000024
walt@spong:~(0)$ eval $c
1  

这表明传递给echo命令的参数是:“ /etc/passwd”,“ |”(竖线字符),“ wc”和“ -l”。

来自man bash

eval [arg ...]  
    The  args  are read and concatenated together into   
    a single command.  This command is then read and  
    executed by the shell, and its exit status is returned  
    as the value of eval.  If there are no args, or only null  
    arguments, eval returns 0.

8

一种解决方案,供将来参考,可以使用“ eval”。这可以确保忘记使用bash解释字符串的任何方式,并且可以像读取整件事一样直接在shell中键入内容(这正是我们想要的)。

因此,在上面的示例中,

$cmd

eval $cmd

解决了。


但是要小心带引号的参数。eval foo "a b"将与相同eval foo "a" "b"
udondan '16

2

@waltinator已经解释了为什么这不能按您预期的那样工作。解决它的另一种方法是bash -c用于执行命令:

$ comm="cat /etc/passwd"
$ comm+="| wc -l"
$ $comm
cat: invalid option -- 'l'
Try 'cat --help' for more information.
$ bash -c "$comm"
51

1
Parsimony告诉我不要使用来启动另一个进程bash -c,而eval要在当前进程中执行命令。
waltinator 2014年

@waltinator当然,我也可能为此使用eval(这就是为什么我赞成你和Lennart的原因)。我只是提供一个替代方案。
terdon 2014年

0

可能更好的方法是避免使用evalBash数组,而只是使用Bash数组,它是内联扩展,可以构建所有参数,然后根据命令执行它们。

runcmd=() # This is slightly messier than declare -a but works
for cmd in $part1 $part2 $part3; do runcmd+="| $cmd "; done
cat infile ${runcmd[@]} # You might be able to do $basecmd ${runcmd[@]}
# but that sometimes requires an `eval` which isn't great
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.