Shell:rsync在文件名/路径中错误地解析空格

时间:2016-02-15 13:12:18

标签: shell ssh ubuntu-14.04 rsync

我试图通过rsync在ssh上提取文件列表,但是我无法使用带有空格的文件名!一个示例文件是:

/home/pi/Transmission_Downloads/FUNDAMENTOS_JAVA_E_ORIENTAÇÃO_A_OBJETOS/2. Fundamentos da linguagem/estruturas-de-controle-if-else-if-e-else-v1.mp4

我尝试使用此shell代码传输它。

cat $file_name | while read LINE
do
    echo $LINE
    rsync -aP "$user@$server:$LINE" $local_folder
done

我得到的错误是:

receiving incremental file list
rsync: link_stat "/home/pi/Transmission_Downloads/FUNDAMENTOS_JAVA_E_ORIENTAÇÃO_A_OBJETOS/2." failed: No such file or directory (2)
rsync: link_stat "/home/pi/Fundamentos" failed: No such file or directory (2)
rsync: link_stat "/home/pi/da" failed: No such file or directory (2)
rsync: change_dir "/home/pi//linguagem" failed: No such file or directory (2)
rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1655) [Receiver=3.1.0]

我不明白为什么它在屏幕上打印正常,但错误地解析文件名/路径!我知道空格实际上是空格的反斜杠,但不知道如何解决这个问题。 Sed(查找/替换)也没有帮助,我也试过这个代码没有成功

while IFS='' read -r line || [[ -n "$line" ]]; do
    echo "Text read from file: $line"
    rsync -aP "$user@$server:$line" $local_folder
done < $file_name

我该怎么做才能解决这个问题,为什么会这样呢?

我从.txt文件中读取文件列表(每行一个文件和路径),并且我使用的是ubuntu 14.04。谢谢!

2 个答案:

答案 0 :(得分:3)

rsync默认执行空格分割。 您可以使用-s(或--protect-args)标志禁用此功能,也可以转义文件名中的空格

答案 1 :(得分:2)

shell正确地将文件名传递给rsync,但rsync将空格解释为在同一服务器上分隔多个路径。因此,除了双引号变量以确保rsync将字符串视为单个参数之外,需要引用文件名中的空格。

如果您的文件名中没有撇号,您可以使用双引号内的单引号执行此操作:

rsync -aP "$user@$server:'$LINE'" "$local_folder"

如果你的文件名中可能包含撇号,那么你需要引用它们(文件名是否也有空格)。您可以使用bash的内置参数替换来执行此操作(只要您使用bash 4;旧版本,例如OS X上附带的/bin/bash,在此类表达式中存在反斜杠和撇号问题) 。这是它的样子:

rsync -aP "$user@$server:'${LINE//\'/\'\\\'\'}'" "$local_folder"

丑陋,我知道,但有效。在其他选项之后进行说明。

如果您使用较旧的bash或其他shell,则可以改为使用sed

rsync -aP "$user@$server:'$(sed "s/'/'\\\\''/g" <<<"$LINE")'" "$local_folder"

...或者如果你的shell也不支持<<< here-strings:

rsync -aP "$user@$server:'$(echo "$LINE" | sed "s/'/'\\\\''/g")'" "$local_folder"

说明:我们想要将所有撇号替换为......在单引号字符串中间成为文字撇号的东西。由于无法在单引号内转义任何内容,我们必须首先关闭引号,然后添加一个文字撇号,然后重新打开字符串其余部分的引号。实际上,这意味着我们希望用序列(撇号,反斜杠,撇号,撇号)替换所有出现的撇号('):'\''。我们可以使用bash参数扩展或sed来实现。

在bash中,${varname/old/new}扩展为变量$varname的值,第一次出现的字符串old被字符串new取代。加倍第一个斜杠(${varname//old/new})会替换所有次出现而不是第一次出现。这就是我们想要的。但由于撇号和反斜杠对于shell都是特殊的,我们必须在两个表达式中的每个字符前放置一个(另一个)反斜杠。这会将old值转换为\',将new值转换为\'\\\'\'

sed版本稍微简单一点,因为撇号不是特别的。反斜杠仍然是,因此我们必须在字符串中放置\\以获得\。由于我们需要字符串中的撇号,因此使用双引号字符串而不是单引号字符串会更容易,但这意味着我们需要将所有反斜杠再次加倍以确保shell传递它们到sed不受干扰。这就是为什么shell命令有\\\\:它被sed传递给\\,它以\输出。