web-dev-qa-db-ja.com

コマンドオプションでのシェル変数の使用

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はコピーされません)。最初にオプションが変数に格納されている場合は機能しません。

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

22
Florian Brucker

一般に、コマンドラインオプションのリストであっても、パス名のリストであっても、個別のアイテムのリストを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

ここでも、$@の展開を二重引用符で囲むことが重要です。

接線的に関連:


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

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

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


¹$IFSが変更されておらず、名前が--exclude=.で始まるファイルが現在のディレクトリにないこと、およびnullglobまたはfailglobシェルオプションがセットする。

42
Kusalananda

@Kusalananda すでに説明済み 基本的な問題とその解決方法、および@ -glenn jackmannによってリンクされた Bash FAQ entry も提供していますこれらのリソースに基づいて、私の問題で何が起こっているのかを詳しく説明します。

各引数を別々の行に出力する小さなスクリプトを使用して、説明します(argtest.bash):

#!/bin/bash

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

オプションを「手動で」渡す:

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

予想どおり、-rnv--exclude='.*'の部分は、引用符で囲まれていない空白で区切られているため、2つの引数に分割されます(これは Word splitting と呼ばれます)。

また、.*を囲む引用符が削除されていることにも注意してください。単一引用符はシェルにコンテンツを渡すように指示します 特別な解釈なししかし、引用符自体はコマンドに渡されません

(配列を使用するのではなく)オプションを文字列として変数に格納する場合、引用符は削除されません

$ 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 doesはパターン展開を実行しますが、パターン--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

最後に、配列を使用すると、(コマンド引数を格納するために配列を使用する他の利点に加えて)引用符の問題を防ぐ理由を見てみましょう。

配列を定義すると、Wordの分割と引用処理が期待どおりに行われます。

$ 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[@]}"という構文を使用します。これは、配列の各要素を個別のWordに展開します。

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

処理するために引数が渡される関数とシェルスクリプトを記述する場合、引数は数値で指定された変数に渡されます。 $ 1、$ 2、$ 3

bash my_script.sh Hello 42 World

my_script.sh内では、コマンドは$1を使用してHelloを参照し、$242に、$3Worldに使用します。

変数参照$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"
0
champion-runner