在带有 Bash 3.2.52(2) 的 CentOS 中,我必须将许多文件(不是全部)从一个目录复制到另一个目录。
我可以创建一个很长的一行,例如,cp /"$HOME"/dir1/{file1,fil2} ... /"$HOME"/dir2
但因为 dir1 中有很多文件,所以我更喜欢以多行方式复制文件。
编辑:我手动创建列表。
如何才能做到这一点?
我更喜欢无反斜杠的解决方案 |我没有找到任何线索man cp
|也许只有这里文档?
答案1
files=(
file1 file2 "the goose incident.png"
"another file"
file3 file-4 "fifth file" file6
"this is file7 with a
newline in the middle of the name" )
cd ~/dir1 &&
cp "${files[@]}" ~/dir2
files
这会将列表中提到的名称复制~/dir1
到~/dir2
。
列表中元素之间的换行符files
并不重要,除了最后一个元素中的换行符,它是嵌入了换行符的文件名(只是为了表明您也可以拥有这些,而不会出现问题)。
该列表也可以写成
files=(
file1
file2
"the goose incident.png"
"another file"
file3
file-4
"fifth file"
file6
"this is file7 with a
newline in the middle of the name"
)
或作为
files=( file1 file2 "the goose incident.png" "another file" file3 file-4
"fifth file" file6 "this is file7 with a
newline in the middle of the name" )
答案2
这个怎么样:
#!/usr/bin/env bash
files="1
2
3
4
4
5
6
7
8
9 10 11"
IFS=$'\n'
for file in $files
do
touch "$file"
done
只需更换touch "$file"
您需要的即可。此解决方案的缺点是,如您所见,它将为每个文件创建一个新进程strace
,因此对于大量文件来说会很慢:
$ strace -f ./cp-here-doc.sh |& grep 'execve("/usr/bin/touch"'
[pid 17917] execve("/usr/bin/touch", ["touch", "1"], [/* 63 vars */]) = 0
[pid 17918] execve("/usr/bin/touch", ["touch", "2"], [/* 63 vars */]) = 0
[pid 17919] execve("/usr/bin/touch", ["touch", "3"], [/* 63 vars */]) = 0
[pid 17920] execve("/usr/bin/touch", ["touch", "4"], [/* 63 vars */]) = 0
[pid 17921] execve("/usr/bin/touch", ["touch", "4"], [/* 63 vars */]) = 0
[pid 17922] execve("/usr/bin/touch", ["touch", "5"], [/* 63 vars */]) = 0
[pid 17923] execve("/usr/bin/touch", ["touch", "6"], [/* 63 vars */]) = 0
[pid 17924] execve("/usr/bin/touch", ["touch", "7"], [/* 63 vars */]) = 0
[pid 17925] execve("/usr/bin/touch", ["touch", "8"], [/* 63 vars */]) = 0
[pid 17926] execve("/usr/bin/touch", ["touch", "9 10 11"], [/* 63 vars */]) = 0
您可以在最终脚本中仅使用一次xargs
runtouch
或,以使脚本运行得更快:cp
#!/usr/bin/env bash
files="1
2
3
4
4
5
6
7
8
9 10 11"
echo "$files" | tr '\n' '\0' | xargs -0 touch
结果:
$ strace -f ./cp-here-doc.sh |& grep 'execve("/usr/bin/touch"'
[pid 18290] execve("/usr/bin/touch", ["touch", "1", "2", "3", "4", "4", "5", "6", "7", "8", "9 10 11"], [/* 63 vars */]) = 0
另请注意,在 Linux 上至少文件名可以包含换行符,因此如果至少一个文件名包含换行符,则必须选择另一个分隔符。
OP问:
IFS=$'\n' 是什么意思
这意味着一个字面上的新行。您可以阅读以下内容man bash
:
Words of the form $'string' are treated specially. The word
expands to string, with backslash-escaped char- acters replaced
as specified by the ANSI C standard. Backslash escape
sequences, if present, are decoded as follows:
\a alert (bell)
\b backspace
\e
\E an escape character
\f form feed
\n new line
您的最终脚本可能如下所示:
#!/usr/bin/env bash
files="1
2
3
4
5
6
7
8
9 10 11"
echo "$files" | tr '\n' '\0' | xargs -0 cp -t {} dir
我真的很愿意去xargs
这样的选择很多更快更安全 - 请参阅下面 Kusalananda 的评论。我没有使用cp
命令运行测试,但当我测试touch
在列表中创建文件时$(seq 1 1000000)
,只花了 16 秒xargs
,但 for 循环版本却花了 42 分钟。另外,令我惊讶的是,xargs
能够将参数列表拆分为多个命令,这样Argument list too long
就不会成为问题。