web-dev-qa-db-ja.com

bashスクリプトのコマンドとしての変数

バックアップメディアは巨大なファイルをサポートしていないため、特定のディレクトリをtarし、その出力を暗号化し、結果のファイルを複数の小さなファイルに分割する非常に簡単なbashスクリプトを書いています。

Bashスクリプトの経験はあまりありません。パラメータにスペースを入れるために変数を適切に引用することに問題があると思います。スクリプトは次のとおりです。

#! /bin/bash

# This script tars the given directory, encrypts it, and transfers
# it to the given directory (likely a USB key).

if [ $# -ne 2 ]
then
    echo "Usage: `basename $0` DIRECTORY BACKUP_DIRECTORY"
    exit 1
fi

DIRECTORY=$1
BACKUP_DIRECTORY=$2
BACKUP_FILE="$BACKUP_DIRECTORY/`date +%Y-%m-%dT%H-%M-%S.backup`"

TAR_CMD="tar cv $DIRECTORY"
SPLIT_CMD="split -b 1024m - \"$BACKUP_FILE\""

ENCRYPT_CMD='openssl des3 -salt'

echo "$TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD"

$TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD 

say "Done backing up"

このコマンドの実行は次のように失敗します:

split: "foo/2009-04-27T14-32-04.backup" aa:そのようなファイルまたはディレクトリはありません

$BACKUP_FILEを設定した$SPLIT_CMDの前後の引用符を削除することで修正できます。ただし、バックアップディレクトリの名前にスペースが含まれていると機能しません。また、「echo」コマンドからの出力を直接端末にコピーして貼り付けると、正常に動作します。明らかに、Bashが物事をどのように逃げているかについて、私には理解できないことがあります。

21
wxs

コマンド全体を変数に入れないでください。引用された引数を復元しようとすると、多くの問題が発生します。

また:

  1. スクリプトですべて大文字の変数名を使用しないでください。足で自分を撃つ簡単な方法。
  2. バッククォートを使用せず、代わりに$(...)を使用してください。ネストしやすくなります。

#! /bin/bash

if [ $# -ne 2 ]
then
    echo "Usage: $(basename $0) DIRECTORY BACKUP_DIRECTORY"
    exit 1
fi

directory=$1
backup_directory=$2
current_date=$(date +%Y-%m-%dT%H-%M-%S)
backup_file="${backup_directory}/${current_date}.backup"

tar cv "$directory" | openssl des3 -salt | split -b 1024m - "$backup_file"
44
Juliano

よくわかりませんが、最初にコマンドでevalを実行する価値があるかもしれません。

これにより、bashは変数$ TAR_CMDなどをその全幅に展開します(ちょうどechoコマンドがコンソールに対して行うように、これは機能します)

その後、Bashは変数を展開して2行目を再度読み取ります。

eval $TAR_CMD | $ENCRYPT_CMD | $SPLIT_CMD 

Google検索を行ったところ、このページは、なぜそれが必要なのかを説明する上でまともな仕事をしているようです。 http://fvue.nl/wiki/Bash:_Why_use_eval_with_variable_expansion%3F

6
Eddie

信頼できないソースがディレクトリ名を生成できる場合、evalは受け入れられません。 evalを使用しない理由については BashFAQ#48 を、この問題の根本原因とその適切な解決策については BashFAQ#5 を参照してください。 、その一部を以下に触れます:

時間の経過とともにコマンドを作成する必要がある場合は、配列を使用します。

tar_cmd=( tar cv "$directory" )
split_cmd=( split -b 1024m - "$backup_file" )
encrypt_cmd=( openssl des3 -salt )
"${tar_cmd[@]}" | "${encrypt_cmd[@]}" | "${split_cmd[@]}"

あるいは、これがコマンドを1つの中央の場所で定義するだけの場合、関数を使用します。

tar_cmd() { tar cv "$directory"; }
split_cmd() { split -b 1024m - "$backup_file"; }
encrypt_cmd() { openssl des3 -salt; }
tar_cmd | split_cmd | encrypt_cmd
6
Charles Duffy

変数にコマンドとオプションだけを入れることにはポイントがあります。

#! /bin/bash

if [ $# -ne 2 ]
then
    echo "Usage: `basename $0` DIRECTORY BACKUP_DIRECTORY"
    exit 1
fi

. standard_tools    

directory=$1
backup_directory=$2
current_date=$(date +%Y-%m-%dT%H-%M-%S)
backup_file="${backup_directory}/${current_date}.backup"

${tar_create} "${directory}" | ${openssl} | ${split_1024} "$backup_file"

コマンドをソースとなる別のファイルに再配置できるため、多くのスクリプトで同じコマンドとオプションを再利用できます。これは、多くのスクリプトがあり、それらがすべてツールを使用する方法を制御する場合に非常に便利です。そのため、standard_toolsには以下が含まれます。

export tar_create="tar cv"
export openssl="openssl des3 -salt"
export split_1024="split -b 1024m -"
4
Jason Catena

シェルが物事を適切に再解釈するように変数内のスペースを引用するのはhardです。より強い言語に手を伸ばすよう促すのは、この種のことです。それがPerlであろうとpythonかRubyまたは何であれ(私はPerlを選択しますが、それは誰にとっても常にそうではありません)、それはただsomethingこれにより、引用のためにシェルをバイパスできます。

それは私がevalのリベラルな用量でそれを正しくすることができなかったということではありませんが、そのevalは私にeebie-jeebiesを与えます(この場合、代わりにあなたが書いたものを取り、それを評価します)、デバッグで頭痛の種になりました。

Perlを使用すると、私の例として、次のようなことができます。

@tar_cmd = ( qw(tar cv), $directory );
@encrypt_cmd = ( qw(openssl des3 -salt) );
@split_cmd = ( qw(split -b 1024m -), $backup_file );

ここで難しい部分はパイプを実行することですが、少しの IO :: Pipe 、fork、およびstdoutとstderrを再開することは悪くありません。シェルを適切に引用するよりも悪いと言う人もいますし、どこから来たのかはわかりますが、私にとっては、これは読みやすく、保守しやすく、書きやすいです。ヘック、誰かがこれから苦労してIO :: Pipelineモジュールを作成し、すべてを簡単にすることができます;-)

1
Tanktalus