Agarrando a extensão em um nome de arquivo


33

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 do bz2caminho /dir/subdir/file.bz2, mas tenho 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 sua compressão ou arquivamento tipo, que poderia ser .tar.gz, .gz, .bz2 etc. Eu acho que isso deve envolver a manipulação de cadeia, por exemplo, se eu chegar a extensão .gzentão eu deve verificar se possui a string .tarantes .gz- se houver, a extensão deve estar .tar.gz.


2
arquivo = "/ dir / subdir / arquivo-1.0.tar.bz2"; eco $ {file ## *.} imprime '.bz2' aqui. Qual é o resultado que você está esperando?
axel_c 04/09/10

1
Eu preciso.tar.bz2
uray

Respostas:


19

Se o nome do arquivo for file-1.0.tar.bz2, a extensão será 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 ou tar.bz2não bz2ou 0.tar.bz2? Você precisa responder a essa pergunta primeiro. Então você pode descobrir qual comando 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 podem ser melhor tratadas 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 POSIX, você precisa usar uma caseinstrução para 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 do bash / ksh / zsh (exigindo shopt -s extglobno bash e setopt ksh_globno zsh):

    basename=$filename
    fileext=
    while [[ $basename = ?*.@(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 0uma extensão em file-1.0.gz.

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


isso deve ser resolvido, verificando se a string antes do último .token é do tipo archive, por exemplo tar, se não for do tipo archive, como a 0iteração, deve terminar.
Uray

2
@ uray: isso funciona nesse caso em particular, mas não é uma solução geral. Considere o exemplo de Maciej.patch.lzma . A melhor heurística seria considerar a corda após o último .: se é um sufixo de compressão ( .7z, .bz2, .gz, ...), continuar a descascar.
Gilles 'SO- stop be evil'

@NoamM O que havia de errado com o recuo? Definitivamente, ele está quebrado após a sua edição: o código duplamente aninhado é recuado da mesma forma que o único nested.
Gilles 'SO- stop be evil'

22

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

Esta solução é lindamente simples.
AsymLabs


2

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

$> TEXT=123.234.345.456.456.567.678
$> echo $TEXT | tr . \\n | tail -n1
678

0
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

Não funciona para todos os casos. Tente com 'foo.7z'
axel_c

Você precisa de aspas e deve ser melhor utilizado printfcaso o nome do arquivo contenha uma barra invertida ou comece com -:"${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}"
Gilles 'SO- stop be evil'

@axel_c: certo, e eu implementei a mesma especificação que Maciej como exemplo. Que heurística você sugere que seja melhor do que "começa com uma letra"?
Gilles 'SO- stop be evil'

1
@ Gilles: acho que não há solução, a menos que você use uma lista pré-computada de extensões conhecidas, porque uma extensão pode ser qualquer coisa.
axel_c

0

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

PS Se você gostou dessas funções ou as encontrou usadas por inteiro, consulte este post :) (e espero colocar um comentário).


0

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.


-1

eu resolvi usando isso:

filename=`basename $filepath`
fileext=${filename##*.}
fileext2=${filename%.*}
fileext3=${fileext2##*.}
if [ "$fileext3" == "tar" ]; then
    fileext="tar."$fileext
fi

mas isso funciona apenas para o tipo de arquivamento conhecido, neste caso apenas tar

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.