我正在尝试从 shellscript 中将多个文件复制到一个目录中。这些文件包含各种“丑陋”的字符,例如空格、括号等等。但是,我在转义这些字符时遇到了麻烦,bash
而且cp
似乎处理得非常奇怪。
场景如下:
当我从我的 shell 中发出此命令时,它就像魔法一样有效:
cp /somedir/a\ file.png /somedir/another\ file.png /someotherdir/
但是,当从字符串中读取要复制的文件时,它突然变得很奇怪: 结果
var="/somedir/a\ file.png /somedir/another\ file.png"
cp "$var" /someotherdir/cp: cannot stat 'a\\ file.png another\\ file.png': No such file or directory
我认为这是由于我将变量作为字符串给出并cp
认为它是一个文件,但实际上它是多个文件。当发出相同的命令而不将变量放在引号 ( var="/somedir/a\ file.png /somedir/another\ file.png"; cp $var /someotherdir/
) 中时,我得到了一个更奇怪的错误:
cp: cannot stat 'a\\ file.png another\\ file.png': No such file or directory
cp: cannot stat 'file.png': No such file or directory
cp: cannot stat 'another\\': No such file or directory
cp: cannot stat 'file.png': No such file or directory
它似乎完全忽略了我的转义。我做错了什么?
编辑:// 这好像是复制文件列表有一个答案xargs
,但我仍然想知道为什么 bash 在这里表现得如此奇怪。
答案1
医生,我这样做的时候会很痛……
那就别这么做!
说真的,你为什么要尝试为单个变量分配一个包含空格的文件名列表?这只会酿成灾难。
回答您的隐含问题:我同意您显示的错误消息对于您说使用的命令没有意义。
由于您没有说明如何确定要复制哪些文件,因此很难知道哪种解决方案对您有用,哪种解决方案对您无用。以下是一种可能的方法:
︙ f1="/somedir/文件.png" ︙ f2="/somedir/另一个文件.png" ︙ cp "$f1" "$f2" "$f3" … /someotherdir
(您不需要/
在 末尾/someotherdir/
使用 ,但这也没有什么坏处。)这受到以下事实的限制:您需要提前知道需要复制的最大文件数,并且您需要某种方法来确定f
将每个文件分配给哪个变量。
这与您现在正在做的事情很接近:
var="/somedir/a\ 文件.png /somedir/another\ 文件.png …" echo "$var" | 同时读取 f1 f2 f3… 做 cp "$f1" "$f2" "$f3" … /someotherdir 完毕
该read
命令将$var
在纯空格处将字符串分开,并删除反斜杠,\
只留下
(空格)。但您仍然需要提前知道需要复制的最大文件数。(while
即使此循环只会迭代一次,您也需要循环构造。)
也许这个已经很接近了:
文件列表=/tmp/my_filelist.$$ ︙ echo "/somedir/a file.png" >> "$filelist" ︙ echo “/somedir/another file.png” >> “$filelist” ︙ 读取文件名时 做 cp "$filename" /someotherdir 完成 < "$filelist"
另一种方法是使用变量数组。您可以在手册页中了解如何操作bash
。
答案2
当 bash 解析命令行时,它首先解释引号、转义符等,然后替换变量。如果变量值中有转义符、引号等,它们永远不会生效,因为当它们被包含在命令中时,它们已经太晚了,无法产生预期的效果。
一般来说,处理这类问题的最佳方法是将文件名存储在一个数组中(每个文件名一个元素),而不是字符串变量;然后使用"${array[@]}"
扩展数组,将每个元素视为一个单独的“单词”:
files=(/somedir/a\ file.png /somedir/another\ file.png)
cp "${files[@]}" /someotherdir/
更新:如果您需要动态构建文件列表,那么使用数组很容易:
files=() # Start with an empty list
for newfile in somethingorother; do
files+=("$newfile")
done
答案3
如果您使用 bash,则可以用双引号括起文件名以避免使用转义字符:
cp /old_location/This\ is\ an\ \[ugly\] \file.tmp /new_location/This\ is\ an\ \[ugly\] \file.tmp
#!/bin/bash
在 shellscript 中使用时请使用以下命令:
cp "/old_location/This is an [ugly] file with extras $$$.tmp" "/new_location/Ugly file with extras $$$.tmp"
在您的例子中,您使用的是变量内的值,但传递变量的方式错误。如果变量中有空格,您也应该用双引号括起来:
var="/somedir/a\ file.png /somedir/another\ file.png"; cp "$var" /someotherdir/
如果不对其进行双引号处理,它就像使用几个用空格分隔的变量一样。