シェルスクリプトの変数から生成されたコマンドに二重引用符を付けます。

シェルスクリプトの変数から生成されたコマンドに二重引用符を付けます。

いくつかのデータを同期する簡単なスクリプトを書こうと思っていましたが、思ったよりも難しかったです。

デフォルトのレイアウトは、同期する必要があるフォルダを参照するサブフォルダを含む設定フォルダを持つことです。各フォルダには[0..2]ファイル(includes.txtとExcepts.txt)が含まれています。その後、スクリプトはそれを読み取り、同期コマンドを実行します。

私が実行したいのは

me@my_machine:~/scripts$ aws s3 sync /home/me/Pictures s3://my_bucket/home/me/Pictures --exclude "*" --include "*.gif" --include "*.jpg" --profile=personal --dryrun
(dryrun) upload: ../Pictures/sample_picture.jpg to s3://my_bucket/home/me/Pictures/sample_picture.jpg

したがって、特定のファイルを無視できます。 AWS CLIでは、パターンを二重引用符で囲む必要があるため、スクリプトから除外と埋め込みをインポートすることはできません。

私が読んだ他の質問には、配列と関数の使用に関する内容が記載されているので、私のスクリプトは次のようになります。

#!/bin/bash

set -x

DRYRUN=true

s3_bucket_uri='s3://my_bucket'
aws_profile='--profile=personal'
config_folder='../config/*'
include_file='includes.txt'
exclude_file='excludes.txt'

includes=()
excludes=()

sync () {
    local params=()
    local local_folder="$HOME/$1"
    local bucket_folder="$s3_bucket_uri""$local_folder"

    params+=("$local_folder" "$bucket_folder")

    if [[ ${excludes[@]} ]]; then
        params+=("${excludes[@]/#/--exclude }")
    fi
    
    if [[ ${includes[@]} ]]; then
        params+=("${includes[@]/#/--include }")
    fi
    
    params+=("$aws_profile")

    if [[ "$DRYRUN" = true ]]; then
        params+=(--dryrun)
    fi

    aws s3 sync ${params[@]}
}


read_parameters () {
    if [[ -f "$1" ]]; then
        while read line; do
            if [[ $2 == "include" ]]; then
                includes+=("$line")
            elif [[ $2 == "exclude" ]]; then
                excludes+=("$line")
            fi
        done < $1
    fi
}

reset () {
    includes=()
    excludes=()
}

for folder in $config_folder; do
    if [[ -d "$folder" && ! -L "$folder" ]]; then
        read_parameters $folder/$exclude_file exclude
        read_parameters $folder/$include_file include
        sync "${folder##*/}"
        reset
    fi
done

入力例は次のとおりです。

"*.jpg"
"*.gif"

内部に.txtを含みます文書。

問題は、包含パターンと除外パターンに二重引用符が必要なため、AWS CLIの引用符を正しくインポートすることです。これは正しくインポートするのが難しいようです。

を使用すると、aws s3 sync ${params[@]}シェルはパターンの周りに一重引用符を追加します。これはコマンドがクラッシュすることはありませんが、すべてのパターンは無視されます。

+ aws s3 sync /home/me/Pictures s3://mybucket/home/me/Pictures --exclude '"*"' --include '"*.gif"' --include '"*.jpg"' --profile=personal --dryrun
(dryrun) upload: ../../../Pictures/Bender_Rodriguez.png to s3://mybucket/home/me/Pictures/Bender_Rodriguez.png

ご覧のとおり、.gifファイルと.jpgファイルを除くすべてのファイルを除外するように指示しているので、除外する必要があるコンテンツをアップロードしようとしています。


シェルは、aws s3 sync "${params[@]}"インクルードまたは除外ステートメント全体に単一引用符を追加して、コマンドが競合するようにします。

+ aws s3 sync /home/me/Pictures s3://mybucket/home/me/Pictures '--exclude "*"' '--include "*.gif"' '--include "*.jpg"' --profile=personal --dryrun
Unknown options: --exclude "*",--include "*.gif",--include "*.jpg"

params+=(--testing "foobar")また、他の質問に提供されたアプローチなので、手動で生成された値を追加してみました。ただし、これによりすべての引用符が失われ、最終結果は次のようになります。

+ aws s3 sync /home/me/Pictures s3://mybucket/home/me/Pictures --testing foobar --profile=personal --dryrun

確認しました。この問題しかし、その答えにもかかわらず、私は次のようになります。

