쉘 스크립트의 변수에서 생성된 명령에 큰따옴표를 유지하십시오.

쉘 스크립트의 변수에서 생성된 명령에 큰따옴표를 유지하십시오.

일부 데이터를 동기화하는 간단한 스크립트를 작성하려고 생각했지만 생각보다 어려웠습니다.

기본 레이아웃은 동기화해야 하는 폴더를 참조하는 하위 폴더가 있는 구성 폴더를 갖는 것입니다. 각 폴더에는 [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[@]}"전체 include 또는 제외 문 주위에 작은따옴표를 추가하여 명령이 충돌하도록 합니다.

+ 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는 따옴표를 제거한 후에 이를 가져오는 것을 보여주려고 하므로 "*"큰따옴표를 명령에 전달해야 합니다. 큰따옴표가 어디서 나오는지 궁금하실 겁니다. 아마도 입력 파일에서 나온 것 같습니다.


이 두 코드 블록은 너무 복잡합니다.

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

그런 다음 추가 조작 없이 이러한 배열을 직접 사용할 수 있습니다.

물론, 필드 분할, 파일 이름 확장 등을 수행하려는 경우가 아니면 변수 주위에 따옴표를 사용하는 것을 잊지 마십시오.

관련 정보