我不太热心,zsh
但我想这对某人来说应该是一个简单的上篮:
sourcedir
我可以使用targetdir
以下命令批量复制文件:
$ find sourcedir -type f -exec cp {} targetdir \;
cp
仅当文件名与以下之一匹配时,我才可以这样做:
top directory
fileA <= directories have same name as single file they enclose
fileA.pdf <= file to move
another file
another file.pdf <= do NOT move
still133another4544file
still133another4544file.pdf <= file to move
从字面上看,有数千个要经历,但只需要移动选定的几个,我有名称,可以将其放入文件或直接输入到脚本中。
可以保留一个简单的 shell 脚本'我也接受 Ruby。
答案1
首先,这是我创建的示例树:
{ pwd
for d in ./*/
do cd "$d"
printf '\t%s\n' "${d#??}"
printf '\t\t%s\n' *
cd ..
done
}
/tmp/top
another file/
another file.pdf
fileA/
fileA.pdf
otherdir/
1
2
3
stillanother4544file/
stillanother4544file.pdf
现在显然您不需要执行任何操作 - 我创建了树来镜像您的示例树,并且只添加了一些异常值。
但我建议你这样做:
pax -rwis'|^./\([^/]*\)/\(\1\....\)$|\2|' -s'|.*||' . ../tgt
pax
这基本上是在-r
ead/ -w
rite 模式下调用 POSIX 标准实用程序 - 直接复制模式。在复制模式下,pax
不会tar
像在-w
仪式模式下那样创建存档,而是直接复制在其[...pattern...]
参数中找到的所有文件 - 这里仅.
针对以当前目录为根的树 - 和/或名称它在其标准输入(在本例中为空)上读取到作为其最后一个参数命名的目录(位于此处)../tgt
。
现在,我知道,你不想移动整棵树。其中大部分是通过-s
替代论点来处理的。您可以根据正则表达式sed
样式语句重命名输出文件,就像我在这里所做的那样。我使用两个-s
替换语句。第一个将所有输入匹配重命名为:
./dirname/samename.???
...到...
samename.???
...在输出时,从而展平输出树以直接放入所有复制的文件../tgt
。如果您希望保留./dirname/
每个目录的父目录,则可以将\2
第一个-s
替换的部分替换为&
.
一旦文件名被pax
-s
ubstitution 成功匹配和修改,就不会再次尝试任何剩余的-s
ubstitution 参数,并且只有那些尚未匹配的文件才会尝试任何剩余的重命名正则表达式。这意味着当前目录中的所有文件的路径名符合以下类型:
./dirname/samename.???
...已成功选择并删除了除最后一个路径名组件之外的所有内容以及第一个-s
ubstitution 语句和所有内容其他当前目录中的文件将被第二个替换完全替换-s|.*||
-s
。这是因为pax
从其处理列表中删除任何替换空字符串的文件名。
一旦pax
处理完所有这些,它就需要完成最后一项任务,然后才能真正复制所选文件 - 这就是-i
我指定的交互式重命名选项,我认为这可能正是您的拿手好戏。
从man pax
:
-i
- 以交互方式重命名文件或存档成员。对于每个匹配 a 的档案成员
pattern
操作数或与文件操作数匹配的每个文件,pax
将提示/dev/tty
给出文件名、文件模式和修改时间。pax
然后将从 读取一行/dev/tty
。如果此行为空,则跳过文件或存档成员。如果该行由单个句点组成,则处理文件或存档成员而不修改其名称。否则,其名称将替换为该行的内容。pax
如果出现以下情况,将立即以非零退出状态退出EOF
读取响应时遇到或/dev/tty
无法打开进行读写。
ATTENTION: pax interactive file rename operation.
-rw-r--r-- Oct 17 04:30 stillanother4544file.pdf
Input new name, or a "." to keep the old name, or a "return" to skip this file.
Input > .
Processing continues, name unchanged.
ATTENTION: pax interactive file rename operation.
-rw-r--r-- Oct 17 04:30 another file.pdf
Input new name, or a "." to keep the old name, or a "return" to skip this file.
Input >
Skipping file.
ATTENTION: pax interactive file rename operation.
-rw-r--r-- Oct 17 04:30 fileA.pdf
Input new name, or a "." to keep the old name, or a "return" to skip this file.
Input > .
Processing continues, name unchanged.
因此,在当前目录中的六个文件中,只有这三个文件能够进入-i
交互式重命名提示,而这三个文件中只有两个能够进入../tgt
:
ls -l ../tgt
-rw-r--r-- 1 mikeserv mikeserv 0 Oct 17 04:30 fileA.pdf
-rw-r--r-- 1 mikeserv mikeserv 0 Oct 17 04:30 stillanother4544file.pdf
答案2
如果您将这些文件名保存在数组中,并且它们都不包含|
(因为这需要转义,但不值得打扰 - 请参阅下面的替代方案),您可以简单地连接数组的元素并将结果用作 glob:
myarr=( file1 file2 ... fileN )
lst=${(j:|:)myarr}
cp -v -- **/($~lst) targetdir
例如,这些文件位于我的随机位置/tmp
:
caxZN.gif
e8ApF.gif
div2k.js.lzo
cmp2jz.ini
并将它们的名称保存在数组中,结果如下:
cp -v -- **/($~lst) targetdir
'tmp/div2k.js.lzo' -> 'targetdir/div2k.js.lzo' 'tmp/其他目录/e8ApF.gif' -> 'targetdir/e8ApF.gif' 'tmp/其他目录/一二/cmp2jz.ini' -> 'targetdir/cmp2jz.ini' 'tmp/some dir/caxZN.gif' -> 'targetdir/caxZN.gif'
或者,这次假设它们列在一个文件中,每行一个文件名,您可以读取数组中的行,然后使用全局限定符/e细绳和修饰语仅全局/选择数组中的文件名:
mylist=(${(f)"$(<list_of_files)"})
cp -- **/*(.e_'(($mylist[(Ie)$REPLY:t]))'_) targetdir
实际上.
仅选择常规文件(D
如果您的列表包含点文件则添加)并e_'expression'_
仅返回那些为expression
true 的文件名,在这种情况下,如果它们基本名称( $REPLY:t
)是数组的一个元素 mylist
,例如:
print -rl -- **/*(.e_'(($mylist[(Ie)$REPLY:t]))'_)
tmp/div2k.js.lzo tmp/其他目录/e8ApF.gif tmp/其他目录/一二/cmp2jz.ini tmp/某个目录/caxZN.gif
当然,以上所有假设zsh
。
无论如何,您自己的解决方案也有效,只要您花时间编写文件名并将正确的选项传递给find
例如
find . -type f \( -name caxZN.gif -o -name e8ApF.gif -o -name div2k.js.lzo -o -name cmp2jz.ini \) -exec cp -- {} targetdir \;
答案3
$ find sourcedir -type f -name 'file?.pdf' -exec cp {} targetdir \;
应该只匹配那些与该模式匹配的文件。
答案4
如果我理解正确的话,您的每个目录只有一个文件,并且您希望移动该文件(如果它在您的列表中)。如果是这样,你可以这样做:
while read -r file; do cp sourcedir/*/"$file" targetdir; done < list.txt
如果您还需要复制目录,您可以读取文件,去掉文件扩展名获取目录名称并复制:
while read -r file; do cp -r sourcedir/"${file%.*}" targetdir; done < list.txt