Afferrare l'estensione in un nome di file


33

Come posso ottenere l'estensione del file da bash? Ecco cosa ho provato:

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

In questo modo posso ottenere l'estensione bz2dal percorso /dir/subdir/file.bz2, ma ho un problema con il percorso /dir/subdir/file-1.0.tar.bz2.

Preferirei una soluzione usando solo bash senza programmi esterni, se possibile.

Per chiarire la mia domanda, stavo creando uno script bash per estrarre qualsiasi archivio dato con un solo comando di extract path_to_file. Come estrarre il file è determinato dallo script vedendo il suo tipo di compressione o archiviazione, che potrebbe essere .tar.gz, .gz, .bz2 ecc. Penso che questo dovrebbe comportare la manipolazione di stringhe, ad esempio se ottengo l'estensione .gzallora io dovrebbe verificare se ha la stringa .tarprima .gz- in tal caso, l'estensione dovrebbe essere .tar.gz.


2
file = "/ dir / subdir / file-1.0.tar.bz2"; echo $ {file ## *.} stampa '.bz2' qui. Qual è l'output che ti aspetti?
axel_c,

1
Ho bisogno.tar.bz2
Uray,

Risposte:


19

Se il nome del file è file-1.0.tar.bz2, l'estensione è bz2. Il metodo che stai usando per estrarre l'estensione ( fileext=${filename##*.}) è perfettamente valido¹.

Come decidi di voler essere l'estensione tar.bz2 e non bz2o 0.tar.bz2? Devi prima rispondere a questa domanda. Quindi puoi capire quale comando shell corrisponde alle tue specifiche.

  • Una possibile specifica è che le estensioni devono iniziare con una lettera. Questo euristico fallisce per alcune estensioni comuni come 7z, che potrebbero essere trattate meglio come un caso speciale. Ecco un'implementazione bash / ksh / zsh:

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

    Per la portabilità POSIX, è necessario utilizzare a case un'istruzione per la corrispondenza dei modelli.

    while case $basename in
            ?*.*) case ${basename##*.} in [A-Za-z]*|7z) true;; *) false;; esac;;
            *) false;;
          esac
    do 
  • Un'altra possibile specifica è che alcune estensioni denotano codifiche e indicano che è necessario un ulteriore stripping. Ecco un'implementazione bash / ksh / zsh (che richiede shopt -s extglobsotto bash e setopt ksh_globsotto 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%.}

    Si noti che questa considera 0un'estensione in file-1.0.gz.

¹ e i relativi costrutti sono in POSIX , quindi funzionano in qualsiasi shell non antico in stile Bourne come ash, bash, ksh o zsh. ${VARIABLE##SUFFIX}


che dovrebbe essere risolto, controllando se la stringa prima dell'ultimo .token è di tipo archivio, ad esempio tar, se il suo tipo di archivio diverso 0dall'iterazione dovrebbe finire.
Uray,

2
@uray: funziona in questo caso particolare, ma non è una soluzione generale. Considera l'esempio di Maciej.patch.lzma . Un'euristica migliore sarebbe quella di prendere in considerazione la stringa dopo l'ultimo .: se si tratta di un suffisso di compressione ( .7z, .bz2, .gz, ...), continuare stripping.
Gilles 'SO- smetti di essere malvagio' il

@NoamM Cosa c'era di sbagliato nell'indentazione? È definitivamente rotto dopo la modifica: il codice doppiamente nidificato è rientrato come quello nidificato singolarmente.
Gilles 'SO- smetti di essere malvagio' il

22

Puoi semplificare le cose semplicemente eseguendo la corrispondenza dei pattern sul nome file anziché estrarre l'estensione due volte:

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

Questa soluzione è meravigliosamente semplice.
AsymLabs,


2

Ecco il mio colpo: tradurre i punti in newline, passare attraverso tail, ottenere l'ultima riga:

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

0
echo ${filename#$(echo $filename | sed 's/\.[^[:digit:]].*$//g;')}

Per esempio:

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

Non funziona per tutti i casi. Prova con "foo.7z"
axel_c il

Hai bisogno di virgolette e un uso migliore printfnel caso in cui il nome del file contenga una barra rovesciata o inizi con -:"${filename#$(printf %s "$filename" | sed 's/\.[^[:digit:]].*$//g;')}"
Gilles 'SO- smetti di essere malvagio'

@axel_c: giusto, e ho implementato la stessa specifica di Maciej come esempio. Quale euristica suggerisci che è meglio di "iniziare con una lettera"?
Gilles 'SO- smetti di essere malvagio' il

1
@Gilles: penso solo che non ci sia una soluzione a meno che tu non usi un elenco precompilato di estensioni conosciute, perché un'estensione può essere qualsiasi cosa.
axel_c,

0

Un giorno ho creato quelle funzioni complicate:

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

Ho trovato questo approccio semplice, molto utile in molti casi, non solo quando si tratta di estensioni.

Per il controllo delle estensioni: è semplice e affidabile

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

Per l'estensione di taglio:

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

Per cambiare l'estensione:

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

Oppure, se ti piacciono le "pratiche funzioni:

~$ 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 ti sono piaciute queste funzioni o le hai trovate utili, ti preghiamo di fare riferimento a questo post :) (e si spera di inserire un commento).


0

la risposta basata su case jackman è piuttosto buona e portatile, ma se vuoi solo il nome del file e l'estensione in una variabile ho trovato questa soluzione:

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

Funziona solo con doppie estensioni e la prima deve essere "tar".

Ma puoi cambiare la riga di test "tar" con un test di lunghezza della stringa e ripetere la correzione più volte.


-1

l'ho risolto usando questo:

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

ma questo funziona solo per il tipo di archiviazione noto, solo in questo caso tar

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.