我试图通过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。谢谢!
答案 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
传递给\\
,它以\
输出。