web-dev-qa-db-ja.com

bashで文字列を連結してビルドコマンド

一度に実行する前に、いくつかのパラメーターに基づいて文字列にコマンドラインを作成する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

何らかの理由で、パイプが機能していないようです。このスクリプトを実行すると、通常はコマンドの最初の部分(最初のパイプの前)に関連するさまざまなエラーメッセージが表示されます。

だから私の質問は、この方法でコマンドを作成することが可能かどうか、そしてそれを行うための最良の方法は何ですか?

12
Lennart Rolland

それはすべて物事が評価される時期に依存します。 $cmdと入力すると、行全体が$cmdの最初のWordに引数として渡されます。

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.
16
waltinator

これに対する解決策の1つは、将来の参照のために、「eval」を使用することです。これにより、bashによる文字列の解釈方法が忘れられ、シェルに直接入力されたように全体が読み取られます(これはまさに必要なことです)。

したがって、上記の例では、

$cmd

eval $cmd

それを解決しました。

7
Lennart Rolland

@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
terdon

おそらくこれを行うためのより良い方法は、evalを使用せず、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
0
dragon788