ti-enxame.com

Agarrando a extensão em um nome de arquivo

Como obtenho a extensão de arquivo do bash? Aqui está o que eu tentei:

filename=`basename $filepath`
fileext=${filename##*.}

Ao fazer isso, posso obter a extensão de bz2 do caminho /dir/subdir/file.bz2, mas estou com um problema com o caminho /dir/subdir/file-1.0.tar.bz2.

Eu preferiria uma solução usando apenas o bash sem programas externos, se possível.

Para deixar minha pergunta clara, eu estava criando um script bash para extrair qualquer arquivo especificado apenas com um único comando de extract path_to_file. Como extrair o arquivo é determinado pelo script, vendo seu tipo de compactação ou arquivamento, que pode ser .tar.gz, .gz, .bz2 etc. Acho que isso deve envolver manipulação de cadeias, por exemplo, se eu receber a extensão .gz então eu deveria verificar se tem a string .tar antes .gz - se sim, a extensão deve ser .tar.gz.

36
uray

Se o nome do arquivo for file-1.0.tar.bz2, a extensão é bz2. O método que você está usando para extrair a extensão (fileext=${filename##*.}) é perfeitamente válido¹.

Como você decide que deseja que a extensão seja tar.bz2 e não bz2 ou 0.tar.bz2? Você precisa responder a essa pergunta primeiro. Então você pode descobrir qual comando do Shell corresponde à sua especificação.

  • Uma especificação possível é que as extensões devem começar com uma letra. Essa heurística falha em algumas extensões comuns como 7z, que pode ser melhor tratado como um caso especial. Aqui está uma implementação bash/ksh/zsh:

    basename=$filename; fileext=
    while [[ $basename = ?*.* &&
             ( ${basename##*.} = [A-Za-z]* || ${basename##*.} = 7z ) ]]
    do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    fileext=${fileext%.}
    

    Para portabilidade do POSIX, você precisa usar uma instrução case para a correspondência de padrões.

    while case $basename in
            ?*.*) case ${basename##*.} in [A-Za-z]*|7z) true;; *) false;; esac;;
            *) false;;
          esac
    do …
    
  • Outra especificação possível é que algumas extensões denotam codificações e indicam que é necessária mais remoção. Aqui está uma implementação bash/ksh/zsh (exigindo shopt -s extglob sob bash e setopt ksh_glob sob zsh):

    basename=$filename
    fileext=
    while [[ $basename = ?*[email protected](bz2|gz|lzma) ]]; do
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    done
    if [[ $basename = ?*.* ]]; then
      fileext=${basename##*.}.$fileext
      basename=${basename%.*}
    fi
    fileext=${fileext%.}
    

    Observe que isso considera 0 para ser uma extensão em file-1.0.gz.

¹ ${VARIABLE##SUFFIX} e construções relacionadas estão em POSIX , portanto, eles funcionam em qualquer Shell não-antigo do estilo Bourne, como ash, bash, ksh ou zsh.

20

Você pode simplificar as coisas apenas fazendo a correspondência de padrões no nome do arquivo em vez de extrair a extensão duas vezes:

case "$filename" in
    *.tar.bz2) bunzip_then_untar ;;
    *.bz2)     bunzip_only ;;
    *.tar.gz)  untar_with -z ;;
    *.tgz)     untar_with -z ;;
    *.gz)      gunzip_only ;;
    *.Zip)     unzip ;;
    *.7z)      do something ;;
    *)         do nothing ;;
esac
25
glenn jackman
$ echo "thisfile.txt"|awk -F . '{print $NF}'

Comentários sobre isso aqui: http://liquidat.wordpress.com/2007/09/29/short-tip-get-file-extension-in-Shell-script/

7
Chris

Aqui está minha chance: traduza pontos para novas linhas, passe por tail, obtenha a última linha:

$> TEXT=123.234.345.456.456.567.678
$> echo $TEXT | tr . \\n | tail -n1
678
2
Michael Bar-Sinai
echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}

Por exemplo:

% echo $filename
2.6.35-zen2.patch.lzma
% echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}
.patch.lzma
0
Maciej Piechotka

Um dia eu criei essas funções complicadas:

# args: string how_many
function get_last_letters(){ echo ${1:${#1}-$2:$2}; }
function cut_last_letters(){ echo ${1:0:${#1}-$2}; }

Eu achei essa abordagem direta, muito útil em muitos casos, não apenas quando se trata de extensões.

Para verificar extensões - É simples e confiável

~$ get_last_letters file.bz2 4
.bz2
~$ get_last_letters file.0.tar.bz2 4
.bz2

Para extensão de corte:

~$ cut_last_letters file.0.tar.bz2 4
file.0.tar

Para alterar a extensão:

~$ echo $(cut_last_letters file.0.tar.bz2 4).gz
file.0.tar.gz

Ou, se você gosta de "funções úteis:

~$ function cut_last_letters_and_add(){ echo ${1:0:${#1}-$2}"$3"; }
~$ cut_last_letters_and_add file.0.tar.bz2 4 .gz
file.0.tar.gz

P.S. Se você gostou dessas funções ou as achou úteis, consulte este post :) (e, com sorte, coloque um comentário).

0
Grzegorz Wierzowiecki

a resposta baseada em casos de jackman é muito boa e portátil, mas se você quiser apenas o nome do arquivo e a extensão em uma variável, encontrei esta solução:

INPUTFILE="$1"
INPUTFILEEXT=$( echo -n "$INPUTFILE" | rev | cut -d'.' -f1 | rev )
INPUTFILEEXT=$( echo -n $INPUTFILEEXT | tr '[A-Z]' '[a-z]' ) # force lowercase extension
INPUTFILENAME="`echo -n \"$INPUTFILE\" | rev | cut -d'.' -f2- | rev`"

# fix for files with multiple extensions like "gbamidi-v1.0.tar.gz"
INPUTFILEEXT2=$( echo -n "$INPUTFILENAME" | rev | cut -d'.' -f1 | rev )
if [ "$INPUTFILEEXT2" = "tar" ]; then
    # concatenate the extension
    INPUTFILEEXT="$INPUTFILEEXT2.$INPUTFILEEXT"
    # update the filename
    INPUTFILENAME="`echo -n \"$INPUTFILENAME\" | rev | cut -d'.' -f2- | rev`"
fi

Funciona apenas com extensões duplas e a primeira deve ser "tar".

Mas você pode alterar a linha de teste "tar" com um teste de comprimento de sequência e repetir a correção várias vezes.

0
eadmaster