![在从 shell 脚本中的变量创建的命令中保留双引号](https://linux22.com/image/193110/%E5%9C%A8%E4%BB%8E%20shell%20%E8%84%9A%E6%9C%AC%E4%B8%AD%E7%9A%84%E5%8F%98%E9%87%8F%E5%88%9B%E5%BB%BA%E7%9A%84%E5%91%BD%E4%BB%A4%E4%B8%AD%E4%BF%9D%E7%95%99%E5%8F%8C%E5%BC%95%E5%8F%B7.png)
以为我会写一个简单的脚本来同步一些数据,但结果比我想象的要困难。
基本布局是有一个配置文件夹,其中有子文件夹引用需要同步的文件夹,每个文件夹包含[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[@]}
,shell 在模式周围添加额外的单引号,这不会导致命令崩溃,但它只是忽略所有模式:
+ 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 文件之外的所有内容。
shellaws 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 SDK,而不是使用 shell 脚本和 AWS CLI?
@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
带有参数的command 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
然后你就可以直接使用这些数组而无需进一步操作。
当然,请记住在变量周围使用引号,除非您希望它们进行字段分割、文件名扩展等。