Script de shell Unix, parâmetros com espaços

1

Eu preciso criar um script de shell chamado chExt.sh . que aceita uma extensão de arquivo e, em seguida, vários arquivos. Em seguida, ele precisa alterar as extensões de arquivo ou afirmar que o arquivo não existe.

./chExt.sh txt ocelot.cpp ../otherFolder/file.H cat.dog.TXT king cobra.dat é a ideia geral do teste de testadores automáticos. O problema é que eu não tenho a menor idéia de como lidar com um arquivo com um espaço em seu nome.

meu código é:

newExt="$1"
shift
for x in $*
do
    fileName="$x"
    if test -f "$fileName";
    then
        name=$(echo "$fileName" | rev | cut -f 2- -d '.' | rev)
        newName="$name.$newExt"
        if test "$newName" != "$fileName";
        then
            mv "$x" "$newName"
        fi
    else
        echo "$fileName: No such file"
    fi
done
    
por Josh 04.12.2015 / 06:48

2 respostas

3

Se você não citar o nome do arquivo, o shell não tem como saber que foo bar.txt é um parâmetro e não dois ( foo e bar.txt ). Portanto, você precisa chamar seu script assim:

./chExt.sh txt ocelot.cpp ../otherFolder/file.H cat.dog.TXT "king cobra.dat"

O próximo problema é que, quando as variáveis são expandidas, o valor resultante é dividido em espaços em branco (ou qualquer que seja o A variável $IFS está definida como). Então, quando você escreve for i in $* , o $* é expandido e dividido no espaço em branco. Portanto, $i se torna king para uma iteração e cobra.txt para a próxima. A maneira de contornar isso é citar a variável que você deseja expandir para que ela não seja dividida.

O que nos leva à próxima edição, que é a diferença entre $* e $@ . Se você usar "$*" , todos os parâmetros serão tratados como uma string longa:

$ cat foo.sh
#!/bin/sh
for i in "$*"; do
    echo "$i"
done
$ foo.sh foo "bar baz"
foo bar baz

Compare o acima para:

#!/bin/sh
for i in "$@"; do
    echo "$i"
done
$ foo.sh foo "bar baz"
foo
bar baz

Como você pode ver, usar "$@" tem o efeito desejado, cada parâmetro de entrada é tratado separadamente, incluindo aquele com espaços.

Como nota final, o seu script também irá se engasgar com nomes de arquivos começando com - e uma letra correspondente a uma opção para echo (tente com um arquivo chamado -new , por exemplo). Uma maneira melhor de remover a extensão é usar os recursos de manipulação de string nativos do shell. ${var%.*} remove a string mais curta que corresponde a . e 0 ou mais caracteres do final da variável. Em outras palavras, a extensão. Em seguida, você também precisará adicionar -- a mv para indicar o final da opção e o início dos parâmetros, para que também possa lidar com nomes de arquivos que começam com - (consulte a Diretriz 10 aqui .

Portanto, uma versão melhorada e funcional do seu script seria:

#!/bin/sh
newExt="$1"
shift
for fileName in "$@"
do
    if test -f "$fileName";
    then
    #    name=$(echo "$fileName" | rev | cut -f 2- -d '.' | rev)
    name=${fileName%.*}
        newName="$name.$newExt"
        if test "$newName" != "$fileName";
        then
            mv -- "$fileName" "$newName"
        fi
    else
        printf '%s: No such file\n' " $fileName"
    fi
done

Leitura adicional:

por 04.12.2015 / 10:17
1

Outra qualidade notável sobre "$*" e "$@" é que eles representam o array shell - o conjunto atual dos argumentos do shell. Elas são alteráveis com o set integrado, mas geralmente são a matriz operacional padrão para qualquer função de matriz incorporada - para incluir a matriz for .

: "${2?no file arguments!}"
for x 
do     if    [ "${2+:}" ]
       then  set ".$1"
       else  [ ".${x##*.}" != "$1" ] &&
             [ -f "$x" ] &&
             mv -- "$x" "${x%.*}$1"
       fi
done

Essa é uma aproximação muito próxima do que seu script faz. É um pouco diferente, pois não faz o echo para um argumento que não é um arquivo regular. Eu não tenho certeza de que você está fazendo o que você acha que está lá: você está verificando se o seu argumento é um arquivo regular, acessível, , mas você é echo ing uma mensagem para stdout que diz argumento: No such file . Em um sistema parecido com um unix, tudo é um arquivo: um pipe é um arquivo, um diretório é um arquivo, um dispositivo é um arquivo. E assim seu teste e sua mensagem não se alinham. Além do mais, se você pedisse a mv para mover um arquivo inexistente, ele imprimiria sua mensagem para você e para o stderr - tudo por conta própria.

Outra coisa que noto é que você está testando se o seu novo nome é igual ao seu nome antigo, e presumo que isso evite mv de reclamar sobre mover um arquivo sobre ele mesmo. Por acaso, mv tem uma opção para acalmar isso.

Eu acho que faria sua parte como:

: "${2?no file arguments!}"
for x 
do     [ "${2+:}" ] &&
       set -- ".$1" && continue
       set -- "${x%.*}$1" "$1"
{      [  -e "$1" ] && printf "%s: exists!\n" "$1"; } ||
{    ! [  -f "$x" ] && printf "%s: not a regular file.\n" "$x"; } ||
       mv -f --    "$x" "$1"
       shift
done   >&2
    
por 04.12.2015 / 11:35