我正在使用 Duplicity 将我的服务器备份到另一个服务器。
由于有多个服务器,我想创建一个按cron
文件夹(和服务器)分离运行的 bash 脚本。为此,我在字符串中对命令进行了剪辑:
printf -v DulicityCMD 'duplicity --log-file %s --progress --no-encryption --include="/etc" --include="/var/log" --include="/var/db" --include="/var/www" --include="/home" --exclude="**" / scp://[email protected]:22//home/duplicity/backups/%s' $LOG_FILE $VPSNAME
echo $DulicityCMD
# THIS
$DulicityCMD
# OR THIS
sudo $DulicityCMD
$DulicityCMD
如果你显示的内容echo
将看起来像这样:
duplicity --log-file /var/log/duplicity/backup.log --progress --no-encryption --include="/etc" --include="/var/log" --include="/var/db" --include="/var/www" --include="/home" --exclude="**" / scp://[email protected]:22//home/duplicity/backups/myhost
当输入此命令时,正如所显示的那样echo
,一切都运行正常。然而,当运行它时,从变量,在内bash
,它不起作用。
即使我用sudo
或以 root 用户身份运行此程序,它也无法正常工作。错误的输出如下:
Traceback (most recent call last):
File "/bin/duplicity", line 1546, in <module>
with_tempdir(main)
File "/bin/duplicity", line 1540, in with_tempdir
fn()
File "/bin/duplicity", line 1375, in main
action = commandline.ProcessCommandLine(sys.argv[1:])
File "/usr/lib64/python2.7/site-packages/duplicity/commandline.py", line 1131, in ProcessCommandLine
set_selection()
File "/usr/lib64/python2.7/site-packages/duplicity/commandline.py", line 969, in set_selection
sel.ParseArgs(select_opts, select_files)
File "/usr/lib64/python2.7/site-packages/duplicity/selection.py", line 268, in ParseArgs
self.add_selection_func(self.glob_get_sf(arg, 1))
File "/usr/lib64/python2.7/site-packages/duplicity/selection.py", line 434, in glob_get_sf
sel_func = self.glob_get_filename_sf(glob_str, include)
File "/usr/lib64/python2.7/site-packages/duplicity/selection.py", line 485, in glob_get_filename_sf
raise FilePrefixError(filename)
FilePrefixError: "/etc"
当我使用 时eval
,我没有收到此错误,但它会失去 root 权限,并且无法正常运行。因为我需要将此的输出与另一个命令 (sendmail) 结合使用|
。
这是什么问题?我该如何解决?
答案1
Stephen Rauch 的回答描述了问题。我有一个不同的解决方案。
但首先:为什么要将这么长的命令放入变量中?只是为了打印它?为什么不直接使用 shell 的详细(显示命令)选项?
设置-v (你的命令) 设置 +v
(执行跟踪)选项x
类似;尝试将上述选项与set -x
和set +x
结合使用,看看有什么不同。如果您喜欢这两个选项,可以将它们与set -vx
和结合起来set +vx
。
用空格分隔的一系列单词构建一个字符串,然后尝试将其拆分以恢复原始组成单词,这是一个非常糟糕的想法。您遇到了其中一个陷阱。另一个是文件名可能包含空格的理论可能性;从命令字符串中无法分辨出cat foo bar baz
这foo bar
是一个文件名还是baz
另一个文件名(并且您已经看到构建字符串 ascat "foo bar" baz
也不起作用)。这些问题和其他问题在此处讨论:
此外,您不需要printf
仅仅连接字符串。
如果您确实想在存储中构建命令行,并且使用 bash,则可以使用数组变量,如下所示:
DulicityCMD=(duplicity --log-file "$LOG_FILE" --progress --no-encryption --include="/etc" --include="/var/log" --include="/var/db" --include="/var/www" --include="/home" --exclude="**" / scp://[email protected]:22//home/duplicity/backups/"$VPSNAME")
请注意,我将$LOG_FILE
和$VPSNAME
放在引号中。除非您有充分的理由不这样做,并且您确定自己知道自己在做什么,否则您应该始终引用所有对 shell 变量的引用。但 Stephen 是对的:您实际上不需要引用常量字符串,例如,,,"/etc"
等等。(但当然,您"/var/log"
"/var/db"
做需要引用"**"
,因为*
是特殊字符。
为了便于阅读,你可以将上面的内容分成多行。请注意,第一行是=
后面几行说+=
,因为它们在之前的内容基础上添加了内容:
DulicityCMD=(duplicity --log-file "$LOG_FILE" --progress --no-encryption)
DulicityCMD+=(--include="/etc" --include="/var/log" --include="/var/db")
DulicityCMD+=(--include="/var/www" --include="/home" --exclude="**")
DulicityCMD+=(/ scp://[email protected]:22//home/duplicity/backups/"$VPSNAME")
然后你可以做
printf "%s\n" "${DulicityCMD[*]}" (打印命令) “${DulicityCMD[@]}” (执行命令)
或使用sudo "${DulicityCMD[@]}"
(如果需要)。请参阅重击(1)有关 bash 语法的更多信息,以及我的答案在这里了解有关此阵列技术的更多信息。
答案2
问题:
这是字符串引用不正确的问题。当您从命令行运行以下命令时:
duplicity --include="/etc"
将"/etc"
被传递给底层程序,因为/etc
将"
被 shell 剥离。但是当您执行如下操作时:
DuplicityCMD='duplicity --include="/etc"'
$DuplicityCMD
传递"/etc"
的正是这个。这是因为,当你定义变量时DuplicityCMD
,你明确要求将"
包含在内'
。我建议删除它们,"
因为它们看起来基本上是不需要的。