bar=( --bar a="b" )
cmd=(foo "${bar[@]}" )
printf '%q ' "${cmd[@]}" && echo  # print code equivalent to the command we're about to run
"${cmd[@]}"                       # actually run this code
+ bar=(--bar a="b")
+ cmd=(foo "${bar[@]}")
+ printf '%q ' foo --bar a=b
foo --bar a=b + echo

+ foo --bar a=b

したがって、二重引用符が失われます。


他の場合に備えて、私のBashバージョンは次のようになります。

me@my_machine:~/scripts$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

それでは、この問題を解決する方法はありますか?または、プログラミング言語でスクリプトを再構築し、シェルスクリプトとAWS CLIを使用する代わりにAWS SDKを使用する必要がありますか?


@muru:引用符を入れないと、包含パターンと除外パターンは使用されません。

me@my_machine:~/scripts$ aws s3 sync /home/me/Pictures s3://my_bucket/home/me/Pictures --exclude * --include *.gif --include *.jpg --profile=personal --dryrun
(dryrun) upload: ../Pictures/Bender_Rodriguez.png to s3://my_bucket/home/me/Pictures/Bender_Rodriguez.png
(dryrun) upload: ../Pictures/Panttaus/sormus_paalta.png to s3://my_bucket/home/me/Pictures/Panttaus/sormus_paalta.png
(dryrun) upload: ../Pictures/Panttaus/sormus_sivusta.png to s3://my_bucket/home/me/Pictures/Panttaus/sormus_sivusta.png
(dryrun) upload: ../Pictures/Screenshot from 2021-03-13 22-30-26.png to s3://my_bucket/home/me/Pictures/Screenshot from 2021-03-13 22-30-26.png
(dryrun) upload: ../Pictures/willow_7_months.jpg to s3://my_bucket/home/me/Pictures/willow_7_months.jpg

二重引用符が一重引用符内にある場合は、次をset -x実行すると入力が表示されます。

me@my_machine:~/scripts$ aws s3 sync /home/me/Pictures s3://my_bucket/home/me/Pictures --exclude '"*"' --include '"*.gif"' --include '"*.jpg"' --profile=personal --dryrun
(dryrun) upload: ../Pictures/Bender_Rodriguez.png to s3://my_bucket/home/me/Pictures/Bender_Rodriguez.png
(dryrun) upload: ../Pictures/Panttaus/sormus_paalta.png to s3://my_bucket/home/me/Pictures/Panttaus/sormus_paalta.png
(dryrun) upload: ../Pictures/Panttaus/sormus_sivusta.png to s3://my_bucket/home/me/Pictures/Panttaus/sormus_sivusta.png
(dryrun) upload: ../Pictures/Screenshot from 2021-03-13 22-30-26.png to s3://my_bucket/home/me/Pictures/Screenshot from 2021-03-13 22-30-26.png
(dryrun) upload: ../Pictures/willow_7_months.jpg to s3://my_bucket/home/me/Pictures/willow_7_months.jpg

除外パターンと埋め込みパターンは、質問で述べたように二重引用符が正しく保持されている場合にのみ機能します。

入力から引用符を完全に削除すると、次のようになります。

.jpg
.gif

そして、スクリプトに何も追加しようとしないでください。

    aws s3 sync ${params[@]}

結果は一重引用符です。

+ aws s3 sync /home/me/Pictures s3://my_bucket/home/me/Pictures --exclude '*' --include '*.gif' --include '*.jpg' --profile=personal --dryrun
(dryrun) upload: ../Pictures/Bender_Rodriguez.png to s3://my_bucket/home/me/Pictures/Bender_Rodriguez.png
(dryrun) upload: ../Pictures/Panttaus/sormus_paalta.png to s3://my_bucket/home/me/Pictures/Panttaus/sormus_paalta.png
(dryrun) upload: ../Pictures/Panttaus/sormus_sivusta.png to s3://my_bucket/home/me/Pictures/Panttaus/sormus_sivusta.png
(dryrun) upload: ../Pictures/Screenshot from 2021-03-13 22-30-26.png to s3://my_bucket/home/me/Pictures/Screenshot from 2021-03-13 22-30-26.png
(dryrun) upload: ../Pictures/willow_7_months.jpg to s3://my_bucket/home/me/Pictures/willow_7_months.jpg

繰り返しますが、.pngファイルは無視されません。

そしてスクリプトに引用符を入れてください。

    aws s3 sync "${params[@]}"

完全引数を参照します。

+ aws s3 sync /home/me/Pictures s3://my_bucket/home/me/Pictures '--exclude *' '--include *.gif' '--include *.jpg' --profile=personal --dryrun

