シェル変数をコマンドオプションとして使用

シェル変数をコマンドオプションとして使用

Bashスクリプトは、使用するオプションを別の変数に保存しようとしますrsync。これは単純なオプション(例:)ではうまく機能します--recursiveが、次の問題が発生します--exclude='.*'

$ find source
source
source/.bar
source/foo

$ rsync -rnv --exclude='.*' source/ dest
sending incremental file list
foo

sent 57 bytes  received 19 bytes  152.00 bytes/sec
total size is 0  speedup is 0.00 (DRY RUN)

$ RSYNC_OPTIONS="-rnv --exclude='.*'"

$ rsync $RSYNC_OPTIONS source/ dest
sending incremental file list
.bar
foo

sent 78 bytes  received 22 bytes  200.00 bytes/sec
total size is 0  speedup is 0.00 (DRY RUN)

ご覧のとおり、「手動」--exclude='.*'で渡すとうまく機能しますが(コピーなし)、オプションが最初に変数に保存されたときは機能しません。rsync.bar

私はこれが引用符やワイルドカード(またはその両方)に関連していると思いますが、何が間違っているのかわかりません。

答え1

一般に、コマンドラインオプションのリストでもパス名のリストでも、個々のエントリのリストを単一の文字列に減らすことはお勧めできません。その理由は、行の下のどこかで文字列を別の内容に再分割する必要があり、文字列に意味論的解釈が容易な場合や重要ではない可能性がある引用符とスペースが含まれていると正しく実行できないためです。文字列のデータです。

代わりに配列を使用してください。

rsync_options=( -rnv --exclude='.*' )

または

rsync_options=( -r -n -v --exclude='.*' )

それから...

rsync "${rsync_options[@]}" source/ target

これにより、個々のオプションへの参照を維持できます(拡張を二重引用符で囲む限り${rsync_options[@]})。また、配列内の個々の項目を簡単に操作できます(呼び出し前にそうする必要がある場合)rsync

すべてのPOSIXシェルで位置引数リストを使用してこれを達成できます。

set -- -rnv --exclude='.*'

rsync "$@" source/ target

ここでも二重引用符の拡張が$@重要です。

接線的に関連する:


問題は、両方のオプションセットを文字列に入れると、--excludeオプション値の一重引用符が値の一部になることです。したがって、

RSYNC_OPTIONS='-rnv --exclude=.*'

効果がありましたが...別の参照項目を持つ配列または位置パラメータを使用する方が安全です。これにより、必要に応じてスペースを含むコンテンツを使用でき、シェルがオプションに対してファイル名を生成するのを防ぎます。


¹ 次の名前で始まるファイルが変更さ$IFSれておらず、現在のディレクトリに存在しないか、シェルオプションが設定されていない場合。--exclude=.nullglobfailglob

答え2

@クサラナンダすでに説明しています基本的な問題とその解決策、バッシュのよくある質問@glenn jackmannへのリンクも有用な情報をたくさん提供します。以下は、これらのリソースに基づいて私の問題に何が起こっているのかについての詳細な説明です。

各引数を別々の行(argtest.bash)に印刷する小さなスクリプトを使用して問題を説明します。

#!/bin/bash

for var in "$@"
do
    echo "$var"
done

「手動」配送オプション:

$ ./argtest.bash -rnv --exclude='.*'
-rnv
--exclude=.*

-rnv予想通り、部分は引用符--exclude='.*'で囲まれていないスペースで区切られているため、2つの引数に分割されます(これを呼び出す)。噴射)。

また、周囲の引用符が.*削除されました。一重引用符は、シェルにコンテンツを渡すように指示します。特別な説明は必要ありませんただし、引用符自体はコマンドに渡されません。

(配列を使用する代わりに)変数にオプションを文字列として保存すると、引用符が付きます。削除されない:

$ OPTS="--exclude='.*'"

$ ./argtest.bash $OPTS
--exclude='.*'

これは2つの理由による。定義で使用される二重引用符は、一重引用$OPTS符の特別な処理を防ぐため、後者の値の一部です。

$ echo $OPTS
--exclude='.*'

これを$OPTSコマンドの引数として使用するときパラメータ拡張前の引用符の処理$OPTSだから、「遅すぎる」という引用が表示されます。

これは(私の元の質問で)パターンの代わりにrsync除外パターン(引用符を含む!)を使用することを意味します。名前が一重引用符で始まり、ドットが続き、一重引用符で終わるファイルを除外します。明らかに、これは私たちの元の意図ではありませんでした。'.*'.*

回避策は、次の定義時に二重引用符を省略することです$OPTS

$ OPTS2=--exclude='.*'

$ ./argtest.bash $OPTS2
--exclude=.*

しかし、これは良い習慣です。常に変数の割り当てを参照してください。より複雑なケースでは微妙な違いがあるからです。

@Kusalanandaが指摘したように、引用を解除するのも.*良いです。回避するために引用符を追加しました。スキーマの拡張しかし、これは必ずしも必要ではありませんこの特別なケースでは:

$ ./argtest.bash --exclude=.*
--exclude=.*

その結果、Bashするパターン拡張を実行しましたが、パターンが--exclude=.*一致するファイルがないため、そのパターンがコマンドに渡されます。比較する:

$ touch some_file

$ ./argtest.bash some_*
some_file

$ ./argtest.bash does_not_exit_*
does_not_exit_*

しかし、パターンを引用しないことは、(何らかの理由で)一致するファイルが存在すると--exclude=.*パターンが拡張されるため危険です。

$ touch -- --exclude=.special-filenames-happen

$ ./argtest.bash --exclude=.*
--exclude=.special-filenames-happen

最後に、配列を使用すると、参照の問題が回避される理由(配列を使用してコマンドパラメータを保存することによる他の利点も含む)を見てみましょう。

配列を定義すると、トークン化と引用符の処理が期待どおりに発生します。

$ ARRAY_OPTS=( -rnv --exclude='.*' )

$ echo length of the array: "${#ARRAY_OPTS[@]}"
length of the array: 2

$ echo first element: "${ARRAY_OPTS[0]}"
first element: -rnv

$ echo second element: "${ARRAY_OPTS[1]}"
second element: --exclude=.*

コマンドにオプションを渡すときに、"${ARRAY[@]}"配列の各要素を別々の単語に拡張する構文を使用します。

$ ./argtest.bash "${ARRAY_OPTS[@]}"
-rnv
--exclude=.*

答え3

関数とシェルスクリプトを作成するときに処理のためにパラメータを渡すと、パラメータは$ 1、$ 2、$ 3などのint数で指定された変数に渡されます。

例えば:

bash my_script.sh Hello 42 World

内部的には、このコマンドはHello、to、およびforを参照するmy_script.shために使用されます。$1$242$3World

Variable reference は$0現在のスクリプトの名前に展開されます。my_script.sh

コマンドを変数として使用してコード全体を実行しないでください。

覚える:

1 スクリプトで変数名をすべて大文字にしないでください。

2バックティックを使用せず、代わりに$(...)を使用します。ネストが良いです。

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"

関連情報