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はこれら2つのケースで異なる動作をします。これはBashバージョンに関するものです4.3.48(1)。問題はもう存在しないようです4.4.12(1)(しかし、場合によってはBashのバージョンがわからないので、この1行の内容は避けることをお勧めします)。

Kusalanandaの回答で提案したようにsed区切り記号を!に変更すると#

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

この問題はまったく発生しません。

答え1

対話型シェルで使用すると、!履歴拡張が有効になる可能性があります(スクリプトでは問題ありません)。

解決策は、式の外!に別の区切り文字を使用することです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のパッケージ、prenameRedHatのパッケージ)を使用できます。このコマンドは(より簡単なIMHO)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

関連情報