【问题标题】:Why do bash parameter expansions cause an rsync command to operate differently?为什么 bash 参数扩展会导致 rsync 命令以不同方式运行?
【发布时间】:2015-06-14 04:45:21
【问题描述】:

我正在尝试运行将文件复制到新位置的 rsync 命令。如果我直接运行 rsync 命令,在命令行上没有任何参数扩展,rsync 会按照我的预期进行

$ rsync -amnv --include='lib/***' --include='arm-none-eabi/include/***' \
  --include='arm-none-eabi/lib/***' --include='*/' --exclude='*' \
  /tmp/from/ /tmp/to/

building file list ... done
created directory /tmp/to
./
arm-none-eabi/
arm-none-eabi/include/
arm-none-eabi/include/_ansi.h
...
arm-none-eabi/lib/
arm-none-eabi/lib/aprofile-validation.specs
arm-none-eabi/lib/aprofile-ve.specs
...
lib/
lib/gcc/
lib/gcc/arm-none-eabi/
lib/gcc/arm-none-eabi/4.9.2/
lib/gcc/arm-none-eabi/4.9.2/crtbegin.o
...

sent 49421 bytes  received 6363 bytes  10142.55 bytes/sec
total size is 423195472  speedup is 7586.32 (DRY RUN)

但是,如果我将过滤器参数包含在一个变量中,并使用该变量调用命令,则会观察到不同的结果。 rsync 复制了一些我确实期望的额外目录:

$ FILTER="--include='lib/***' --include='arm-none-eabi/include/***' \
  --include='arm-none-eabi/lib/***' --include='*/' --exclude='*'"
$ rsync -amnv ${FILTER} /tmp/from/ /tmp/to/

building file list ... done
created directory /tmp/to
./
arm-none-eabi/
arm-none-eabi/bin/
arm-none-eabi/bin/ar
...
arm-none-eabi/include/
arm-none-eabi/include/_ansi.h
arm-none-eabi/include/_syslist.h
...
arm-none-eabi/lib/
arm-none-eabi/lib/aprofile-validation.specs
arm-none-eabi/lib/aprofile-ve.specs
...
bin/
bin/arm-none-eabi-addr2line
bin/arm-none-eabi-ar
...
lib/
lib/gcc/
lib/gcc/arm-none-eabi/
lib/gcc/arm-none-eabi/4.9.2/
lib/gcc/arm-none-eabi/4.9.2/crtbegin.o
...

sent 52471 bytes  received 6843 bytes  16946.86 bytes/sec
total size is 832859156  speedup is 14041.53 (DRY RUN)

如果我 echo 失败的命令,它会生成成功的确切命令。复制输出并直接运行会给我预期的结果。

对于 bash 参数扩展的工作原理,我显然遗漏了一些东西。有人可以解释一下为什么这两种不同的调用会产生不同的结果吗?

【问题讨论】:

  • 扩展FILTER 变量时,内部单引号被删除。在rsync 行上方添加set -x 并比较手动运行和变量运行的输出以了解我的意思。这就是mywiki.wooledge.org/BashFAQ/050 的全部意义所在。
  • 谢谢,set -x 提供了很多关于这里发生了什么的见解。 wiki 条目解释了很多。

标签: bash rsync


【解决方案1】:

shell 在扩展变量之前解析引号,因此将引号放在变量的值中并不能达到您的预期——当它们到位时,它们做任何有用的事情都为时已晚。详情请见BashFAQ #50: I'm trying to put a command in a variable, but the complex cases always fail!

在您的情况下,解决此问题的最简单方法似乎是使用数组而不是纯文本变量。这样,在创建数组时会解析引号,每个“单词”都会存储为单独的数组元素,如果正确引用变量(使用双引号和[@]),数组元素将包含在命令的参数列表,没有任何不需要的解析:

filter=(--include='lib/***' --include='arm-none-eabi/include/***' \
  --include='arm-none-eabi/lib/***' --include='*/' --exclude='*')
rsync -amnv "${filter[@]}" /tmp/from/ /tmp/to/

请注意,数组在 bash 和 zsh 中可用,但不是所有其他 POSIX 兼容的 shell。另外,我将filter 变量名小写了——推荐的做法是避免与shell 的特殊变量(都是大写的)发生冲突。

【讨论】:

  • 感谢您的链接和解释。我知道我错过了什么,只是没有意识到有多少;)
【解决方案2】:

为了方便起见,我喜欢将参数分成单独的行:

ROPTIONS=(
   -aNHXxEh
   --delete
   --fileflags
   --exclude-from=$EXCLUDELIST
   --delete-excluded
   --force-change
   --stats
   --protect-args
)

然后这样称呼它:

rsync "${ROPTIONS[@]}" "$SOURCE" "$DESTINATION"

【讨论】:

  • 我已经重构了我的脚本来做同样的事情。
猜你喜欢
  • 2018-10-12
  • 2017-07-27
  • 1970-01-01
  • 1970-01-01
  • 2017-12-05
  • 2011-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多