Agarrando a extensão em um nome de arquivo

31

Como faço para obter 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 bz2 do caminho /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 bash sem programas externos, se for possível.

Para esclarecer minha dúvida, eu estava criando um script bash para extrair qualquer arquivo dado apenas por um único comando de extract path_to_file . Como extrair o arquivo é determinado pelo script ao ver seu tipo de compactação ou arquivamento, que poderia ser .tar.gz, .gz, .bz2 etc. Acho que isso deve envolver a manipulação de strings, por exemplo, se eu obtiver a extensão .gz , então devo verificar se tem a string .tar antes de .gz - se for o caso, a extensão deve ser .tar.gz .

    
por uray 04.09.2010 / 13:49

8 respostas

17

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 tar.bz2 e não bz2 ou 0.tar.bz2 ? Você precisa responder a essa pergunta primeiro. Então você pode descobrir qual comando shell corresponde à sua especificação.

  • Uma possível especificação é 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 do 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 instrução case 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ário mais decapagem. Aqui está uma implementação de bash / ksh / zsh (exigindo shopt -s extglob sob bash e setopt ksh_glob sob 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 0 como 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 estilo Bourne, como ash, bash, ksh ou zsh.

    
por 04.09.2010 / 14:17
20

Você pode simplificar as coisas apenas fazendo 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
    
por 07.03.2011 / 12:33
5
$ echo "thisfile.txt"|awk -F . '{print $NF}'

Comentários sobre isso aqui: link

    
por 04.09.2010 / 13:57
1

Aqui está minha chance: Traduzir pontos para novas linhas, percorrer tail , obter a última linha:

$> TEXT=123.234.345.456.456.567.678
$> echo $TEXT | tr . \n | tail -n1
678
    
por 21.01.2016 / 16:18
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
    
por 04.09.2010 / 14:15
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 encontrei essa abordagem simples, 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, por favor consulte este post :) (e esperamos colocar um comentário).

    
por 07.11.2011 / 20:32
0

a resposta baseada em casos do jackman é bastante 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 string e repetir a correção várias vezes.

    
por 17.02.2016 / 15:23
-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

    
por 04.09.2010 / 14:36