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

Bow*_*ark 3 shell bash ubuntu sed files

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

name (10).ext
name (11).ext
...
Run Code Online (Sandbox Code Playgroud)

name_10.ext
name_11.ext
...
Run Code Online (Sandbox Code Playgroud)

这个单行作品:

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

但这个没有:

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

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


使用

$ bash --version
GNU bash, versione 4.3.48(1)-release (x86_64-pc-linux-gnu)
Run Code Online (Sandbox Code Playgroud)

在 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
Run Code Online (Sandbox Code Playgroud)

这个问题根本不会出现。

Kus*_*nda 7

在交互式 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
Run Code Online (Sandbox Code Playgroud)