Unknown options: --exclude sync.sh,--include *.png,--include *.jpg

また、スクリプトを単純化すると、次のようになります。

#!/bin/bash

set -x

DRYRUN=true

s3_bucket_uri='s3://my_bucket'
aws_profile='--profile=personal'
backup_config_folder='../config/*'
include_file='includes.txt'
exclude_file='excludes.txt'

includes=()
excludes=()

sync () {
    local params=()
    local local_folder="$HOME/$1"
    local bucket_folder="$s3_bucket_uri""$local_folder"

    params+=("$local_folder" "$bucket_folder")

    if [[ ${excludes[@]} ]]; then
        params+=("${excludes[@]}")
    fi
    
    if [[ ${includes[@]} ]]; then
        params+=("${includes[@]}")
    fi

    params+=("$aws_profile")

    if [[ "$DRYRUN" = true ]]; then
        params+=(--dryrun)
    fi

    aws s3 sync "${params[@]}"
}

read_parameters () {
    if [[ -f "$1" ]]; then
        while read line; do
            if [[ $2 == "include" ]]; then
                includes+=(--include "$line")
            elif [[ $2 == "exclude" ]]; then
                excludes+=(--exclude "$line")
            fi
        done < $1
    fi
}

reset () {
    includes=()
    excludes=()
}

for folder in $backup_config_folder; do
    if [[ -d "$folder" && ! -L "$folder" ]]; then
        read_parameters $folder/$exclude_file exclude
        read_parameters $folder/$include_file include
        sync "${folder##*/}"
        reset
    fi
done

出力に一重引用符を付けると、ついに動作しました。

+ aws s3 sync /home/me/Pictures s3://my_bucket/home/me/Pictures --exclude '*' --include '*.gif' --include '*.jpg' --profile=personal --dryrun
(dryrun) upload: ../../../Pictures/willow_7_months.jpg to s3://my_bucket/home/me/Pictures/willow_7_months.jpg

だから私の考えでは、教訓は:最初に二重引用符を使用しようとしないでください。

答え1

デバッグ出力を誤って解釈しましたset -x。 Bashは、結果として実行されたコマンドを記録するときにset -x標準の引用符を表示します。ここで、引用符を適用して使用された実際のコマンドを派生することができます。

foo "a b"空白を含むパラメータなどのコマンドがあるとします。 bashがそれを記録する必要がある場合は、それが単一の引数であることをset -x示す方法が必要です。a bしたがって、出力に参照されているバージョンが表示されます。コマンドラインでそのバージョンを使用すると、次の出力が表示されます。

$ foo a\ b
+ foo 'a b'

foo引数を使用してコマンドを実行する必要があると仮定すると、a"b"つまり、コマンドがこれらの引用符を受け取る必要があると仮定すると、bashは通常次のいずれかのバリアントを実行します。

foo 'a"b"'
foo a\"b\"
foo 'a"b'\"

このコマンドを記録する必要がある場合、bashは引用符も引用符で囲まれていることを示す必要があります。そうでなければ、人々はこれらの引用が最初のコードブロックの問題によるものであると考えることができます。だから私たちは次のようになります:

$ foo a\"b\"
+ foo 'a"b"'

Bashは何も追加または削除しません。実行する内容を引用符で明確に表示するだけです。

'"*"'したがって、デバッグ出力でこれを見ると、bashが一重引用符を追加するのを見ることはできません。 Bashは引用符を削除した後にそれらをインポートすることを示したいので、"*"二重引用符をコマンドに渡す必要があります。二重引用符がどこから出るのか気になるでしょう。おそらく入力ファイルから来たようです。


これら2つのコードブロックは複雑すぎます。

if [[ ${excludes[@]} ]]; then
    params+=("${excludes[@]/#/--exclude }")
fi

if [[ ${includes[@]} ]]; then
    params+=("${includes[@]/#/--include }")
fi   
if [[ $2 == "include" ]]; then
    includes+=($line)
elif [[ $2 == "exclude" ]]; then
    excludes+=($line)
fi

読み取りモードでオプションを追加します。

if [[ $2 = include ]]; then
    includes+=(--include "$line")
elif [[ $2 = exclude ]]; then
    excludes+=(--exclude "$line")
fi

その後、追加の操作なしでこれらの配列を直接使用できます。

もちろん、フィールドの分割、ファイル名の拡張などを行う場合を除き、変数の周りに引用符を使用することを忘れないでください。

関連情報