我有一些这样的文件:
├── abc.conf
├── def.conf
├── xyz.conf
├── 10-abc.conf.postix
├── 10-def.conf.postix
并且我想.postfix
从所有以 开头的文件中删除10-
。为了在不安装任何工具的情况下执行此操作,我尝试使用bash
、find
和 来执行此操作sed
。
我建了这个:
$ find . -name "10-*.conf.postfix" -exec mv '{}' $(echo {} | sed -E 's/(.*)\.postfix$/\1/') \;
但是它失败了,因为 -commandmv
将被呈现为这样:
$ mv '10-abc.conf.postix' 10-abc.conf.postix
如果我测试子 shell 内的代码,它会按预期工作(它返回不带 的文件名.postifx
)。我不确定哪里出了问题。你有什么提示给我吗?
答案1
子 shell 和变量一样,在运行整个命令行之前进行处理。由您的父 shell 解释,而不是由“find”解释$()
,并且没有对“find -exec”进行特殊处理,因为“find”本身不是特殊的 shell 构造,而只是一个常规的外部命令。
因此处理顺序如下:
- 子 shell
echo {} | sed -E 's/(.*)\.postfix$/\1/'
产生输出{}
。 - 命令
find . -name "10-*.conf.postfix" -exec mv '{}' {} \;
运行。 - 当“find”找到匹配的文件时,它会直接
mv
作为子进程执行,并用找到的文件名替换每个 {} 参数。
有几种方法可以做你想做的事情:
引用子 shell,以便它不会被父 shell 扩展(使用单引号或反斜杠转义
$
),和随后要求 'find' 通过 shell 运行它(否则它根本不会扩展):find . -name "10-*.conf.postfix" -exec sh -c 'mv "$1" "$(echo "$1" | sed -E "s/.../")"' -- {} \;
可以使用 缩短此代码
sed 's/\.postfix$//'
,因为您实际上没有做任何其他事情,只是删除后缀。但是,Bash 的参数扩展会使用 使其更短${var%suffix}
:find . -name "10-*.conf.postfix" -exec bash -c 'mv "$1" "${1%.postfix}"' -- {} \;
使用
perl-rename
(在 Debian 上称为“rename”)或其他无需子 shell 即可直接转换源名称的工具:find . -name "10-*.conf.postfix" -exec prename 's/\.postfix$//' {} \;
如果不需要包含子目录,您只需运行:
prename 's/\.postfix$//' 10-*.conf.postfix
使用
for
循环是一个外壳构造,其中主体的处理被延迟:for file in ./10-*.conf.postfix; do # mv "$file" "$(echo "$file" | sed -E 's/(.*)\.postfix$/\1/')" # mv "$file" "$(echo "$file" | sed 's/\.postfix$//')" mv "$file" "${file%.postfix}" done
使用 bash 时,您可以启用
shopt -s globstar
并指定**/*.conf.postfix
以递归方式匹配文件。只要匹配的数量不是很大,这是一个很好的替代品find -name
。for x in $(find)
(注意:除非您确切知道它会扩展成什么样,否则请避免使用。)使用
vidir
或其他交互式“目录编辑”工具。