在 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)
Run Code Online (Sandbox Code Playgroud)
As you can see, passing --exclude='.*'to rsync"manually" works fine ( .barisn't copied), it doesn't work when the options are stored in a variable first.
我猜这与引号或通配符(或两者)有关,但我一直无法弄清楚到底出了什么问题。
Kus*_*nda 54
通常,将单独的项目列表降级为单个字符串是一个坏主意,无论是命令行选项列表还是路径名列表。
改用数组:
rsync_options=( -rnv --exclude='.*' )
Run Code Online (Sandbox Code Playgroud)
或者
rsync_options=( -r -n -v --exclude='.*' )
Run Code Online (Sandbox Code Playgroud)
然后...
rsync "${rsync_options[@]}" source/ target
Run Code Online (Sandbox Code Playgroud)
这样,单个选项的引用得以保留(只要您将 的扩展双引号${rsync_options[@]})。它还允许您在调用rsync.
在任何 POSIX shell 中,可以为此使用位置参数列表:
set -- -rnv --exclude='.*'
rsync "$@" source/ target
Run Code Online (Sandbox Code Playgroud)
同样,双引号的扩展在$@这里很重要。
切线相关:
问题是,当您将两组选项放入字符串中时,--exclude选项值的单引号成为该值的一部分。因此,
RSYNC_OPTIONS='-rnv --exclude=.*'
Run Code Online (Sandbox Code Playgroud)
本来可以工作¹...但最好(因为更安全)使用数组或带有单独引用的条目的位置参数。这样做还允许您在需要时使用其中包含空格的内容,并避免让 shell 对选项执行文件名生成(通配)。
¹ 前提$IFS是未修改且--exclude=.当前目录中没有名称以开头的文件,并且未设置nullgloborfailglob外壳选项。
@Kusalananda已经解释了基本问题以及如何解决它,@glenn jackmann 链接的Bash FAQ 条目也提供了很多有用的信息。这是基于这些资源对我的问题中发生的事情的详细说明。
我们将使用一个小脚本在单独的行上打印每个参数来说明事情 ( argtest.bash):
#!/bin/bash
for var in "$@"
do
echo "$var"
done
Run Code Online (Sandbox Code Playgroud)
“手动”传递选项:
$ ./argtest.bash -rnv --exclude='.*'
-rnv
--exclude=.*
Run Code Online (Sandbox Code Playgroud)
正如预期的那样,部分-rnv和--exclude='.*'被拆分为两个参数,因为它们由不带引号的空格分隔(这称为分词)。
另请注意,周围的引号.*已被删除:单引号告诉 shell 在没有特殊解释的情况下传递其内容,但引号本身不会传递给命令。
如果我们现在将选项作为字符串存储在变量中(而不是使用数组),则不会删除引号:
$ OPTS="--exclude='.*'"
$ ./argtest.bash $OPTS
--exclude='.*'
Run Code Online (Sandbox Code Playgroud)
这是因为两个原因:定义时使用的双引号$OPTS防止对单引号进行特殊处理,因此后者是值的一部分:
$ echo $OPTS
--exclude='.*'
Run Code Online (Sandbox Code Playgroud)
当我们现在$OPTS用作命令的参数时,引号会在参数扩展之前处理,因此引号$OPTS“太晚了”。
这意味着(在我的原始问题中)rsync使用排除模式'.*'(带引号!)而不是模式.*——它排除名称以单引号开头、后跟一个点并以单引号结尾的文件。显然这不是本意。
一种解决方法是在定义时省略双引号$OPTS:
$ OPTS2=--exclude='.*'
$ ./argtest.bash $OPTS2
--exclude=.*
Run Code Online (Sandbox Code Playgroud)
但是,由于在更复杂的情况下存在细微差别,因此始终引用变量赋值是一种很好的做法。
正如@Kusalananda 指出的那样,不引用.*也可以。我已经添加了引号以防止模式扩展,但在这种特殊情况下这不是绝对必要的:
$ ./argtest.bash --exclude=.*
--exclude=.*
Run Code Online (Sandbox Code Playgroud)
事实证明,Bash确实执行了模式扩展,但该模式--exclude=.*不匹配任何文件,因此该模式被传递给命令。相比:
$ touch some_file
$ ./argtest.bash some_*
some_file
$ ./argtest.bash does_not_exit_*
does_not_exit_*
Run Code Online (Sandbox Code Playgroud)
但是,不引用模式是危险的,因为如果(无论出于何种原因)有文件匹配,--exclude=.*则模式会被扩展:
$ touch -- --exclude=.special-filenames-happen
$ ./argtest.bash --exclude=.*
--exclude=.special-filenames-happen
Run Code Online (Sandbox Code Playgroud)
最后,让我们看看为什么使用数组可以防止我的引用问题(除了使用数组存储命令参数的其他优点之外)。
定义数组时,分词和引号处理按预期进行:
$ 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=.*
Run Code Online (Sandbox Code Playgroud)
将选项传递给命令时,我们使用语法"${ARRAY[@]}",它将数组的每个元素扩展为一个单独的单词:
$ ./argtest.bash "${ARRAY_OPTS[@]}"
-rnv
--exclude=.*
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
20810 次 |
| 最近记录: |