Subshel​​l 无法按缩进方式运行

Subshel​​l 无法按缩进方式运行

我有一些这样的文件:

├── abc.conf
├── def.conf
├── xyz.conf
├── 10-abc.conf.postix
├── 10-def.conf.postix

并且我想.postfix从所有以 开头的文件中删除10-。为了在不安装任何工具的情况下执行此操作,我尝试使用bashfind和 来执行此操作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 构造,而只是一个常规的外部命令。

因此处理顺序如下:

  1. 子 shellecho {} | sed -E 's/(.*)\.postfix$/\1/'产生输出{}
  2. 命令find . -name "10-*.conf.postfix" -exec mv '{}' {} \;运行。
  3. 当“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或其他交互式“目录编辑”工具。

相关内容