我想复制具有可变子文件夹名称的多个子文件夹中的文件。
例子:
mkdir rootdir
mkdir rootdir/dir1
mkdir rootdir/dir2
mkdir rootdir/dir3
touch rootdir/dir1/foo.txt
touch rootdir/dir2/foo.txt
touch rootdir/dir3/foo.txt
使用已知的子文件夹名称,我可以单独复制每个文件。
cp rootdir/dir1/foo.txt rootdir/dir1/bar.txt
cp rootdir/dir2/foo.txt rootdir/dir2/bar.txt
cp rootdir/dir3/foo.txt rootdir/dir3/bar.txt
但是,由于子文件夹数量未知且子文件夹名称未知(我知道文件名),我无法再这样做了。
我可以找到这些文件...
ls ./**/foo.txt
find . -name foo.txt
...但我没有找到允许将此信息传输到 cp (或替代工具)的语法。
答案1
有以下几种选择:
find rootdir -type f -name foo.txt -execdir cp {} bar.txt \;
foo.txt
这将搜索在 中或下的任何位置调用的常规文件rootdir
,当找到一个文件时,cp
用于将其复制到bar.txt
同一目录中的名称。该-execdir
选项是非标准的,但通常实现,并将在找到文件的目录中执行给定的实用程序。将{}
替换为找到的文件的名称。
或者,
find rootdir -type f -name foo.txt -exec sh -c '
for pathname do
cp "$pathname" "${pathname%/*}/bar.txt"
done' sh {} +
这基本上做同样的事情,但调用一个简短的内联sh -c
脚本,其中包含批量找到的foo.txt
文件。 in循环cp
会将其中每个文件复制到与找到的文件相同的目录,但路径名的文件名部分替换为bar.txt
.
使用**
,正如您在问题中提到的那样(假设有bash
shell):
shopt -s globstar nullglob dotglob
for pathname in rootdir/**/foo.txt; do
cp "$pathname" "${pathname%/*}/bar.txt"
done
在 中bash
,设置globstar
shell 选项可以使用**
递归地匹配子目录,并且dotglob
使模式也可以匹配隐藏名称。nullglob
如果没有匹配项,shell 选项会使模式完全消失,而不是保持未展开状态。
再次,但使用(zsh
明确要求常规文件并启用与dotglob
和相同的通配符处理):nullglob
bash
for pathname in rootdir/**/foo.txt(.ND); do
cp $pathname $pathname:h/bar.txt
done
这里,与删除路径名的文件名部分$pathname:h
相同(如“仅头部”,而不是尾部位)。$pathname
:h
答案2
通过zsh
,您还可以使用它的zmv
自动加载功能:
autoload -Uz zmv # best in ~/.zshrc
zmv -C '(**/)(foo.txt)(#q.)' '${1}bar.txt'
或者:
zmv -C '**/foo.txt(#q.)' '$f:h/bar.txt'
与手动编写循环相比,使用它zmv
的优点是它有一些安全检查,可以防止您犯不可逆转的错误。