Bash interpretando erroneamente uma instrução sed para renomear arquivos [duplicado]

3

Gostaria de renomear uma série de arquivos com o nome

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

para

name_10.ext
name_11.ext
...

Este one-liner funciona:

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

Mas este não:

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

Por quê? Como evitar isso?

Usando

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

no Ubuntu 18.04.

Enquanto nesta pergunta semelhante , um caso simples com ! é mostrado, aqui um ! apenas dentro de aspas simples é considerado e comparado a um ! entre aspas simples, entre aspas duplas. Como apontado nos comentários, Bash se comporta de maneira diferente nesses dois casos. Trata-se da versão do Bash 4.3.48(1) ; este problema parece não estar mais presente em 4.4.12(1) (no entanto, é recomendado evitar este one-liner, porque a versão do Bash pode ser desconhecida em alguns casos).

Como sugerido na resposta de Kusalananda, se o sed delimitador ! for substituído por # ,

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

este problema não surge de todo.

    
por BowPark 22.11.2018 / 13:39

3 respostas

7

Quando usado em um shell interativo, o ! pode iniciar a expansão do histórico (não é um problema em scripts).

A solução é usar outro delimitador do que ! na expressão sed .

Um loop bash alternativo:

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
    
por 22.11.2018 / 13:43
3

Você pode usar o comando rename do Larry Wall ( rename package no Debian, prename no RedHat): ele usa a sintaxe (regex regl do Perl muito mais simples), e irá iterar o arquivo args (não precisa codificar um loop):

rename 's/ \(\d+\)/_$1/' name\ *
    
por 22.11.2018 / 13:58
1

Se você sabe o intervalo exato, você pode renomear os arquivos assim:

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

Ou, se você quiser ser POSIX-pedantic:

for i in 'seq 10 99'; do mv "name ($i).ext" "name_$i.ext"; done
    
por 22.11.2018 / 17:17