Bash 误解 sed 语句来重命名文件

Bash 误解 sed 语句来重命名文件

我想重命名一系列以此方式命名的文件

name (10).ext
name (11).ext
...

name_10.ext
name_11.ext
...

这一行的作用是:

$ for i in name\ \(* ; do echo "$i" | sed -e 's! (\([0-9]\{2\}\))!_\1!' ; done
name_10.ext
name_11.ext
...

但这个没有:

$ for i in name\ \(* ; do mv "$i" "$(echo "$i" | sed -e 's! (\([0-9]\{2\}\))!_\1!')" ; done
bash: !_\1!': event not found

为什么?如何避免这种情况?


使用

$ bash --version
GNU bash, versione 4.3.48(1)-release (x86_64-pc-linux-gnu)

在 Ubuntu 18.04 上。


而在这类似的问题!显示了一个简单的情况,这里!考虑内部单引号并将其与!内部单引号、内部双引号进行比较。正如评论中指出的,Bash 在这两种情况下的行为方式不同。这是关于 Bash 版本4.3.48(1);这个问题似乎不再存在4.4.12(1)(但是建议避免这种单行,因为在某些情况下 Bash 版本可能未知)。

正如 Kusalananda 答案中所建议的,如果sed分隔符!替换为#,

$ for i in name\ \(* ; do mv "$i" "$(echo "$i" | sed -e 's# (\([0-9]\{2\}\))#_\1#')" ; done

这个问题根本不会出现。

答案1

当在交互式 shell 中使用时,!可能会启动历史扩展(在脚本中不是问题)。

解决方案是使用表达式!中以外的另一个分隔符sed

另一种bash循环:

for name in "name ("*").ext"; do
    if [[ "$name" =~ (.*)" ("([^\)]+)").ext" ]]; then
        newname="${BASH_REMATCH[1]}_${BASH_REMATCH[2]}.ext"
        printf 'Would move %s to %s\n' "$name" "$newname"
        # mv -i "$name" "$newname"
    fi
done

答案2

您可以使用 Larry Wall 的rename命令(renameDebian 中的 package,prenameRedHat 中的 package):它使用(更简单的恕我直言)Perl 正则表达式语法,并将迭代文件 args(无需编写循环代码):

rename 's/ \(\d+\)/_$1/' name\ *

答案3

如果您知道确切的范围,您可以像这样重命名文件:

for i in {10..99}; do mv "name ($i).ext" "name_$i.ext"; done

或者,如果你想成为 POSIX 迂腐的人:

for i in `seq 10 99`; do mv "name ($i).ext" "name_$i.ext"; done

相关内容