Saisir l'extension dans un nom de fichier

33

Comment puis-je obtenir l'extension de fichier à partir de bash? Voici ce que j'ai essayé:

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

En faisant cela, je peux obtenir une extension du bz2chemin /dir/subdir/file.bz2, mais j'ai un problème avec le chemin /dir/subdir/file-1.0.tar.bz2.

Je préférerais une solution utilisant uniquement bash sans programmes externes si cela est possible.

Pour que ma question soit claire, je créais un script bash pour extraire n’importe quelle archive donnée à l’aide d’une seule commande extract path_to_file. Comment extraire le fichier est déterminé par le script en voyant sa compression ou le type d' archivage, qui pourrait être tar.gz, .gz, .bz2 etc. Je pense que cela devrait impliquer la manipulation de chaînes, par exemple , si je reçois l'extension .gzalors je devrait vérifier si elle contient la chaîne .taravant .gz- si oui, l’extension devrait l'être .tar.gz.

uray
la source
2
fichier = "/ répertoire / sous-répertoire / fichier-1.0.tar.bz2"; echo $ {fichier ## *.} affiche '.bz2' ici. Quelle est la sortie que vous attendez?
axel_c
1
j'ai besoin.tar.bz2
uray

Réponses:

19

Si le nom du fichier est file-1.0.tar.bz2, l'extension est bz2. La méthode que vous utilisez pour extraire l'extension ( fileext=${filename##*.}) est parfaitement valide¹.

Comment décidez-vous que vous voulez que l'extension soit tar.bz2et non bz2ou 0.tar.bz2? Vous devez d'abord répondre à cette question. Ensuite, vous pouvez déterminer quelle commande shell correspond à votre spécification.

  • Une spécification possible est que les extensions doivent commencer par une lettre. Cette heuristique échoue pour quelques extensions communes telles que 7z, qui pourraient être traitées comme un cas particulier. Voici une implémentation bash / ksh / zsh:

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

    Pour la portabilité POSIX, vous devez utiliser une caseinstruction pour la correspondance de modèle.

    while case $basename in
            ?*.*) case ${basename##*.} in [A-Za-z]*|7z) true;; *) false;; esac;;
            *) false;;
          esac
    do 
    
  • Une autre spécification possible est que certaines extensions dénotent des codages et indiquent qu'une suppression supplémentaire est nécessaire. Voici une implémentation de bash / ksh / zsh (nécessitant shopt -s extglobsous bash et setopt ksh_globsous 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%.}
    

    Notez que ceci est considéré 0comme une extension dans file-1.0.gz.

¹ et les constructions associées sont dans POSIX , ils fonctionnent donc dans n’importe quel shell de style Bourne non ancien, tel que cendres, bash, ksh ou zsh. ${VARIABLE##SUFFIX}

Gilles, arrête de faire le mal
la source
cela devrait être résolu, en vérifiant si la chaîne précédant le dernier .jeton est de type archive, par exemple tar, si ce n'est pas un type d'archive comme l' 0itération doit se terminer.
Uray
2
@uray: cela fonctionne dans ce cas particulier, mais ce n'est pas une solution générale. Prenons l'exemple de Maciej.patch.lzma . Serait une meilleure heuristique pour tenir compte de la chaîne après la dernière .: si elle est un suffixe de compression ( .7z, .bz2, .gz, ...), continuer de décapage.
Gilles 'SO- arrête d'être méchant'
@NoamM Quel était le problème avec l'indentation? Il est définitivement cassé après votre modification: le code à double imbriqué est en retrait de la même manière que celui à imbriqué.
Gilles, arrête de faire le mal.
22

Vous pouvez simplifier les choses simplement en faisant une correspondance de motif sur le nom du fichier plutôt que d'extraire l'extension deux fois:

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
Glenn Jackman
la source
Cette solution est magnifiquement simple.
AsymLabs
2

Voici mon objectif: convertissez les points en nouvelles lignes tail, passez à la dernière ligne:

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

Par exemple:

% echo $filename
2.6.35-zen2.patch.lzma
% echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}
.patch.lzma
Maciej Piechotka
la source
Ne fonctionne pas dans tous les cas. Essayez avec 'foo.7z'
axel_c
Vous avez besoin de guillemets, et une meilleure utilisation printfsi le nom du fichier contient une barre oblique inverse ou commence par -:"${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}"
Gilles 'SO, arrête d'être méchant'
@axel_c: oui, et j'ai implémenté la même spécification que Maciej à titre d'exemple. Quelle heuristique suggérez-vous qui vaut mieux que «commence par une lettre»?
Gilles 'SO- arrête d'être méchant'
1
@ Gilles: Je pense simplement qu'il n'y a pas de solution à moins d'utiliser une liste précalculée d'extensions connues, car une extension peut être n'importe quoi.
axel_c
0

Un jour, j'ai créé ces fonctions délicates:

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

J'ai trouvé cette approche simple, très utile dans de nombreux cas, pas seulement pour les extensions.

Pour vérifier les extensions - c'est simple et fiable

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

Pour l'extension de coupure:

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

Pour changer d'extension:

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

Ou, si vous aimez "les fonctions pratiques:

~$ 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 Si vous avez aimé ces fonctions ou si vous les avez trouvées utiles, veuillez vous reporter à ce message :) (et mettre un commentaire, espérons-le).

Grzegorz Wierzowiecki
la source
0

la réponse basée sur le cas jackman est assez bonne et portable, mais si vous voulez juste le nom de fichier et l'extension dans une variable, j'ai trouvé cette solution:

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

Il ne fonctionne qu'avec des extensions doubles et le premier doit être "tar".

Mais vous pouvez modifier la ligne de test "tar" avec un test de longueur de chaîne et répéter le correctif plusieurs fois.

eadmaster
la source
-1

je l'ai résolu en utilisant ceci:

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

mais cela ne fonctionne que pour un type d'archivage connu, dans ce cas seulement tar

uray
la source