コマンドオプションにシェル変数を使用する


19

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
または、当社のサイトでこの回答をご覧ください。
スコット

回答:


38

一般に、コマンドラインオプションのリストでもパス名のリストでも、別々の項目のリストを単一の文字列に降格することはお勧めできません。

代わりに配列を使用します。

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

繰り返しますが、$@ここでは拡張の二重引用符が重要です。

接線関連:


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

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

働いていた¹...しかし、(より安全なように)個別に引用されたエントリで配列または位置パラメータを使用する方が良いです。そうすることで、必要に応じてスペースを含むものを使用できるようになり、オプションでシェルがファイル名の生成(グロビング)を実行するのを防ぎます。


¹は、$IFS変更されておらず--exclude=.、現在のディレクトリに名前が始まるファイルがなくnullglobfailglobシェルオプションが設定されていないことを条件とします。


配列を使用するとうまくいきます。詳細な回答をありがとうございます!
フロリアン・ブラッカー

3

@Kusalananda 基本的な問題とその解決方法をすでに説明ており、@ glenn jackmannからリンクされているBash FAQエントリも多くの有用な情報を提供しています。これらのリソースに基づいて、私の問題で何が起こっているのかを詳しく説明します。

物事を説明するために、各引数を別々の行に出力する小さなスクリプトを使用します(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=.*

このことは長い間混乱していたので、このような詳細な説明は役に立ちます。
ジョー

0

引数が処理されるために渡される関数とシェルスクリプトを作成する場合、引数は数値名の変数に渡されます(例:$ 1、$ 2、$ 3)

bash my_script.sh Hello 42 World

内部ではmy_script.sh、コマンドは$1Hello、$2to 42、および$3for を参照するために使用しますWorld

変数参照、$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"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.