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

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

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


エラーメッセージは何ですか?
キャメロンネモ14年

私のスクリプト(この単純化よりもやや複雑)では、「ファイルが見つかりません」と表示されます
レナート・ローランド14年

infile現在のディレクトリに存在すると仮定しても安全ですか?
saiarcot895 14年

はい。私のコードでは、ファイルではなくwget -O-です。実際、連結された文字列をコピーしてターミナルに貼り付けるだけで、正常に実行されます
レナート・ローランド14年

回答:


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

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

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

$cmd

eval $cmd

それを解決しました。


ただし、引用符で囲まれたパラメーターには注意してください。eval foo "a b"と同じになりeval foo "a" "b"ます。
うどんだん

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確かに、おそらくこれにもevalを使用するでしょう(だから、私はあなたとLennartを支持しました)。私はただ代わりを提供しています。
テルドン

0

おそらくこれを行うためのより良い方法は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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.